This post continues from Part 4: Wireless

In my opinion DNS is one of the most important, but most overlooked network protocols out there. The internet is basically useless without it and yet it is ripe for attack. It's an unencrypted and unauthenticated protocol, but whoever controls it can have near total control over your internet experience.

On the bright side, that means it's easy to screw with for your own benefit. If you have your own DNS server you can create custom domains and blocks domains you don't like (say...all ad domains?). Plus once you have a DNS server running it's possible to add some security on top of the DNS protocol to better protect you from all the tomfoolery out there on the wider internet.

Basic setup

First off, let's get the DNS server running. The de facto standard these days is BIND and the current version is 9 point something so it can be installed with:

$> sudo apt-get install bind9

Next edit the configuration file at /etc/bind/named.conf.options and add a couple forwarders. These are the servers that BIND will ask to resolve domains when it doesn't know the answer. We'll just use Google's public DNS servers:

# /etc/bind/named.conf.options
...
forwarders {  
    8.8.8.8;
    8.8.4.4;
};
...

Restart the DNS server for the new settings to take effect:

$> sudo systemctl restart bind9

Test it out with the dig utility by trying to resolve some domain name from the local resolver. If it works you should get something in the "Answer" section:

$> dig @127.0.0.1 killtacknine.com
...
;; ANSWER SECTION:
killtacknine.com.  300  IN  A  42.42.42.42  
...

The last step is to tell the things on your LAN that they should use the router to resolve DNS queries. They get their DNS servers as part of their DHCP configuration so all we need to do is modify the domain-name-servers option in /etc/dhcp/dhcpd.conf to use the router's IP address:

...
option domain-name-servers 192.168.0.1;  
...

Restart the DHCP server and next time the clients connect they will use the router to resolve queries:

$> sudo systemctl restart isc-dhcp-server

Custom domains

Now that you have your own DNS server you can start having some fun. As far as the devices on your LAN are concerned you own every domain in the world ('member how I said DNS is an unauthenticated protocol?). Technically you could resolve queries for google.com and send them where ever you want, but things like virtual servers and SSL will muck it up a bit. A simple and useful thing you can do though is to create custom domains.

Let's say for example you have a web server at home on the IP address 192.168.0.10. You probably type http://192.168.0.10/ into your browser to access it. Or do you ssh to a home server with something like ssh [email protected]? There's some ways around this like editing your hosts file or creating an SSH config but they all kind of suck. You have to make the changes on every device you connect to the servers from and if the IP changes you have to change it on all the devices. DNS solves this problem on the internet and we can get the same solution by adding some DNS entries for the LAN hosts. Let's add some entries to the DNS server like media.home and web.home that resolve to the local IPs.

First you need to tell BIND about the domains it should resolve locally. Since we're going to use the .home TLD (you can pick anything you want, you're in charge now) I add a .home zone to /etc/bind/named.conf.local like this:

# /etc/bind/named.conf.local
zone "home" {  
    type master;
    file "/etc/bind/db.home";
};

This says that anything in the .home domain should be resolved using the entries in /etc/bind/db.home and that this DNS server is the master for the domain. Next lets add that file with a few entries:

; /etc/bind/db.home
$TTL    604800
@        IN    SOA    home.    root.home. (
                           1        ; Serial
                           604800   ; Refresh
                           86400    ; Retry
                           2419200  ; Expire
                           604800)  ; Negative Cache TTL
@        IN    NS     router.home.
router   IN    A      192.168.0.1  
media    IN    A      192.168.0.11  
web      IN    A      192.168.0.10  

This is what a typical DNS zone file might look like. Most of it is self explanatory but there are a few things to note. The SOA (Start of Authority) record configures things for the domain like how long records should be cached. Importantly, the serial number starts at 1 and must be incremented every time any changes are made to the file. BIND uses the serial number to know if the zone has changed so if you modify the file without changing the serial number it won't actually pick up the changes. The NS (NameServer) record is just there in case something asks for the nameservers that resolve .home. In that case we just tell them to ask router.home which is of course defined right below as a regular IPv4 A (Address) record pointing to 192.168.0.1 (the router's IP). Feel free to add as many entries as you like after that for other hosts on the network or even add other types of records like TXT or MX if you're feeling particularly wild.

Now clients can ask your DNS server things like "what's the IP for router.home" and it can answer. But your DNS server can't do the opposite, what's known as a reverse DNS lookup. A client might ask "what's the domain name for 192.168.0.1" and you're router will begin to smoke and smolder until it ends its own life in order to hide its embarrasement over not knowing the answer. To prevent that we need to add a reverse zone file that resolves reverse DNS lookups for the LAN subnet.

Add another zone to /etc/bind/named.conf.local that looks like this:

# /etc/bind/named.conf.local
...
# Reverse for .home domain
zone "168.192.in-addr.arpa" {  
    type master;
    notify no;
    file "/etc/bind/db.192.168";
};

The zone name gets read backwards because the designers were pompous assholes with no sense of usability. So arpa and in-addr say it's a standard IPv4 address and then 192 and 168 say the addresses will start with 192.168. Similarly to the other zone this server is the master and the reverse entries can be found in /etc/bind/db.192.168. Create that file and edit it to look something like this:

; /etc/bind/db.192.168
$TTL    604800
@      IN    SOA    router.home.    root.router.home. (
                             1        ; Serial
                             604800   ; Refresh
                             86400    ; Retry
                             2419200  ; Expire
                             604800 ) ; Negative Cache TTL
@      IN    NS     router.home.
1.0    IN    PTR    router.home.  
11.0   IN    PTR    media.home.  
10.0   IN    PTR    web.home.  

It's pretty similar to the normal zone file but instead of A records it has PTR (pointer) records. The IP addresses read backwards so for example 1.0 resolves the domain for 192.168.0.1 since this is the 192.168 zone. As you can see it points to router.home. Add reverse records for the other domains you added and save the file. Note that each domain ends with a period.

Restart the DNS server with:

$> sudo systemctl restart bind9

If the configurations are valid it should start right back up otherwise check the logs and see what the errors are. To test it out try resolving one of your new domains with dig:

$> dig @192.168.0.1 media.home
...
;; ANSWER SECTION:
media.home.  300  IN  A  192.168.0.11  
...

It works! Gone are the days of remembering IP addresses. Best of all, if the IP of your server changes you can update the zone file and none of your devices will know the difference.

Ad blocking

This is without a doubt the best part about running your own DNS server. Ads use domain names, you control how domain names resolve, and therefore you control all the ads. Whenever a device on the LAN tries to resolve a domain for an ad you can just send it into a black hole. Nothing gets out of a black hole. Not even ads. The client will fail to load the ad and voila, no more ads for anything on the LAN ... browsers, phones, tablets, EVERYTHING ad free.

Now it's not perfect. I still see ads sometimes since some ads are served from the same domain name as the content you actually want. It also may cause problems if it blocks a domain you actually want to load. In my experience it's pretty damn good though. It blocks pretty much everything you normally notice and I've had no major false positives. In my opinion it's definitely worth doing.

It works exactly the same way as the custom domain name resolution worked. We tell the DNS server which domains to resolve locally by adding zones for them, but rather than pointing the domains to real IPs we send them somewhere else. The only trick is getting a list of all the ad domains and creating a zone for each one. It would be a major pain in the ass to do it yourself so luckily someone has already done it for you! Hurrah for teamwork. This will download the list of ad servers as a list of BIND zones, replace the zone file name with /etc/bind/db.null, and save the file to /etc/bind/ad-blacklist:

$> curl "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=bindconfig&showintro=0&mimetype=plaintext" \
  | sed 's/null.zone.file/\/etc\/bind\/db.null/g' \
  > /etc/bind/ad-blacklist

Check out the resulting file and it should just be a massive list of zones for ad domains that point to the db.null zone file. Include this file into your locally resolved domains by adding an include line to /etc/bind/named.conf.local like this:

# /etc/bind/named.conf.local
...
include "/etc/bind/ad-blacklist"  
...

And finally you'll need to add that /etc/bind/db.null zone file:

$TTL    86400   ; one day  
@       IN      SOA     ads.home. root.ads.home. (
                         20164269
                         28800
                         7200
                         864000
                         86400 )          
                NS      router.home         
                A       127.0.0.1 
@       IN      A       127.0.0.1 
*       IN      A       127.0.0.1

This just says resolve everything to 127.0.0.1 which is of course localhost. When the client asks itself for the address of an ad domain it will be all like "WTF I dunno" and the ad will fail to load.

Restart DNS and test out an ad domain to see how it resolves:

$> sudo systemctl restart bind9
$> dig @router.home doubleclick.com
...
;; ANSWER SECTION:
doubleclick.com.  300  IN  A  127.0.0.1  
...

Great success.

DNSSEC and DNSCrypt

Most people probably won't care about this one, and if you don't know what you're doing it may cause you more problems than it's worth. But if you're the paranoid privacy conscious type then pay attention.

Like I said before DNS is unencrypted, unauthenticated, and absolutely critical to the internet's infrastructure. It deserves the same sort of protection and privacy as your regular everyday HTTP traffic. Anybody listening to your network traffic can look at your DNS requests to see what you're visiting and when. If an adversary can control the network they can even inject bad DNS results and send you somewhere you weren't meaning to go (...kind of like how we black holed ad domains). With HTTP traffic there is TLS to protect against these types of shenanigans, but with DNS there is ... well ... nothing. In today's internet environment that is absolutely terrifying. In my opinion it's only a matter of time before there is a massive attack that catches the public's eye and initiates a change.

Luckily, you don't need to wait for a shitstorm to happen before protecting your internet bits. Solutions to authentication and encryption for DNS already exist in the forms of DNSSEC and DNSCrypt.

DNSSEC involves a hierarchy of keys that go back to the root DNS servers in order to authenticate domains. It's very similar to the way TLS certificates are signed up to root CAs to verify servers.

It's counterpart is DNSCrypt which encrypts DNS traffic between two points. It still comes out unencrypted on the other end, but hopefully that end is somewhere safer than where it began. Ideally it would be the DNS server and the query can be resolved locally without ever being put on the network unencrypted. At the very least it should mix traffic with other users to give some anonymity (go use TOR if you want real anonymity though).

Setting both of these up is incredibly simple since you already have your own DNS server running. You just need to run a DNSCrypt proxy and point it to a DNS server that does DNSSEC authentication. Unfortunately the proxy package isn't in the official repos so you'll first need to add the repo:

$> sudo add-apt-repository ppa:anton+/dnscrypt

Then update the package lists and install the proxy:

$> sudo apt-get update
$> sudo apt-get install dnscrypt-proxy

Enable it so it will start at boot:

$> sudo systemctl enable dnscrypt-proxy

If it all works you should now have a DNS resolver running at 127.0.2.1. Use dig to try it out:

$> dig @127.0.2.1 killtacknine.com
...
;; ANSWER SECTION:
killtacknine.com.  300  IN  A  42.42.42.42  
...

Beauty. So what's going on? The DNSCrypt proxy is taking regular DNS queries, encrypting them, and sending them off to a DNS server running a DNSCrypt proxy of it's own. When the remote proxy receives the request it decrypts it, gets the response, and encrypts the response back to the local machine. By default the dnscrypt-proxy package uses the dnscrypt.eu DNS servers. Those are great since they do DNSSEC authentication as well. They have flaked out on me from time to time so you can change it if you want, but make sure you pick new servers that also do DNSSEC authentication. To change it just modify the dnscrypt-proxy.service file in your systemd directories.

Now that its running the last step it to tell your DNS server to use the proxy as its upstream resolver. This way any queries that it can't answer locally will be encrypted and authenticated by the proxy. Just change the forwarders configuration in your /etc/bind/named.conf.options file:

...
forwarders {  
    127.0.2.1;
}
...

Restart the DNS server for the new settings to take effect:

$> sudo systemctl restart bind9

Now let's test it out to make sure its resolving and authenticating records properly. First let's try a domain with a valid signature:

$> dig +dnssec sigok.verteiltesysteme.net

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50227
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 3, ADDITIONAL: 1

;; ANSWER SECTION:
sigok.verteiltesysteme.net. 40  IN      A       134.91.78.139  
sigok.verteiltesysteme.net. 40  IN      RRSIG   A 5 3 60 20170530030003 20170228030003 30665 verteiltesysteme.net. rnHPDLJ4U1Jaq+h1RVUka4tpe+LIwPSy5j2IJlwCpsya0MSRHlhIue9+ /nb5XqkrryUU1KvcIzBRSHLxqK960HwsZv6Qm/qFlNxOYc+huwiAllk+ qdTuJtX/jqmh+pnlijJJZbftgr0ZNtWbUJGzPIfp8nLz4H6nl2XYOQWw /7I=

;; AUTHORITY SECTION:
...
verteiltesysteme.net.   3580    IN      RRSIG   NS 5 2 3600 20170530030003 20170228030003 30665 verteiltesysteme.net. QuD6SdKFoTZ1Nb+vpXQqegWhPFtSUQ+VEYU2z/Fgg4TeLESV8ADoTYOd X8W9z8it4ROZ/uhUE0/qwZYg1tzqc5nKBKzO5Wre9HLX4F3/fuE24xq2 GhOQqAzKWN14km574Loq6fqjgFDy03v4ZA55FJNAI2/lPswIuB44daTX lEE=  
...

There's a lot of stuff in that output but there are really only two important things to point out. First, an A record is returned with the domain's IP address. If the signatures didn't check out you wouldn't have been given the address. Second, on line 5, there is the flag ad. This flag marks that the record was successfully authenticated with DNSSEC. By adding +dnssec to the dig command the RRSIG records are also returned. These are the signatures of the domain and its parent used to authenticate the domain. Now let's contrast that with an invalid domain:

$> dig +dnssec sigfail.verteiltesysteme.net
...
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 57498
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
...

You can see that there is no ad flag in the response and no A record is returned. This means the resolver is properly validating the signatures and tossing out any results that don't check out. And with that, your internets are a little bit safer.

Next up, we'll look at another one of my favorite features to add to your router: remote access with a VPN.

Other Posts in This Series