← First Pair Library

5 Messages on the wire

5.1 The twelve-byte header

A DNS message begins with a fixed twelve-byte header:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
|              ID               |            flags              |
+-------------------------------+-------------------------------+
|          question count       |          answer count         |
+-------------------------------+-------------------------------+
|         authority count       |         additional count      |
+-------------------------------+-------------------------------+

The transaction ID lets a client associate a response with a query. Important flags include QR (query versus response), opcode, AA (authoritative answer), TC (truncated), RD (recursion desired), RA (recursion available), and the four-bit response code.

The four following sections contain questions, answers, authority records, and additional records. A normal question carries a name, requested type, and class. Resource-record sections add TTL, RDATA length, and RDATA.

All multibyte integers are network byte order. Every count and length comes from an untrusted peer. A decoder must prove that bytes exist before reading them, cap allocations, reject invalid labels and pointers, and finish with a coherent message rather than a partially trusted structure.

5.2 Name compression

Repeating full names would waste scarce datagram space. DNS permits a name suffix to be replaced by a two-byte pointer whose high bits are 11 and whose remaining bits are an offset earlier in the message.

Compression turns name decoding into graph traversal. A malicious packet can contain a pointer loop, excessive indirection, or an offset outside the packet. A safe decoder tracks visited offsets or imposes a strict jump bound, checks every target, and separately enforces the expanded 255-octet name limit.

rgbdns’s Reader in src/packet.rs keeps all reads within a borrowed byte slice. Name decoding validates pointer targets and bounds traversal. Record decoding confines each RDATA parser to the declared RDLENGTH. EDNS option iteration likewise checks the option header and value before advancing.

The Writer performs the reverse operation. Encoding is fallible: counts must fit 16 bits, names and RDATA must fit their fields, and the result must remain valid. This symmetry—decode into typed data, manipulate typed data, encode with checks—is the packet layer’s central safety property.

5.3 Errors are protocol results

Several results that sound similar are materially different:

Negative answers normally include the zone’s SOA so resolvers can cache the negative result. Confusing NODATA with NXDOMAIN can suppress other valid types at the same name.

rgbdns expresses authoritative lookup outcomes as Lookup::Answer, Referral, NoData, NxDomain, and Refused. That internal sum type forces the response builder to handle each protocol meaning explicitly.