Introduction

Unbound is a validating, recursive, caching open-source DNS resolver primarily developed by NLnet Labs, VeriSign Inc., Nominet, and Kirei. To help increase our online privacy, unbound supports DNS-over-TLS and DNS-over-HTTPS which allows clients to encrypt their communication. Unbound runs on FreeBSD, OpenBSD, NetBSD, MacOS, Linux and Microsoft Windows, with packages available for most platforms.

This guide is intended for unbound installation in conjunction with Pi-hole and therefore Pi-hole is the only prerequisite.

The Pi-hole is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software. (documentation)

You can install and configure Pi-hole with the command below on any distribution which utilizes systemd or sysvinit. To verify if your distribution is supported, check out Supported Operating Systems section in the Pi-hole documentation. This guide will not go through the Pi-hole installation steps.

$ curl -sSL https://install.pi-hole.net | bash

Personal setup

We decided to install unbound on a dedicated system connected directly to our router via ethernet. Here is the complete list of hardware and accessories we have used for this project:

If you are familiar with how does DNS and unbound work, feel free to skip to the Installation section.


How does it actually work?

In this section, we will learn how does DNS work, what is the difference between recursive and iterative DNS resolver, how does unbound work and what are the pros and the cons of running your own recursive DNS resolver at home.

How does DNS actually work?

After a user types a domain name (e.g. “thehackernews.com”) into their browser, DNS lookup is triggered. A group of DNS servers then help to find the IP address for the domain and return it back to the user’s computer.

Because we will be installing and setting up unbound as a recursive DNS resolver, let’s talk about the difference between recursive and iterative DNS resolver.

Recursive DNS resolver is a middleman between a client (you) and a DNS nameserver. This type of a DNS resolver is recursively querying other DNS servers until it has an IP address for the requested domain, which is returned to the client. If a DNS resolver has already performed the same query in the recent past, this DNS query is cached and when performed again, our resolver respond to us with the cashed data instead of querying other DNS servers.

Here are other DNS servers that are queried when using a recursive DNS resolver:

  1. DNS root nameserver
    • responds by directing the recursive resolver to a TLD nameserver, based on the extension of that domain (.com, .net, .org, etc.)
  2. DNS TLD nameserver
    • maintains information for all the domain names that share a common domain extension (.com, .net, .org, etc.)
  3. DNS authoritative nameserver
    • contains information specific to the domain name it serves and it also provides a recursive resolver with the IP address of that server found in the DNS A record
    • if the domain has a CNAME record it will provide the recursive resolver with an alias domain which means the recursive resolver will have to perform a new DNS lookup for this record

Let’s visualize what a DNS lookup looks like when a client is using a recursive DNS resolver.

On the other hand, when using an iterative DNS resolver client will allow a DNS server to return the best answer it can. If the queried DNS server does not have a match for the query name, it will return a referral to a DNS server authoritative for a lower level of the domain namespace. The DNS client will then make a query to the referral address. This process continues with additional DNS servers down the query chain until either an error or timeout occurs.

Now let’s visualize what a DNS lookup looks like when a client is using a iterative DNS resolver.

How does unbound work?

In a few steps below, we will describe what are the differences when running only a Pi-hole and a Pi-hole with unbound.

  • Standard Pi-hole configuration:

    1. Client asks the Pi-hole who is domain.com .
    2. Pi-hole will check its cache and reply if the answer is known.
    3. Pi-hole will check the blocking lists and reply if the domain is blocked.
    4. If neither 2. nor 3. point is true, the Pi-hole forwards the DNS request to the configured upstream DNS (usually it is default one from your ISP)
    5. When Pi-hole receives the answer, it will reply to a client with the answer.
    6. Lastly, Pi-hole will cache the answer so it will be able to respond faster next time a client queries the same domain.
  • Pi-hole setup with unbound as a local recursive DNS resolver:

    1. Client asks the Pi-hole who is domain.com .
    2. Pi-hole will check its cache and reply if the answer is known.
    3. Pi-hole will check the blocking lists and reply if the domain is blocked.
    4. If neither 2. nor 3. point is true, the Pi-hole forwards the DNS request to the local recursive DNS resolver = unbound.
    5. Unbound will send a query to the DNS root servers asking who is handling .com .
    6. The root server answers with a referral to the TLD (Top level domain) server for .com .
    7. Unbound will send a query to one of the TLD DNS server for .com asking who is handling domain.com .
    8. The TLD server answers with a referral to the authoritative name servers for domain.com .
    9. Unbound will send a query to the authoritative name servers asking what is the IP for domain.com .
    10. The authoritative server will answer with the IP address of the domain domain.com .
    11. Unbound will send the reply to Pi-hole which will reply with the answer to a client.
    12. Lastly, Pi-hole will cache the answer so it will be able to respond faster next time a client queries the same domain.

Pros and Cons

  • Pros
    • Privacy : Directly contacting the responsive servers means that no server can fully log the exact paths you’re going (e.g. Google DNS servers will only be asked if you want to visit a Google website).
  • Cons
    • When traversing the path for the first, especially visiting a website for the first time, it may be slower than when we are using big DNS providers which have answers for common used domains cached. The first request to a formerly unknown TLD may take up to a second. Subsequent requests to domains under the same TLD usually complete in < 0.1s. Fortunately, our setup will be configured for efficient caching to minimize the number of queries that will actually have to be performed.

Installation

Unbound can be simply installed from a package manager with the command below, if you are using apt as a package manager.

$ sudo apt install unbound

Or you can download unbound from Github and then compile and install it with the following command.

$ ./configure && make && make install

If you are not installing unbound from a package manager, you have to download the current root hints file (list of primary root servers). After downloading this file, you will need to uncomment the root-hints: configuration line in the suggested config file provided below.

$ wget https://www.internic.net/domain/named.root -qO- | sudo tee /var/lib/unbound/root.hints

Add a cronjob or run this command roughly every six months to update the root.hints file.


Configuration

Now we need to create a config file called pi-hole.conf in /etc/unbound/unbound.conf.d/ directory. Run the following command to create such a file and then copy the content of suggested config file provided to us from an official unbound documentation.

$ sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf

Suggested config file

# Unbound config file
server:
    # If no logfile is specified, syslog is used
    # logfile: "/var/log/unbound/unbound.log"
    verbosity: 0

    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes

    # May be set to yes if you have IPv6 connectivity
    do-ip6: no

    # You want to leave this to no unless you have *native* IPv6. With 6to4 and
    # Terredo tunnels your web browser should favor IPv4 for the same reasons
    prefer-ip6: no

    # Use this only when you downloaded the list of primary root servers!
    # If you use the default dns-root-data package, unbound will find it automatically
    #root-hints: "/var/lib/unbound/root.hints"

    # Trust glue only if it is within the server's authority
    harden-glue: yes

    # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
    harden-dnssec-stripped: yes

    # Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
    # see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
    use-caps-for-id: no

    # Reduce EDNS reassembly buffer size.
    # IP fragmentation is unreliable on the Internet today, and can cause
    # transmission failures when large DNS messages are sent via UDP. Even
    # when fragmentation does work, it may not be secure; it is theoretically
    # possible to spoof parts of a fragmented DNS message, without easy
    # detection at the receiving end. Recently, there was an excellent study
    # >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<<
    # by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/)
    # in collaboration with NLnet Labs explored DNS using real world data from the
    # the RIPE Atlas probes and the researchers suggested different values for
    # IPv4 and IPv6 and in different scenarios. They advise that servers should
    # be configured to limit DNS messages sent over UDP to a size that will not
    # trigger fragmentation on typical network links. DNS servers can switch
    # from UDP to TCP when a DNS response is too big to fit in this limited
    # buffer size. This value has also been suggested in DNS Flag Day 2020.
    edns-buffer-size: 1232

    # Perform prefetching of close to expired message cache entries
    # This only applies to domains that have been frequently queried
    prefetch: yes

    # One thread should be sufficient, can be increased on beefy machines. In reality for most users running on small networks or on a single machine, it should be unnecessary to seek performance enhancement by increasing num-threads above 1.
    num-threads: 1

    # Ensure kernel buffer is large enough to not lose messages in traffic spikes
    so-rcvbuf: 1m

    # Ensure privacy of local IP ranges
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

After saving pi-hole.conf configuration file, we will start our local DNS recursive server with the command below.

$ sudo service unbound restart

Testing DNS lookup

Finally, we will test our unbound recursive DNS resolver with a dig command querying a DNS record for a pi-hole.net domain. Notice status value NOERROR and the IP address for our requested domain.

$ dig pi-hole.net @127.0.0.1 -p 5335

; <<>> DiG 9.16.22-Raspbian <<>> pi-hole.net @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6970
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;pi-hole.net.                   IN      A

;; ANSWER SECTION:
pi-hole.net.            300     IN      A       3.18.136.52

;; Query time: 19 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Thu Jan 27 18:41:51 CET 2022
;; MSG SIZE  rcvd: 56

Testing DNSSEC

Last step before changing our network settings will be testing DNSSEC validation using once again dig command.

The first command should give us a status report of SERVFAIL and no IP address.

$ dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335

; <<>> DiG 9.16.22-Raspbian <<>> sigfail.verteiltesysteme.net @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 22604
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;sigfail.verteiltesysteme.net.  IN      A

;; Query time: 299 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Thu Jan 27 18:03:00 CET 2022
;; MSG SIZE  rcvd: 57

The second command should give us NOERROR and IP address.

$ dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335

; <<>> DiG 9.16.22-Raspbian <<>> sigok.verteiltesysteme.net @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64024
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;sigok.verteiltesysteme.net.    IN      A

;; ANSWER SECTION:
sigok.verteiltesysteme.net. 60  IN      A       134.91.78.139

;; Query time: 29 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Thu Jan 27 18:03:15 CET 2022
;; MSG SIZE  rcvd: 71

Pi-hole settings

In your Pi-hole web console, navigate to the Settings, then DNS. Make sure to uncheck all upstream DNS servers.

Now, scroll down to the second Upstream DNS Servers section and specify 127.0.0.1#5335 as the Custom DNS (IPv4). This means that your Pi-hole will use your recursive DNS server provided by unbound.

At last, scroll all the way down and save your settings. After pressing the Save button you should see a pop up Info window


Validating our setup

At the time of writing this post, we have been running Pi-hole with unbound for almost 24 hours. We are able to validate that our configuration works from the Upstream servers pie chart, where 65% of all the DNS queries were handled by our local recursive DNS resolver and 20% were cached queries. Remaining 15% of DNS queries were handled by our preconfigured Quad9 upstream server and other public resolver while we were configuring local recursive DNS resolver unbound.


Resources


Thank you for reading and we hope you learned something new._