Hosting a fun DNS server with Go and a DNS library
Published on under the Programming category.Toggle Memex mode

TL;DR: I now have my own DNS resolver at jamesg.blog with some fun utilities. Read the GitHub documentation for a complete reference manual of all queries, and keep reading below for an explanation of the project and how it works.
In this week's Homebrew Website Club, someone mentioned dns.toys, a set of playful tools including unit converters that are available by making a DNS query. I found this idea fascinating and I wanted to explore it a bit more. I learned more about how DNS resolution worked last month when reading about traceroute and how it works. I was left with an itch: how can I make something fun over DNS?
When considering what to build, I thought: how about a temperature converter? The idea of a fully-offline, web-powered, free and private temperature conversion tool came up in HWC so unit conversion was top of mind. This project didn't aim to be fully offline or web powered though: DNS is a networking protocol, after all(!).
To get started, I first went back to the miekg/dns Go library that I saw a while ago. This library implements, if I recall correctly, most if not all DNS Request for Comments documents. While I understand a bit about the inner workings of DNS, I didn't want this project to be too technical on that side of things. DNS is big. Beyond Tellerrand reminded me that I don't need to try to reinvent the wheel to make something cool. So I decided to worry less about low-level DNS and more about what I wanted to build.
Getting started
Building a temperature converter helped me get my foot in the door with this idea. I had a goal in mind, a library that would abstract away a lot of technical details, and a motivating factor: to build a cool DNS utility.
My first goal was to build the core application logic. This logic:
- Initialises a DNS server object.
- Registers handlers for different resource names (i.e. "is.it.hwc.day").
- Starts the server.
I started by writing a handler for temperature conversion, which took some time as I am newer to Go and I haven't worked a lot with floating point arithmatic. I learned about the float32 and float64 functions and I did some manipulation to convert the temperature numbers into strings and the resultant number that would be fed back in the DNS response into a string. I wrote this logic for both Celsius to Fahrenheit and Fahrenheit to Celsius.
While building the temperature converter, I found the "." handler matches any query. I use this handler to check for a temperature query. If you query "fc100" my application recognises fc as Fahrenheit-to-Celsius and does the conversion on the next number. This involves a bit of string manipulation to remove the fc. Conversely, cf30 converts 30 degrees Celsius to Fahrenheit.
When you run the application, the miekg/dns library creates a server. This server can be run locally and queried.
Here's an example query made using dig:
""" dig @jamesg.blog resource.name """
This tells dig to query the jamesg.blog DNS server. This server is opened publicly on port 53. But, my application doesn't host directly on port 53. Ports under 1024 are reserved. I would need to give Go special access to directly host applications on port 53. I didn't want to do this so I did some Googling and found an alternative: host the application on a different port internally and forward all traffic from port 53 to this application. I did this using iptables. I also opened up port 53 so you could make DNS queries to my server.
All data from my application is sent via TXT records, which allow ASCII-only string contents.
Locally, when testing, I could not use dig without specifying the port. I did this using this command:
""" dig @localhost -p 5000 resource.name """
This makes a query for the resource "resource.name" on port 5000.
Adding new features
Next, I started exploring new features. I wondered: what else would be cool to show over DNS? I thought: how about some IndieWeb and personal website features? I decided to host a few TXT records with some information about projects I have worked on, available by querying the "resume" resource on my DNS server. Part of me wants to extend this to parse my h-resume and show my full resume over DNS. That is a project for another day. I also made the "recent.blog" query which gets the title and slug of the blog post I published most recently.
I wrote three IndieWeb-related features:
- Check if it is HWC day (Wednesday).
- Check if it is Newsletter day (Friday).
- Retrieve the next event from events.indieweb.org, if one is scheduled.
The first two functions were simple. My application gets the current day of the week and returns a response depending on whether the current day of the week is equal to Wednesday for HWC day or Friday for newsletter day. The feature that retrieves the next event from the IndieWeb events page took more time to implement. I used the Go microformats parser to parse the events page. Then, I go through all events from top to bottom. The first event that is after the current time is displayed as the next event. I don't order the events page in the application because events.indieweb.org is ordered by the most recent upcoming event first.
Here's an example of a query to retrieve the next event from the IndieWeb events page:
dig @jamesg.blog next.indieweb.event
This query returns:
next.indieweb.event. 0 IN TXT "IndieWeb Create Day - 2022-09-18 09:00:00 -0700 -0700"
Conclusion
I had a lot of fun building this project. Projects like this keep me thinking about how joyful computing can be (as well as how you can do some really fun things with protocols!). Now that I have the shell of a DNS server and some utilities, I can add new functionalities at any time as I think of them. I don't have any ideas just yet, but I think there's more I can do with my personal website.
Let me end by extending a big thank you to miekg and everyone who worked on the aforementioned DNS library in Go. This library (which also powers dns.toys that I mentioned at the beginning of the article) let me build this project without having to implement a whole DNS server myself.
Responses
Comment on this post
Respond to this post by sending a Webmention.
Have a comment? Email me at readers@jamesg.blog.