// Personal website of Chris Smith

DNS-over-TLS on the EdgeRouter Lite

Published on

An EdgeRouter Lite
An EdgeRouter Lite

DNS-over-TLS is a fairly recent specification described in RFC7858, which enables DNS clients to communicate with servers over a TLS (encrypted) connection instead of requests and responses being sent in plain text. I won’t ramble on about why it’s a good thing that your ISP, government, or neighbour can’t see your DNS requests…

I use an EdgeRouter Lite from Ubiquiti Networks at home, and recently configured it to use DNS-over-TLS for all DNS queries. Here’s how I did it.

Installing unbound

Out of the box, the ERL uses dnsmasq to service DNS requests from local clients. To get DNS-over-TLS support I switched to using Unbound, an open source DNS resolver with support for many modern features such as DNSSEC and DNS-over-TLS.

Installing unbound on the ERL is a simple case of SSHing in, and then:

sudo apt-get update
sudo apt-get install unbound

And then configuring the ERL to use the new local resolver for DNS requests, turn off dnsmasq, and and tell DHCP clients to send DNS requests to it (obviously substituting network names and subnets as appropriate):

set system name-server 127.0.0.1
set service dhcp-server shared-network-name lan1 subnet 192.168.1.0/24 dns-server 192.168.1.1
set service dhcp-server use-dnsmasq disable
set service dns

At this point DNS should still work, but Unbound will still be sending requests out in plain text.

Configuration

The unbound configuration lives in /etc/unbound/unbound.conf, here’s a basic example that I use to enable DNS-over-TLS:

# Unbound configuration file for Debian.
#
# See the unbound.conf(5) man page.
#
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
# reference config file.

server:
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    verbosity: 1
    interface: 0.0.0.0
    interface: ::0
    port: 53
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes
    access-control: 192.168.0.0/16 allow
    access-control: 127.0.0.0/8 allow
    access-control: 10.0.0.0/8 allow
    root-hints: "/var/lib/unbound/root.hints"

    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes

    cache-min-ttl: 900
    cache-max-ttl: 14400
    prefetch: yes
    rrset-roundrobin: yes
    ssl-upstream: yes
    use-caps-for-id: yes

    private-address: 192.168.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8

    logfile: "/var/lib/unbound/unbound.log"
    verbosity: 0
    val-log-level: 3

forward-zone:
    name: "."
    forward-addr: 9.9.9.9@853

Notice the server directive ssl-upstream, and that the forward zone specifies the quad9 resolver on its TLS port (853).

DNSSEC

To enable Unbound to validate DNSSEC signatures, we need to provide it with some information about the root nameservers that we trust. First, download the list of root nameservers to the root-hints file specified in the unbound config:

wget ftp://FTP.INTERNIC.NET/domain/named.cache -O /var/lib/unbound/root.hints

Then we need to add the root keys to the auto-trust-anchor-file. The trust anchor at the time of writing is below, but you can get the latest values from the IANA.

.       172800  IN      DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQu
.       172800  IN      DNSKEY  257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuY

Redirecting unencrypted requests

I have a slew of devices on my network that, over time, I have configured to use 8.8.8.8 as a DNS server. They’re not going to care about the DHCP reply, and I don’t really feel like going around checking every weird and wonderful internet-connected device in the house, so I decided to just intercept requests to 8.8.8.8 and send them to Unbound. A simple NAT rule does the trick:

set service nat rule 1 description "HonestDNS Redirect"
set service nat rule 1 destination address 8.8.8.8
set service nat rule 1 destination port 53
set service nat rule 1 inbound-interface eth0
set service nat rule 1 inside-address address 192.168.1.1
set service nat rule 1 inside-address port 53
set service nat rule 1 log disable
set service nat rule 1 protocol tcp_udp
set service nat rule 1 type destination

You could of course redirect any traffic to port 53, but that would prevent you from explicitly querying any other DNS server. By just intercepting traffic to 8.8.8.8 I’m taking care of the vast majority of my statically configured devices, and can still issue manual queries to other resolvers when needed.

Validating

To check that everything is working, you can use tcpdump on the router to inspect packets on the WAN interface directed at port 53:

sudo tcpdump -Xi eth0 port 53

You should, hopefully, not see anything.


What do you think?

Have feedback? Spotted a mistake? Drop me an e-mail or a message on BlueSky.

Related posts

???

Shoring up SSHd configuration

I recently came across a useful tool on GitHub called ssh-audit. It’s a small Python script that connects to an SSH server, gathers a bunch of information, and then highlights any problems it has detected. The problems it reports range from potentially weak algorithms right up to know remote code execution vulnerabilities.

Lead image for Simple backups with Restic and Hetzner Cloud

Simple backups with Restic and Hetzner Cloud

I have a confession: for the past few years I’ve not been backing up any of my computers. Everyone knows that you should do backups, but actually getting around to doing it is another story.

???

Docker reverse proxying, redux

Six years ago, I described my system for configuring a reverse proxy for docker containers. It involved six containers including a key-value store and a webserver. Nothing in that system has persisted to this day. Don’t get me wrong – it worked – but there were a lot of rough edges and areas for improvement.

???

Reproducible Builds and Docker Images

Reproducible builds are builds which you are able to reproduce byte-for-byte, given the same source input. Your initial reaction to that statement might be “Aren’t nearly all builds ‘reproducible builds’, then? If I give my compiler a source file it will always give me the same binary, won’t it?” It sounds simple, like it’s something that should just be fundamentally true unless we go out of our way to break it, but in reality it’s actually quite a challenge. A group of Debian developers have been working on reproducible packages for the best part of a decade and while they’ve made fantastic progress, Debian still isn’t reproducible. Before we talk about why it’s a hard problem, let’s take a minute to ponder why it’s worth that much effort.