Creating a DNS Server with Go

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up the DNS Server
  4. Creating the DNS Handler
  5. Handling DNS Requests
  6. Testing the DNS Server
  7. Conclusion

Introduction

In this tutorial, we will learn how to create a DNS server using the Go programming language. A DNS server translates human-readable domain names into their corresponding IP addresses. By the end of this tutorial, you will be able to build a simple DNS server that can receive DNS queries and provide responses.

Prerequisites

Before you begin, make sure you have the following:

  • Basic knowledge of the Go programming language
  • Go installed on your machine
  • Access to a terminal or command prompt

Setting Up the DNS Server

To get started, create a new directory for your project. Open a terminal and run the following command:

mkdir dns-server

Next, navigate into the project directory:

cd dns-server

Create a new Go module by running the command:

go mod init github.com/your-username/dns-server

This will initialize a new Go module for our DNS server. Replace your-username with your actual GitHub username or any other suitable name.

Creating the DNS Handler

Now let’s create a file called dns_handler.go in the project directory. This file will contain the code for handling DNS requests.

touch dns_handler.go

Open dns_handler.go in a text editor and add the following code:

package main

import (
	"fmt"
	"net"
	"strings"
)

func handleDNSRequest(conn *net.UDPConn, addr *net.UDPAddr, buffer []byte) {
	domain := strings.TrimSpace(string(buffer))
	ip, err := net.LookupIP(domain)
	if err != nil {
		fmt.Println(err)
		return
	}

	response := []byte(ip[0].String())
	_, err = conn.WriteToUDP(response, addr)
	if err != nil {
		fmt.Println(err)
		return
	}
}

func main() {
	udpAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:53")
	if err != nil {
		fmt.Println(err)
		return
	}

	conn, err := net.ListenUDP("udp", udpAddress)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	buffer := make([]byte, 512)

	for {
		n, addr, err := conn.ReadFromUDP(buffer)
		if err != nil {
			fmt.Println(err)
			continue
		}

		go handleDNSRequest(conn, addr, buffer[:n])
	}
}

The handleDNSRequest function takes care of processing DNS requests. It receives the connection, client address, and the DNS query as input. It trims any leading or trailing spaces from the query, uses the net.LookupIP function to resolve the domain name to an IP address, and sends the IP address back as the response.

The main function sets up a UDP listener on 127.0.0.1:53, which is the default DNS port. It listens for incoming DNS queries and calls the handleDNSRequest function in a separate Goroutine to handle each request concurrently.

Handling DNS Requests

Now that we have our DNS handler, let’s test it by creating a simple DNS client. Create a new file called dns_client.go in the project directory:

touch dns_client.go

Open dns_client.go in a text editor and add the following code:

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: go run dns_client.go <domain>")
		return
	}

	domain := os.Args[1]

	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1053")
	if err != nil {
		fmt.Println(err)
		return
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	_, err = conn.Write([]byte(domain))
	if err != nil {
		fmt.Println(err)
		return
	}

	buffer := make([]byte, 512)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(buffer[:n]))
}

The main function reads a domain name from the command-line arguments. It then resolves the address of our DNS server (127.0.0.1:1053) and establishes a UDP connection. It sends the domain name as a query to the server and receives the response.

Testing the DNS Server

To test our DNS server, open two terminals. In the first terminal, start the DNS server by running the command:

go run dns_handler.go

In the second terminal, run the DNS client with a domain name as an argument:

go run dns_client.go example.com

You should see the IP address corresponding to the domain name printed in the second terminal.

Conclusion

In this tutorial, you learned how to create a DNS server using the Go programming language. You set up a UDP listener, implemented a handler function to process DNS requests, and tested the server using a client. With this knowledge, you can further customize and enhance the DNS server to suit your specific needs.

Remember, this is a basic implementation, and there are many improvements and additional features you can add, such as caching, error handling, and support for different query types. Further exploration of the net package in Go will help you discover even more possibilities for network programming. Happy coding!