My very own DNS server

I wrote my own DNS server in Python and tested it for some time. Here are some details about this project.

Motivation

I was reading about the new “scriptable DNS” feature of BunnyCDN, the CDN that I was using at the time. I was familiar with some fancy DNS stuff you can do with Amazon AWS’s Route 53 DNS service, and now there is another DNS service that lets you run code for every DNS request in order to respond dynamically.

I had to get in on the fun, so I decided to write my own scriptable DNS name server. Aside from learning a lot about DNS, this project also ties in nicely to some future project plans about building my own CDN.

Implementation

RFC 1035 has all the information you need if you want to write your own DNS server or client. For this project I only wrote the server side, but in the future I also want to write my own DNS client.

In order to get DNS requests to my own server, I had to set up something called a Glue Record, and then enter the domain of the server I wanted to use for DNS as the name server. After this, I started receiving UDP packets to port 53 whenever I ran a dig command.

Deserializing the DNS header and query are rather simple using Python’s int.from_bytes method and following the RFC. Responding to queries is simplified because DNS queries and DNS responses follow the same format.

EDNS and EDNS client subset

I also implemented EDNS (RFC 6891). EDNS specifies how extensions to the DNS protocol should be handled.

The reason I looked at EDNS was to use the EDNS Client Subset extension (RFC 7871).

This extension passes a truncated prefix of the user IP to the DNS server if the user is using a public resolver such as Google’s 8.8.8.8. This can be useful for returing responses that are geographically closer to the user and improving performance.

Results

I ended up making my server respond to “normal” queries that resolve websites, along with some special behaviour like

  • time.example.com resolving to the current date and time.
  • random.example.com resolving to a random number.
  • Transmitting data over DNS by decoding hex or Base32 encoded subdomains.

In the future, I want to do stuff like automatically responding with a random IP from AWS spot instances so I can domains that resolve to a cheap and rotating fleet of ephemeral servers.

Maybe doing health-checks and removing servers that are down from DNS rotation would be good too.

Some findings

Google’s DNS resolver randomizes the letter case of domains. In DNS, domain names are case-insensitive. But I forgot this fact when I was writing my own DNS server. Everything seemed to work fine with other resolvers and clients, but Google’s DNS resolver randomizing it actually uncovered this bug for me.

I also noticed that after I implemented the EDNS client subset extension, Cloudflare still wouldn’t send anything. It turns out, they intentionally do not support this for “privacy reasons”. Some people suspect it’s because they want to prevent other CDNs from working optimally by routing users to servers that are closer to them while Cloudflare can do it for their customers because they know the real IP.