Skip to content

DNS Parsing the Body

Justin Jones edited this page Feb 24, 2026 · 3 revisions

The DNS body follows the 12-byte header and contains up to four sections:

  1. Questions
  2. Answers
  3. Authority Records
  4. Additional Records

Unlike the header, the body is variable-length and fully driven by the four count fields:

  • QDCOUNT
  • ANCOUNT
  • NSCOUNT
  • ARCOUNT

These values were stored in DNSContext during header parsing. The body parser uses them to control exact repetition with h_repeat_n.

Source file: dns.c, bodyParser()
Hammer concepts: h_length_value · h_choice · h_repeat_n · h_attr_bool · h_tell · h_sequence


DNS Body Structure

Conceptually:

Body := Questions Answers Authorities Additionals

Each section appears exactly the number of times declared in the header.

If a section count is zero, that section parses as empty.


Step 1: Parsing DNS Labels

DNS names are composed of one or more labels. Each label has:

  • A length byte (1–63)
  • That many data bytes

h_length_value(length_parser, value_parser)

Parameter Type Description
length_parser HParser * Produces the length
value_parser HParser * Repeated length times

In the DNS parser:

HParser *label = h_length_value(
    h_ch_range(0x01, 0x3f), h_ch_range(0x00, 0xff)
);

This enforces:

  • Labels must be at least 1 byte
  • Labels cannot exceed 63 bytes (spec limit)

Step 2: Parsing Compression Pointers

DNS supports name compression using 2-byte pointers.

11xxxxxx xxxxxxxx
  • Top two bits must be 11
  • Remaining 14 bits form an offset from the start of the DNS message

Parsing the Pointer Flag Bits

HParser *pflag  = h_bits(2, false); 
HParser *vpflag = h_attr_bool(pflag, validate_pflag, ctx);

validate_pflag ensures the value equals 3 (binary 11).

If the top two bits are not 11, the parser fails immediately.


Parsing the 14-bit Offset

HParser *temppointer = h_bits(14, false);
HParser *vpointer = h_attr_bool(temppointer, validate_pointer, ctx);

The offset is validated to ensure:

  • It points to a valid position in the message
  • It satisfies any additional safety constraints

Using h_tell for Contextual Validation

Pointer validation requires knowing the current parsing position.

h_tell() returns the current bit position in the input stream.

HParser *curr = h_tell();
HParser *newCurr = h_action(curr, updateCurr, ctx);

This stores the current byte offset in DNSContext, allowing the pointer offset to be validated safely.


Step 3: Defining a DNS Name

A DNS name can legally be encoded as:

  1. Labels + terminating 0x00
  2. Labels + compression pointer
  3. Pointer only

We model this using h_choice.

h_choice(p1, p2, ..., NULL)

Attempts each parser in order until one succeeds.

Repeating Label Fields with h_many1

When labels are present, a DNS packet may have one or more labels. h_many1 handles this:

h_many1(parser)

Matches parser one or more times, collecting results into a sequence. It's greedy: it keeps matching until parser fails, then succeeds with however many matches it found.

HParser *name_end = h_choice(name_termination, pointer, NULL);
HParser *QName = h_choice(
    h_sequence(h_many1(label), name_end, NULL),
    pointer,
    name_termination,
    NULL
);

This ensures:

  • Proper label termination
  • Proper pointer encoding
  • Invalid pointer flag bits are rejected
  • Zero-length labels (except termination) are rejected

Step 4: Parsing the Question Section

Each Question contains:

  • QNAME
  • QTYPE (16 bits)
  • QCLASS (16 bits)
HParser *QType  = h_uint16();
HParser *vQType = h_attr_bool(QType, validate_type, NULL);

HParser *QClass  = h_uint16();
HParser *vQClass = h_attr_bool(QClass, validate_qclass, NULL);

HParser *question =
    h_sequence(QName, vQType, vQClass, NULL);

The section is repeated exactly QDCOUNT times:

HParser *questions =
    h_repeat_n(question, ctx->qdcount);

Step 5: Parsing Resource Records

Answers, Authorities, and Additionals share the same structure:

Field Size
NAME variable
TYPE 16 bits
CLASS 16 bits
TTL 32 bits
RDLENGTH 16 bits
RDATA variable

TYPE and CLASS Validation

Both TYPE and CLASS are validated with h_attr_bool against allowed ranges.

Invalid values cause immediate failure.


RDATA with h_length_value

HParser *RDLength = h_uint16();

HParser *RData =
    h_length_value(
        RDLength,
        h_bits(8, false)
    );

This ensures:

  • Exactly RDLENGTH bytes are consumed
  • Truncated RDATA causes parse failure
  • Extra bytes are not silently ignored

Repeating Record Sections

Each section is repeated according to the header:

HParser *answers     = h_repeat_n(response, ctx->ancount);
HParser *authorities = h_repeat_n(response, ctx->nscount);
HParser *additionals = h_repeat_n(response, ctx->arcount);

The body parser combines everything:

return h_sequence(
    questions,
    answers,
    authorities,
    additionals,
    NULL
);

Failure Propagation

If any of the following occurs:

  • Invalid pointer flag bits
  • Invalid pointer offset
  • Invalid TYPE or CLASS
  • Label too long
  • Missing termination byte
  • Truncated RDATA
  • Section count mismatch

The parse fails immediately.

Hammer automatically propagates failure upward through h_sequence, h_choice, and h_repeat_n.


Summary

Concept Hammer Function Why It Matters in DNS
Length-prefixed parsing h_length_value Labels and RDATA depend on dynamic lengths
Grammar alternatives h_choice Names may be labels or pointers
Exact repetition h_repeat_n Section counts must match header
Context validation h_tell + h_action Required for safe pointer handling
Immediate invariant checks h_attr_bool Reject malformed packets early

Next: Assembling the Full Parser
Previous: Parsing the Header

Clone this wiki locally