RobeeDS Logo

robeeds

Homelabbing

Self-Hosting a Recursive DNS Resolver with Unbound DNS

Author

robeeds

Date Published

Introduction

Unbound is a validating, caching, and recursive DNS resolver. This means that Unbound is capable of verifying the authenticity and integrity of DNS data, storing frequently accessed DNS records to speed up future lookups, and translating user-friendly domain names into IP addresses. In doing so, Unbound communicates directly with the authoritative nameserver (a server that holds definitive information for a specific domain or zone), preventing third-party companies (such as Google) from logging our traffic. In conjunction with that, operating our own DNS resolver can help reduce our likelihood of being targeted for a DNS poisoning attack.

As part of our homelabbing series, we will install Unbound DNS through Portainer on our home server.


NOTE: I'll be testing a configuration with Unbound and Adguard Home on separate Docker containers. For these containers to communicate, they will have to be on the same Docker network and be visible to each other. If you're using Portainer like me, this can be viewed under Networks on the left-hand side, then click on the network name that your container(s) are connected to (default being bridge).


Installation

We'll be continuing with installing Unbound through Portainer.

First, let's log into Portainer and pull the Docker image.

We'll navigate to Images (on the left hand side) and use the alpinelinux/unbound image. Click 'Pull Image'.

Next, we'll go to Containers (once again on the left hand side), and create a new container using the image we just pulled.

After, set the Port Mappings as such.

Port Mappings:

- 5335:5335 TCP

- 5335:5335 UDP

Then, set the restart policy to 'Always'.

Finally, deploy the container.


Configuring Unbound

Reference

Since we didn't install Unbound from a package manager, we will need to install the root.hints file. We'll need a shell in our Unbound Container.

In the container list, click on the Unbound instance. Under container details > container status > console. Set the command to /bin/sh, then click 'connect'. Alternatively, SSH into your server and connect to the container's shell (this is much more stable).

Create the unbound directory in /var/lib/

1mkdir /var/lib/unbound

Run the following command to install the root.hints file.

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

Now, let's create and configure our unbound.conf file

1vi /etc/unbound/unbound.conf

Copy and paste the following text

1server:
2 # If no logfile is specified, syslog is used
3 # logfile: "/var/log/unbound/unbound.log"
4 verbosity: 0
5
6 # Enable visibility to other containers on the same network
7 interface: 0.0.0.0
8 access-control: 0.0.0.0/0 allow
9 port: 5335
10 do-ip4: yes
11 do-udp: yes
12 do-tcp: yes
13
14 # May be set to no if you don't have IPv6 connectivity
15 do-ip6: yes
16
17 # You want to leave this to no unless you have *native* IPv6. With 6to4 and
18 # Terredo tunnels your web browser should favor IPv4 for the same reasons
19 prefer-ip6: no
20
21 # Use this only when you downloaded the list of primary root servers!
22 # If you use the default dns-root-data package, unbound will find it automatically
23 root-hints: "/var/lib/unbound/root.hints"
24
25 # Trust glue only if it is within the server's authority
26 harden-glue: yes
27
28 # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
29 harden-dnssec-stripped: yes
30
31 # Trust anchor file for DNSSEC validation, as per archlinux.org/unbound
32 trust-anchor-file: "/etc/unbound/root.key"
33
34 # Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
35 # see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
36 use-caps-for-id: no
37
38 # Reduce EDNS reassembly buffer size.
39 # IP fragmentation is unreliable on the Internet today, and can cause
40 # transmission failures when large DNS messages are sent via UDP. Even
41 # when fragmentation does work, it may not be secure; it is theoretically
42 # possible to spoof parts of a fragmented DNS message, without easy
43 # detection at the receiving end. Recently, there was an excellent study
44 # >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<<
45 # by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/)
46 # in collaboration with NLnet Labs explored DNS using real world data from the
47 # the RIPE Atlas probes and the researchers suggested different values for
48 # IPv4 and IPv6 and in different scenarios. They advise that servers should
49 # be configured to limit DNS messages sent over UDP to a size that will not
50 # trigger fragmentation on typical network links. DNS servers can switch
51 # from UDP to TCP when a DNS response is too big to fit in this limited
52 # buffer size. This value has also been suggested in DNS Flag Day 2020.
53 edns-buffer-size: 1232
54
55 # Perform prefetching of close to expired message cache entries
56 # This only applies to domains that have been frequently queried
57 prefetch: yes
58
59 # 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.
60 num-threads: 1
61
62 # Ensure kernel buffer is large enough to not lose messages in traffic spikes
63 so-rcvbuf: 1m
64
65 # Ensure privacy of local IP ranges
66 private-address: 192.168.0.0/16
67 private-address: 169.254.0.0/16
68 private-address: 172.16.0.0/12
69 private-address: 10.0.0.0/8
70 private-address: fd00::/8
71 private-address: fe80::/10
72
73 # Ensure no reverse queries to non-public IP ranges (RFC6303 4.2)
74 private-address: 192.0.2.0/24
75 private-address: 198.51.100.0/24
76 private-address: 203.0.113.0/24
77 private-address: 255.255.255.255/32
78 private-address: 2001:db8::/32

Save the file, and exit the shell. We'll go back to Portainer to restart our containers.


Adding Unbound as an Upstream DNS for Adguard Home

Since I have Unbound in a separate docker container, I'll need to know the IP address of the container. This can be seen under Networks on the left-hand side, then click on the network name that your container(s) are connected to (default being bridge).

After setting Unbound as your upstream DNS server, scroll down on the same page to test it.

After a few seconds, it should state 'Specified DNS servers are working properly'. To finish things up, we will disable the DNS cache in Adguard.

Scroll down on further, and we'll see the DNS cache configuration. Set the cache size to '0'

And we're done!


Summary and Next Steps

Now that we've made our own local, self-hosted, validating, caching, and recursive DNS resolver, we've improved our own security in the sense that we're less likely to be targeted for a DNS poisoning attack. Additionally, we've been granted the ability to speed up our own DNS queries after building Unbound's cache for some time. In the near future, we'll be going over securing access to our internal web services with Nginx Proxy Manager.

Homelabbing

Docker container management can get a bit silly sometimes. Portainer, a container management platform, can help simplify this activity even further.