Step 1 of 5 · Reading · ~4 min
Read
DNS Wire Format
Encoded Names & Compression
A DNS message carries no delimiters. There are no quotes around a name, no comma between fields, nothing to scan for — a resolver reads the packet by knowing exactly how many bytes each piece occupies. So example.com cannot travel as the eleven characters you typed. It travels as a structure that tells the reader where it stops.
One length byte per label
Split the name at its dots. Each piece — each label — goes on the wire as a single byte holding its length, immediately followed by that many bytes of text. A zero length byte marks the end of the whole name.
example.com -> 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
e x a m p l e c o m
^ ^ ^
length 7 length 3 end of name
Two hard limits fall straight out of that one byte. A length only ever has to count to 63, because the top two bits of every length byte are reserved — which is exactly why no label may exceed 63 bytes. And a complete name is capped at 255 bytes including its length bytes, not just its text. (DNS compares names case-insensitively, but encoding is not a normalisation step: the bytes you write are the bytes that arrive.)
Reading it back: mind the length byte
Decoding is a walk. Read a length byte, take that many bytes as the label, and land on the next length byte. The step is 1 + n, not n — the length byte is itself a byte in the buffer, and skipping only the text is the single most common way to get this wrong:
Look at the loop condition, because it is doing real work. With while pos < len(wire), a wrong step reads garbage lengths and wanders — but it still stops, and a wrong answer is something you can debug. Write while True instead and the identical bug becomes a program that never finishes, which a grader can only report back to you as a run it was unable to complete. Bound the walk by the length of the buffer and the worst case stays debuggable.
Compression: the pointer every parser must understand
Real messages repeat names relentlessly — one question for example.com, then three answer records that all name example.com again. So a name is allowed to stop early and hand off to a pointer at a name already written earlier in the same message. Here the question's name sits at offset 12, immediately after the fixed 12-byte header:
offset 12, in the question: 07 'example' 03 'com' 00
later, inside an answer: 03 'w' 'w' 'w' c0 0c
^^^^^
1100 0000 0000 1100
^^ marker: not a length, a pointer
low 14 bits = 12 = the offset
That marker is why the top two bits of a length byte were reserved: 11 means "read me as an offset instead". The offset counts from the start of the whole message, never from the start of the current record. A name on the wire is therefore one of three shapes: labels closed by 00, a pointer standing alone, or labels that stop early and hand off to a pointer. You may choose to write messages that never use one; you do not get to read only messages that never use one.
Which is where the bugs live. Follow a pointer that aims at itself, and a trusting parser spins forever on a packet an attacker can craft in four bytes. The lesson Name Compression Decoder builds that resolver, with the loop protection it has to carry.
Your exercise: Encode/Decode Names
Read ENCODE <name> and DECODE <hex> lines and convert both ways. The input here never contains a pointer — every DECODE argument is plain labels ending in 00 — so do not build a pointer chaser yet.
Your starter already reads the lines, separates the command from its argument, and converts hex in both directions. It leaves two holes, one in encode_name and one in decode_name, and they are the two ideas above: a label on the wire is its length byte plus its text, and reading one label means stepping past both.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…