DNS Basics and Building Simple DNS Server in Go

This is my experience on prototyping a DNS server in Golang. After going through this blog you will have a nice idea on how DNS works underhood and you should be able to build something like this.

Well…… mostly better than this !!!.

In this blog, We will be covering

DNS Basics:

  1. Basics of DNS
  2. Internal DNS Query
  3. External DNS Query
  4. DNS Zone types and Resource Record
  5. More DNS Zone types
  6. Structure of DNS Query and Answers

Building DNS Server in Golang

  1. Server to handle UDP Packets
  2. Extracting DNS Message from UDP Packets
  3. Handling based on the URL and query

In the end, we should be able to run a DNS Server and it can possibly serve request to your DNS lookups

Basics of DNS:

A key thing DNS server do is, it helps the requester to find the IP Address of a site, provided the domain name of the site.

A simple analogy would be Phone directory, where you can search for a Phone number using the name of the contact or use the phone number to check who the contact is.

DNS is more like phone directory but in this case, the contact name will be site name and phone number will be an IP Address

What about hosts_file?

Ans: Since there are myriad of sites hosted, it would be challenging to store lookup data in a single file and use the data from it. And also if any of the new sites is added, then the local hosts file has to be updated, which would be often the case.

Well, if there are only genuine and authentic sites there is a possibility that we could have just used hosts file but the internet is loaded with meaningless sites, so DNS helps to locate all possible machines in the network.

Order of DNS Query:

  1. When DNS tries to resolve a name to an IP Address, it first checks in Host file (/etc/hosts)
    The hosts file is the mapping of IP Address to Domain Name
  2. DNS Cache will be checked after hosts file and the duration the DNS answers cached will be different from OS.
  3. When the Answer is not found, DNS will start to look for answers out of the local machine, hence the DNS Servers.

Internal DNS Query:

External DNS Query:

To start with this let us take look at what is

  1. Root Hints
  2. Authoritative and Non-Authoritative answers

Authoritative Response:

In the example below, “corp.com” DNS server has data for all domains ending with “corp.com”. ie: “confidential.corp.com”.

So when we get the data from the server who has the data within it, it is considered to be safe and authoritative.

(i) Authoritative Query

Non-Authoritative response:

(ii) Non-Authoritative Response

When our DNS Servers don’t have an answer for that Query, they will ask the root hints servers and root hints provide answers or send possible server list (.com, .net…etc)that can resolve our query and this keeps on going until we find the DNS Server which can resolve the query (careers.github.com). So once the DNS Server for resolving “careers.github.com” is found, the answer will be made and sent to the DNS query requester(our Corp.com DNS Server) and it will be cached there for future references. Since the answer is not from our DNS Server, it will be considered a non-authoritative response.

Recursive vs Non-recursive query:

Recursive DNS query risks:

  1. DOS attacks
  2. DNS cache poisoning
  3. Unauthorized use of resources
  4. Root name server performance degradation

Root Hints:

You can check it in ubuntu machine by running : dig . ns

ICANN( The Internet Corporation for Assigned Names and Numbers) manages the DNS Root Servers.

Hierarchy of DNS Servers:

The root DNS Server contains the top level domains like .com, .org, .io ..etc. And the top level domains contains the subdomains and so on.

Running dig google.com +trace will give a good idea of how the IP for google.com is fetched by going through root DNS Servers to Google DNS Server.

DNS Zone types:

Reverse lookup zone: IP Address to hostname lookup

The lookup zones should contain the data/resource records for serving the DNS queries. The resource records can be stored in DNS server as Zone file.

The zone file contains mappings between domain names and IP addresses and other resources, organized in the form of text representations of resource records (RR). A zone file may be either a DNS master file, authoritatively describing a zone, or it may be used to list the contents of a DNS cache.

A zone file is a sequence of entries for resource records. Each line is a text description that defines a single resource record (RR). The description consists of several fields separated by white space (spaces or tabs) as follows:

name | ttl | record class | record type | record data

example zone file

Few prime Resource records that can be placed in lookup zone are:

  1. A
  2. AAAA
  3. CNAME
  4. MX
  5. PTR
  6. NS

A and AAAA:

One of the most common resource records out there is a host record. An A record is an IPv4 host record. An AAAA, well, that’s an IPv6 host record.

eg: running dig google.com -t A will get all A (IPv4) records for the hostname google.com.

The DNS server should be maintaining those records and serve according to the query from DNS client.

CNAME: (Canonical name)

CNAME provides alias name to any server.

Consider you have multiple subdomains(mail.corp.com, docs.corp.com) for your corp server and calling on all the subdomains should go to main corp server, you can use CNAME record since the CNAME record points to another record and it will not point to any IP.

Visualization of CNAME usage and CNAME entries in DNS table

MX Record (Mail exchange records):

Mail exchange records play a crucial role in sending and receiving emails.

How does our mail server know where to send the mail data (inter mail transfer [gmail to yahoo] )?

Ans: MX Record

Consider Bob (bob@gmail.com) is sending mail to Alan (alan@yahoo.com).

  1. Bob writes a mail with “to address” to “alan@yahoo.com” and send the mail. The data reached Gmail mail server and it should be redirected to yahoo mail server, so Alan could receive his mail from there.
  2. GMail server reads the “to address details“ and finds out the data has to be sent to yahoo exchange server and
    Our Gmail server does not know the yahoo mail exchange server address, so it requests DNS server for yahoo with type MX.

3. The DNS checks in resource records and should be sending an MX type result in the response.

4. The GMail server knows the location of yahoo mail exchange servers and it can send the data

MX Records usage

Now that we covered how the Terminology of a DNS Query and DNS Zone Types and Resource Records, we can start looking more deep on how the DNS client and server communicate (message format they use for communication)

DNS Message format:

Based on the request and response the content of the message will be filled appropriately.

For more information: https://tools.ietf.org/html/rfc1035#section-4.1.1

DNS Header:

For better understanding, the DNS message is split in 2 octets(16 bits per row) so each cell is one bit.

In reality, this will be a continuous message.

ID: A 16-bit identifier assigned by the program that generates any kind of query. This identifier is copied the corresponding reply and can be used by the requester to match up replies to outstanding queries.

ie: ID will be generated by the DNS client which initiates the query lookup and when the response comes the response can be mapped to query using the ID

QR: A one-bit field that specifies whether this message is a query (0), or a response (1).

A four-bit field that specifies kind of query in this message. This value is set by the originator of a query and copied into the response. eg: standard query or inverse query

AA : Authoritative Answer, this bit is valid in responses, and specifies that the responding name server is an authority for the domain name in question section

TC: Is the message truncated due to the large size

RD: Is the recursion desired (can be set when making the query)

RA: Denotes is the recursion available in response from DNS Server

Z: Reserved for future purpose

RCODE: Response Code, 4-bit field is set as part of responses

0 — No error condition

1- Format error

2 — Server failure

3 — Name Error

4 — Not Implemented

5 — Refused

QDCOUNT: 16-bit field, denoting the number of questions in the request

ANCOUNT: 16-bit field, denoting the number of answers in response

NSCOUNT: 16-bit field, denoting the number of name server resource records in the authority records

ARCOUNT: 16-bit field, denoting the number of resource records in the additional records section

DNS Question:

DNS Question

For better understanding, the DNS Question is split in 2 octets(16 bits per row) so each cell is one bit.

In reality, this will be a continuous message.

QNAME: This fields contains the domain names that we wish for resolving.

The domain name will be represented as a sequence of labels. Each label is represented as a one octet length field followed by that number of octets.

The domain name terminates with the zero-length octet for the null label of the root.

A quick example for the domain name: “example.com”

Step 1: the domain name “example.com” consists of two sections “example” and “com”

Step 2: “example” and “com” will be URL encoded to “69 88 65 77 80 76 69” and “99 111 109” respectively. This will be called as labels

Step 3: The label will be preceded by an integer byte containing the number of bytes in the section

ie: “example” will “7 69 88 65 77 80 76 69”

Step 4: Each value in the label can be converted to single octet value

ie: (7) (69) (88)

00000111 01000101 01011000 ………..

The final data can be placed in the QNAME question section.

QTYPE: this 2-bit value specifies the type of Query

QCLASS: specifies the class of Query, mostly IN(internet)

DNS Answers, Additional and Authority records:

DNS Resource record

NAME: a domain name to which this resource record pertains.

TYPE: specifies the type of query. eg. Standard or inverse query

TTL: this 32-bit field denotes the amount of time the resource record(answer) can be cached. Zero values denote the resource record should not be cached.

RDLENGTH: Response Data Length, a 16-bit field denotes the length of octets in Response Data field.

RDATA: a variable length string of octets that describes the resource. The format of this information varies according to the TYPE and CLASS of the resource record. For example, the if the TYPE is A (IPv4) and the CLASS is IN,
the RDATA field is a 4 octet ARPA Internet address.

Now that we know how the DNS Message (Queries and Answers) has to be structured we can try and implement a mock DNS Server.

Building DNS Server in Golang:

TCP will be used when sending a large answer as DNS Message, eg: Zone transfers, where the entire content of DNS Server is copied to another DNS Server.

To create a DNS Server we have to listen to a particular port for incoming queries. By default DNS Server runs on port 53.

Listening on a port:

When we receive data as UDP message we have to decode the DNS message from it. As the DNS Message structure is complex, it is hard to write DNS encoding and decoding on our own. We can instead use an already existing opensource library for it or we can build a library for more tailored need.

In this example, we will be using Google Packets.

we can now listen on the UDP port and get UDP message and use Google packets to decode it to DNS Message.

Before implementing serveDNS function we might need few records to serve DNS query.

The records are initialised at the beginning of the main function.

The program constantly listens on port “8090” and serves query using the server DNS function.

The serveDNS function gets the connection, client Address and the query request as parameters. The request message is then used to mock response message and the values that have to change in response is then changed.

Then the response message is serialized to UDP message format and write back to the client using the client address obtained on receiving the DNS message as UDP package.

Run this program and you should be able to resolve DNS Queries for the records specified in the main function.

ie: running the below command should bring DNS Response from the DNS server we created.

nslookup google.com localhost -port=8090

The serveDNS function can be optimized and the entire flow can be made into a library, so we can easily create a DNS server.

A prototype that I developed for fun — https://github.com/openmohan/lightdns

Thanks for reading, share and clap if you like the content :-)

Reference materials and credits to:


Definitely not a Blogger. I like to develop things and love naming new cool functions. Know me more here: https://openmohan.github.io