[Raspberry Pi General] Oswald Boelcke:
Unbound / Recursive DNS server

Updated on 2019-03-18

******************

Unbound / Recursive DNS server

I decided to install Unbound in order to operate the Pi as my own (tiny) recursive DNS server.

Via the Pi-hole admin GUI, I disabled DNSSEC in Settings => DNS, as Unbound is handling that later on.

As we require for the very last step , the local root zone, a new version of Unbound than the one currently available via the default sources of Raspbian Stretch we need to play tricky via Apt-Pinning to allow to retrieve the software from the testing branch of Debian.

We install dirmngr and fetch a GPG key to verify the downloaded packages from the testing branch:

sudo aptitude install dirmngr
sudo apt-key adv --receive-keys 0x7638D0442B90D010

Now we edit the sources.list and add the link to the package.

sudo nano /etc/apt/sources.list
#Testing
deb http://ftp.de.debian.org/debian/ testing main non-free contrib

Now we give the testing branch a lower priority than stable:

sudo nano /etc/apt/preferences
Package: *
Pin: release a=stable
Pin-Priority: 600
Package: *
Pin: release a=testing
Pin-Priority: 400

Update of the database and installation of Unbound:

sudo aptitude update
sudo aptitude install unbound/testing

During the installation, Raspbian provides suggestions how to resolve the dependencies. First suggestion is to simply not install Unbound what we deny by "N". In the second suggestion, all current dependencies ought to be updated from the testing branch what we confirm by two times "Y". And we allow the services to be automatically re-started during the installation. Don't care about possible red error messages; we'll take care of that later.

During the installation you'll see following message:

Please answer with "N" in order to keep the Lighttpd configuration that was installed by Pi-hole.

In order to avoid network issues per DHCP during a network re-start to add to following file:

sudo nano /etc/network/interfaces

[FONT=Verdana] auto lo[/FONT]
[FONT=Verdana]iface lo inet loopback[/FONT]
[FONT=Verdana]
[/FONT]
[FONT=Verdana]auto eth0[/FONT]
[FONT=Verdana]iface eth0 inet dhcp[/FONT]

Now we provide Unbound with a file containing name and address of the root server.

wget -O root.hints https://www.internic.net/domain/named.root sudo mv root.hints /var/lib/unbound/

We adapt the additional config-file for Unbound provided by Pi-hole:

sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf
server: # If no logfile is specified, syslog is used # logfile: "/var/log/unbound/unbound.log" verbosity: 0 port: 5353 do-ip4: yes do-udp: yes do-tcp: yes # May be set to yes if you have IPv6 connectivity do-ip6: no # Use this only when you downloaded the list of primary root servers! root-hints: "/var/lib/unbound/root.hints" # Trust glue only if it is within the servers 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. # Suggested by the unbound man page to reduce fragmentation reassembly problems edns-buffer-size: 1472 # TTL bounds for cache cache-min-ttl: 3600 cache-max-ttl: 86400 # 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 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

Re-start Unbound and let's test functionality, commencing with a simple DNS request followed by DNSSEC:

sudo systemctl restart unbound
dig kuketz-blog.de @127.0.0.1 -p 5353 dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5353 dig sigok.verteiltesysteme.net @127.0.0.1 -p 5353

The second request should provide "status: SERVFAIL" while the last one a "status: NOERROR".

Now go back into the Pi-Hole admin GUI, and under Settings => DNS delele the entry in "Custom 2" and untick it.

For "Custom 1" modify the entry to:

Pi-hole lighttpd Workaround

The upgrade of Debian Buster Packages (Testing) of Unbound (v. 1.9.0-2) also upgrades the Lighttpd-Webserver-Package from version 1.4.45-1 (now, stable) -> 1.4.53-3 (testing). Currently, Pi-hole isn't yet compatible with the new Lighttpd-Syntax. After an upgrade to the new version, the Lighttpd webserver doesn#t start and the Pi-hole web interface can't be reached. In order to solve this issue, here's the follwoing work-around:

sudo nano /etc/lighttpd/lighttpd.conf

Comment the following line

#include_shell "/usr/share/lighttpd/create-mime.assign.pl"

and insert this line:

include_shell "/usr/share/lighttpd/create-mime.conf.pl"

Start or re-start the lighttpd service:

service lighttpd start

service lighttpd restart

Some users, including me, complained about starting issues of the lighttpd webserver, therefore I also commented the following line:

#include_shell "cat external.conf 2>/dev/null"

In order to spare us the first DNS request by Unbound to the DNS root server, we provide Unbound with the respective configuration. It must occasionally be updated, as the DNS root server themselves sometimes receive changes e.g. their IP addresses. As I don't want to update everything manually, I created to things:

a mechanism that automatically notifies my about updates

and a script that eases the replacement by new configuration files.

We create a dynamic Message of the Day (MOTD). This is supposed to be a notification that always appears whenever we log into the Pi via SSH. We delete the static MOTD in order to only have our dynamic one be displayed.

sudo rm /etc/motd

We now edit a file in that folder that is analysed for the creation of MOTD's. This will ensure that we're always informed about the currentness of the Hyperlocal configuration when we log into the Pi via SSH.

sudo nano /etc/update-motd.d/20-info

The codes of this file will be executed and the resulting output transferred to the MOTD. Add the following lines but replace "NAME" with your Pi user name.

#!/bin/bash

echo

echo -e "\e[1mUptime:\e[m	$(uptime)"

echo -e "\e[1mDate:\e[m	$(date)"

echo -e "\e[1mHyperlocal conf:\e[m $(cat /home/NAME/.unbound/update.txt)"

echo

In the folder for user scripts we now create the new script that will provide the update notifications:

sudo nano /usr/local/bin/autoupdatelocalroot

#!/bin/bash ## VARIABLES ## DIR=$HOME/.unbound hints=/var/lib/unbound/root.hints conf=/etc/unbound/unbound.conf.d/localroot.conf infile=${DIR}/root.hints outfile=${DIR}/localroot.conf update=${DIR}/update.txt ## SCRIPT ## # check for existence of update.txt file and .unbound directory if [[ ! -d $HOME/.unbound ]]; then	mkdir ${DIR} fi if [[ ! -e $HOME/.unbound/update.txt ]]; then	echo "up to date" > ${update} fi # get the file with the root servers and save as "root.hints" wget --timeout=30 -O ${infile} https://www.internic.net/domain/named.root # extract name and IP addresses (A + AAAA) of root servers and nicely put them into the file for unbound awk '\	BEGIN\	{	print "auth-zone:\n\tname: \".\""	}	{	if($0 ~ /[	]NS[	]/)	{	print "\t# "$NF	}	if($0 ~ /[	]A[	]/)	{	print "\tmaster: "$NF	}	if($0 ~ /[	]AAAA[	]/)	{	print "\tmaster: "$NF	}	}	END\	{	print "\tfallback-enabled: yes\n\tfor-downstream: no\n\tfor-upstream: yes\n\tzonefile: \"root.zone\"\n"	}\ ' ${infile} > ${outfile} #update the motd update notification if neither outfile nor diff file empty if [[ -e ${outfile} && "$(diff -Niw ${conf} ${outfile})" != "" ]] || [[ -e ${infile} && "$(diff -Niw ${hints} ${infile})" != "" ]]; then	echo "Update available – please run: sudo updateunboundconf" > ${update} else	echo "up to date" > ${update} fi #print update status cat ${update} echo

The script will be executable for all users and be added to Crontab for regulat execution. When I was asked, which editor to use while editing Crontab, I stayed with nano.

sudo chmod 755 /usr/local/bin/autoupdatelocalroot

crontab -e

In order to e.g. execute the script on Sundays at 04:20 we add the following line:

20 4 * * 0 /usr/local/bin/autoupdatelocalroot

Now we create the second script that will ease the update of the configuration files. It won't be executed automatically but only after a manual launch for a simple reason: I won't to control this and be personally present in case of any unforeseen event.

sudo nano /usr/local/sbin/updateunboundconf

#!/bin/bash ## VARIABLES ## DIR=/home/$(logname)/.unbound hints=/var/lib/unbound/root.hints conf=/etc/unbound/unbound.conf.d/localroot.conf infile=${DIR}/root.hints outfile=${DIR}/localroot.conf update=${DIR}/update.txt PLSUPDATE="Please run 'autoupdatelocalroot' first." NOTHING="Update skipped, nothing done." ## SCRIPT ## #update root.hints file if [[ -e ${infile} ]] && [[ "$(diff -Niw ${hints} ${infile})" != "" ]]; then	input=r	echo "Install new root.hints file for Unbound (overwrites old file)?"	echo "Yes / No / Re-Read differences?"	while [[ "$input" =~ [rR] ]]; do	diff -Niw ${hints} ${infile} | less	read -e -p "	[Default = no]	(y/n/r): " input	done	if [[ "$input" =~ [yY] ]]; then	mv -fv ${infile} ${hints}	chown unbound:unbound ${hints}	chmod 644 ${hints}	yes1=TRUE	else	echo	echo $NOTHING	echo	fi else	if [[ ! -e ${infile} ]]; then	echo	echo $PLSUPDATE	echo	exit 1	else	yes1=TRUE	fi fi #update localroot.conf file if [[ -e ${outfile} ]] && [[ "$(diff -Niw ${conf} ${outfile})" != "" ]]; then	input=r	echo "Install new localroot.conf file for Unbound (overwrites old file)?"	echo "Yes / No / Re-Read differences?"	while [[ "$input" =~ [rR] ]]; do	diff -Niw ${conf} ${outfile} | less	read -e -p "	[Default = no]	(y/n/r): " input	done	if [[ "$input" =~ [yY] ]]; then	mv -fv ${outfile} ${conf}	yes2=TRUE	else	echo	echo $NOTHING	echo	fi else	if [[ ! -e ${outfile} ]]; then	echo	echo $PLSUPDATE	echo	exit 1	else	yes2=TRUE	fi fi #update motd update notification if [[ "$yes1" == TRUE ]] && [[ "$yes2" == TRUE ]]; then	echo "up to date" > ${update}	echo	echo "Unbound's local root config is up to date!"	echo else	echo	echo "Entire or partial Update still pending."	echo fi

This script also gets the permissions to be executable; however, only for root users (in case you use multiple users).

sudo chmod 744 /usr/local/sbin/updateunboundconf

Let's perform a test run and hereby simultaneously create the effective configuration files.

sudo autoupdatelocalroot

For the second script we're using the toll diff that illustrates the diffences betwenn the files. The symbol "<" at the beginning of a line shows that this line is removed in the second file (i.e. the new, updated configuration), while ">" means this line will be added. To visit all differences, navigate with the arrow keys and terminate with the key "q".

For final installation of the new files confirm with "Y".

sudo updateunboundconf

All credits go to Mike Kuketz and Max Tschaeggaer for their German speaking tutorial.