In this exercise, you will build your own version of a Domain Name System (DNS) resolver, the system your computer uses to translate a domain name into an IP address. You are not expected to know anything about DNS; everything is explained in the directions.
Implement each step in order so the provided unit tests pass. It is normal not to complete all the steps.
Step 0 – Normalize
In DNS, names are case insensitive, and by convention always end in a trailing dot. For example, "Anthropic.COM" normalizes to "anthropic.com.".
Implement normalize() in dns_exercise.py.
Step 1 – Annotated Example
Suppose you type "Anthropic.COM" into your web browser. With your normalize() function, the normalized name is anthropic.com..
You are provided with a function send_query(normalized_name, server_ip) that simulates the network call to server_ip, and the IP address of a “root server” which is the starting point of any DNS query.
Finally, the answer field holds a type A record, and we return its rdata.
Step 2 – CNAME records
Sometimes, instead of containing an A record, the answer section will contain a CNAME record.
CNAME chains can be longer than one hop. Implement handling for this case in resolve().
Step 3 – Missing glue records
Sometimes we are directed to a nameserver whose IP isn’t in the additional section. When there is no matching glue record, you must resolve the nameserver’s IP address before continuing.
Step 4 – Error handling and NS fallback
In the authority section, there can be multiple NS records, and each NS record may or may not have a matching glue record. Try each nameserver in order until one succeeds.
NXDOMAIN means the name definitely doesn’t exist in this zone, so return None.
REFUSED means the server can’t help you and does not know about this name. Try the next nameserver if available.
Step 5 – Cycle Handling
Implement a basic form of cycle detection: if you haven’t succeeded after max_queries calls to send_query, assume you’re caught in a cycle and return None.
Step 6 – Cached batch resolution
Implement resolve_all(). Given a list of domains, resolve each one and return a mapping from each input domain (note that inputs are not necessarily normalized) to its resolved IP or None if it couldn’t be resolved.
Cache the result of every send_query call so that no (name, server) pair is queried twice across the batch. NXDOMAIN and REFUSED responses should also be cached.
max_queries is the limit per domain. A cache hit does not call send_query, but still counts against the limit for that domain.
The cache must be scoped to each resolve_all() call and not persisted globally.
Step 7 – Parallel resolution
Make resolve_all() resolve domains concurrently to complete faster in wall-clock time.
At most max_workers (default 5) calls to send_query may be in flight at any time.
If a query is already in flight from another domain’s resolution, wait for it rather than sending a duplicate.
[execution time limit] 30 seconds
[memory limit] 4g
This problem is a DNS resolution simulator with several layers of complexity: name normalization, CNAME following, missing glue-record fallback, NXDOMAIN/REFUSED handling, per-call caching, and finally parallel batch resolution. The key implementation challenge in the last steps is to share both completed results and in-flight queries across domains, while respecting the per-domain query limit and the global max_workers concurrency cap.