DNS packets are attacker-controlled binary graphs: compression
pointers can jump backward, names can share suffixes, section counts can
lie, and RDATA lengths can disagree with actual bytes. packet.rs
contains the codec and deliberately keeps the reader state small:
struct Reader<'a> {
b: &'a [u8],
p: usize,
name_offsets: Vec<bool>,
}The lifetime on b prevents the reader from outliving its
input. Every scalar read uses slice bounds checks. Name decoding
separately limits pointer hops, requires pointers to move backward, and
records valid prior name boundaries. The last rule is stricter than
merely checking that a pointer lands inside the packet: an interior byte
can accidentally look like a valid label.
Decoded records become an RData variant. Unknown types
are not discarded; they become opaque bytes paired with their numeric
RecordType. This is the extension-safe behavior required by
modern DNS. It also means an encoder can round-trip data it does not
understand.
The writer uses compression, but optimization remains subordinate to a valid message. A last-owner cache handles repeated owners cheaply while suffix sharing reduces wire size across related names. The July 2026 benchmark shows the trade: a 64-record answer fell from 2,147 to 1,059 bytes, while compression made encoding slower than the uncompressed baseline. On DNS, fewer datagrams and less amplification surface can be worth several microseconds of local CPU.
Truncation uses a bounded search for the largest response that fits
instead of repeatedly rebuilding one record at a time. The result
preserves complete RRsets and required EDNS state. Performance work is
therefore expressed as an algorithmic improvement behind the same
Message::encode contract.