]> Encrypting DNS traffic on Linux 🌐:aligrant.com

Encrypting DNS traffic on Linux

Alastair Grant | Tuesday 21 December 2021

DNS traffic is famously unencrypted - this generally doesn't matter, but does expose your traffic to being snooped on untrusted networks - and even in some scenarios intercepted by providers.  When dealing with an untrusted environment, such as a cloud/hosting provider, it is worth configuring the system to encrypt DNS traffic.

DNS over HTTPS (DoH) wraps those svelte DNS packets in a relatively large HTTP message and creates a TLS connection with a server to perform the query.  There is an overhead to these, but HTTPS is effectively providing a secure tunnel for DNS.  There are other options, such as simply DNS over TLS.  Whatever mechanism, we are going to use the comprehensively featured dnscrypt-proxy application.

Hopefully this is available through your distribution, for openSUSE 15.3 I had to add the Server:DNS repository to get a recent version.

Configuration is simple, by updating /etc/dnscrypt-proxy/dnscrypt-proxy.toml - and even then, it's optional.  I wanted to explicitly choose Cloudflare's IPv6 DNS servers with malware filtering, so I adjusted the server_names value:

server_names = ['cloudflare-security-ipv6']

Where do you get the server names from?  Why the server-list on dnscrypt's web site.

Daemonising

There are two systemd units that are available with this.  By default the configuration file doesn't specify what interface to bind to, and instead relies on dnscrypt-proxy.socket to manage that.  You can't start both, so you have to enable and start the .socket unit.

I did make a small change to the /usr/lib/systemd/system/dnscrypt-proxy.socket file to allow querying on ::1 as well as 127.0.0.1 (get with the times folks!).  I updated and added:

ListenStream=lo:53
ListenDatagram=lo:53
BindIPv6Only=both

This binds to the loop back/localhost interface for both IPv4 and IPv6.

NB: When updating a systemd unit file, it shouldn't be done in situ, copy the file into /etc/systemd/system as otherwise your changes can be lost when an update occurs.  You can also selectively only override parts, but that's outside the scope of this article.

2022 Update

After updating my distribution, my socket file was no longer working, complaining that it "has a bad unit file setting".  Looking at the status the problem was with the "lo" as the listening device.  As per the documentation I changed this to simply have the port number, and now the socket listens on all interfaces; we can combine that with BindToDevice to restrict the interface.

My adjustments now look like this:

ListenStream=53
ListenDatagram=53
BindIPv6Only=both
BindToDevice=lo

Switching Over

Finally I need to update my system's DNS settings to use the local dnscrypt-proxy server instead of going out to the Internet directly.  This will differ depending on your system and setup.  Traditionally DNS configuration is done through /etc/resolv but that's often in turn managed by sysconfig or similar.  I set NETCONFIG_DNS_STATIC_SERVERS="::1" in sysconfig and ensured that /etc/resolv wasn't overriding anything.

You can check whether it's working by either blocking port 53 on the firewall and seeing if you can still do lookups, or by using tcpdump to see what queries are being performed.

Breaking from the voyeuristic norms of the Internet, any comments can be made in private by contacting me.