dnscrypt-proxy: Configuring on Debian / Raspbian Jessie

After installing dnscrypt-proxy, at this point I diverge from the Shell Hacks guide when configuring it. In particular they did this with rc.local, whereas nowadays on Debian Jessie we use systemd units instead.

To elaborate a bit more on my DNS setup:

  • Each Raspberry Pi runs two instances of dnscrypt-proxy, each which talks to a different upstream dnscrypt server. These run on a custom port, as they are for localhost use only.
  • Each Raspberry Pi runs an instance of BIND9, which uses the RPi’s two local dnscrypt-proxy instances as forwarders. BIND runs on port 53 as normal.
  • Devices in my network are configured to use both Raspberry Pis BIND instance as their DNS server, either through DHCP options or statically.

So, to go through an example query:

  1. A network device makes a DNS query to one of the RPis on port 53 for google.com.
  2. BIND then queries one of the dnscrypt-proxy instances on the custom port for google.com. (This is assuming BIND does not already have google.com in its cache from a recent query.)
  3. dnscrypt-proxy then queries its upstream resolver for google.com
  4. The upstream resolver provides dnscrypt-proxy with the answer 216.58.198.110. dnscrypt-proxy passes this back to BIND, and BIND passes this to the querying device, which now has its answer.

The reason for this setup is that dnscrypt-proxy originally was not a caching DNS server; every lookup was a fresh lookup. It makes sense to cache locally – for speed, for politeness to the upstream servers, etc. (I understand that as of 1.9.0 they have added a cache plugin, so perhaps that is worth looking into). And just like how with a normal DNS caching setup you would usually have at least two forwarders, with dnscrypt we have at least two instances that BIND uses as forwarders.

(I use BIND personally because I also have some internal DNS zones that I prefer to use BIND for. If you don’t want any of that, you can probably use any caching DNS server you like, such as Dnsmasq or Unbound.)

So we need to create two systemd units on each RPi. I’ve adapted this guide from Nurdletech.

Nurdletech starts by having the services run as the nobody user account. Using the nobody account seems to be common advice, and it’s certainly better than using root. However it is against best practice / the principle of least privilege, because as soon as you have two or more daemons running under the nobody account, being able to compromise one can mean compromising them all.

If you run top on any Linux system, you will see some services that run on their own account – for example ntp has ntp, and apache has www-data. So we do the same for dnscrypt-proxy, by creating a user dnscrypt-proxy to run our daemons. This way compromising the dnscrypt-proxy account only compromises our dnscrypt-proxy daemons.

> sudo useradd --system --user-group --shell /usr/sbin/nologin dnscrypt-proxy

(Note that whether you use nobody or a custom unprivileged account, if you want to use port 53 or any port below 1024, you will need to grant dnscrypt-proxy the ability to run on privileged ports. I’ll be using unprivileged ports 40053 and 40054, so this isn’t an issue here).

We need a place to keep the PID lock files for each service, to which dnscrypt-proxy has permissions. These days on Debian they should be in /run, which is a tmpfs filesystem created on boot. Creating a configuration file in /etc/tmpfiles.d will precreate this temporary folder on boot, and with the specified permissions.

> sudo nano /etc/tmpfiles.d/dnscrypt-proxy.conf
d /run/dnscrypt-proxy 0775 root dnscrypt-proxy - -

If we now reboot to test, we can see that the folder is created on boot.

> sudo reboot

> ls -ld /run/dnscrypt-proxy
drwxrwxr-x 2 root dnscrypt-proxy 40 Jan 28 22:52 /run/dnscrypt-proxy/

Now we can create our systemd unit.

> sudo nano /etc/systemd/system/dnscrypt-proxy1.service
[Unit]
Description = dnscrypt-proxy1
After = network.target

[Service]
ExecStart = /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/run/dnscrypt-proxy/proxy1.pid -R cs-ch --ephemeral-keys --local-address=127.0.0.1:40053
Restart = always
Type = forking
User = dnscrypt-proxy
PIDfile = /run/dnscrypt-proxy/proxy1.pid

[Install]
WantedBy = default.target

Clarifying a few things:

  • Description – Defines the name of the service, as we will call it using systemctl or service commands.
  • After = network.target – Ensures we do not start until after the network is up.
  • --daemonize – Tells dnscrypt-proxy to fork the process, and --pidfile where to save the PID file. Correspondingly, systemd itself is aware of this with the Type and PIDfile lines.
  • -R cs-ch – Specifies the resolver (upstream dnscrypt server), by its short name in dnscrypt-resolvers.csv. In this case it’s the CS Switzerland DNSCrypt server.
  • --local-address=127.0.0.1:40053 – Tells dnscrypt-proxy to run on localhost on the custom port 40053. If you were running on the standard port 53 and having clients connect directly, you would use your RPi’s IP address and no port, e.g. --local-address=192.168.10.11.

Now we tell systemd to reload this new unit file, enable and start the service, and then test it works with an nslookup.

> sudo systemctl daemon-reload
> sudo systemctl enable dnscrypt-proxy1
> sudo systemctl start dnscrypt-proxy1
> nslookup -port=40053 google.com 127.0.0.1

Non-authoritative answer:
Name:   google.com
Address: 216.58.211.14

For the second dnscrypt-proxy instance, I simply create another systemd unit file with a different upstream dnscrypt server specified, a different port, and all instances of proxy1 replaced with proxy2.

[Unit]
Description = dnscrypt-proxy2
After = network.target

[Service]
ExecStart = /usr/local/sbin/dnscrypt-proxy --daemonize --pidfile=/run/dnscrypt-proxy/proxy2.pid -R cisco --ephemeral-keys --local-address=127.0.0.1:40054
Restart = always
Type = forking
User = dnscrypt-proxy
PIDfile = /run/dnscrypt-proxy/proxy2.pid

[Install]
WantedBy = default.target

Once both instances are working, you can configure your caching DNS server BIND / Dnsmasq / Unbound / etc. to use them as forwarders. Or if you have done this on standard port 53 and listening on your real network interface, you can point your network devices to these instances – just be mindful of the lack of caching.

Final note: Your caching server needs to be able to talk to forwarders running on a non-standard port (i.e. not 53). Not all caching servers support this – for example Windows Server’s DNS server can only use DNS servers listening on port 53 as forwarders. How do you work around this?

Answer: If you are running your dnscrypt-proxy resolvers on the same server as your Windows DNS Server, instead of running on different ports, run them on different loopback IP addresses. For example instead of dnscrypt-proxy listening on 127.0.0.1:40053 and 127.0.0.1:40054, listen on 127.0.53.1:53 and 127.0.53.2:53. You can then configure your Windows DNS server to forward to 127.0.53.1 and 127.0.53.2 on standard port 53. Your Windows DNS server will happily listen on its real interfaces (e.g. 192.168.10.11:53) and the normal loopback IP 127.0.0.1:53 without any conflict with dnscrypt-proxy.

Leave a Comment

Your email address will not be published. Required fields are marked *