Have you ever wanted to manipulate DNS data on the fly like most CDN networks do, perhaps you have a specific reason to rewrite DNS data in your corporate network, for example in a split brain scenario?
UPDATE – I have added an follow-up blog entry as this did not work as expected (for my primary usecase) you can read about it here.
I must admit that this blog post is one of the most weird ones I’ve ever written, not that it’s strange to write DNS responses in real-time but for the purpose I’m intended to use that functionality.
If you have been following my blog or LinkedIn posts you’d know that I enjoy switching over to IPv6 for as many services as I can. This blog for example is fully accessible using IPv6, incoming emails can be delivered using IPv6 to my email edge server.
Home ISP and Port 25
But as I run my services at home my ISP provider ComHem blocks port 25 (as most ISPs today do) to limit the amount of spam that can be generated from a hacked computer. For that reason I’m using a “smart host” on my email server that routes all outbound SMTP connections to a specific SMTP (relay) server hosted at my ISP. That has worked well for many years now and I’m hoping that I can continue to run things this way. Inbound connections are not blocked so both IPv4 (ISP) and IPv6 (HE.NET) works well. HE.NET however blocks both Inbound and Outbound connections by default, took me some time to figure that out 🙂
Now, the only thing that needs to be taken care of is Outbound IPv6 as “smart host” that I’m currently relying on will send all emails to a specific SMTP server independent on if they are IPv6 capable email servers out there. The reason behind this would be that the sending email server does not perform any MX lookups, i.e. does not tries to identify any mail servers at the receiving domain and can therefore not identify any A (IPv4) or AAAA (IPv6) records and select to use “smart host” only for IPv4 enabled domains.
PowerDNS and scripting to the rescue
Before we go into the actual solution I of course tried a few different options. The first most logical solution was to write the script in PHP as that’s my first language of choice. However PHP is compiled on execution and cannot be changed until it’s reloaded. Writing a DNS server in PHP is not that complicated and there are a few great classes available online. They are however will not able to change their output depending on DNS queries. This might be accomplished by running the PHP script as a daemon and letting the daemon do the DNS handshake and executing the PHP script for every DNS query. For me that would require me to write some DNS logic and as I have never done that before I wanted to find alternativs.
As I have some experience with PowerDNS or pdns for short (Yes its powerful) and know it has various backends for DNS i wanted to explore that option first. I was surprised to find that pdns offers a generic remote backend. It allows for Un*x PIPEs where I could execute my PHP script as I was exploring in the first case. However it also offers a RESTful interface over HTTP that I decided would be a good start as you should try to separate logic as much as you can in development. Also it’s easy to troubleshoot http requests, that showed very useful in this case.
Before I start I must say that the documentation was very vague and n0t many libraries (code) examples available. I found a few Python scripts that was not really that helpful, so I wrote my PHP RESful integration solely using the examples on the powerdns website.
Before writing I setup the goals for my DNS integration in a few bullet points:
- Needs to be able to perform SOA and ANY/MX lookups of real domain names.
- Needs to parse MX records and their corresponding A or AAAA records.
- Needs to be able to lookup the IP address of my ISPs SMTP relay server.
- Needs to be able to replace all IPv4 A records returned from the domain with the A record of my ISPs SMPT relay server.
- Outputs the result back to powerdns in well formatted json.
- Be able to return null if no records of the above is found, i.e no domain found, no MX records found etc.
I have intentionally left out domains that does not have any MX records even thought you should try to deliver emails to A or AAAA records available at the root domain. If I end up in non-deliverable emails as a result of that I just need to add a few more lines to my code.
The code I ended up with is 82 lines. I tried to keep it as simple as possible, the main reason being that pdns asks in real-time and I don’t want my “dns proxy” activities taking to much time causing the dns request to time out. This is due to the fact that I need to query DNS servers myself in the script to get the actual SOA and MX records of the domain.
PowerDNS is really, as it should be sensitive about the results from the remote script, if some parts of the result are not properly formatted the entire request fails and could throw a number of various error codes (using nslookup or dig) – most common are Connection timed out; no servers could be reached but also communications error to 127.0.0.1#53: end of file have been seen.
The example above shows the result from gmail.com returning A and AAA records for all MX SMTP servers. The AAAA (IPv6) records are intact, however the A (IPv4) records has been replaced by the IP address of my ISPs SMTP server. The IP adress is behind a load balancer so no need for additional IPs as far as I understand.
In the next blog I’m going to try to configure my Exchange server or my Email Edge server to benefit from the new DNS server, hopefully that will allow me to send emails over IPv6 🙂
Other useful applications?
We now know that it’s possible to manipulate DNS data. In a split brain DNS server scenario where you have the same namespace both externally and internally it could perhaps be useful to rewrite requests coming from the external DNS an replace their IP addresses with the corresponding internal ones? Let’s say you do destination NAT in your firewall? So a mapping from external IP to the internal one already exists. Using a modern firewall, for example Juniper SRX you could easily just ask the Firewall for the Internal IP and, on the fly rewrite the DNS request. This might be a better solution instead of directly accessing the internal IP as that might throw warnings or errors if your using SSL/TLS certificates.
Another cool example could be load balancing as most state of the art load balancers do. In this scenario you could ping the client who’s making the request (It’s available in the HTTP header much like reverse proxies uses the x-forwarded-for header). Let’s say you want the user to connect to not the closest CDN cache but the one with the quickest roundtrip? Your CDN locations could have an fast ping probe (There are various ways that can be accomplished) that have a build in API that the DNS rewrite app could consume. All locations would then ping the client and the one with the shortest ping response would be added to the DNS response to a specific resource. Of course this can be accomplished in the application layer as well with routing and HTTP headers moving a user to another server, dns is however application agnostic and would work for all applications and would not require any additional code. Ping was of course one example, the decision could also be done based on load on the specific datacenter, GeoIP or any combination that works best for your case.
I’m sure there are other applications where rewriting dns queries could be useful, perhaps not manipulating the actual request rather the TTL, lets say your developing an application and want to work with real DNS data but do not want to change the TTL of the public DNS zone, instead you could rewrite the TTL record on the fly and use a rewriting dns instead. If your a developer you might have had to change your HOST file to add remote static FQDN records to override a development site for the production, a perfect case where a rewriting dns could come in handy 🙂
Thanks for reading.