Today we will learn how to create your own recursive DNS server using Unbound. We will also see how to add custom records to block ads, as well as internal records for the LAN. Using your own DNS server can improve performance through caching. We will also look at adblocking and encryption.
Table of contents
- How Caching Improves Performance
- Preparing Unbound
- Testing It Out
- Adding Local Data
- Running a Pixel Server
- Self-hosted DNS and Privacy
- Encrypted DNS with TLS
- Encrypted DNS with DNSCrypt
How caching improves performanceThe primary advantage of running a local DNS server is caching. Let's demonstrate this by querying a record twice using my recursive server.
[root@desktop ~]# sudo systemctl restart unbound [root@desktop ~]# dig etherarp.net | grep "Query time" ;; Query time: 762 msec [root@desktop ~]# dig etherarp.net | grep "Query time" ;; Query time: 0 msec
Notice how the query went from taking 0.9s to 0.001s. Why is that?
Initially, to find the answer, we performed recursive DNS resolution, a slow multi-step process. Once we have this answer, we store it in cache (contains recently seen records). I restarted unbound to ensure the cache was empty.
Answering a record from cache is fast and doesn't require any internet traffic.
This is because it doesn't need to the slow process of recursively finding the answer by contacting external nameservers, it remembers the first answer and stores it in cache.
Especially in a lan environment this can greatly reduce the number of external requests and result in a significant speed-up.
Downloading root hints and setting up trust anchors
$ sudo wget -qO /etc/unbound/root.hints https://www.internic.net/domain/named.root $ sudo systemctl enable unbound-anchor $ sudo systemctl start unbound-anchor
Testing it out
$ sudo curl -fsSL https://s3.amazonaws.com/etherarp.net/unbound.conf \ >unbound.conf $ sudo unbound-checkconf unbound-checkconf: no errors in /etc/unbound/unbound.conf $ sudo service unbound start [ ok ] Starting recursive DNS server: unbound. $ dig +short CAA etherarp.net @127.0.0.1 0 issue "comodoca.com"
Adding Local data[This](https://s3.amazonaws.com/etherarp.net/hosts-to-unbound.sh) is a script I wrote to convert hostfile data into unbound local-data.
It escapes comments, blank lines, and empty
local-data lines. It reads either from
/etc/hosts or from a file specified by a supplied argument. It's kinda crude but it works
curl -fsSL https://s3.amazonaws.com/etherarp.net/hosts-to-unbound.sh \ | bash -s >lan.conf local-data:"gateway.example.lan. IN A 10.0.0.1" local-data-ptr:"10.0.0.1 gateway.example.lan" local-data:"10-0-0-2.example.lan. IN A 10.0.0.2" local-data-ptr:"10.0.0.2 10-0-0-2.example.lan" local-data:"10-0-0-3.example.lan. IN A 10.0.0.3" local-data-ptr:"10.0.0.3 10-0-0-3.example.lan" local-data:"10-0-0-4.example.lan. IN A 10.0.0.4" local-data-ptr:"10.0.0.4 10-0-0-4.example.lan"
Please see my Github repo on this.
There are two ways we can block domains.
Option 1 - Refuse lookup
We create a local-zone for the ad-domain with the option refuse.
Unbound will respond with an NXDOMAIN and the domain will appear not to exist.
[root@dns ~]# curl -fsSL https://s3.amazonaws.com/etherarp.net/gen-adblock-refuse.sh | bash -s [root@dns ~]$ unbound-checkconf<br /> unbound-checkconf: no errors in /etc/unbound/unbound.conf<br />
Option 2 - Override IP
As an alternative to black-holing them with the refuse option, we can respond to the ad-domains with a custom IP address. This is useful for when you wish to create a pixel server that replies with a blank page to ad requests. You can also use the pixel server to log the origin of the ads etc.
Script to generate a records-file
[root@dns ~]# curl -fSsL https://s3.amazonaws.com/etherarp.net/gen-adblock-refuse.sh | bash -s local-zone: "000free.us" redirect local-data: "000free.us A 127.0.0.2 local-zone: "000owamail0.000webhostapp.com" redirect local-data: "000owamail0.000webhostapp.com A 127.0.0.2" [root@dns ~]$ unbound-checkconf unbound-checkconf: no errors in /etc/unbound/unbound.conf
Running a pixel-serverWe can run a pixelserver to redirect ad-domains to a blank page and optionally log the blocked requests. To achieve this, we run a small server in lighttpd.
[root@dns ~]# apt-get install lighttpd [root@dns ~]# curl -fSsL https://github.com/rohan-molloy/unbound-adblock/blame/master/lighttpd/adblock.conf >/etc/lighttpd/adblock.conf [root@dns ~]# mkdir -p /var/www/adblock && cd /var/www/adblock<br /> [root@dns adblock]# for ext in png html js; do wget -q https://github.com/rohan-molloy/unbound-adblock/blame/master/lighttpd/var/www/adblock/default.$ext; done<br /> <br /> [root@dns adblock]# lighttpd -f /etc/lighttpd/adblock.conf [root@dns adblock]# curl traffic.outbrain.com/foo/bar.js console.log("Adblock");
Self-hosted DNS and privacy
You may think that running your own DNS server is good for privacy as third-parties like Google won't be able to see your requests; this is indeed correct. However, there is a different privacy concern.
When you self-host DNS, you are retrieving records yourself, and therefore you're exposing your public IP address to a large number of authoritative nameservers. This does not occur when using public recursive servers like Google DNS which retreive records on your behalf; the nameservers see a Google IP address rather than your own.
Let me demonstrate this (192.0.245 is my public ip address):
[user@dns ~]$ dig @126.96.36.199 whoami.akamai.net +short 188.8.131.52 [user@dns ~]$ dig @184.108.40.206 whoami.akamai.net +short 220.127.116.11 [user@dns ~]$ dig whoami.akamai.net +short 192.0.2.245
We can mitigate this by telling unbound to forward upstream requests to an additional server (such as Google DNS or Quad9).
Append this to
/etc/unbound/unbound.conf to forward to Quad9
forward-zone: name: "." forward-addr: 18.104.22.168
Encrypted DNS with TLS
DNS traffic is unencrypted and can easily be intercepted in transit.
As a part of an intrusion detection system, some organizations (such as workplaces or campuses) may intercept and analyze every DNS request to look for malicious activity.
This is what intercepted DNS traffic looks like
20:56:19.012015 IP desktop.41440 > google-public-dns-a.google.com.domain: 36098+ [1au] A? etherarp.net. (53) 20:56:19.256672 IP google-public-dns-a.google.com.domain > desktop.41440: 36098$ 1/0/1 A 22.214.171.124 (57)
To ensure the confidentiality of DNS in the last-mile we can forward requests to a TLS capable recursive server. This provides encryption in a manner analogous to how HTTPS protects HTTP.
How to use Quad9's TLS service
Quad9 provides a TLS service on port 853
Let's replace the
forward-zone we created earlier with this
forward-zone: name:"." forward-addr:126.96.36.199@853 forward-ssl-upstream:yes
Looking in Wireshark, we can see that it performs a TLS handshake, negotiating encryption via the certificate. The
application data contains unintelligible data that corresponds to the DNS requests/responds
Encrypted DNS with DNSCrypt
DNSCrypt is an arguably more complicated way to encrypt DNS requests. It uses its own custom protocol (based on TLS) that hasn't been standardized with an RFC. It is not directly supported in Unbound, and instead requires a client side application known as
The server side implementation of dnscrypt is known as
We will talk about this in another post.
It is available in most package repositories
A list of resolvers is found in
/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv. This is where the resolver-name option gets its entries from. Be warned, the file may be out-of-date, get the latest from here
Creating a systemd unit
/etc/systemd/system/dnscrypt-proxy.service with the following
[Unit] Description=DNSCrypt Proxy After=syslog.target network.target [Service] Type=forking ExecStart=/usr/sbin/dnscrypt-proxy --user=nobody --resolver-name=dnscrypt.ca-1 --local-address=127.53.53.53 --daemonize [Install] WantedBy=multi-user.target
Then start it with
sudo systemctl daemon-reload && sudo systemctl start dnscrypt-proxy
Check it works by running
dig @127.53.53.53 example.com +short
Then add the following to
/etc/unbound/unbound.conf (replacing any of the earlier forward rules)
forward-zone: name: "." forward-addr:127.53.53.53