Packet Processing Notes¶
In order to process packets efficiently, we want to perform the least amount of work possible. This means:
- Not parsing parts of the packet until necessary.
- Only performing any necessary parsing once.
A DNS packet contains a lot of information, much of which is not necessarily used by a program. For example, there are 7 flag bits in the header, and none of them are useful in all curcumstances; an authoritative server does not care about the AA bit when reading a query, and a recursive resolver does not care about the RD bit when reading an answer from an authoritative server.
A typical example of unnecessary parsing would be if an authoritative server receives an answer message (that is, a DNS message with the QR bit set). Doing a full parsing of the answer message involves decoding a lot of variable-length fields, including things like extracting EDNS pseudo-options. All of that work is wasted since the authoritative server will drop the message based on a single bit.
We achieve our goal of avoid work by doing the minimal amount of parsing necessary to be able to skip a given section of a packet with the object associated with it. If a program needs to access the header, there is no need to parse any of the other sections. If a program needs access to the question, then the header section must be parsed first, but the answer section can be deferred.
Within a section we follow the same principle. For example, each RR includes the length of the RDATA, which means that we do not actually need to parse the RDATA in order to continue to the next RR. (An exception to this is that we _do_ need to parse some DNS names in the RDATA, if these are used in name compression.)
To avoid doing work, we use the __getattr__() method on our packet
objects. This often allows us to put off work. The basic idea is that
if you access a member of a Python object that is not present and you
have a __getattr()__ method then Python will call that method and
let you run some code to get the value. So we save the “raw” packet
data, and then if needed we parse the field that we are trying to
access, saving it so we don’t need to do the work again.
Using this approach does mean that an exception can be raised if there
are errors in the packet. Programs should be aware of this, and make
sure that they use try handling for objects that use this lazy
parsing approach.
Programs that want to validate a packet can use the parse() method
on either the entire packet or on any individual part of the packet.
That will do parse all of the information and verify the structure is
correct, raising an exception if there are any problems.
Note that all sorts of errors are possible even if a packet parses correctly. For example, invalid combinations of flags, deprecated cryptographic algorithms, and so on, will pass parsing without error.