rthalley / dnspython

a powerful DNS toolkit for python
http://www.dnspython.org
Other
2.42k stars 508 forks source link

Make it possible to access the TTL of individual records in an RRSET #915

Closed tykling closed 1 year ago

tykling commented 1 year ago

Motivation I would like to monitor for TTL inconsitencies (protocol violations) where the records in an RRSET do not all have the same TTL. It happens in the real world sometimes even though it is not supposed to. I am writing a monitoring tool based on dnspython and want to be able to detect this case.

Describe the solution you'd like. I have been unable to find a way to access the TTL of the individual records in an RRSET of a response. The data structure seems to (correctly, sort of) assume that all records in an RRSET always have the same TTL.

I think it is not possible today with dnspython to do what I want. I might be wrong about this, in which case I'd love to be pointed in the right direction.

Thank you in advance :+1: :)

rthalley commented 1 year ago

There is a way to do what you want, which is the "one_rr_per_rrset" option available in dns.message.from_wire() and also in the various dns.query functions.

When this option is False (the default), Dnspython's RRSets are like RFC 2181 section 5's notion of RRSet, and when it adds a record to the set, it is preventing duplicates (i.e. the RRSet really is a set and not a list) and also doing TTL minimization, i.e. the TTL of the set is the least TTL of all the RRs added to the set. Dnspython does not follow RFC 2181's directives to discard RRsets with differing TTLs if learned from a non-authoritative source, as it's more pragmatic to just minimize all the time. Dnspython furthers this RRSet view by having a message have sections which are lists of RRSets instead of lists of RRs. This is usually (except in cases like yours) far more convenient than working with RRs directly.

When one_rr_per_rrset is True, dnspython makes a separate RRset instance per RR, thus preserving the full information in the DNS wire form messages albeit a little awkwardly. This is necessary for dynamic update messages, but is also useful for your purposes, as you can process all of these singleton RRSets to answer the question "are there any things with the same name, type, and class that have differing TTLs?".

rthalley commented 1 year ago

Put another way, the "one_rr_per_rrset" form of a message is a 1 to 1 mapping of the actual DNS wire format received, instead of the higher-level RFC 2181 RRSet view that messages usually give you.

rthalley commented 1 year ago

Also, should you ever wish to construct wire messages which are not following RFC 2181 rules, you can use this same technique and create messages by hand with one RR per RRset, and then the message code will convert that faithfully into wire format. So you could have wire messages with duplicate rdata, differing TTLs, etc.

tykling commented 1 year ago

Fantastic! Thank you, I was hoping for something like this. :+1: :1st_place_medal:

Thanks for the explanation as well, I agree the current behaviour is the best, but very nice that it can be changed for weird cases.