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.
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
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:
You will need this for the next step.
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=184.108.40.206 --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.
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.
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
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.
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:
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=220.127.116.11 # Missouri server=18.104.22.168 # Texas server=22.214.171.124 # Texas server=126.96.36.199 # Texas server=188.8.131.52 # Washington server=184.108.40.206 # California server=220.127.116.11 # 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.
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: query[A] google.com from 127.0.0.1 Oct 27 18:01:49 hostname dnsmasq: forwarded google.com to 127.0.0.1 Oct 27 18:01:49 hostname dnsmasq: reply google.com is 18.104.22.168 Oct 27 18:02:12 hostname dnsmasq: query[A] google.com from 127.0.0.1 Oct 27 18:02:12 hostname dnsmasq: cached google.com is 22.214.171.124
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.
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 126.96.36.199 nameserver 188.8.131.52 # Washington nameserver 184.108.40.206 # Texas nameserver 220.127.116.11 nameserver 18.104.22.168 nameserver 22.214.171.124 # Missouri nameserver 126.96.36.199 # These are from Google. They should always work. nameserver 188.8.131.52 nameserver 184.108.40.206 # 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.
To assure things are working as expected, run:
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.
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.