6 min read #networking #DNS
On this page

DNS Protocol and Resolution

The Internet's "phone book"—from root servers to recursive resolvers to authoritative servers, a single DNS query can trigger dozens of delegation lookups. Mechanisms such as EDNS0, Anycast, and negative caching keep DNS available under daily query volumes in the trillions.

Overview

DNS is the Internet's "phone book"—translating human-readable domain names into IP addresses. Designed in 1983 (RFC 882/883), its distributed and hierarchical architecture allows it to scale to Internet size: from root to TLD to authoritative, each layer is responsible only for its own namespace. Recursive resolvers do the "heavy lifting"—completing the entire process from root to answer on behalf of stub clients. DNS was originally designed as an insecure protocol (UDP cleartext); later additions like DNSSEC and encrypted transports (DoT/DoH/DoQ) have been added to address this deficiency.

DNS Query Flow

DNS Query Flow: From Stub Resolver to Authoritative ① Stub Resolver (libc / systemd-resolved) Read /etc/resolv.conf → nameserver (e.g., 127.0.0.1:53, AdGuard) ② Recursive Resolver (e.g., AdGuard → upstream DoH/DoT) Check local cache → miss → start from Root hints ③ Root (a–m.root-servers.net) Query "com. NS?" → + glue: a.gtld-servers.net A 192.5.6.30 ④ TLD (com) Query "example.com NS?" → + glue: ns1.example.com A ... ⑤ Authoritative (ns1.example.com) Query "www.example.com A?" → answer: 93.184.216.34 (+ optional RRSIG/NSEC) ⑥ Stub Resolver → Return to Client The recursive resolver acts on behalf of the stub (recursive): it initiates iterative queries hop-by-hop itself (root → TLD → authoritative), assembling the final answer before returning it.

Three types of queries:

  • Recursive (RD=1): The resolver recursively handles the entire process on behalf of the client (stub → recursive)
  • Iterative: Returns a referral or answer in one step (recursive → authoritative)
  • Inverse: PTR query (IP → name, rarely used)

DNS Wire Format

DNS Message (TCP or UDP):

Header (12 bytes):
  Transaction ID (2B): Random, matches request ⇔ response
  Flags (2B):
    QR (bit 0): 0=Query, 1=Response
    OPCODE (bits 1-4): 0=Standard, 1=Inverse, 2=Status, 4=Notify, 5=Update
    AA (bit 5): Authoritative Answer
    TC (bit 6): Truncated (use TCP)
    RD (bit 7): Recursion Desired
    RA (bit 8): Recursion Available
    Z (bits 9-11): 0 (reserved)
    RCODE (bits 12-15): 0=NoError, 1=FormErr, 2=SrvFail, 3=NXDomain, 5=Refused
  QDCOUNT (2B): Questions count
  ANCOUNT (2B): Answers count
  NSCOUNT (2B): Authority records count
  ARCOUNT (2B): Additional records count

Question Section (variable):
  QNAME: label format (see below)
  QTYPE (2B): 1=A, 28=AAAA, 5=CNAME, 15=MX, 2=NS, 6=SOA, 33=SRV, ...
  QCLASS (2B): 1=IN (Internet)

Answer/Authority/Additional Sections (variable):
  Each RR:
    NAME: label format (usually compressed — pointer to previous name)
    TYPE (2B)
    CLASS (2B): 1=IN
    TTL (4B): Cache validity period (seconds)
    RDLENGTH (2B): RDATA length
    RDATA: type-specific

Label Format and Compression

Label encoding:
  [length 1B][label data ...] → length + data, each label is independent
  Last label: length=0 (root, empty label)

  Example: www.example.com → 0x03 'w' 'w' 'w' 0x07 'e' 'x' 'a' 'm' 'p' 'l' 'e' 0x03 'c' 'o' 'm' 0x00

DNS Compression (saves space):
  High 2 bits of length byte = 11 → this is a pointer (not a label!)
  pointer = low 14 bits of offset (from DNS message start)
  Example: 0xC0 0x0C → points to offset 12 (QNAME of the first question)

  This allows referencing domain names that appeared earlier in the same message
  → "example.com" appears in QNAME, NAME of A record, RDATA of NS record → three references → saves ~12+12+12 = 36 bytes

Key Record Types

A (type 1):  RDATA = 4-byte IPv4 address
AAAA (type 28): RDATA = 16-byte IPv6 address
CNAME (type 5): RDATA = canonical name (label format) — alias resolution
  → Restriction: CNAME cannot coexist with other record types (except RRSIG/NSEC)
    That is: if www.example.com is a CNAME, it cannot also have an A record
MX (type 15): RDATA = preference (2B) + exchange (label format)
  → Priority: lower preference value = higher priority
SOA (type 6): RDATA = mname + rname + serial + refresh + retry + expire + minimum TTL
  → Authoritative information for the zone: serial is used for zone transfer version comparison
NS (type 2): RDATA = nameserver (label format) — nameserver authorized for this zone
SRV (type 33): RDATA = priority + weight + port + target
  → _service._proto.name → target host:port
  Example: _sip._tcp.example.com SRV 10 60 5060 sipserver.example.com
PTR (type 12): RDATA = name (label format) — reverse lookup (IP → name)
  → in-addr.arpa for IPv4, ip6.arpa for IPv6
TXT (type 16): RDATA = one or more text strings (length-prefixed)
  → SPF, DKIM, DMARC, verification tokens
CAA (type 257): RDATA = flags + tag + value
  → Restricts which CAs can issue certificates for this domain
  → Example: 0 issue "letsencrypt.org" → only Let's Encrypt can issue

EDNS0 (RFC 6891)

Traditional DNS limitation: UDP payload max 512B → large responses (DNSSEC RRSIG) require TCP fallback

EDNS0: Adds OPT pseudo-RR in the Additional section:
  NAME=0 (root), TYPE=41 (OPT), CLASS=UDP payload size, TTL=flags+RCODE

  UDP payload size: Announces the maximum UDP response size the receiver can handle (usually 1232 or 4096)
  → Reduces the number of TCP fallbacks
  DNSSEC OK (DO bit): Client supports DNSSEC → server should include RRSIG

  flags: DNSSEC OK (DO bit 15)

AXFR (Zone Transfer)

AXFR (full zone transfer):
  Commonly used for: primary → secondary nameserver synchronization

  TCP connection (zone transfer may be >512B):
    secondary: AXFR request (type 252, class IN) → primary
    primary: Returns all RRs in the zone (SOA ... SOA, with SOA at both ends)

IXFR (incremental zone transfer, RFC 1995):
  Transfers only changes (based on SOA serial)

References

  • RFC: 1034, 1035, 6891, 3596 (AAAA), 2782 (SRV), 6844 (CAA)
  • Tools: dig +trace, drill, kdig, delv, tcpdump -nn port 53
  • Source Code: glibc resolv/, systemd-resolved

Keywords: DNS, A/AAAA/MX/NS/SOA/SRV/CNAME/TXT/CAA, recursive resolver, EDNS0, label compression, zone transfer