ProjectsDNS Resolver
A command-line tool that turns a host name into an IP address by building DNS messages byte by byte, sending them over UDP and decoding the binary replies, and that ends by walking the name tree itself, from a root server down to the server that owns the answer. Building it teaches how a binary protocol is laid out, how a request and its reply find each other with no connection between them, and how the internet's naming system is split across many servers that each know only who to ask next.
From Coding Challenges #22 ↗ by John Crickett
No sign-up. You tell the mentor your language: Java, Python or Node.js.
What you'll learn · 8 concepts
In the order the steps use them. Each one links to the best places to learn it, and you explain it in your own words once its steps are built.
Step 1 writes a header and a question by hand, and step 3 reads all four sections back from the server's reply.
Covers: one format for queries and responses · the fixed 12-byte header · the transaction ID · the flags and the response code · the four section counts · the question echoed in the response
RFC 1035, section 4.1: Message format ↗ · Implement DNS in a weekend (Julia Evans), part 1: Build a DNS query ↗
Step 1 builds the header from 16-bit numbers and packed flags, and its check compares your bytes with a hex sample, where a swapped pair of bytes shows up at once.
Covers: big-endian network byte order · how a processor stores numbers in memory · packing small fields into one number · shifts and masks · bit numbering in RFC diagrams · reading a hex dump
Endianness (Wikipedia) ↗ · Beej's Guide to Network Programming: Byte Order ↗ · Mask (computing) (Wikipedia) ↗
Step 1 encodes the query's name as labels, and step 3 must decode names in the reply that are partly or wholly pointers.
Covers: length-prefixed labels · the empty root label · limits on label and name length · compression pointers and their offsets · where reading continues after a pointer · pointer loops and offsets out of range · names compared without case
RFC 1035, section 4.1.4: Message compression ↗ · Implement DNS in a weekend (Julia Evans), part 2: Parse the response ↗ · RFC 4343: Domain Name System case insensitivity clarification ↗
Step 2 sends the query in one datagram and checks that the reply carries your ID, and step 4 sends many queries to servers that may never answer.
Covers: datagrams versus a stream · no connection and no delivery guarantee · timeouts and retries · matching a reply by ID, address and port · forged replies and random IDs and ports · the size limit, the TC flag and falling back to TCP
RFC 5452: Measures for making DNS more resilient against forged answers ↗ · RFC 7766: DNS transport over TCP ↗ · DNS Flag Day 2020: message size and fragmentation ↗
Step 3 reads the answer records with their types and TTLs, and the optional last step follows CNAME records to the address behind an alias.
Covers: the shared record layout · record types such as A, AAAA, NS and CNAME · the data length and skipping unknown types · the TTL and caching · aliases and CNAME chains · no such name versus no data
RFC 1035, section 3.2: Resource record definitions ↗ · RFC 2181: Clarifications to the DNS specification ↗ · List of DNS record types (Wikipedia) ↗
Step 4 starts at a root server instead of a public resolver and walks down the tree one zone at a time.
Covers: the name space as a tree · zones and delegation · root servers and anycast · top-level domain servers · authoritative servers and the AA flag · root hints as the starting point
RFC 1034: Domain names, concepts and facilities ↗ · Root Server Technical Operations Association ↗ · Root Servers (IANA) ↗
Steps 1 to 3 let a public resolver do the recursion for you, and step 4 clears the RD flag and does that work in your own program.
Covers: stub resolvers, recursive resolvers and authoritative servers · the RD and RA flags · iterative queries and following referrals yourself · caching along the way · where your own resolver sits
RFC 1034, section 4.3.1: Queries and responses ↗ · RFC 9499: DNS terminology ↗ · Implement DNS in a weekend (Julia Evans), part 3: Implement our resolver ↗
Step 4 has to read each referral, use its glue when it is there, and resolve a name server on its own when it isn't.
Covers: what a referral looks like · the authority and additional sections · glue and the chicken-and-egg problem · name servers without glue · trusting only in-bailiwick records · limits on loops and depth
RFC 9471: DNS glue requirements in referral responses ↗ · An Illustrated Guide to the Kaminsky DNS Vulnerability (Steve Friedl) ↗
What you'll build · 5 steps
Steps of 30 to 60 minutes, in your own editor and language. Each one has a check, so you know when it works.
Create the project in a language you're comfortable using for network programming, and keep a tool such as dig and a hex dump tool at hand to compare your bytes and answers with.
Works whenYour program runs, and dig answers a lookup for a well-known name on your machine.
Build a DNS query for a host name as raw bytes: a header with a random ID, only the recursion desired flag set, a question count of 1 and the other counts at 0, then a question holding the name as length-prefixed labels, type A and class IN. Let the ID be fixed for testing.
Works whenWith the ID set to the one the challenge's sample uses, your bytes for dns.google.com match its hex exactly.
Concepts: the DNS message · byte order and bit fields · domain names on the wire
Send the query in one UDP datagram to port 53 of a public recursive resolver, wait for the reply with a timeout, and print its bytes in hex. The server's address and port can be set from outside, with the public resolver as the default, so a test can point the program at a local server.
Works whenA reply arrives, its first two bytes equal the ID you sent, and its layout looks like the challenge's sample response.
Concepts: UDP exchanges
Decode the reply: the header and its flags, the question, and every record in the answer, authority and additional sections, following compression pointers to rebuild each name with dots. Confirm that the QR flag marks it as a response and that the ID is yours.
Works whenFor dns.google.com the parsed reply has your ID, one question, two answers and empty authority and additional sections. Each answer has the queried name, type A, class IN, a TTL above zero and one of the two addresses of Google's public resolver.
Concepts: the DNS message · domain names on the wire · resource records
Clear the recursion desired flag and resolve the name yourself. Start at a root server, and for each response either return the address from an A record or follow the referral: pick a listed name server, use its glue address if there is one, otherwise resolve that server's own name first, then ask it. Print each server you query. The starting root server's address, and the port used for every query, can be set from outside, with a real root server and port 53 as the defaults, so a sandbox can run the whole walk against local servers.
Works whenResolving dns.google.com prints a root server, then a com server, then one of google.com's own servers, and ends with an address that step 3 also returned. A name whose name servers come without glue resolves too.
Concepts: the DNS hierarchy · recursive and iterative resolution · referrals and glue records
When the answer for a name is a CNAME, resolve the canonical name it points to, and keep going until you reach an address, with a limit so a loop of aliases can't run forever.
Works whenA name that is an alias resolves to the address of its canonical name, and the output shows each name in the chain.
Concepts: resource records