# Unbound

I've been running a local DNS resolver for the last decade. I do that for two reasons: first of all, to bypass censorship and surveillance, and second, to profit from the essentially instantaneous answers of a local DNS cache. The latter point is becoming increasingly important in the last years, since modern websites tend to invoke links to dozens of other domains all of which have to be resolved. A satisfactory web experience thus requires, first of all, a low-latency connection to the DNS server we use.

To specify “low”, let's have a look at the typical connections we have at home. With my vanilla ADSL2+, the latency to the fastest DNS servers around amounts to 8 ms for my desktop, which is connected to the router via a GB switch, or 12 ms for all devices connected via WIFI (802.11g). These values are not too bad, but not what I'd associate with “low latency”. At work, for example, we use a dedicated DNS server available in the intranet with a latency of 0.3 ms. Now we're talking.

I have the same speed at home since 2009, when I started using pdnsd. Unfortunately, the development of this caching DNS proxy has stopped 2012. In addition, DNSSEC was initiated 2010 and is now an indispensable part of the modern internet. To keep up with this development, I hence needed a local recursive DNS resolver that not only caches, but also validates. The article in c't 12/2017 about the validating, recursive and caching DNS resolver Unbound thus came just in time.

The setup provided by c't applied to Ubuntu, and proved to be incomplete anyway (see the comments at the end of the article). With the help of the Archwiki and Calomel, I came up with the following configuration that works as desired on Archlinux. On Debian or Fedora/CentOS, some of the initial steps may be not be necessary.

We first install unbound

pacman -S unbound


and enable the service

systemctl enable unbound.service


We need to edit the service unit, and do that by issuing

systemctl edit unbound.service


to create a drop-in_snippet. The command above automatically opens your $EDITOR, i.e., in my case vim. The content of the snippet should be: [Service] ExecStartPre=sudo -u /usr/bin/unbound-anchor -a /etc/unbound/root.key  After saving the file, give it a meaningful name: cd /etc/systemd/system/unbound.service.d mv override.conf update_rootkey.conf  We can now turn to the configuration of Unbound. Replace the default configuration file /etc/unbound/unbound.conf by a file with the following content: # Unbound configuration file # # See the unbound.conf(5) man page. # # See /etc/unbound/unbound.conf.example for a commented # reference config file. # # The following line includes additional configuration files from the # /etc/unbound/unbound.conf.d directory. include: "/etc/unbound/unbound.conf.d/*.conf"  Next, we create this directory: mkdir /etc/unbound/unbound.conf.d  Let's put the following four files in this directory: 01_Basic.conf ## Basic configuration # server: interface: ::0 interface: 0.0.0.0 access-control: ::1 allow access-control: 2001:DB8:: allow # Beispiel f. ULA # access-control: fd00:aaaa:bbbb::/64 allow access-control: 192.168.178.0/16 allow verbosity: 1 forward-zone: name: "." # hopefully free of censoring and logging, definitely with DNSSEC Support: forward-addr: 194.150.168.168 # dns.as250.net (CCC) forward-addr: 194.95.202.198 # omni.digital.udk-berlin.de (Universität der Künste) forward-addr: 85.214.20.141 # h1768020.stratoserver.net (Digitalcourage e.V.) forward-addr: 80.237.196.2 # dnsc1.dtfh.de (CCC) forward-addr: 194.8.57.12 # ns.n-ix.net (Nürnberger Internet eXchange) forward-addr: 84.200.69.80 # resolver1.ihgip.net (DNS Watch) forward-addr: 84.200.70.40 # resolver2.ihgip.net (DNS Watch) forward-addr: 77.109.148.136 # xiala.net forward-addr: 77.109.148.137 # xiala.net forward-addr: 91.239.100.100 # anycast.censurfridns.dk (UncensoredDNS) forward-addr: 89.233.43.71 # unicast.censurfridns.dk (UncensoredDNS) forward-addr: 213.73.91.35 # dnscache.berlin.ccc.de (CCC) forward-addr: 62.113.203.55 # secondary.server.edv-froehlich.de (OpenNIC) forward-addr: 62.113.203.99 # OpenNIC  02_Advanced.conf ## Advanced configuration # server: verbosity: 1 do-ip4: yes do-ip6: yes do-udp: yes do-tcp: yes root-hints: /etc/unbound/root.hints auto-trust-anchor-file: /etc/unbound/root.key hide-identity: yes hide-version: yes harden-glue: yes harden-dnssec-stripped: yes use-caps-for-id: yes minimal-responses: yes prefetch: yes qname-minimisation: yes rrset-roundrobin: yes use-caps-for-id: yes cache-min-ttl: 3600 cache-max-ttl: 604800 include: /etc/unbound/adservers  03_DumbFirewalls.conf ## reduce edns packet size to help big udp packets # over dumb firewalls # server: edns-buffer-size: 1232 max-udp-size: 1232  04_Optimize.conf # Performance optimization # https://www.unbound.net/documentation/howto_optimise.html <https://www.unbound.net/documentation/howto_optimise.html>_ server: # use all CPUs num-threads: 8 # power of 2 close to num-threads msg-cache-slabs: 8 rrset-cache-slabs: 8 infra-cache-slabs: 8 key-cache-slabs: 8 # more cache memory, rrset=msg*2 rrset-cache-size: 200m msg-cache-size: 100m # more outgoing connections # depends on number of cores: 1024/cores - 50 outgoing-range: 100 # Larger socket buffer. OS may need config. so-rcvbuf: 8m so-sndbuf: 8m # Faster UDP with multithreading (only on Linux). so-reuseport: yes  We're almost done now. Two cronjobs in /etc/cron.weekly complete the configuration: unbound_updates #!/bin/bash # Updating root hints. ###[ root.hints ]### curl -sS -L --compressed -o /etc/unbound/root.hints.new https://www.internic.net/domain/named.cache <https://www.internic.net/domain/named.cache>_ if $? -eq 0  <>_; then
mv /etc/unbound/root.hints /etc/unbound/root.hints.bak
mv /etc/unbound/root.hints.new /etc/unbound/root.hints
unbound-checkconf >/dev/null
if  $? -eq 0 <>_; then rm /etc/unbound/root.hints.bak systemctl restart unbound.service else echo "Warning: Errors in newly downloaded root hints probably due to incomplete download:" unbound-checkconf mv /etc/unbound/root.hints /etc/unbound/root.hints.new mv /etc/unbound/root.hints.bak /etc/unbound/root.hints fi else echo "Download of unbound root.hints failed!" fi  adserver_updates #!/bin/bash # Updating adserver list. ###[ adservers ]### curl -sS -L --compressed -o /etc/unbound/adservers.new "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext <https://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext>_" if $? -eq 0  <>_; then
unbound-checkconf >/dev/null
if  $? -eq 0 <>_; then rm /etc/unbound/adservers.bak systemctl restart unbound.service else echo "Warning: Errors in newly downloaded adserver list probably due to incomplete download:" unbound-checkconf mv /etc/unbound/adservers /etc/unbound/adservers.new mv /etc/unbound/adservers.bak /etc/unbound/adservers fi else echo "Download of unbound adservers failed!" fi  The adserver component is of course optional, but I've found it to be a very efficient way of blocking ads. I'll compare the various possibilities to block ads in a forthcoming post. For the moment, let's concentrate on the core competences of our new DNS resolver. To do so, we first start it by issuing systemctl start unbound.service  We can test the resolver on the command line using either dig or its near drop-in replacement drill. dig +dnssec +multi @localhost debian.org drill -D @localhost debian.org  What's essential here are the first two lines and the entries in rcode and flags: 'NOERROR' and 'ad', with the latter standing for 'Authenticated Data'. In other words, the DNS response is authentic because it was validated using DNSSEC. The RRSIG blocks provide, among other data, the public key of the domain as explained here. Let's try that with a domain which is not validated by DNSSEC: dig +dnssec +multi @localhost archlinux.org  NOERROR, but no 'ad' flag. Quite all right. And now a domain with a broken/bogus DNSSEC record: dig +dnssec +multi @localhost dnssec-failed.org  Status: SERVFAIL. Works as well. Last but no least, let's test the cache of unbound: for i in$(seq 1 5); do dig www.tuvaluislands.com | grep 'Query time' | awk '{print substr($0, index($0, \$2))}'; done
Query time: 746 msec
Query time: 0 msec
Query time: 0 msec
Query time: 0 msec
Query time: 0 msec


Works. ;)

If the command line appears to be too cryptic, we can also test the basic DNSSEC functionality with a browser:

For addresses with broken/bogus DNSSEC records, such as this one, the browser should just display an ERR_NAME_NOT_RESOLVED page. It does? Excellent.

Still...that page is depressing. Let's boost our morale by visiting https://dnssec.vs.uni-due.de/ :

Thank you, Matthäus ;) .