Thuta Learning
IntermediateDevOps & Toolsbeginner

How DNS Resolution Works

What you'll walk away with

  • Explain the core ideas behind How DNS Resolution Works
  • Read the diagram and trace how data actually moves through it
  • Run the sample code and verify its output

Build the mental model

DNS is usually called a phone book for domain names, but that image hides what matters most: a phone book is one list in one place, and DNS deliberately is not. A single server holding every domain would be a single point for all query traffic, all update authority, and all failures. So DNS is arranged as a hierarchy in which each layer mostly just points at the next. The root servers know only which servers are responsible for each top-level domain: .com, .org, .mm. The TLD servers know only which authoritative nameservers hold each domain beneath them. The actual records live on the authoritative server, which is what lets you edit your own domain immediately without asking anyone.

In practice your browser does not walk this path. A stub resolver in the operating system sends one query to a recursive resolver — your ISP's, or a public one — and asks for an answer. The recursive resolver is what queries root, then TLD, then authoritative, following referrals.

The record type says what a name maps to: A gives an IPv4 address, AAAA an IPv6 address, CNAME points one name at another, MX the mail servers, NS the authoritative nameservers, TXT arbitrary text such as ownership proofs. Every answer also carries a TTL, meaning how long it may be cached. That field has an outsized consequence: hours before migrating to a new server, lower the TTL to something like 300 seconds. Otherwise resolvers that cached the old address with a TTL of 86400 keep handing it out for another full day, no matter how quickly you edit the record.

text
RECURSIVE DNS RESOLUTION, STEP BY STEP
--------------------------------------
  Stub          Recursive       Root          TLD             Auth
  resolver      resolver        servers       (.test)         NS
  |             |               |             |               |
  |-----(1)---->|               |             |               |
  | A? www.shop.test            |             |               |
  |             |------(2)----->|             |               |
  |             |<-----(3)------|             |               |
  |             | "ask the .test servers"     |               |
  |             |-------------(4)------------>|               |
  |             |<------------(5)-------------|               |
  |             | "ask ns1.shop.test"         |               |
  |             |---------------------(6)-------------------->|
  |             |<--------------------(7)---------------------|
  |             | A 203.0.113.10   TTL 300    |               |
  |<----(8)-----|               |             |               |
  | answer, cached for 300 s    |             |               |

Connect it to a real scenario

Say you are moving a web server to a new hosting provider next week. The common mistake is the plan that reads 'change the A record at midnight and we are done'. What actually happens is that some users reach the new server immediately while others keep landing on the old one for another day — which means some customers' orders are flowing into a database you are about to switch off.

The correct sequence has four steps. First, 48 hours before the migration, lower the A record's TTL from 86400 to 300. You must do this in advance because that change itself only propagates at the speed of the old TTL. Second, bring the new server up and verify it by pinning the name in your own machine's hosts file, before any real user is affected. Third, change the record — the world now follows within about five minutes. Fourth, do not switch the old server off; leave it running for a few hours and watch its logs until incoming requests reach zero. Once stable, raise the TTL again. The model to keep is that DNS is not an instant switch but many independent caches, and the TTL you set earlier already decided how long your cutover takes.

Try the working example

python
import struct

# A real DNS response, captured once and hard-coded so this runs offline.
# Query was "www.example.test A"; the answer uses a documentation address.
MESSAGE = bytes.fromhex(
    "1a2b"      # ID: echoed back so the client can match reply to request
    "8180"      # flags: response, recursion desired + available, no error
    "0001"      # QDCOUNT: one question
    "0001"      # ANCOUNT: one answer
    "0000"      # NSCOUNT
    "0000"      # ARCOUNT
    "03777777076578616d706c65047465737400"   # www.example.test as labels
    "0001"      # QTYPE  = A
    "0001"      # QCLASS = IN
    "c00c"      # answer name: pointer back to offset 12 (compression)
    "0001"      # TYPE  = A
    "0001"      # CLASS = IN
    "0000012c"  # TTL, in seconds
    "0004"      # RDLENGTH
    "cb00710a"  # RDATA: the IPv4 address
)

TYPES = {1: "A", 2: "NS", 5: "CNAME", 15: "MX", 16: "TXT", 28: "AAAA"}
RCODES = {0: "NOERROR", 2: "SERVFAIL", 3: "NXDOMAIN"}

# ---- 1. The fixed 12-byte header ---------------------------------------
ident, flags, qd, an, ns, ar = struct.unpack("!HHHHHH", MESSAGE[:12])
print("transaction id :", hex(ident))
print("QR             :", "response" if flags >> 15 else "query")
print("recursion      : desired=%d available=%d"
      % ((flags >> 8) & 1, (flags >> 7) & 1))
print("authoritative  :", bool((flags >> 10) & 1))
print("rcode          :", RCODES.get(flags & 0xF, "OTHER"))
print("counts         : qd=%d an=%d ns=%d ar=%d" % (qd, an, ns, ar))


# ---- 2. Names are length-prefixed labels, not dotted strings ------------
def read_name(msg, pos):
    labels = []
    while True:
        n = msg[pos]
        if n == 0:
            return ".".join(labels), pos + 1
        if n & 0xC0 == 0xC0:                       # compression pointer
            target = struct.unpack("!H", msg[pos:pos + 2])[0] & 0x3FFF
            name, _ = read_name(msg, target)
            labels.append(name)
            return ".".join(labels), pos + 2
        labels.append(msg[pos + 1:pos + 1 + n].decode())
        pos += 1 + n


qname, pos = read_name(MESSAGE, 12)
qtype, qclass = struct.unpack("!HH", MESSAGE[pos:pos + 4])
pos += 4
print()
print("question       : %s  type=%s" % (qname, TYPES.get(qtype, qtype)))

# ---- 3. The answer record ----------------------------------------------
aname, pos = read_name(MESSAGE, pos)
atype, aclass, ttl, rdlen = struct.unpack("!HHIH", MESSAGE[pos:pos + 10])
pos += 10
rdata = MESSAGE[pos:pos + rdlen]
print("answer name    :", aname)
print("answer type    :", TYPES.get(atype, atype))
print("ttl            : %d seconds (%d minutes)" % (ttl, ttl // 60))
print("rdata          :", ".".join(str(b) for b in rdata))
print()
print("whole message  : %d bytes (%d of them the header)"
      % (len(MESSAGE), 12))
You should see
transaction id : 0x1a2b
QR             : response
recursion      : desired=1 available=1
authoritative  : False
rcode          : NOERROR
counts         : qd=1 an=1 ns=0 ar=0

question       : www.example.test  type=A
answer name    : www.example.test
answer type    : A
ttl            : 300 seconds (5 minutes)
rdata          : 203.0.113.10

whole message  : 50 bytes (12 of them the header)

5-minute try-it

Change the TTL in the hex string from 0000012c to 00015180 (86400) and rerun. Then set ANCOUNT to 0002 and reason about why the parser now misbehaves: how does a DNS parser know how many records to read, and what would you have to add to the code to handle more than one answer?

One important caution

Assuming a record change takes effect worldwide immediately. Existing cache entries only disappear when their own TTL expires.

Trying to put a CNAME at the zone apex. The standard forbids a CNAME alongside the SOA and NS records that must exist there; you need a provider-specific ALIAS or ANAME record instead.

Cloudflare Learning Center - What is DNS?Computer Networking

Easy traps

  • Assuming a record change takes effect worldwide immediately. Existing cache entries only disappear when their own TTL expires.
  • Trying to put a CNAME at the zone apex. The standard forbids a CNAME alongside the SOA and NS records that must exist there; you need a provider-specific ALIAS or ANAME record instead.
  • Validate sample code in a local or test environment before applying it to a production network.

Exercise

Change the TTL in the hex string from 0000012c to 00015180 (86400) and rerun. Then set ANCOUNT to 0002 and reason about why the parser now misbehaves: how does a DNS parser know how many records to read, and what would you have to add to the code to handle more than one answer?

You'll know it worked when: transaction id : 0x1a2b QR : response recursion : desired=1 available=1 authoritative : False rcode : NOERROR counts : qd=1 an=1 ns=0 ar=0 question : www.example.test type=A answer name : www.example.test answer type : A ttl : 300 seconds (5 minutes) rdata : 203.0.113.10 whole message : 50 bytes (12 of them the header)

How DNS Resolution Works | Thuta Learning