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.
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
- Vpn may degrade the performance of all network operations – which is not interesting to me, and
- 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:
- we want to tell Dnsmasq to be a dumb proxy,
- we want it to forward requests to dnscrypt-proxy, and
- 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.