How to Set Up a Local DNS Server With Python

cover
8 Jun 2024

Introduction

In today’s connected world, DNS servers play a crucial role in translating human-friendly domain names into IP addresses that computers use to identify each other on the network. While many rely on public DNS services, setting up your own local DNS server can be beneficial for various reasons, including local development, internal network management, and educational purposes.

Development and Testing: Easily create and manage custom domains for testing web applications.

Educational Purposes: Learn how DNS works and experiment with DNS configurations.

Local Network Management: Manage internal domains within a private network.

Create HTML Files

Create two HTML files (a.html and b.html) in a directory named html-files. These files will be served when you access a.com and b.com Respectively.

<!-- a.com -->
<!DOCTYPE html>
<html>
<head>
    <title>A Page</title>
</head>
<body>
    <h1>Welcome to A.com</h1>
</body>
</html>

<!-- b.com -->
<!DOCTYPE html>
<html>
<head>
    <title>B Page</title>
</head>
<body>
    <h1>Welcome to B.com</h1>
</body>
</html>

Create a Simple HTTP Server

Next, we’ll create a simple HTTP server using Python’s built-in. http.server Module to serve the HTML files.

#http_server.py

import http.server
import socketserver

PORT = 8080
DIRECTORY = "html-files"

class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/a.com':
            self.path = '/a.html'
        elif self.path == '/b.com':
            self.path = '/b.html'
        return http.server.SimpleHTTPRequestHandler.do_GET(self)

    def translate_path(self, path):
        return f"./{DIRECTORY}/{path}"

handler_object = MyHttpRequestHandler
my_server = socketserver.TCPServer(("0.0.0.0", PORT), handler_object)

print(f"Serving HTTP on port {PORT}")
my_server.serve_forever()

Explanation

  1. Imports:
    • http.server: Provides basic HTTP server functionality.

    • socketserver: A framework for network servers.

  2. Configuration:
    • PORT = 8080: The server will listen on port 8080.

    • DIRECTORY = "html-files": The directory where HTML files are stored.

  3. Request Handler:
    • MyHttpRequestHandler: Custom request handler class that overrides do_GET to serve specific files for a.com and b.com.

  4. Request Handling:
    • do_GET(self): Checks the requested path and maps /a.com to /a.html and /b.com to /b.html. The http.server.SimpleHTTPRequestHandler Serves these files.

    • translate_path(self, path): Translates the requested path to the correct file path within the html-files directory.

  5. Server Setup:
    • socketserver.TCPServer(("0.0.0.0", PORT), handler_object): Binds the server to all available interfaces on port 8080.
    • my_server.serve_forever(): Starts the server to handle incoming requests indefinitely.

Create a Simple DNS Server

We’ll use the dnslib Library to create a DNS server that resolves a.com and b.com to your local machine’s IP address.

from dnslib import DNSRecord, QTYPE, RR, A, DNSHeader
import socket
import socketserver

# Get the local IP address
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)

# DNS server configuration
DOMAIN_TO_IP = {
    'a.com.': local_ip,
    'b.com.': local_ip,
}

class DNSHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request[0].strip()
        socket = self.request[1]
        try:
            request = DNSRecord.parse(data)
            print(f"Received request for: {str(request.q.qname)}")

            # Create a DNS response with the same ID and the appropriate flags
            reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q)

            qname = str(request.q.qname)
            qtype = QTYPE[request.q.qtype]

            if qname in DOMAIN_TO_IP:
                reply.add_answer(RR(qname, QTYPE.A, rdata=A(DOMAIN_TO_IP[qname])))
                print(f"Resolved {qname} to {DOMAIN_TO_IP[qname]}")
            else:
                print(f"No record found for {qname}")

            socket.sendto(reply.pack(), self.client_address)
        except Exception as e:
            print(f"Error handling request: {e}")

if __name__ == "__main__":
    server = socketserver.UDPServer(("0.0.0.0", 53), DNSHandler)
    print("DNS Server is running...")
    server.serve_forever()

Explanation

  1. Imports:
    • dnslib: Provides DNS record creation and parsing.

    • socket, socketserver: Modules for network communication.

  2. Get Local IP Address:
    • hostname = socket.gethostname(): Retrieves the hostname of the local machine.

    • local_ip = socket.gethostbyname(hostname): Resolves the hostname to an IP address.

  3. DNS Configuration:
    • DOMAIN_TO_IP: A dictionary mapping domain names to the local machine’s IP address.

  4. DNS Request Handler:
    • DNSHandler: Custom request handler class for handling DNS requests.

  5. Request Handling:
    • handle(self): Processes incoming DNS requests.

    • data = self.request[0].strip(): Reads the incoming data.

    • request = DNSRecord.parse(data): Parses the DNS request.

    • print(f"Received request for: {str(request.q.qname)}"): Logs the requested domain.

    • DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q): Creates a DNS response with the same ID and appropriate flags.

    • if qname in DOMAIN_TO_IP: Checks if the requested domain is in the configuration.

    • reply.add_answer(RR(qname, QTYPE.A, rdata=A(DOMAIN_TO_IP[qname]))): Adds an answer to the DNS response.

    • socket.sendto(reply.pack(), self.client_address): Sends the DNS response back to the client.

  6. Server Setup:
    • socketserver.UDPServer(("0.0.0.0", 53), DNSHandler): Binds the server to all available interfaces on port 53.
    • server.serve_forever(): Starts the server to handle incoming requests indefinitely.

Configure Your Devices With New DNS Address(Mobile)

To access a.com and b.com from your mobile device, you need to configure your mobile device to use your local DNS server.

On Android:

  1. Open Wi-Fi Settings:
    • Go to Settings > Network & Internet > Wi-Fi.

    • Tap on the Wi-Fi network you are connected to.

  2. Modify Network:
    • Tap on Advanced > IP settings And change it from DHCP to Static.

  3. Enter DNS Server Information:
    • IP address: Your device's IP address in the same subnet (e.g., 192.168.1.*).
    • Gateway: Your router's IP address (e.g., 192.168.1.*).
    • Network prefix length: Usually 24.
    • DNS 1: Your local DNS server IP address (your PC’s IP address, e.g., 192.168.1.*).
    • DNS 2: Leave blank or set to 8.8.8.8 (optional).

On iOS:

  1. Open Wi-Fi Settings:
    • Go to Settings > Wi-Fi.

    • Tap on the i Icon next to the connected network.

  2. Configure DNS:
    • Tap on Configure DNS.
    • Select Manual.
    • Add your local DNS server IP address (your Mac’s IP address, e.g., 192.168.1.*)

Access the Sites

Open a web browser on your mobile device, and navigate to:

http://a.com:8080
http://b.com:8080

Troubleshooting

Common Issues and Solutions

  1. DNS Server Not Running:
    • Ensure you are running the DNS server with root privileges.

    • Check for errors in the console output.

  2. Firewall Settings:
    • Ensure your firewall allows incoming connections on ports 53 (DNS) and 8080 (HTTP).

  3. Network Configuration:
    • Verify that your local machine and mobile device are on the same network.

  4. DNS Resolution:
    • Use the dig command to test DNS resolution locally:

      dig @127.0.0.1 b.com
      

Conclusion

Setting up a local DNS server with Python is a powerful way to manage custom domains for development and internal network management. By following this guide, you can create a fully functional local DNS server and serve custom HTML pages from local domains accessible from any device on your network.

I hope this article helps you understand the process and benefits of running your own DNS server.

Next, we’ll discuss security considerations when running your own DNS server.

Happy coding!