Howto: secure your DNS with a Raspberry Pi, Unbound and Cloudflare 1.1.1.1

04Apr18

Why?

Everything you access on the Internet starts with a Domain Name System (DNS) query to turn a name like google.com into an IP address like 216.58.218.14. Typically the DNS server that provides that answer is run by your Internet Service Provider (ISP) but you might also use alternative DNS servers like Google (8.8.8.8). Either way regular DNS isn’t encrypted – it’s just plain text over UDP Port 53, which means that anybody along the way can snoop on or interfere with your DNS query and response (and it’s by no means unheard of for unscrupulous ISPs to do just that).

Cloudflare recently launched their 1.1.1.1 DNS service, which is fast (due to their large network of local data centres plus some clever engineering) and offers some good privacy features. One of the most important capabilities on offer is DNS over TLS, where DNS queries are sent over an encrypted connection, but you don’t get that by simply changing the DNS settings on your device or your home router. To take advantage of DNS over TLS you need your own (local) DNS server that can answer queries in a place where they won’t be snooped or altered; and to do that there are essentially two approaches:

  1. Run a DNS server on every machine that you use. This is perhaps the only workable approach for devices that leave the home like laptops, mobiles and tablets, but it isn’t the topic of his Howto – maybe another day.
  2. Run a DNS server on your home network that encrypts queries that leave the home. I already wrote about doing this with OpenWRT devices, but that assumes you’re able and willing to find routers that run OpenWRT and re-flash firmware (which usually invalidates any warranty). Since many people have a Raspberry Pi, and even if you don’t have one they’re inexpensive to buy and run, this guide is for using a Pi to be the DNS server.

Prerequisites

This guide is based on the latest version of Raspbian – Stretch. It should work equally well with the full desktop version or the lite minimal image version.

If you’re new to Raspberry Pi then follow the stock guides on downloading Raspbian and flashing it onto an SD card.

If you’re already running Raspbian then you can try bringing it up to date with:

sudo apt-get update && sudo apt-get upgrade -y

though it may actually be faster to download a fresh image and reflash your SD card.

Installing Unbound and DNS utils

Unbound is a caching DNS server that’s capable of securing the connection from the Pi to 1.1.1.1. Other options are available. This guide also uses the tool dig for some testing, which is part of the DNS utils package.

First ensure that Raspbian has up to date package references:

sudo apt-get update

Then install Unbound and DNS utils:

sudo apt-get install -y unbound dnsutils

At the time of writing this installs Unbound v1.6.0, which is a point release behind the latest v1.7.0, but good enough for the task at hand. Verify which version of Unbound was installed using:

unbound -h

which will show something like:

usage:  unbound [options]
        start unbound daemon DNS resolver.
-h      this help
-c file config file to read instead of /etc/unbound/unbound.conf
        file format is described in unbound.conf(5).
-d      do not fork into the background.
-v      verbose (more times to increase verbosity)
Version 1.6.0
linked libs: libevent 2.0.21-stable (it uses epoll),
OpenSSL 1.1.0f  25 May 2017
linked modules: dns64 python validator iterator
BSD licensed, see LICENSE in source package for details.
Report bugs to [email protected]

At this stage Unbound will already be running, and the installer will have taken care of reconfiguring Raspbian to make use of it; but it’s not yet set up to provide service to other machines on the network, or to use 1.1.1.1.

Configuring Unbound

The Raspbian package for unbound has a very simple configuration file at /etc/unbound/unbound.conf that includes any file placed into /etc/unbound/unbound.conf.d, so we need to add some config there.

First change directory and take a look at what’s already there:

cd /etc/unbound/unbound.conf.d
ls

There should be two files: qname-minimisation.conf and root-auto-trust-anchor-file.conf – we don’t need the former as it will be part of the config we introduce, so remove it with:

sudo rm qname-minimisation.conf

Create an Unbound server configuration file:

sudo bash -c 'cat >> /etc/unbound/unbound.conf.d/unbound_srv.conf \
<<UNBOUND_SERVER_CONF
server:
 qname-minimisation: yes
 do-tcp: yes
 prefetch: yes
 rrset-roundrobin: yes
 use-caps-for-id: yes
 do-ip6: no
 interface: 0.0.0.0
 access-control: 0.0.0.0/0 allow
UNBOUND_SERVER_CONF'

It's necessary to use 'sudo bash' and quotes around the content here as 'sudo cat' doesn't work with file redirection.

These settings are mostly lifted from ‘What is DNS Privacy and how to set it up for OpenWRT‘ by Torsten Grote, but it’s worth taking a look at each in turn to see what they do (based on the unbound.conf docs):

  • qname-minimisation: yes – Sends the minimum amount of information to upstream servers to enhance privacy.
  • do-tcp: yes – This is the default, but better safe than sorry – DNS over TLS needs a TCP connection (rather than UDP that’s normally used for DNS).
  • prefetch: yes – Makes sure that items in the cache are refreshed before they expire so that the network/latency overhead is taken before a query needs an answer.
  • rrset-roundrobin: yes – Rotates the order of Round Robin Set (RRSet) responses according to a random number based on the query ID.
  • use-caps-for-id: yes – Use 0x20-encoded random bits in the query to foil spoof attempts. This perturbs the lowercase and uppercase of query names sent to authority servers and checks if the reply still has the correct casing. Disabled by default. This feature is an experimental implementation of draft dns-0x20.
  • do-ip6: no – Turns off IPv6 support – obviously if you want IPv6 then drop this line.
  • interface: 0.0.0.0 – Makes Unbound listen on all IPs configured for the Pi rather than just localhost (127.0.0.1) so that other machines on the local network can access DNS on the Pi.
  • access-control: 0.0.0.0/0 allow – Tells Unbound to allow queries from any IP (this could/should be altered to be the CIDR for your home network).

Next configure Unbound to use Cloudflare’s DNS servers:

sudo bash -c 'cat >> /etc/unbound/unbound.conf.d/unbound_ext.conf \
<<UNBOUND_FORWARD_CONF
forward-zone:
    name: "."
    forward-addr: [email protected] # Cloudflare primary
    forward-addr: [email protected] # Cloudflare secondary
    forward-ssl-upstream: yes
UNBOUND_FORWARD_CONF'

There are two crucial details here:

  1. @853 at the end of the primary and secondary server IPs tells unbound to connect to Cloudflare using port 853, which is the secured end point for the service.
  2. forward-ssl-upstream: yes – is the instruction to use DNS over TLS, in this case for all queries (name: “.”)

Restart Unbound to pick up the config changes:

sudo service unbound restart

Testing

First check that the Unbound server is listening on port 53:

netstat -an | grep 53

Should return these two lines (not necessarily together):

tcp    0    0 0.0.0.0:53    0.0.0.0:*    LISTEN
udp    0    0 0.0.0.0:53    0.0.0.0:*

Then try looking up google.com

dig google.com

The output should end something like:

;; ANSWER SECTION:
google.com.    169    IN    A    172.217.4.174

;; Query time: 3867 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Apr 04 14:46:09 UTC 2018
;; MSG SIZE  rcvd: 55

Note that the Query time is pretty huge when the cache is cold[1]. Try again and it should be much quicker.

dig google.com
;; ANSWER SECTION:
google.com.    169    IN    A    172.217.4.174
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Apr 04 14:46:09 UTC 2018
;; MSG SIZE rcvd: 55

Using it

Here’s where you’re somewhat on your own – you have a Pi working as a DNS server, but you now need to reconfigure your home network to make use of it. There are two essential elements to this:

  1. Make sure that the Pi has a known IP address – so set it up with a static IP, or configure DHCP in your router (or whatever else is being used for DHCP) to have a reservation for the Pi.
  2. Configure everything that connects to the network to use the Pi as a DNS server, which will usually be achieved by putting the (known from the step above) IP of the Pi at the top of the list of DNS servers passed out by DHCP – so again this is likely to be a router configuration thing.

Updates

10 Apr 2018 – Thanks to comments from Quentin and TJ I’ve added the access-control line to the server config. Also check out Quentin’s repo (and Docker Hub) for putting Unbound into a Docker container.

Note

[1] Almost 4 seconds is ridiculous, but that’s what I’m getting with a Pi on a WiFi link to a US cable connection. From my home network it’s more like 160ms. I suspect that Spectrum might be tarpitting TCP to 1.1.1.1





10 Responses to “Howto: secure your DNS with a Raspberry Pi, Unbound and Cloudflare 1.1.1.1”

  1. Hey, thanks for the tutorial ! I’m trying to connect to my Raspberry Pi directly from my Windows 10 computer. So I set the DNS server to the LAN IP address of my Raspberry Pi but it seems like it tries to connect on port 53 UDP only (which fails) and not 53 TCP. Any idea how to resolve this ? Thanks!

    • The clients should be connecting on 53 UDP, so that’s fine, but a firewall (on the Pi) could be standing between the Windows PC and the Pi. Are you using nslookup on the PC to test (using ‘server yo.ur.pi.ip’ so that’s). You can also use netstat on the Pi to see the incoming connections.

      • 3 qmcgaw

        Hi Chris, thanks for your reply ! Hopefully I won’t go too much out of scope from the Raspberry Pi ! I’m trying to run unbound on a Docker container (based on Alpine, quite simple) to connect to Cloudflare 1.1.1.1 over TLS. Using nslookup.exe, when I enter `server 192.168.1.210` (IP of my Docker host – NAS machine) I obtain :

        Default Server: [192.168.1.210]
        Address: 192.168.1.210

        and I would obtain a “DNS request timed out.” if the container is not running, as expected. That’s promising. On the other hand, when configuring it in the Internet Protocol Version 4 (TCP/IPv4) of the Ethernet adapter properties in Windows 10, I still obtain the error “Your DNS server might be unavailable”. If the container is not running, I obtain the expected error “The DNS server isn’t responding”.

        My unbound configuration is:

        server:
        verbosity: 5
        qname-minimisation: yes
        do-tcp: yes
        prefetch: yes
        rrset-roundrobin: yes
        use-caps-for-id: yes
        do-ip4: yes
        do-ip6: yes
        interface: 0.0.0.0
        forward-zone:
        name: “.”
        forward-addr: [email protected]
        forward-addr: [email protected]
        forward-addr: 2606:4700:4700::[email protected]
        forward-addr: 2606:4700:4700::[email protected]
        forward-ssl-upstream: yes

        The repository is available at https://github.com/qdm12/cloudflare-dns-server if you want to have a look. Thanks !

      • Docker adds some complexity around port mapping, but looking at your Dockerfile and docker-compose.yml you have that all figured out.

        So it becomes the usual step by step troubleshooting process…

        1. Throw dig (and bash) into the container, and get a shell to it and test from within
        2. Test from the Pi itself
        3. Test from beyond the Pi

        One of the other comments noted that they had to add an ‘access-control: th.ey.re.net/CIDR’ which wasn’t needed for my testing, but that might also be worth a shot.

      • 5 qmcgaw

        Hi Chris, thanks again for the help. It’s now working and was indeed caused by the access-control setting in unbound.conf. Thanks to TJ for the help as well ! Now the container is fully working with “access-control: 0.0.0.0/0”.

      • Cool – I’m glad to hear you got it working. I’ve backported that line into the post and noted your repo in an update. Will you be putting it on Docker Hub (as I can link to that too and provide an example Docker run line)?

      • 7 qmcgaw

        It’s at https://hub.docker.com/r/qmcgaw/cloudflare-dns-server

        Note that 0.0.0.0/0 might be too permissive, I will test with more restrictive configurations.

      • 0.0.0.0/0 is a good match for the interface: 0.0.0.0, and yes it’s permissive, but if somebody’s opening up their Pi to the outside world then this parameter isn’t what they should be depending on for access control.

  2. 9 TJ

    Thank you for the nice writeup. FWIW, I had to add an access control to the unbound configuration file to get it to work with my local network clients (I think unbound blocks everything but localhost by default). I added the following to unbound_srv.conf (my local network is 10.0.0.0/24) and it worked:
    access-control: 10.0.0.0/24 allow

    Again – thanks for taking the time to write this up – very helpful.


  1. 1 Cambridge Analytica – JamesBook

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s


%d bloggers like this: