Pi-hole with dns-over-tls

Some time ago I installed a Pi-hole on a Raspberry Pi at home to filter unwanted ads. This works very well after some startup problems (self-made firewall problems). This will now filter all network traffic on my network, including TVs, smartphones, tablets, and so on.
Especially devices such as smartphones, which often feature in-app ads, benefit from filtering DNS requests directly on the DNS server. Not only ads are filtered, but also tracker from Samsung TV, Google Analytics or Sonos (which can’t be turned off).

More information about the Pi-hole and instructions for installation can be found here:

Recently, a new DNS service Quad9 launched. Quad9 differentiates from similar services by focussing on ease-of-use, scalability, security and privacy. One interesting and seemingly undocumented feature is the fact that you can communicate with the service using DNS-over-TLS. This encrypts the communication between your client and the DNS server, safeguarding your privacy until it reaches the dns servers. To date, Quad9 has promised not to collect any personal data.

Your DNS Choose
Carefully choose your DNS provider because you are exposing your data to it. Who you trust is your decision.
There are many alternatives and probably better options than Quad9.

I now would like to set up the Pi-Hole, that forwarded DNS queries are sent encrypted with TLS to Quad9.

Stubby

For this we need a DNS client that is DNS-over-TLS capable. The most advanced (experimental) client is currently Stubby from getdns.

Update: There is now stubby in the Package Sources of Debian Buster. My Raspberry Pi is currently running with Debian Stretch, so I compile the client from the source.

Build

On the Raspberry Pi:

# install (automake and cmake) dependencies for build and run
sudo apt-get install build-essential libssl-dev libtool m4 autoconf automake libyaml-dev libev4 libuv1

Now check out the Git-Repo and update the Git-Submodules

git clone https://github.com/getdnsapi/getdns.git
cd getdns
git checkout develop
sed -i 's#git://#https://#g' .gitmodules # fix for git checkout
git submodule update --init

# stubby < 0.3 / getdns < 1.6
# libtoolize -ci
# autoreconf -fi
# mkdir -v build && cd build
# ../configure --prefix=/usr/local --without-libidn --without-libidn2 --enable-stub-only --with-stubby

# stubby >= 0.3 / getdns >= 1.6
# stubby switched to cmake
cmake -DBUILD_STUBBY=ON -DENABLE_STUB_ONLY=ON -DUSE_LIBIDN2=OFF -DCMAKE_INSTALL_PREFIX=/usr/local

Coffee time:

make && sudo make install
sudo /sbin/ldconfig -v

Konfiguration

So far Stubby is compiled and installed. Now we create the Stubby configuration and integrate it into Systemd so that it can be managed reliably:

cd ../stubby
sudo useradd stubby
sudo /usr/bin/install -Dm644 stubby.yml.example /etc/stubby.yml

Attention! By default the Strict-Mode is active, therefore no fallback to tcp or udp dns.

The transport options in strict mode limit the communication to TLS and additionally require an authentication of the upstream DNS.

dns_transport_list:
  - GETDNS_TRANSPORT_TLS
#  - GETDNS_TRANSPORT_TCP   # disabled, cleartext queries
#  - GETDNS_TRANSPORT_UDP   # disabled, cleartext queries

# For Strict use        GETDNS_AUTHENTICATION_REQUIRED
# For Opportunistic use GETDNS_AUTHENTICATION_NONE
tls_authentication: GETDNS_AUTHENTICATION_REQUIRED

We edit the Stubby configuration and enter under listen_addresses which address and port the client should listen to. Since the client is used by the Pi-hole, Stubby only needs to be accessible locally. In addition, we need a different port, since the Pi-hole itself runs a DNS service on the standard port 53.
I used 127.0.2.2.2 as local IP to distinguish in the log and the graphs of Pi-Hole where the queries might be forwarded. Thus it is clear that these are placed at Stubby.

# sudo vi /etc/stubby.yml
listen_addresses:
  - 127.0.2.2@5353
  - 0::2@5353

As Upstream Servers (to which the DNS queries are forwarded), I have now added the Quad9 servers to the list below. This can of course be any DNS service that supports TLS. I also verify the authenticity of the server via the tls_auth_name, so that no one else than pretend to be dns.quad9.net.

upstream_recursive_servers:
  - address_data: 9.9.9.9
    tls_auth_name: "dns.quad9.net"
  - address_data: 2620:fe::fe
    tls_auth_name: "dns.quad9.net"

Alternatively, authentication with public-key pinning (SPKI) for TLS could be used (RFC7858) (tls_pubkey_pinset), but this requires a manual customization of the configuration when renewing the certificate.
The pubkey digest value can be queried by yourself (works for every TLS DNS server):

echo | openssl s_client -connect '9.9.9.9:853' 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

And there we have the answer for Quad9: MujBQ+U0p2eZLTnQ2KGEqs+fPLYV/1DnpZDjBDPwUqQ=

The Quad9’s certificate is from Let’s Encrypt, so it is to be assumed that after renewing the certificate the digest changes and the authentication does not work anymore. It is therefore recommended to use only tls_auth_name.

Here is my final Stubby-Configfile /etc/stubby.yml:

resolution_type: GETDNS_RESOLUTION_STUB
dns_transport_list:
  - GETDNS_TRANSPORT_TLS

tls_authentication: GETDNS_AUTHENTICATION_REQUIRED
tls_query_padding_blocksize: 256

edns_client_subnet_private: 1
idle_timeout: 10000
round_robin_upstreams: 0

listen_addresses:
  - 127.0.2.2@5353
  - 0::2@5353
upstream_recursive_servers:
  - address_data: 9.9.9.9
    tls_auth_name: "dns.quad9.net"

Installation of the Systemd-Services

sudo vi /lib/systemd/system/stubby.service

[Unit]
Description=stubby DNS resolver
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/local/bin/stubby -C /etc/stubby.yml
Restart=on-abort
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
User=stubby

[Install]
WantedBy=multi-user.target

As a last step for the TLS-DNS-Client part we start and test Stubby:

sudo systemctl daemon-reload
sudo systemctl enable stubby
sudo systemctl start stubby
STUBBY: Stubby version: Stubby 0.3.0-beta.1
pi@raspberrypi:~/ $ dig @127.0.2.2 -p 5353 quad9.net

; <<>> DiG 9.10.3-P4-Raspbian <<>> @127.0.2.2 -p 5353 quad9.net
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12094
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; CLIENT-SUBNET: 9.9.0.0/20/0
;; QUESTION SECTION:
;quad9.net.			IN	A

;; ANSWER SECTION:
quad9.net.		120	IN	A	216.21.3.77

;; Query time: 142 msec
;; SERVER: 127.0.2.2#5353(127.0.2.2)
;; WHEN: Sun Jan 14 11:49:22 UTC 2018
;; MSG SIZE  rcvd: 74

Hooray!

Pi-hole with Stubby

In order for Pi-hole to use our local DNS-Resolver Stubby, we reconfigure the forwarding DNS servers from Pi-hole. ! The Pi-hole Webgui doesn’t let you configure DNS servers with ports, so you have to adjust it manually. I create a new configfile /etc/dnsmasq.d/02-stubby.conf and enter the Stubby addresses:

server=127.0.2.2#5353

Adaptation to the Pi-hole so that the servers are not duplicated or incorrectly configured:

  • delete server= from /etc/dnsmasq.d/01-pihole.conf.
  • delete PIHOLE_DNS_1 from /etc/pihole/setupVars.conf.

And finally restart the Pi-hole DNS service:

sudo systemctl restart pihole-FTL

Test

In the Pi-hole log /var/log/pihole.log or in the webgui the requests should now be forwarded to Stubby:

forwarded maps.googleapis.com to 127.0.2.2
forwarded fedoraproject.org to 127.0.2.2

With tcpdump I recorded the sent and received packets on the router and evaluated them in Wireshark.
As expected, DNS traffic is now encrypted:

Wireshark Segment

Troubleshoot

If your system refused to start stubby due to its inability to load libgetdns.so.10, to this or a reboot (thx @Kai):

sudo /sbin/ldconfig -v