DNS
20 min
DNS resolves a human-readable hostname to an IP address — but the
answer isn't always a single hop. A CNAME record says "this name is
really just an alias for another name," so a resolver may have to
follow a whole chain of aliases before finally landing on an A record
(the actual IP).
def resolve_chain(records, hostname):
current = hostname
while True:
record_type, value = records[current]
if record_type == "A":
return value
current = value # follow the CNAME to the next name
This is the exact same parent-pointer-graph-walk shape as Git's commit_history from the previous module -- just following a different kind of chain to its terminal node.
Every DNS record carries a TTL (time-to-live, in seconds) telling resolvers how long they're allowed to reuse a cached answer before re-querying the authoritative server. A resolver checks its cache first: an unexpired entry is a hit (instant, no network round trip), and the resolver doesn't even contact the authoritative server — which is exactly why a hit returns the OLD cached value even if the "real" answer has since changed upstream, until the TTL finally runs out and forces a fresh miss.
Write `resolve_chain(records, hostname)`: `records` maps each hostname to either `("A", ip)` or `("CNAME", target)`. Follow CNAME redirects until you hit an A record, and return that IP.
Write `resolve_with_ttl(cache, hostname, ip, now, ttl=60)`: if `hostname` is already in `cache` with an unexpired entry (`now < expires_at`), return the CACHED ip unchanged (a hit — don't touch the cache). Otherwise store `(ip, now + ttl)` in `cache` and return the fresh `ip` (a miss).
Why does a DNS resolver cache respect a record's TTL instead of always querying the authoritative server?
You can follow a CNAME chain to its terminal A record, and implement TTL-based cache hit/miss logic that trades staleness for latency.