Learn/ Docs/ Protocol/ Dns Message Format

protocol

DNS Message Format

The wire format of DNS — headers, questions, answers, and how they fit into 512 bytes

Every DNS message is the same shape

Queries and responses use an identical structure. A DNS message is a binary format — not JSON, not XML, not protocol buffers. It is a tightly packed sequence of fields designed in 1987 to fit into a single UDP datagram. The format has survived four decades because it is fast to parse, compact on the wire, and simple enough to implement in any language.

Every DNS message consists of five sections, always in the same order:

+---------------------+
|       Header        |   12 bytes, always present
+---------------------+
|      Question       |   What is being asked
+---------------------+
|       Answer        |   Resource records answering the question
+---------------------+
|      Authority      |   Nameserver records for delegation
+---------------------+
|      Additional     |   Extra records (glue, OPT, TSIG)
+---------------------+

The Header is always exactly 12 bytes. The other four sections can be empty — and in a query, most of them are.

The header: 12 bytes that control everything

The 12-byte header is the control plane of every DNS message:

OffsetFieldSizePurpose
0–1ID16 bitsTransaction identifier — the client picks a random value, the server echoes it back
2–3Flags16 bitsQR, Opcode, AA, TC, RD, RA, Z, AD, CD, RCODE
4–5QDCOUNT16 bitsNumber of questions (almost always 1)
6–7ANCOUNT16 bitsNumber of answer records
8–9NSCOUNT16 bitsNumber of authority records
10–11ARCOUNT16 bitsNumber of additional records

The Flags field packs multiple subfields into 16 bits:

  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA| Z|AD|CD|   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

The most important flags:

  • QR (1 bit): 0 = query, 1 = response. The single bit that distinguishes a question from an answer.
  • RD (Recursion Desired): Set by clients that want the resolver to chase referrals on their behalf. Almost always 1.
  • RA (Recursion Available): Set by resolvers that support recursion. Tells the client it can rely on this server to do the work.
  • AA (Authoritative Answer): Set when the responding server is authoritative for the zone. A response from Cloudflare’s authoritative DNS will have AA=1; a response from Google’s 8.8.8.8 resolver will have AA=0.
  • TC (Truncated): The response was too large for UDP. The client should retry over TCP.
  • AD (Authentic Data): The resolver validated this response with DNSSEC.
  • RCODE (4 bits): The response status — 0 (NOERROR), 3 (NXDOMAIN), 2 (SERVFAIL), and others.

The ID field deserves attention. It is only 16 bits — 65,536 possible values. An attacker who can guess the transaction ID and source port can forge a response before the real one arrives, poisoning the resolver’s cache. This weakness was the basis of the Kaminsky attack in 2008, which led to universal deployment of source port randomization as a second layer of unpredictability.

The question section

The question section describes what the client is asking for. Despite the QDCOUNT field allowing multiple questions, every DNS implementation in practice sends exactly one question per message.

A question has three fields:

FieldSizeDescription
QNAMEVariableThe domain name being queried
QTYPE16 bitsRecord type (A=1, AAAA=28, MX=15, etc.)
QCLASS16 bitsAlmost always IN (Internet, value 1)

QNAME uses DNS wire format for domain names: each label is preceded by a length byte, and the name terminates with a zero byte. www.example.com becomes:

03 77 77 77  07 65 78 61 6d 70 6c 65  03 63 6f 6d  00
 3  w  w  w   7  e  x  a  m  p  l  e   3  c  o  m   (root)

The maximum label length is 63 bytes (since the length byte uses 6 bits; the top 2 bits are reserved for compression pointers). The maximum total domain name length is 255 bytes in wire format, or 253 characters in dotted text representation.

Resource records: the answer format

The Answer, Authority, and Additional sections all contain resource records (RRs). Every resource record has the same structure:

FieldSizeDescription
NAMEVariableThe domain name this record applies to
TYPE16 bitsRecord type (A, AAAA, MX, NS, etc.)
CLASS16 bitsAlmost always IN (1)
TTL32 bitsTime to live in seconds
RDLENGTH16 bitsLength of RDATA in bytes
RDATAVariableThe record data (depends on TYPE)

The TTL is a signed 32-bit integer per RFC 2181, giving a maximum value of 2,147,483,647 seconds — about 68 years. In practice, TTLs range from 60 seconds (for fast failover) to 86,400 seconds (24 hours, common for stable records). The TTL tells resolvers how long they can cache this record before asking again.

RDATA varies by record type. An A record’s RDATA is exactly 4 bytes (an IPv4 address). An AAAA record is 16 bytes. An MX record contains a 2-byte preference value followed by a domain name. A TXT record contains one or more character strings, each prefixed with a length byte.

Name compression: saving bytes by pointing backward

DNS messages frequently repeat domain names — the question section contains www.example.com, and the answer section contains it again. Name compression avoids this waste.

A compression pointer replaces a domain name (or suffix) with a 2-byte reference to an earlier occurrence in the same message. The pointer is identified by its two high bits being set to 11:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| 1  1|                OFFSET                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

The 14-bit offset points to the position in the message where the name (or name suffix) was first written. For example, c0 0c means “the name starting at byte offset 12” — which is typically the beginning of the QNAME in the question section.

Compression is elegant but has been a source of security vulnerabilities. The NAME:WRECK vulnerabilities (2021) exploited malformed compression pointers in embedded TCP/IP stacks, causing buffer overflows in over 100 million IoT devices. Robust parsers must limit pointer-following depth and validate that offsets point backward within the message boundaries.

Walking through a real query and response

A query for www.example.com A record — 45 bytes of DNS payload:

Header (12 bytes): Transaction ID 0xABCD, flags 0x0100 (QR=0, RD=1), QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 (the OPT record for EDNS).

Question (21 bytes): QNAME=www.example.com, QTYPE=A (1), QCLASS=IN (1).

Additional (11 bytes): An OPT pseudo-record signaling EDNS support with a 4096-byte UDP buffer.

The response echoes the header with QR=1 and RA=1, repeats the question, and adds an answer section:

c0 0c        → NAME: pointer to offset 12 (www.example.com)
00 01        → TYPE: A
00 01        → CLASS: IN
00 00 54 60  → TTL: 21,600 seconds (6 hours)
00 04        → RDLENGTH: 4
5d b8 d8 22  → RDATA: 93.184.216.34

The compression pointer c0 0c saves 17 bytes by referencing the domain name already present in the question section. In a response with multiple records for the same name, this saving multiplies.

The 512-byte constraint and what broke it

RFC 1035 specified that DNS messages over UDP must not exceed 512 bytes. This limit was chosen because 576 bytes was the minimum IP datagram size that all hosts were required to handle (RFC 791), and 512 bytes left room for IP and UDP headers.

For the first decade of DNS, 512 bytes was sufficient. But several developments pushed responses beyond this limit:

  • DNSSEC signatures (RRSIG records) can easily be 256+ bytes each
  • Large delegations with many nameservers
  • TXT records for email authentication (SPF, DKIM) growing longer as policies become more complex
  • IPv6 glue records doubling the size of referral responses

When a response exceeded 512 bytes, the server set the TC (truncated) flag, and the client had to retry over TCP — adding a full round trip of latency. This was a performance penalty that the DNS community tolerated for years before EDNS provided a better solution.

Response codes: what the numbers mean

The 4-bit RCODE field in the header communicates the outcome of a query:

RCODENameMeaning
0NOERRORSuccess. If the answer section is empty, the name exists but has no records of the requested type (a condition called NODATA).
1FORMERRThe server could not parse the query. Often indicates an EDNS incompatibility.
2SERVFAILThe server encountered an internal error — DNSSEC validation failure, authoritative timeout, or lame delegation.
3NXDOMAINThe domain name does not exist. The strongest negative assertion in DNS.
4NOTIMPThe server does not support the requested opcode.
5REFUSEDThe server refuses the query, typically for policy reasons (e.g., recursion not available to this client).

EDNS extends the RCODE to 12 bits, enabling additional codes like BADVERS (16), BADCOOKIE (23), and others used for TSIG authentication and dynamic updates.

The distinction between NXDOMAIN and NODATA is subtle but critical. NXDOMAIN means the name itself does not exist — no records of any type. NODATA (NOERROR with an empty answer) means the name exists but not with the requested type. Confusing the two can break wildcard matching and DNSSEC validation.