Encrypted DNS with Caching

Domain Name Service (DNS) is an important vulnerability for most systems, and particularly so for laptops which are generally set up to be quite promiscuous when it comes to DNS.

The basic concern is that when you connect to a network that uses DHCP (most WiFi networks are this way), then you generally accept the network’s choice of name servers. They may offer a nefarious name server. The name server converts domain names to IP addresses. In that way it is like a phone book. The problem is that a nefarious name server might convert the domain name of your bank to the IP address of a site that is looks just like your banks site but is designed to steal your username and password. To see a humorous and largely harmless version of this, see Upside-Down-Ternet. This note tells you how to reduce your chances of succumbing to this particular attack.

In order to harden DNS on my laptop, I compiled and ran dnscrypt as a daemon and configured my laptop to always use it to provide DNS service. This protects me in two ways: first, dnscrypt assures that it talks only to the name server I have chosen, and second, it encrypts the connection so the responses cannot be observed or modified. I use OpenNIC as my trusted source of name servers because of its open and democratic policies (opennicproject.org).

In addition, I will also install and configure DNSMasq, which is optional. DNSMasq provides DNS caching to speed up your DNS performance.

I will describe how I did all of this on Fedora Linux.

Compiling and Installing DNSCrypt

When this was first written dnscrypt was not available through yum. That has changed. On Redhat systems you can now install it using:

sudo yum install dnscrypt-proxy

If that works for you, you can skip the rest of this section, which describes how to compile and install dnscrypt manually.

First, install the dnscrypt dependencies:

sudo yum install libsodium-devel

Download the source for dnscrypt from dnscrypt.org and untar it into a convenient directory.

Change into the dnscrypt directory and configure and make the program:

cd ~/packages/dnscrypt-proxy
./configure
make
sudo make install

Now, dnscrypt-proxy should be in /usr/local/sbin.

Alternatively, you can use get the absolute latest version of dnscrypt by using the version on github:

sudo yum install libtool-ltdl-devel libsodium-devel
git clone https://github.com/jedisct1/dnscrypt-proxy.git
cd dnscrypt-proxy
./autogen.sh
./configure
make
sudo make install

You can update your local repository and reinstall using:

git pull
make
sudo make install

Finding a DNSCrypt Resolver

There are dnscrypt resolvers all over the world. You should choose one that is near you. To do so, get the generate-dnscrypt-cmdline script:

git clone https://github.com/KenKundert/generate-dnscrypt-cmdline.git

You may need to install some dependences (see the README file for instructions). Then run it to get a list of resolvers:

cd generate-dnscrypt-cmdline
./generate-dnscrypt-cmdline

Choose a suitable resolver. I like the policies of OpenNIC, so I tend to choose the closest OpenNIC server. Before you follow my lead, you should know that the OpenNIC servers are run by volunteers, and the servers can come and go. At least I have this problem in California. If you are using dnscrypt and the service stops working, you simply repeat this process and choose another resolver.

Once you have chosen your resolver, say the OpenNIC server in Dallas, generate the dnscrypt-proxy command line using:

./generate-dnscrypt-cmdline fvz-rec-us-dal-01

You will need this for the next step.

Running DNSCrypt as a Daemon

For security reasons, dnscrypt should not be run as root. Instead it will be run as nobody, a user with few if any privileges. If you try to run dnscrypt-proxy as a normal user on its default port (53, a privileged port), you will get a permission denied message when it tries to open the port. So you either have to use a non-privileged port (1024 to 65535) or you can label the executable in such a way that it can open secured ports. You can do the latter with:

sudo setcap cap_net_bind_service=ep /usr/local/sbin/dnscrypt-proxy

To run dnscrypt-proxy as a daemon that starts automatically, create a file named /etc/systemd/system/dnscrypt.service that contains:

[Unit]
Description = DNScrypt
After = network.target

[Service]
PermissionsStartOnly = true
ExecStartPre = /bin/touch /var/run/dnscrypt.pid
ExecStartPre = /bin/chmod 666 /var/run/dnscrypt.pid
ExecStart = /usr/local/sbin/dnscrypt-proxy
   --provider-key=690D:F9B7:49C4:B968:0850:04BC:8447:D657:4D1D:205B:75F5:503E:9FC5:4416:5A1F:CE46
   --provider-name=2.dnscrypt-cert.fvz-rec-us-dal-01.dnsrec.meo.ws
   --resolver-address=45.35.66.151
   --local-address=127.0.0.1:2053
   --pidfile=/var/run/dnscrypt.pid
   --daemonize
Restart = always
Type = forking
User = nobody
PIDFile = /var/run/dnscrypt.pid

[Install]
WantedBy = default.target

where the value given for ExecStart should be the output produced by generate-dnscrypt-cmdline.

Important

For clarity the ExecStart command was given above using multiple lines, but in the dnscrypt.service file the entire ExecStart entry should be on the same line.

The dnscrypt process is run as nobody, which would not normally have permission to write the pid file in /var/run. We cannot create a directory to hold the PID file manually in advance because /var/run is deleted and re-created whenever the system is rebooted. To resolve this issue, prestart commands are added to the systemd config file that create the PID file in advance, and then make it writeable by all.

Important

The PID file specified on the dnscrypt-proxy command line should match the file specified for PIDFile.

The local address is configured to be port 2053 on localhost (127.0.0.1). Normally DNS should be served on port 53, but in this case a DNS caching service (dnsmasq) will be used and dnsmasq will serve from port 53, and it will be configured to talk to dnscrypt via 2053.

If you do not wish to use DNSMasq, you can simple tell dnscrypt-proxy to use 127.0.0.1:53 for --local-address and skip the next steps where DNSMasq is configured and started.

After updating the dnscrypt.service file, you should run:

sudo systemctl daemon-reload

To assure that dnscrypt-proxy starts automatically when the machine boots, run:

sudo systemctl enable dnscrypt

To start dnscrypt-proxy now, without waiting for a reboot, run:

sudo systemctl start dnscrypt

To assure it is working correctly, run:

sudo systemctl status dnscrypt

Be sure the status messages include the message: ‘This certificate looks valid’. If not, there may be a problem with the resolver you chose. You might try choosing another.

To rerun dnscrypt after you have changed the .service file:

sudo systemctl --system daemon-reload
sudo systemctl restart dnscrypt
sudo systemctl status dnscrypt

Testing DNSCrypt

To test dnscrypt-proxy to assure it is working properly, run:

dig google.com @localhost -p 2053

It should complete quickly with a list of IP addresses for google.com.

Configuring and Running DNSMasq

Generally Linux is configured such that NetworkManger controls DNS, and while NetworkManager is easily configured to use dnsmasq, it is difficult to control and will often subvert our desire to use dnscrypt when running on networks that use DHCP. So, our first step is to configure NetworkManager so that it does not manage dns. To do so, edit the file /etc/NetworkManager/NetworkManager.conf and assure that it contains the line:

dns=none

If you changed this file, restart NetworkManager so it honors the new setting:

sudo systemctl restart NetworkManager

Now add dnscrypt.conf to /etc/dnsmasq.d where dnscrypt.conf contains:

# Configure DNSMasq to provide DNS caching, but not DHCP service.
# This file configures DNSMasq to work with DNSCrypt, which is expected to be
# running on localhost#2053. If there is no response via DNSCrypt, DNSMasq
# will try to directly query the OpenNIC servers.
user=nobody
port=53
bogus-priv
no-resolv
clear-on-reload
domain-needed
strict-order
log-queries

# Servers seem to be taken in reverse order (highest priority server should be given last).
# These dns servers were culled from http://wiki.opennicproject.org/ClosestT2Servers
server=107.150.40.234  # Missouri
server=104.237.136.225 # Texas
server=104.219.55.89   # Texas
server=50.116.23.211   # Texas
server=23.226.230.72   # Washington
server=104.245.33.185  # California
server=74.207.241.202  # California
server=127.0.0.1#2053

Do not use these server addresses blindly. If you are using OpenNIC, visit their Wiki to determine the best servers to use. The OpenNIC servers seem to be somewhat transient, so if you go with OpenNIC you will have to review and update your servers from time to time.

Now configure dnsmasq to run as a daemon:

sudo systemctl enable dnsmasq
sudo systemctl start dnsmasq
sudo systemctl status dnsmasq

The first command causes dnsmasq to start up automatically everytime your machine restarts, the second starts it immediately, and the third prints out status information on dnsmasq so that you can tell whether it is running properly or not.

Testing DNSMasq

To test dnsmasq to assure it is working properly, run:

dig google.com @localhost
dig google.com @localhost

It should complete quickly with a list of IP addresses for google.com. Then view /var/log/messages. You should see commentary from dnsmasq that is similar to this:

Oct 27 18:01:49 hostname dnsmasq[24670]: query[A] google.com from 127.0.0.1
Oct 27 18:01:49 hostname dnsmasq[24670]: forwarded google.com to 127.0.0.1
Oct 27 18:01:49 hostname dnsmasq[24670]: reply google.com is 216.58.192.46
Oct 27 18:02:12 hostname dnsmasq[24670]: query[A] google.com from 127.0.0.1
Oct 27 18:02:12 hostname dnsmasq[24670]: cached google.com is 216.58.192.46

The first line acknowledges our first request. The second line indicates that the request was forwarded to 127.0.0.1, which is dnscrypt (the port is not shown). The third line indicates that dnscrypt was able to retrieve the address for google.com. The fourth line acknowledges our second request. And the fifth line indicates that dnsmasq has the address for google.com in its cache.

Once you are comfortable that things are working properly, comment out the ‘log-queries’ line from /etc/dnsmasq.d/dnscrypt.conf to suppress the extensive logging.

Configuring Your Machine to Use DNSMasq and DNSCrypt

Change /etc/resolv.conf so that it contains:

# Name Servers

# Assumes that dnsmasq and dnscrypt are running to provide encrypted dns with caching
nameserver 127.0.0.1

# Provide alternative trusted name servers in case something goes wrong with the default
# These dns servers were culled from http://wiki.opennicproject.org/ClosestT2Servers

# California
nameserver 74.207.241.202
nameserver 104.245.33.185
# Washington
nameserver 23.226.230.72
# Texas
nameserver 50.116.23.211
nameserver 104.219.55.89
nameserver 104.237.136.225
# Missouri
nameserver 107.150.40.234

# These are from Google. They should always work.
nameserver 8.8.8.8
nameserver 8.8.4.4

# This file was made immutable using "chattr +i /etc/resolv.conf" so that the
# file is not overwritten by Network Manager.
# Use "chattr -i /etc/resolv.conf" to remove the immutable attribute.
# Use "lsattr" to list immutable attributes.
# Use "chattr +i /etc/resolv.conf" to make the file immutable again.

Doing so tells all of the network aware services on your machine to use localhost for DNS traffic. Of course, you should change the alternative name servers to ones that would be appropriate for your location.

Finally, you must make this file immutable, otherwise NetworkManager will replace it whenever the network changes. To do so, use:

chattr +i /etc/resolv.conf

This will not work properly is /etc/resolv.conf is a symbolic link. In this case, you should delete the symbolic link, then create the file, then make it immutable.

Testing

To assure things are working as expected, run:

dig google.com

It should complete quickly with a at least one IP address for google.com. It should indicate the DNS server with:

;; SERVER: 127.0.0.1#53(127.0.0.1)

Finally, visit dnsleaktest.com and make sure you are actually using the dns server you specified to dnscrypt.

My Experience With The OpenNIC Project

I have been using The OpenNIC Project DNS servers for some time. It has very good policies and I am quite pleased with the service except for one thing. The DNS servers are run by volunteers, and the servers that support DNSCrypt seem to come and go. At least that seems to be the case in the United States. When a server you have chosen disappears, your computer will start using the backup DNS servers you specified, but the resolution process can become quite slow and painful as it first tries your primary DNS server, that times out, and then it uses your backup server.

To cope with this I have written two scripts that check the DNS server and emails me a warning if they are not working. The are named check-dnscrypt-resolver and you can find them on github. I simply place them in a cron script that runs every 10 minutes.

The first script, check-dnscrypt-resolver, must run as root. It examines /var/log/messages looking for a ‘No useable certificates found’ message, which indicates that the resolver used by dnscrypt has gone stale. The date on these messages are checked (after reconstructing the year) and if they are newer than the modification date on the dnscrypt.service file a warning is mailed to the maintainer.

The second script, check-dnscrypt-resolvers, runs:

dig google.com @localhost -p 2053

to see if dnscrypt is able to resolve addresses.

The first script can be slow to recognize problems. The second script is much faster but will complain about transient problems.