Exercises

Here are a bunch of ways to extend this toy DNS resolver. All of these are features that real DNS resolvers have. They’re not in any particular order – do whichever one(s) seem the most fun to you, or none of them.

1. make it work with CNAME records

Some domain don’t have an A record: instead they have a CNAME record redirecting to another domain name. For example www.facebook.com is like this. Modify the code so that it works with CNAME records.

Here’s an example of the code failing:

from part_3 import resolve
TYPE_A = 1
resolve("www.facebook.com", TYPE_A)
Querying 198.41.0.4 for www.facebook.com
Querying 192.12.94.30 for www.facebook.com
Querying 129.134.30.12 for www.facebook.com
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In [4], line 1
----> 1 resolve("www.facebook.com", TYPE_A)

File ~/work/tinydns/book/part_3.py:105, in resolve(domain_name, record_type)
    103     nameserver = resolve(ns_domain, TYPE_A)
    104 else:
--> 105     raise Exception('something went wrong')

Exception: something went wrong

2. support other record types by name

Right now to query for an A record, you need to pass the number of the “A” record type (TYPE_A = 1). It would be way more usable if you could pass in a string like "A" and have it translated to the correct number. There are some mappings in section 3.2.2 of the RFC, as well as some newer ones you might need hunt down that were invented after 1987.

Also the way you parse the contents (the data) of a DNS record depends on the type, so you could implement parsing for NS records, AAAA records, TXT records, etc.

3. don’t allow loops in DNS compression

A malicious actor could exploit our DNS compression code by sending a DNS response with a DNS compression entry that points to itself, so that read_domain_name would end up in an infinite loop. Fix it to avoid that attack.

For example, here’s the code that avoids loops in miekg/dns

4. cache DNS records

Real DNS resolvers implement caching, so that if you make a second query 1 second later, it doesn’t need to go make a million DNS queries to figure out the answer.

One thing to keep in mind here is that DNS is case insensitive.

5. implement EDNS0 (extended DNS)

You might have noticed that in section 3.4, the additionals section we got from the root nameserver was truncated. If you run dig +all @a.root-servers.net example.com, you’ll get a different list.

This is because in the original DNS spec, response sizes were limited to 512 bytes. But if you implement EDNS0, you can get a larger response.

One way you could approach this is by using tcpdump or Wireshark to look at the DNS request dig is sending, and the mimicking what it does. The specification is RFC 2671 (though personally I find the spec to be a pretty confusing read, I’d start by mimicking dig).

You can test whether this works by running send_query("198.41.0.4", "google.com", TYPE_A) and checking if the total size of the response you get is more than 512 bytes. You can also use the DNS Reply Size Test Server to test.

6. implement TCP DNS

To get really big DNS responses, you can implement DNS over TCP. This is mostly the same as DNS over UDP, you just open a TCP socket instead. You can run dig +tcp example.com if you want to capture some TCP DNS traffic with Wireshark to see what it should look like. The length field is handled differently than it is with UDP, see the RFC for more.

I’m not sure how to actually find a gigantic 50KB DNS response to test your TCP DNS implementation out on though, and I’m too lazy right now to figure out how to create a few hundred DNS records to make one. Let me know if you find one.

7. make your resolver into a DNS server

Instead of running your toy resolver as a command line program, you can run it as a UDP server and listen on port 5353 or something. You can test it like this:

dig @127.0.0.1 -p 5353 example.com

If you’re feeling very brave (this is not a good idea!!), you could even configure your personal computer to use it a resolver and see what breaks. Just make sure you know how to change it back, and probably don’t keep it that way for too long. If you haven’t implemented caching, it’ll make your DNS queries slower than they should be and also put some unnecessary pressure on the authoritative DNS servers you’re querying. In general caching is very important to the health of the overall DNS system :).