Jan 19, 2020

Building an Authoritative DNS Nameserver

In my previous post, I went over the fundamentals of how the Domain Name System, DNS in short, resolves hostnames to IP addresses, and how the Border Gateway Protocol can be utilized to create a globally-distributed infrastructure to reach users around the world without ever switching hostnames. If you've got the resources to scale this big, there's no reason not to use this method, even if you choose managed solutions, like NS1 is offering.

Now, there's the other possible case, one where you're a developer who wants to research how application deployments can be optimized to be available regardless of the user's origin. Without the use of the Border Gateway Protocol (BGP) for intelligent or location-aware routing, we'll have to emulate this behavior ourselves.

Practically speaking, we're going to build a custom authoritative nameserver that will resolve DNS requests to our services and enhance performance by sending users to the closest datacenter or deployment available to them.

By dismissing the BGP, we're losing out on a list of very important guarantees we'd have to have in a production environment. This list notably includes network-level high-availability and failover capabilities and increased latency for users that aren't close to the nameserver. This makes it more suitable to run internally to connect services to each other while keeping track of system metrics like the current load to distribute requests across multiple deployments, so rather a measure to optimize and improve internal networks than to expose your application to the open internet.

One key part of the nameserver implementation will be that the server has to know about the rough geolocation of its clients. The first answer would probably be that we should just check for the client's remote address that we receive for a request. This method, however, has some flaws to it, since DNS queries are resolved recursively, which includes more than just one party as the original requester. Building a nameserver on this assumption would result in us returning responses optimized for the recursive resolver, not the actual client. Luckily, there's an extension of the DNS protocol which allows resolvers to add information about the client that asked them to resolve a query.

Let's take a moment to talk about DNS extensions: Back in the 90s when the usage of the Domain Name System grew, people started to think about extending the DNS protocol. The limitation was that the DNS header could not be extended due to size limitations in the original specification, which is why EDNS came to be. Extension mechanisms for DNS allow for placing additional information into the actual DNS message, formatted as pseudo-resource-records (pseudo-RRs) of the new OPT type. An example use case of EDNS is DNSSEC, which includes the following pseudo-RRs in the nameserver's response:

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096

Now the only thing we'd need is for the DNS resolver to add client information to the DNS message sent to our nameserver. This idea has been verbalized in multiple documents, most notably an IETF draft from May 2010 that includes exactly what we'd need: An extension of the datagram (DNS message) to include the originating network details in the request, followed by a scope of network addresses, which should use the tailored result.

Although this draft was never finalized, there has been a successor document that reads very similar and got widely adopted: RFC 7871.

A significant part of the latter document includes the following:

This document defines an EDNS0 option to convey network information that is relevant to the DNS message. It will carry sufficient network information about the originator for the Authoritative Nameserver to tailor responses. It will also provide for the Authoritative Nameserver to indicate the scope of network addresses for which the tailored answer is intended. This EDNS0 option is intended for those Recursive Resolvers and Authoritative Nameservers that would benefit from the extension and not for general purpose deployment. This is completely optional and can safely be ignored by servers that choose not to implement or enable it.

Due to including metadata that can be abused in terms of privacy measures, using this approach should be evaluated carefully.

We recommend that the feature be turned off by default in all nameserver software, and that operators only enable it explicitly in those circumstances where it provides a clear benefit for their clients. We also encourage the deployment of means to allow users to make use of the opt-out provided. Finally, we recommend that others avoid techniques that may introduce additional metadata in future work, as it may damage user trust.

As to the adoption of both the draft outlined earlier and the current RFC, it contains an interesting section:

At least a dozen different client and server implementations have been written based on earlier draft versions of this specification. The protocol is in active production use today. While the implementations interoperate, there is varying behavior around edge cases that were poorly specified. Known incompatibilities are described in this document, and the authors believe that it is better to describe the system as it is working today, even if not everyone agrees with the details of the original specification. The alternative is an undocumented and proprietary system.

With all of this, we can build an authoritative nameserver that will receive the details about its client it needs, fall back to the requester's remote address, if there's no sign of the edns-client-subnet extension (ECS).

As to the actual development part, I'll update this post with steps to write the nameserver in Go, since I'm currently still exploring options regarding the implementation details.


As always, if you've got questions, suggestions or any feedback at all, don't hesitate to send a DM or write a mail.