Two Wrongs

Secure DNS on Laptop with Debian

Secure DNS on Laptop with Debian

In case you didn't know already: dns is really cool. dns is fundamentally a huge, global, distributed, incredibly fast key-value database. It is the original web scale nosql implementation.

It is also fully, completely and entirely insecure.

All queries and responses are sent in plain text. When your computer sends a query anyone can read which domain name you want to access. When you get a response, you have no idea who sent the response and whether it's even correct.

Oh, and what server have you actually configured to receive responses from? With high probability, you're getting a server from the network owner through dhcp. Are all networks you connect to trusted, or is there a small risk that some network owner might suggest their own insecure dns server to your computer?

Let's make that concrete: whenever you want to visit www.mybank.com, anyone can see that you're about to, and anyone can send you a response with the IP address for mybank.phishing.com instead of the one you were expecting.

Of course, in the example above, your bank uses https (because they do, right?) so the phishing site will not have the same certificate as your real bank. However, would you notice if the green padlock said "My Bank Cornpany"? That's a different certificate for a different domain. So that's only a partial solution. Similarly, dnssec (which lets key-value associations be cryptographically signed by "the key owner") has its own problems (and, according to some, solves practically no real problems.)

Besides, both the above attempts at solving the problem still leave your privacy unguarded. Anyone can see which domain names you look up. That is terrifying.

Encryption to the Rescue

So there are two components to this problem: integrity (the reply I get back is the one I want to get back) and confidentiality (other people do not get to know what queries I make).

Dnssec solves the first problem, but we're still waiting for people to actually use it. Since adoption of dnssec is so low, I have settled with a simpler "solution" to this problem. Instead of relying on the network owner to suggest a dns server for me through dhcp, I have configured my computer to always issue queries to a dns service I feel like I can trust: the Opennic project.

The second problem we can easily solve by using an encrypted version of the dns protocol called dnsCrypt. Adoption of this is low too, but unlike dnssec, dnsCrypt only needs to be supported by two parties: my computer, and the dns server I connect to. Fortunately, the Opennic project supports dnsCrypt.

There's a third piece to the puzzle, though. A dns query through dnsCrypt to the Opennic anycast address can take anywhere between one and four seconds. That's an unreasonable time to wait before your browser even can start loading the web page you want to visit, but it is simply a caching problem. So I also run a lightweight dns cache on my machine, which means that queries take 0 seconds as soon as I have made them once.

I created a highly professional illustration of the solution below. Top is how dns is normally used. Bottom is how I use it.

An illustration of dnscrypt+dnsmasq

VPN

Although I'm not going to talk more about vpn, it does deserve a headline of its own. A vpn allows you to pretend your computer is attached to another, fixed network, regardless of which actual network it's connected to. If you decide to pretend you're connected to a trusted network, it doesn't matter that dns queries exit the trusted network unencrypted.

A vpn would indeed solve the same problems with dns that I'm trying to solve, but I'm not going to talk about it any more in this article because

  1. Vpn may degrade the performance of all network operations – which is not interesting to me, and
  2. I wanted to learn about dnsCrypt.

DNSCrypt

The restraints on this problem are that programs on our computer want to issue dns requsts normally, but we want them to exit our computer encrypted. To this end, we'll use dnscrypt-proxy which acts as a dns server on our machine, but simply forwards any dns requests to a real dns server – after it encrypts them, of course.

As users of Debian, we are fairly lucky in that the default configuration of dnscrypt-proxy is pretty much spot on for our usage. Start by installing it through

sudo apt-get install dnscrypt-proxy

Then edit /etc/dnscrypt-proxy/dnscrypt-proxy.conf to make sure it contains

ResolverName fvz-anyone

This is the name for an Opennic anycast address. Look at the list of dnscrypt-proxy resolvers if you specifically want some other dns service.

Since we want to run a dns cache in front of dnscrypt-proxy, we need to reconfigure dnscrypt-proxy to not listen on port 53. I picked 3053 as the dnscrypt-proxy port.

Note that the dnscrypt-proxy configuration file will contain a LocalAddress entry, but this is not used in the default systemd based configuration. Instead, edit /lib/systemd/system/dnscrypt-proxy.socket such that both ListenStream and ListenDatagram are 127.0.0.1:3053. Then to update this configuration and launch dnscrypt-proxy, run

sudo systemctl daemon-reload
sudo systemctl enable dnscrypt-proxy    # auto-start dnscrypt-proxy
sudo systemctl restart dnscrypt-proxy   # and launch it now

I suggest you test that your dnscrypt-proxy configuration works by asking dig to perform a dns query through your dnscrypt-proxy server. You can do that with a command like the following:

dig www.sunet.se @127.0.0.1 -p 3053

If dnscrypt-proxy is up and running, this should (after a few seconds) return a regular dns query response.

Edit on 2017-06-26: In case you run into firewall problems, it appears the fvz-anyone servers use remote port 5353, which needs to be opened for outgoing traffic using both udp and tcp.

Interlude: Types of dns Servers

So there are basically four different types of dns servers. First are the root servers. If you want to know the address of a domain name (say www.sunet.se), but you don't know anything about the domain name, you ask the root servers first. The root servers don't know very much themselves, but they should know who to ask to get more information. In our example, they will respond "I don't know, but you could ask this other dns server here, who is responsible for all .se domain names!"

So you ask that server instead, which will say, "oh gee, I'm glad you asked, but I can't tell you. However, this other dns server here is responsible for all sunet.se domain names, so they might know." Then you ask that server and that server will know.

These servers that can either respond directly or delegate to a server below them in the hierarchy are called authoritative name servers. Highest in their hierarchy are the root servers.

Ideally, these servers are all the servers you need. In practise, if every internet user in the world started their dns lookups at the root servers, the root servers would choke under the pressure. So we introduce recursive name servers. The recursive name servers do this iterative dance with autoritative servers on the behalf of the user. The user asks for www.sunet.se, the recursive server does the above and then responds only once with the address for the domain name.

These recursive servers also cache results, so if you next want to know the address of www.riksdagen.se, the recursive server doesn't have to consult the root server, because it already knows which server is responsible for all .se domain names. Since most users of a recursive name server are interested in similar domain names, these recursive servers in practise almost never have to hit the root servers.

The fourth type of server is a thin proxy, which you have seen already with dnscrypt-proxy. These are not recursive servers, they simply forward the request to another known recursive server. However, some proxies are also able to cache the results that go through them. That's what we're interested in now!

Dnsmasq

We will use Dnsmasq as our caching proxy, because it is lightweight and fast. This means that we want any dns requests on our computer to first go to Dnsmasq, which will then either respond directly (if it remembers the response) or forward the request to dnscrypt-proxy, which then encrypts them and sends them out to an Opennic server, which does the actual recursive lookup.

Just as before, the out-of-the-box Debian configuration for Dnsmasq is great. All we need to change are three things:

  1. we want to tell Dnsmasq to be a dumb proxy,
  2. we want it to forward requests to dnscrypt-proxy, and
  3. we want to increase the cache size.

This is accomplished with the following three lines of configuration.

no-resolv
server=127.0.0.1#3053
cache-size=65536

I want to note here that the default cache-size for Dnsmasq is 150 entries. It might sound insane to set a size that is over 400 times as big as the default. The reasoning is that Dnsmasq is primarily used in low-power, low-performance environments like home routers. These need a tiny default. On a modern laptop that's not going to be an issue.

While you're in the configuration file, you may want to double check that the dhcp server feature in Dnsmasq is disabled. We don't want that. Then run

sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq

and you have Dnsmasq running as a dns server on 127.0.0.1:53 (the default dns port.) Make sure it is working by issuing

dig www.riksdagen.se @127.0.0.1

twice. The first time you run the command, it should take a few seconds. The next time it should finish in 0 milliseconds (because it's getting the result from your local Dnsmasq cache).

Hands off, Network Manager!

As the final step, you need to tell your system to use your local dns server. First, edit the Network Manager configuration at /etc/NetworkManager/NetworkManager.conf and add to the [main] section the definition

dns=none

which tells Network Manager to not reset your dns settings every time you connect to a new network. Then delete the existing symlink at /etc/resolv.conf and create a new file there. Edit the file to contain only 127.0.0.1 (i.e. the only dns server allowed is the one on your machine).

Restart Network Manager to make the setting take effect:

sudo systemctl restart network-manager

You're done! Any further dns queries will go through your local cache to an encrypted connection to Opennic. If you want to, you can verify this through packet sniffing.

Final Advice

I'm using a static resolv.conf file, which means that regardless of which network I connect to, I'm going to use this encrypted dns solution. This might not be what you want. For example, I really trust my home LAN and the ISP we have there, so in principle I might want to use their dns unencrypted when I'm there because it's faster. I get the impression you can use the "resolvconf" program to let your resolv.conf file depend on which network you connect to. I have not explored this.

It's worth mentioning that many dnsCrypt servers use non-standard ports (443, 5353) by default to avoid common dns tracking/blocking configurations. This might cause problems in some scenarios when dns is expected to be performed over port 53. You can change this in the dnscrypt-proxy configuration.

Some open wireless networks have a sort of captive portal with a login page where you are redirected the first time you attempt to visit a website. These are implemented through dns hijacking and they don't work at all when you have the above dnsCrypt configuration. That might be a good or bad thing depending on who you ask, but to restore their functionality you have to temporarily change your resolv.conf to contain the dns server suggested by dhcp.

Initial Impressions

I've been running this setup for a couple of days now and it's working surprisingly well. I was afraid it would be too slow for me to stand, but it's actually fairly fast, given what it does. Sure, the first request for a domain is slightly slower than I'm used to, but any subsequent requests are incredibly fast since I now have a local caching proxy.

I also feel slightly more confident connecting to foreign networks now.