.. title: Unbound Plus
.. slug: unbound-plus
.. date: 2019-11-30 11:32:15 UTC
.. tags: linux, web
.. category: 
.. link: 
.. description: 
.. type: text

As detailed in `a previous post <https://cobra.pdes-net.org/posts/unbound.html>`_, I'm running Unbound as local DNS resolver. I've `configured <https://cobra.pdes-net.org/posts/dns-privacy.html>`_ it to use `DNS over TLS <https://en.wikipedia.org/wiki/DNS_over_TLS>`_, and while things were a little shaky with the few available servers supporting this security protocol in the beginning, I didn't need to switch back to plain DNS for at least a year. And I'm not using the commercial providers that have decided to jump on the bandwagon, namely, Google (8.8.8.8), Cloudflare (1.1.1.1), and Quad9 (9.9.9.9). I wouldn't touch them except for testing purposes. 

However, my initial motive to run a local resolver was not security or privacy, but latency, and DNS over TLS, being based on TCP instead of UDP as plain DNS, definitely doesn't help with that. In fact, unencrypted queries over UDP are generally way faster than encrypted ones over TCP, but the actual latency can strongly vary depending on the server queried.

`Dnsperf <https://www.dns-oarc.net/tools/dnsperf>`_ is the standard tool for measuring the performance of an authoritative DNS server, but it doesn't support TCP, and the `patched version <https://github.com/Sinodun/dnsperf-tcp>`_ is seriously outdated. `Flamethrower <https://github.com/DNS-OARC/flamethrower>`_ is a brand-new alternative looking very promising, but I've got inconsistent results from it (I'm pretty sure that was entirely my fault). 

The standard DNS query tools dig (part of bind) and drill (part of ldns) don't support TLS, but kdig (part of knot) supposedly does. An alternative is `pydig <https://github.com/shuque/pydig>`_, which I've used already two years ago to check if an authoritative server offers DNS over TLS, and which turned out to be just as helpful in determining the latency of a list of DNS servers (one IP per line). After updating ('git pull origin master'), I've fed this list (called, let's say, dns-servers.txt) to pydig using

:: 

	while read p; do ./pydig @$p +dnssec +tls=auth ix.de | grep 'TLS response' | awk '{print substr($0, index($0, $10))}'; done < dns-servers.txt

with an explicit (+) requirement for DNSSEC and TLS (or without for plain DNS).

I've got a few really interesting results this way. For example, Cloudflare is invariably the fastest service available, with a latency of 9 and 60 ms for plain and encrypted UDP queries here at home, respectively. From pdes-net.org, the situation is different: Cloudflare takes 4 and 20 ms, while `dnswarden <https://github.com/bhanupratapys/dnswarden>`_ returns results within 1 and 9 ms, respectively. Insanely fast!

This latter service (where the hell did it come from all of a sudden?) is also very competitive compared to Google or Quad9 here at home: all of them require about a 100 ms to answer TLS requests. That seems terribly slow, but it's not as bad as it sounds. First, I've configured Unbound to be a caching resolver, so many, if not most, requests are answered with virtually zero latency. Second, I minimize external requests by making the root zone local – which is also known as the `hyperlocal concept <https://www.heise.de/newsticker/meldung/Hyperlocal-Wenn-die-DNS-Root-Zone-zu-Hause-steht-4215364.html>`_. 

Due to this added functionality, I've found it necessary to revamp the configuration. All main and auxiliary configuration files of my current Unbound installation are attached below. 

Main configuration files
------------------------

/etc/unbound/...
^^^^^^^^^^^^^^^^

.../unbound.conf
""""""""""""""""
:: 

		include: "/etc/unbound/unbound.conf.d/*.conf"


.../unbound.conf.d/01_Basic.conf
""""""""""""""""""""""""""""""""
:: 

	server:
		verbosity: 1
		do-ip4: yes
		do-ip6: yes
		do-udp: yes
		do-tcp: yes
	
		use-syslog: yes
		do-daemonize: no
		username: "unbound"
		directory: "/etc/unbound"
	
		root-hints: root.hints
	
		#trust-anchor-file: trusted-key.key
		auto-trust-anchor-file: trusted-key.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
	
		## reduce edns packet size to help big udp packets over dumb firewalls
		#edns-buffer-size: 1232
		#max-udp-size: 1232
	
		cache-min-ttl: 3600
		cache-max-ttl: 604800
	
		include: /etc/unbound/adservers
		
		

.../unbound.conf.d/02_Forward.conf
""""""""""""""""""""""""""""""""""
:: 
	
	server:
		  interface: ::0
		  interface: 0.0.0.0
		  access-control: ::1 allow
		  access-control: 2001:DB8:: allow
		  #access-control: fd00:aaaa:bbbb::/64 allow
		  access-control: 192.168.178.0/16 allow 
		  verbosity: 1
		  ssl-upstream: yes
	
	forward-zone:
	# forward-addr format must be ip "@" port number "#" followed by the valid public 
	# hostname in order for unbound to use the tls-cert-bundle to validate the dns 
	# server certificate.
		  name: "."
		  # Servers support DNS over TLS, DNSSEC, and (partly) QNAME minimization
		  # see https://dnsprivacy.org/jenkins/job/dnsprivacy-monitoring/
	      
		   ### commercial servers for tests
			#forward-addr: 1.1.1.1@853                      #cloudflare-dns.com
			#forward-addr: 8.8.8.8@853                      #dns.google
			#forward-addr: 9.9.9.9@853                      #dns.quad9.net
			
			### fully functional (ordered by performance)
			forward-addr: 116.203.70.156@853                #dot1.dnswarden.com
			forward-addr: 116.203.35.255@853                #dot2.dnswarden.com     
			#forward-addr: 185.49.141.37@853                #getdnsapi.net
			#forward-addr: 185.95.218.43@853                #dns.digitale-gesellschaft.ch
			#forward-addr: 146.185.167.43@853               #dot.securedns.eu
			#forward-addr: 145.100.185.15@853               #dnsovertls.sinodun.com
			#forward-addr: 145.100.185.16@853               #dnsovertls1.sinodun.com
			#forward-addr: 46.182.19.48@853                 #dns2.digitalcourage.de 
			#forward-addr: 80.241.218.68@853                #fdns1.dismail.de       
			#forward-addr: 89.233.43.71@853                 #unicast.censurfridns.dk
	
			### temporarily (2019/11/05) or permanently broken  
			#forward-addr: 145.100.185.17@853               #dnsovertls2.sinodun.com
			#forward-addr: 145.100.185.18@853               #dnsovertls3.sinodun.com
			#forward-addr: 158.64.1.29@853                  #kaitain.restena.lu
			#forward-addr: 199.58.81.218@853                #dns.cmrg.net
			##forward-addr: 81.187.221.24@853               #dns-tls.bitwiseshift.net
			##forward-addr: 94.130.110.185@853              #ns1.dnsprivacy.at
			##forward-addr: 94.130.110.178@853              #ns2.dnsprivacy.at
			#forward-addr: 89.234.186.112@853               #dns.neutopia.org
	

.../unbound.conf.d/03_Performance.conf
""""""""""""""""""""""""""""""""""""""
:: 

	# https://www.unbound.net/documentation/howto_optimise.html
	server:
			# use all cores
			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
			


.../unbound.conf.d/04_Rootzone.conf
"""""""""""""""""""""""""""""""""""
:: 

	# “Hyperlocal“ configuration.
	# see https://forum.turris.cz/t/undbound-rfc7706-hyperlocal-concept/8761 
	# furthermore
	# https://forum.kuketz-blog.de/viewtopic.php?f=42&t=3067 
	# https://tools.ietf.org/html/rfc7706#appendix-A 
	# https://tools.ietf.org/html/rfc7706#appendix-B.1 
	# https://www.iana.org/domains/root/servers
	
	auth-zone:
	 name: .
	 for-downstream: no
	 for-upstream: yes
	 fallback-enabled: yes
	  #master: 198.41.0.4                   # a.root-servers.net
	 master: 199.9.14.201                   # b.root-servers.net
	 master: 192.33.4.12                    # c.root-servers.net
	  #master: 199.7.91.13                  # d.root-servers.net
	  #master: 192.203.230.10               # e.root-servers.net
	 master: 192.5.5.241                    # f.root-servers.net
	 master: 192.112.36.4                   # g.root-servers.net
	  #master: 198.97.190.53                # h.root-servers.net
	  #master: 192.36.148.17                # i.root-servers.net
	  #master: 192.58.128.30                # j.root-servers.net
	 master: 193.0.14.129                   # k.root-servers.net
	  #master: 199.7.83.42                  # l.root-servers.net
	  #master: 202.12.27.33                 # m.root-servers.net
	 master: 192.0.47.132                   # xfr.cjr.dns.icann.org
	 master: 192.0.32.132                   # xfr.lax.dns.icann.org
	
	 zonefile: "root.zone"
	


Auxiliary configuration files
-----------------------------

/etc/cron.weekly/...
^^^^^^^^^^^^^^^^^^^^

.../adserver_updates
""""""""""""""""""""
:: 

	#!/bin/bash
	# Updating Unbound resources.
	# Place this into e.g. /etc/cron.weekly
	
	###[ 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
	  mv /etc/unbound/adservers /etc/unbound/adservers.bak
	  mv /etc/unbound/adservers.new /etc/unbound/adservers
	  unbound-checkconf >/dev/null
	  if ` $? -eq 0  <>`_; then
		rm /etc/unbound/adservers.bak
		systemctl restart unbound.service
	  else
		echo "Warning: Errors in newly downloaded adservers file 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
	
	

.../roothint_updates
""""""""""""""""""""

:: 

	#!/bin/bash
	# Updating Unbound resources.
	# Place this into e.g. /etc/cron.weekly
	
	
	###[ 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 file 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
	
	

/etc/systemd/system/unbound.service.d
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I've discarded my `custom snippet <https://cobra.pdes-net.org/posts/unbound.html>`_ for systemd to get the DNS anchor. Archlinux does provide the anchor automatically as a dependency of unbound (dnssec-anchors), so why complicate things. For other distributions, however, the snippet may still be useful, so here it is:

:: 

	[Service]
	ExecStartPre=sudo -u /usr/bin/unbound-anchor -a /etc/unbound/trusted-key.key


