Domain Name Resolution (or DNS for short) plays a large part in networking infrastructure on the internet and intranets. DNS is how clients resolve domain names to IP addresses. Normally DNS is available as a service on the public internet for websites or any publicly available server. Companies usually have internal DNS for their intranets at work. There are plenty of DNS libraries out there such as bind9, PowerDNS and dnsmasq which will cover most common scenarios.
But what happens when there needs to be complex logic involved when resolving DNS? It could be anything, I recently ran into a scenario where the domain names for production and development environments are exactly the same and I need to hit different name servers to determine the destination. None of the current libraries supported this, essentially what I call “destination-based DNS”. Let me introduce dnslib, a powerful library to construct DNS responses and run DNS servers.
Let’s analyse the sample code provided in the comments of https://bitbucket.org/paulc/dnslib/src/default/dnslib/server.py:
>>> from dnslib.server import DNSServer, DNSLogger>>> from dnslib.dns import RR>>> class TestResolver:... def resolve(self, request, handler):... reply = request.reply()... reply.add_answer(*RR.fromZone("abc.def. 60 A 126.96.36.199"))... return reply>>> resolver = TestResolver()>>> logger = DNSLogger(prefix=False)>>> server = DNSServer(resolver, port=8053, address="localhost", logger=logger, tcp=True)>>> server.start_thread()>>> a = q.send("localhost", 8053, tcp=True)Request: [...] (tcp) / 'abc.def.' (A)Reply: [...] (tcp) / 'abc.def.' (A) / RRs: A>>> print(DNSRecord.parse(a));; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:;abc.def. IN A;; ANSWER SECTION:abc.def. 60 IN A 188.8.131.52>>> server.stop()
Above we create a TestResolver class with the resolve method. This method is called when the DNS server receives the DNS query, inspecting the request object we can get details of the DNS query such as what domain was requested and what “questions” were asked. In this case we return a hardcoded response and return the reply. The DNSServer class hosts our DNS server taking the custom resolver class and logger also notice here this is a TCP server. We then start a thread and run a query to test out our custom resolver and DNS server.
After we write our DNS server we will deploy on Kubernetes. Let’s break down what we need to create to run our DNS servers.
- Create a Docker image with your custom DNS server
- The dns server will need to be designed such that UDP and TCP are controlled by a flag and port can be customised.
- Create a Deployment that deploys two containers in a Pod, one UDP and one TCP. Be aware that both containers cannot listen on port 53 inside of the Pod.
- Create a UDP Service and a TCP Service to point to the relevant containers. The ports can be 53 but the targetPort inside of the container can be different.
The setup of the above will wary slightly depending on where your Kubernetes infrastructure is hosted and there are multiple ways of deploying such as have completely different deployments for UDP and TCP servers. For metallb an annotation needs to be passed to allow the same IP to listen on the same port.
The above does not dwell into the actual code and the details as it is meant to cover the concept. If you need help with any of the above, please reach out in the comments section, I’ll be glad to offer assistance.