-
Notifications
You must be signed in to change notification settings - Fork 1
DNS Assembling the Parser
In the DNS example, "assembling the parser" is less about building one
giant HParser * and more about wiring together:
- A shared context (
DNSContext) that holds header-derived information\ - A header parse that fills that context\
- A body parse that uses the stored values to validate the rest of the message
This document explains how main.c, dns.h, and the DNS parser
entrypoints work together.
DNS is a header-driven protocol. The header tells you how many Questions/Answers/etc. you must parse. To support that, the parser uses a shared context struct between header and body parsing:
typedef struct {
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
uint16_t packet_len;
uint16_t curr_storage;
} DNSContext;| Field | Set By | Used By | Meaning |
|---|---|---|---|
qdcount |
header parser (h_action) |
body parser (h_repeat_n) |
number of Question entries |
ancount |
header parser (h_action) |
body parser (h_repeat_n) |
number of Answer RRs |
nscount |
header parser (h_action) |
body parser (h_repeat_n) |
number of Authority RRs |
arcount |
header parser (h_action) |
body parser (h_repeat_n) |
number of Additional RRs |
packet_len |
main.c |
pointer validation | total message length in bytes |
curr_storage |
body parser (h_tell + h_action) |
pointer validation | "current position" used to validate compression pointers |
Key idea: the header parser doesn't just "recognize bytes" - it also initializes state the body parser depends on.
The current implementation performs orchestration directly inside
main.c.
#include "dns.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
uint8_t input[1024] = {0};
FILE *f_input = stdin;
if (argc > 1) {
f_input = fopen(argv[1], "rb");
if (!f_input) {
perror("Failed to open file");
return 1;
}
} else {
puts("Reading from stdin");
}
size_t input_size = fread(input, 1, sizeof(input), f_input);
if (f_input != stdin) fclose(f_input);
DNSContext ctx = {0};
ctx.packet_len = input_size;
// Header
HParser *header = headerParser(&ctx);
HParseResult *header_result =
h_parse(header, input, input_size);
if (!header_result){
puts("Bad header, Packet Failed");
return 1;
}
// Body
if (input_size < 12){
puts("Bad body, Packet Failed");
h_parse_result_free(header_result);
return 1;
}
HParser *body = bodyParser(&ctx);
HParseResult *body_result =
h_parse(body, input + 12, input_size - 12);
if (!body_result){
puts("Bad body, Packet Failed");
h_parse_result_free(header_result);
return 1;
}
puts("Packet Passed");
h_parse_result_free(header_result);
h_parse_result_free(body_result);
return 0;
}- Reads up to 1024 bytes
- Accepts either a file argument or stdin
- Stores total size in
ctx.packet_len
HParser *header = headerParser(&ctx);
HParseResult *header_result = h_parse(header, input, input_size);If successful:
- Validates the fixed 12-byte DNS header
- Stores
qdcount,ancount,nscount, andarcountinctx
If it fails → packet is rejected immediately.
if (input_size < 12)Ensures safe slicing for the body parse.
The body parser is run against the bytes after the header:
HParser *body = bodyParser(&ctx);
HParseResult *body_result =
h_parse(body, input + 12, input_size - 12);The body parser now relies on the stored counts:
-
qdcount→ drives question parsing -
ancount,nscount,arcount→ drive RR parsing -
packet_len+curr_storage→ validate compression pointer bounds
If body parsing fails → packet is rejected.
If both parses succeed, the function frees the parse results and returns 0 (success):
h_parse_result_free(header_result);
h_parse_result_free(body_result);Hammer allocates structures for parse results. Even if you don't use the AST, you should free the results to avoid leaks.
What makes this assembly pattern work is the shared context:
- The header parse validates the fixed structure and populates
DNSContext - The body parse uses that stored information to validate variable-length sections
-
main.caccepts a packet only if both parses succeed
This is a clean, practical approach for a validator:
- It rejects invalid packets early
- It keeps header and body logic modular
- It uses the DNS spec's "counts drive structure" rule directly in the grammar
Next: DNS-Running-and-Testing Previous: DNS-Parsing-the-Body
Learn Hammer
Protocol Examples
NTP
- NTP Overview
- Parsing the Header
- Parsing Data Fields
- Extension Fields and MAC
- Assembling the Parser
- Hex Input Preprocessing
- Running and Testing
DNS
TFTP
- TFTP Overview
- RRQ/WRQ Packets
- DATA Packets
- ACK Packets
- ERROR Packets
- Assembling the Parser
- Running and Testing
References
- Hammer Quick Reference
- Parsing Backends
- Unit Testing
- Using RTEMS
- Extending Hammer
- Adding a New Example
- Adding a New Binding
Further Reading