DNS Recursive Resolution, Packet by Packet
This walkthrough uses the capture from Chris Greer's video How DNS Works Under the Hood (Packet by Packet), Part 3 of his DNS series. We decoded it with VisualEther, which turns a Wireshark PCAP into a sequence diagram with a plain-English caption on every message. Chris hasn't reviewed or endorsed this article; any annotation errors are ours.
Chris's capture, dns_full_recursion.pcapng, is available from his GitHub repository. Download it there to follow along in Wireshark.
💡 Read the walkthrough and the diagram side by side. Open the interactive diagram (or PDF) in a split-screen window so the captions stay in view as you read. In Edge, Chrome, or Firefox, right-click the link and pick your browser's split-screen option (Edge labels it "Open link in split screen window"). On macOS, prefer Chrome or Firefox — Safari has no in-browser split-screen. Otherwise, open the diagram in a new tab and snap the two windows side by side.
Overview
When a laptop looks up a name, it sends one question to its DNS resolver and gets one answer back. Everything in between happens where the laptop can't see it: the resolver walks the DNS tree from the root down, asking a different server at each level. The capture was taken at the resolver, so both sides are on the wire: the client's two packets over IPv4 and the resolver's 31 upstream frames over IPv6.
The client asks for the address of b2b.infoblox.com. The whole lookup takes 159 ms and involves three servers beyond the resolver:
The walkthrough follows the capture in six phases:
- The client asks once (frame 1). A recursive query: "find this for me."
- A cold resolver starts at the root (frames 2–5). The resolver primes its list of root servers and asks for the name, but both replies come back truncated.
- Retry over TCP (frames 6–20). The resolver repeats both questions over TCP and gets the root server list and a referral to
com. - The com referral (frames 21–23). A
comserver refers the resolver to theinfoblox.comservers, with their addresses. - The authoritative answer (frames 24 and 30). An
infoblox.comserver returns the address. - The client gets its answer (frame 31). The resolver passes the address back.
The remaining frames close the two TCP connections to the root, including one retransmitted FIN.
A primer on recursive resolution
A few ideas make the frames easy to read:
- Three roles. The client runs a small stub resolver that can only ask questions. The recursive resolver does the legwork of finding answers. Authoritative servers hold the actual data for their zone: the root zone,
com,infoblox.com, and so on. - The DNS tree and delegation. DNS names form a tree read right to left: the root, then
com, theninfoblox.com, thenb2b.infoblox.com. Each level delegates the zone below it to that zone's own servers. A server that doesn't hold the answer but knows who does replies with a referral: "ask these servers instead." - Header bits tell the story.
RD(recursion desired) is the client asking for the whole lookup.RA(recursion available) is a server saying it offers that service.AA(authoritative answer) marks a reply from the server that owns the zone.TC(truncated) says the reply didn't fit. - Glue. A referral names servers, but a name is useless without an address. The referral carries those addresses in its additional section. When the name servers live inside the zone they serve, as
ns5.infoblox.comdoes, these glue records are the only way to reach them. - UDP size and TCP fallback. DNS normally uses UDP. With EDNS (RFC 6891), the sender advertises how large a UDP reply it can accept. If the reply is larger, the server sends a truncated reply with
TC=1, and the resolver asks again over TCP. - DNSSEC records. Signed zones add
RRSIGrecords (signatures) to answers andDSrecords to referrals. ADSrecord is the parent zone vouching for its child's key. A resolver sets theDObit to ask for these records. - Caching and TTL. Every record carries a time-to=live (TTL). A resolver reuses cached records until they expire, which is why most lookups skip the root entirely. This one didn't, as we'll see.
Phase 1 — The client asks once (frame 1)
The client (192.168.80.4) asks the resolver (192.168.0.140) for the A record, the IPv4 address, of b2b.infoblox.com. The query sets RD=1: do the whole lookup for me.
It also sets AD=1, which asks the resolver to report whether it validated the answer with DNSSEC (RFC 6840). It includes an EDNS client cookie (RFC 7873) and advertises a 1,232-byte UDP buffer. It leaves the DO bit clear, so it doesn't want DNSSEC records in the reply.
The client sends nothing else until the answer arrives in frame 31.
Phase 2 — A cold resolver starts at the root (frames 2–5)
The resolver behaves like one with an empty cache: it starts at the root and primes. Both replies come back truncated.
Frame 2 — the real question, sent to a root server. 0.6 ms after the client's query, the resolver asks 2001:500:12::d0d for b2b.infoblox.com. Frame 15 later proves this is g.root-servers.net. Nothing in the capture supplies that address before this query, so it comes from the resolver's built-in root hints: the list of root server addresses every resolver ships with.
Four details matter here:
RD=0. This is an iterative query: the root answers only from its own zone and won't chase the name further.- The full name goes to the root. A resolver using QNAME minimization (RFC 9156) would ask the root only about
com. This one doesn't minimize. - The query has a fresh random ID (
0x5d9b) and source port (51976). Every upstream query in the capture gets its own, which makes forged replies hard to land. - The resolver advertises a 512-byte UDP limit, with
DO=1asking for DNSSEC records. That combination drives the rest of this phase.
Frame 3 — priming. 0.1 ms later, the resolver sends the same root server a priming query (RFC 8109): "who are the root servers?", an NS query for the root zone. Priming replaces the built-in hints with the live, signed list. The resolver doesn't wait for it; the real query is already on its way.
Frames 4 and 5 — both replies truncated. 27 ms later, the root answers both queries with TC=1 and empty answer and authority sections. Each truncated reply is just the header, the question, and the EDNS record: 28 bytes for the priming reply, the same size as its query. The full answers, seen later over TCP, are 1,109 and 1,179 bytes.
Here is frame 5 as Wireshark decodes it. Under Flags, the Truncated bit is set, and the record counts show one question, no answers, no authority records, and one additional record: the EDNS record.
frame : Frame 5: Packet, 109 bytes on wire (872 bits), 109 bytes captured (872 bits) on interface unknown, id 0
Interface id : 0 (unknown)
sll : Linux cooked capture v1
ipv6 : Internet Protocol Version 6, Src: 2001:500:12::d0d, Dst: 2001:470:e49c:1000::140
.... 0000 0000 .... .... .... .... .... = Traffic Class : 0x00 (DSCP: CS0, ECN: Not-ECT)
Source Address : 2001:500:12::d0d
Source or Destination Address : 2001:500:12::d0d
Destination Address : 2001:470:e49c:1000::140
Source or Destination Address : 2001:470:e49c:1000::140
udp : User Datagram Protocol, Src Port: 53, Dst Port: 51976
Timestamps
dns : Domain Name System (response)
Flags : 0x8210 Standard query response, No error
Queries
b2b.infoblox.com : type A, class IN
Additional records
<Root> : type OPT
Z : 0x8000
Frame 5, the root's truncated reply to the A query in frame 2. Flags → Truncated: Message is truncated. The counts read Answer RRs 0, Authority RRs 0, Additional RRs 1: only the question and the EDNS record came back.
Two header bits in these replies are worth checking. Frame 4 still has AA=1, because the root is authoritative for its own server list. Frame 5 copies back the CD (checking disabled) bit from frame 2, as RFC 4035 requires.
The root did nothing wrong: with the DNSSEC records the resolver asked for, neither answer fits in 512 bytes. Both would have fit in the 1,232-byte buffer the client itself advertised in frame 1.
Phase 3 — Retry over TCP (frames 6–20)
A resolver that gets TC=1 must ask again over TCP. RFC 7766 requires every general-purpose DNS implementation to support TCP, and truncated replies are a main reason why.
Frames 6–12 — two connections. 0.4 ms after the truncated replies, the resolver opens two TCP connections to the root on port 53: source port 44653 for the priming query and 43879 for the real one. RFC 7766 would let it send both queries on one connection; this resolver opens one per query.
The resolver's SYN offers an MSS of 1,220 bytes: 1,280 minus 40 bytes of IPv6 header and 20 of TCP. That is consistent with a 1,280-byte MTU, the IPv6 minimum, on the resolver's path. The root's SYN-ACK arrives 25.9 ms later. The handshake costs a full round trip before any DNS can flow.
Frames 10 and 13 — the same questions, over TCP. Each query gets a new ID. Over TCP, every DNS message is preceded by a 2-byte length field (RFC 1035), so the 28-byte priming query travels as 30 bytes. The EDNS UDP size now reads 1,220, but it only limits UDP replies.
Frame 15 — the root server list. The full priming answer, 1,109 bytes:
AA=1, because the root owns this data.- 14 answer records: the 13 root server names,
athroughm.root-servers.net, plus anRRSIGsigning that set. - 27 additional records: an
Aand anAAAAaddress for each server, plus the EDNS record. - A TTL of 518,400 seconds, six days.
This is where the capture proves the resolver has been talking to G-root: the AAAA record for g.root-servers.net is 2001:500:12::d0d.
Frame 19 — the referral to com. The root doesn't know b2b.infoblox.com. It has no answer (AA=0, zero answer records), but sends a pointer instead:
- 15 authority records: 13
NSrecords naming thecomservers,athroughm.gtld-servers.net, then aDSrecord forcomand anRRSIGover it. - 27 additional records:
AandAAAAglue for all 13 servers, plus the EDNS record.
The DS record (key tag 19718, algorithm 13, SHA-256 digest) is the root vouching for the signing key of com. The RRSIG covers only the DS record. A parent zone never signs the NS records at a delegation, because the child zone is authoritative for them. The NS records and glue carry a two-day TTL (172,800 seconds); the DS record, one day.
Frames 14, 16, 17, 18, and 20 — TCP bookkeeping. These are acknowledgments and the resolver's first FIN. The resolver closes each connection as soon as it has its answer. Because it closes first, it holds the TCP TIME_WAIT state, not the root server.
Phase 4 — The com referral (frames 21–23)
Frame 21. 0.6 ms after the root's referral, the resolver asks 2001:503:eea3::30, which the glue in frame 19 names as g.gtld-servers.net. It sends the same full-name question, with RD=0, back over UDP with 512 bytes advertised.
Frame 23. The com server replies in 7.9 ms, the fastest exchange of the lookup, with another referral:
- 6 authority records: 4
NSrecords forinfoblox.com(ns5,ns6,ns7, andns1), aDSrecord (key tag 19717, algorithm 8), and anRRSIGover it, signed bycom. - 7 additional records: glue for
ns5(AAAAandA),ns6(A),ns7(AandAAAA), andns1(A), plus the EDNS record.
Frame 23 decoded, with the four NS records, the DS record, and its signature under Authoritative nameservers, and the glue addresses under Additional records:
frame : Frame 23: Packet, 448 bytes on wire (3584 bits), 448 bytes captured (3584 bits) on interface unknown, id 0
Interface id : 0 (unknown)
sll : Linux cooked capture v1
ipv6 : Internet Protocol Version 6, Src: 2001:503:eea3::30, Dst: 2001:470:e49c:1000::140
.... 0000 0000 .... .... .... .... .... = Traffic Class : 0x00 (DSCP: CS0, ECN: Not-ECT)
Source Address : 2001:503:eea3::30
Source or Destination Address : 2001:503:eea3::30
Destination Address : 2001:470:e49c:1000::140
Source or Destination Address : 2001:470:e49c:1000::140
udp : User Datagram Protocol, Src Port: 53, Dst Port: 41447
Timestamps
dns : Domain Name System (response)
Flags : 0x8010 Standard query response, No error
Queries
b2b.infoblox.com : type A, class IN
Authoritative nameservers
infoblox.com : type NS, class IN, ns ns5.infoblox.com
infoblox.com : type NS, class IN, ns ns6.infoblox.com
infoblox.com : type NS, class IN, ns ns7.infoblox.com
infoblox.com : type NS, class IN, ns ns1.infoblox.com
infoblox.com : type DS, class IN
infoblox.com : type RRSIG, class IN
Additional records
ns5.infoblox.com : type AAAA, class IN, addr 2600:1f14:3515:8202:da8:7299:3926:a2c1
ns5.infoblox.com : type A, class IN, addr 35.163.191.194
ns6.infoblox.com : type A, class IN, addr 104.40.90.56
ns7.infoblox.com : type A, class IN, addr 18.209.84.34
ns7.infoblox.com : type AAAA, class IN, addr 2600:1f18:b5d:c600:e66b:3429:41ff:5da3
ns1.infoblox.com : type A, class IN, addr 23.96.113.219
<Root> : type OPT
Z : 0x8000
Frame 23, the com server's referral to infoblox.com. Answer RRs is 0, and the Authoritative flag is clear: this server is pointing elsewhere, not answering. Open any record to see its TTL: 172,800 seconds for the NS records and glue, 86,400 for the DS record and its signature.
Here glue is essential, not a convenience. The name servers for infoblox.com are themselves inside infoblox.com. Without their addresses in this referral, the resolver would need to resolve ns5.infoblox.com to reach the server that holds ns5.infoblox.com.
At 384 bytes, this referral fits in 512 bytes. No truncation, no TCP.
Phase 5 — The authoritative answer (frames 24 and 30)
Frame 24. The resolver asks 2600:1f14:3515:8202:da8:7299:3926:a2c1, which the glue in frame 23 names as ns5.infoblox.com. Only ns5 and ns7 came with IPv6 glue, and this resolver used IPv6 for every upstream query.
Frame 30. ns5 answers after 65.6 ms, the slowest single exchange of the lookup, with AA=1: this server owns the zone, so its answer is final.
- 3 answer records: the
Arecord8.39.143.138and twoRRSIGrecords over it. Both signatures use algorithm 8, but come from two different keys (key tags 5349 and 33823). - A TTL of 10 seconds. The delegations above this record last for days, but the address itself may be cached for only 10 seconds.
Phase 6 — The client gets its answer (frame 31)
0.4 ms later, the resolver answers the client. The transaction ID 0x2380 matches frame 1.
RA=1: this server offers recursion.AA=0: the resolver is relaying the answer, not authoritative for it.- Only the
Arecord comes back. The client didn't setDO, so the signatures are left out. AD=0: the resolver doesn't claim to have validated the answer (more on this below).- The resolver echoes the client's cookie and adds its own server cookie.
From the client's side, the lookup was two packets and 159 ms. The other 31 frames happened behind the resolver.
Loose ends: closing the TCP connections (frames 22, 25–29, 32–33)
While the resolver works on the com and infoblox.com steps, the two root connections close in the background. Connection 43879 closes cleanly: the resolver's FIN (frame 22), the root's ACK and FIN (frames 27 and 28), and the resolver's final ACK (frame 29).
Connection 44653 takes longer. The root's FIN in frame 26 gets no ACK in the capture, so the root retransmits it one second later, in frame 32. The resolver acknowledges the retransmission in frame 33. The capture doesn't show why the first FIN went unacknowledged. It made no difference to the client, which had its answer 950 ms earlier.
Where the time went
| Step | Frames | Time |
|---|---|---|
| Root, over UDP (truncated) | 2–5 | 27 ms |
| Root, retry over TCP | 6–19 | 56 ms |
com server | 21–23 | 8 ms |
infoblox.com server | 24–30 | 66 ms |
| Total, client query to answer | 1–31 | 159 ms |
The truncation detour costs about 56 ms, more than a third of the lookup: a TCP handshake plus a second query round trip. A resolver advertising 1,232 bytes, the size the client used, would have received both root answers over UDP the first time.
Long delegation TTLs make this cost rare. Once cached, the root and com referrals last for days: the next lookup for another .com name starts at the com servers, and one for another infoblox.com name starts at its own servers.
DNSSEC on the wire, and what's missing
The resolver asked for DNSSEC records (DO=1), and the chain of trust is visible in the replies:
- Frame 15 carries the root's signature over its own server list.
- Frame 19 carries the
DSrecord forcom, signed by the root. - Frame 23 carries the
DSrecord forinfoblox.com, signed bycom. - Frame 30 carries the signatures over the final address.
Validation also needs the zone keys: the DNSKEY records for the root, com, and infoblox.com. The capture contains no DNSKEY queries, and the reply to the client leaves AD clear. The signatures ride along, but nothing in this capture shows them being checked.
The capture was recorded in November 2025. The key tags and TTLs above describe the zones at that time.
Takeaways
- The client sees almost nothing. It sends one question and gets one answer. Troubleshooting a slow or failed lookup means capturing behind the resolver, as Chris did here.
- Referrals and glue drive the walk. Each level answers "not me, ask them," and the glue addresses let the resolver take the next step, especially when a zone's name servers live inside the zone.
- The header bits tell you who is speaking.
RD=1from a client,RD=0upstream,AA=1from the zone owner,RA=1from the resolver. - The EDNS buffer size has a real cost. A 512-byte limit with DNSSEC turned two UDP exchanges into two TCP connections and added a third of the lookup time.
- Caching hides all this. With a two-day TTL on the
comdelegation and a 10-second TTL on the address, most repeat lookups start partway down the tree.
Try it on your own capture
Every arrow in this walkthrough was decoded and captioned by VisualEther from the raw Wireshark PCAP, and the interactive diagram above opens each frame's full field tree. Point it at your own DNS, routing, or 5G trace and read it the same way.