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-dns.com
    forward-addr: [email protected]#cloudflare-dns.com
    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.
4 Jun 2018 – Modified the config to use #cloudflare-dns.com per comment from Daniel Aleksandersen (see this thread for background).
20 Jul 2018 – I was just trying to use the Pi that I first tested this on, and it was stubbornly refusing to work. The issue turned out to be time. The Pi hadn’t got an NTP sync when it came up, and was unable to establish a trusted connection with Cloudflare when the clock was too far off.

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





30 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.

  3. 10 george

    Thanks so much for putting this together. It really is well written and I appreciate the details provided on the settings that were chosen.

    Quick comment regarding Quentin’s Docker repo. I wasn’t able to get that to run in docker on raspi without updating to work on an ARM platform. I didn’t work through it because I’m not sure I could come up with a good use case to run unbound in docker on a raspberry pi (other than I wanted to try it ;-)). I did however, steal his inclusion of the malicious sites blacklist.

    Thanks again

  4. You’re not validating the TLS certificates in this setup. Your DNS requests can still be intercepted and modifid with a self-signed certificate.

    It should be:

    forward-addr: [email protected]#cloudflare-dns.com
    forward-addr: [email protected]#dns.quad9.net

    The # character as used here is not a comment but actually tells unbound what domain to validate the certificates against.

  5. Hi Chris! Thanks for the excellent guide.

    Do you have a way to verify that this is working properly? I have it set up as you described, but I’m not sure that Unbound is actually using TLS. With Wireshark, I can see that normal DNS (53) traffic is going out to the Rpi. I’m assuming it’s being forwarded from there over TLS, but I’d like to confirm.

    • I just used netstat on the Pi to confirm it was connecting to the right ports on CloudFlare when I made DNS queries to it.

      There’s probably a lot more I could do to confirm the right certificates are being exchanged etc.

    • Hi mattersubject,

      I believe you could see more information from unbound by setting the verbosity to 5 (maximum) in the unbound.conf file and by launching unbound with the flags “-v -v -v” for extra verbosity. You can check the Unbound website if you are unsure about it or check my github at https://github.com/qdm12/cloudflare-dns-server#testing-it

      • 16 darave

        @quentin mcgaw, thanks for the help.
        Building the client from scratch made the difference!
        I had to upgrade my poor Raspberry Pi 1 from jessie to stretch (took a day on that old hardware), but now it runs.
        Appreciate your help!

  6. 17 darave

    Hi,

    Thanks for the nice tutorial.
    I went through all the steps, but the service fails to run for some reason.
    It looks like it tries to bind twice the listening ports:

    [1536472175] unbound[3827:0] notice: Start of unbound 1.6.0.
    [1536472175] unbound[3827:0] debug: increased limit(open files) from 1024 to 4164
    [1536472175] unbound[3827:0] debug: creating udp4 socket 0.0.0.0 53
    [1536472175] unbound[3827:0] debug: creating tcp4 socket 0.0.0.0 53
    [1536472175] unbound[3827:0] debug: creating udp4 socket 0.0.0.0 53
    [1536472175] unbound[3827:0] debug: creating tcp4 socket 0.0.0.0 53
    [1536472175] unbound[3827:0] error: can’t bind socket: Address already in use for 0.0.0.0 port 53 (len 16)
    [1536472175] unbound[3827:0] fatal error: could not open ports

    In any case, no one listening on port 53:
    tcp6 0 0 192.168.55.200:51828 192.168.55.29:53108 ESTABLISHED
    udp 0 0 0.0.0.0:5353 0.0.0.0:*
    udp 0 0 0.0.0.0:5353 0.0.0.0:*
    udp6 0 0 :::5353 :::*
    unix 2 [ ACC ] STREAM LISTENING 15485 /root/.forever/sock/worker.15364161623804AM.sock
    unix 3 [ ] STREAM CONNECTED 14853 /run/systemd/journal/stdout
    unix 3 [ ] STREAM CONNECTED 14534 /var/run/docker/containerd/docker-containerd.sock
    unix 3 [ ] STREAM CONNECTED 8453 /run/systemd/journal/stdout
    unix 3 [ ] STREAM CONNECTED 14533

    Any idea…?
    Thanks

    • That’s a bit of a head scratcher. There’s probably some other reason why it’s failing to bind, but an explanation of why that might be doesn’t spring to mind.

    • 20 quentin mcgaw

      What does the command:
      netstat -tulpn | grep 53
      give you? There must something occupying port 53.
      It is also odd it gives you “unbound[3827:0] debug: creating tcp4 socket 0.0.0.0 53” twice (for both tcp4 and udp4).

      • 21 darave

        I did run netstat and the output is in the original message, nothing is occupying port 53:

        (Not all processes could be identified, non-owned process info
        will not be shown, you would have to be root to see it all.)
        udp 0 0 0.0.0.0:5353 0.0.0.0:* -
        udp 0 0 0.0.0.0:5353 0.0.0.0:* -
        udp6 0 0 :::5353 :::* -

        Very strange.. :(
        M assumption is that for some obscure reason, unbound is trying to doubly bind and fails. Any idea…?
        BTW, trying on other ports fails the same way.

      • It sounds almost like unbound is failing to get the ports because of a permission issue rather than because the port is already in use, which would lead me to ask what user is it running as, and whether the same issue happens with ports >1024? Beyond that my head goes to peculiar cgroup and kernel capability configs possibly getting in the way. Are you doing this with stock (latest) Raspian or an image that’s accumulated a bunch of cruft from a variety of past projects?

      • 23 quentin mcgaw

        What’s your unbound.conf file? Maybe try with:
        server:
        verbosity: 2
        use-syslog: no
        qname-minimisation: yes
        do-tcp: yes
        prefetch: yes
        rrset-roundrobin: yes
        use-caps-for-id: yes
        do-ip4: yes
        do-ip6: no
        interface: 0.0.0.0
        access-control: 0.0.0.0/0 allow
        forward-zone:
        name: “.”
        forward-addr: [email protected]#cloudflare-dns.com
        forward-addr: [email protected]#cloudflare-dns.com
        forward-ssl-upstream: yes

  7. 24 darave

    I tried port 2222, got exactly the same result, so I don’t think it is permissions but I might be wrong.
    I am on Raspberry PI 1, with Debian stretch, updated to last apt-get update/upgrade…

    [email protected] ~ $ cat /etc/os-release
    PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
    NAME="Raspbian GNU/Linux"
    VERSION_ID="9"
    VERSION="9 (stretch)"
    ID=raspbian
    ID_LIKE=debian
    HOME_URL="http://www.raspbian.org/"
    SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
    BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

    Unbound is from apt-get, version 1.6.
    The conf files are exactly as described in the @chris's wonderefull howto above, I changed nothing (very similar to quentin's post).

    • 25 quentin mcgaw

      Did you try running Unbound not as a daemon with
      unbound -d
      Did you check your Iptables? Otherwise installing Unbound without a package manager?

      • 26 darave

        Yes, running without /d yields same results.
        When can I check re. iptables?
        And installing w/o a package manager, you mean build it from sources? I haven’t tried this. Not sure which version still buildable on the pi 1… :|

      • 27 quentin mcgaw

        Try running nc -l -k -p 53 to see if port 53 is indeed free (probably is). If so then:

        First uninstall unbound:
        sudo apt-get uninstall unbound -y
        Then download and compile it (see https://nlnetlabs.nl/documentation/unbound/howto-setup/#compiling for more information):
        wget http://www.unbound.net/downloads/unbound-latest.tar.gz
        tar -xvzf unbound-latest.tar.gz && rm unbound-latest.tar.gz && cd unbound-1.6.4/
        sudo ./configure && sudo make && sudo make install
        Now try to run unbound. You might have to search where to place your unbound.conf or recompile it with different flags for ./configure to specify where you want the unbound.conf file to be.

  8. 28 darave

    Thanks quentin, I’ll give it a try.


  1. 1 Cambridge Analytica – JamesBook
  2. 2 secure your dns on a a raspberry pi | leonslessons

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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.


%d bloggers like this: