Philipp's Computing Blog

Success is about speed and efficiency

Deploy Your Own Bind9 based DDNS Server

This post is based on my previous blog post, Updating DNS Entries (with nsupdate or alternative implementations) – Run Your Own DDNS, and on Cédric Félizard's post "Your Own Dynamic DNS".

Running your own DynDNS (DDNS) server is easy when you have your own domain registered and an own Linux server up and running. A .de domain, for example, costs only about 6 EUR per year if you register at an inexpensive domain hoster in Germany. Let's say you have registered example.com. Now you can use a subdomain such as d.example.com to contain all your dynamic IPs. An individual host could then be named something like eric.d.example.com. The advantage of using a subdomain .d.example.com instead of example.com itself is that you can refer the authority for this subdomain to your own server while the upper zone example.com can still be managed by the DNS hoster. The following sections will describe how to set this up in detail.

Start by installing the DNS server BIND9 on your Debian/Ubuntu server via apt-get update && apt-get install bind9.

Next, set up /etc/bind/named.conf.local with your local zone.

// ---***--- Own DynDNS
include "/etc/bind/ddns-keys.conf";
zone "d.example.com" IN {
        type master;
        file "/var/lib/bind/db.d.example.com";
        // for Apple OS X 10.8 "dynamic global hostname":
        //allow-update { key mac.d.example.com; };
        update-policy {
                grant *.d.example.com. selfsub d.example.com. A AAAA TXT;
                //grant *.d.example.com. self d.example.com. A AAAA TXT;
                //grant sb.d.example.com. name sb.d.example.com. A AAAA TXT;
                //grant sb.d.example.com. subdomain d.example.com.;
        };
        notify no;
};

/var/lib/bind/db.d.example.com

$ORIGIN .
$TTL 10 ; 10 seconds
d.example.com.             IN SOA  ns1.d.example.com. hostmaster.example.com. (
                                2014080101      ; serial
                                120             ; refresh (2 minutes)
                                120             ; retry (2 minutes)
                                2419200         ; expire(4 weeks)
                                120             ; minimum (2 minutes)
                                )
                        NS      ns1.d.example.com.
                        NS      ns2.d.example.com.

$ORIGIN d.example.com.
$TTL 30 ; 30 seconds
ipv4                    A       38.68.84.19
ipv4v6                  A       38.68.84.19
                        AAAA    2001:0db8::2
ipv6                    AAAA    2001:0db8::2

ns1                     A       38.68.84.19
ns2                     AAAA    2001:0db8::2

Fill /etc/bind/ddns-keys.conf with all the keys you want to support. Generate them with dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST sb.d.example.com. (to be found in the .key file):

key "sb.d.example.com." {
    algorithm HMAC-SHA512;
    secret "5f2It/b/7wF0QmnFQ2DiTVIF6Z/cN8a8dxyIf149my61ihkgNHqn4KjG eTlbYQ7CKVskV4lmV1R0M1xAPj4Ipg==";
};

Double check and reload BIND:

named-checkconf
named-checkzone d.example.com /var/lib/bind/db.d.example.com
/etc/init.d/bind9 reload

Debug with

tail -f /var/log/syslog

If your DDNS server was running for some time and receiving dynamic updates, you will have a /var/lib/bind/db.d.example.com.jnl file along with the /var/lib/bind/db.d.example.com file. So change the zone file with care only after syncing in the journal file's changes. On Bind 9.9+ you can do this with rndc sync -clean, see here. A complete shutdown and restart of Bind should also have the same effect.

Checks

To check the whole chain of DNS entries, do:

  1. Ask for the nameserver for example.com using dig example.com ns
  2. Use one of the returned nameservers (ns.inwx.de.) and ask it to tell you about the name servers for the zone d.example.com: dig @ns.inwx.de. d.example.com ns.
  3. Use one of the returned nameservers (ns1.d.example.com.) and ask it to tell you about the zone d.example.com: dig @ns1.d.example.com. d.example.com soa.
  4. Ask that server for your dynamic domain name using dig @ns1.d.example.com. sb.d.example.com. a.
  5. Check if the server allows/refuses to do a zone transfer from different machines: dig @ns1.d.example.com. d.example.com. axfr

Also disable DNS-Rebind protection for the domain d.example.com if you use a Fritz!Box router (found here):

DNS-Rebind-Protection

FRITZ!Box suppresses DNS answers that point to IP addresses in your home network (DNS-Rebind-Protection). Provide a list of domain names here to be excluded from this protection.

(You find this setting at Heimnetz/Netzwerk -> Netzwerkeinstellungen.)

If you ever need to flush the cached entries of your BIND9 server, run rndc flush.

If you're interested in checking the times your responses are being cached and the performance of your DNS server, you may want to take a look at this forum post.

FQDN=example.com.
for i in {1..30}; do echo $FQDN; done | xargs -I^ -P10 dig ^ | grep time | awk /time/'{sum+=$4} END { print "Average query = ",sum/NR,"ms"}'

On my Raspberry Pi running Bind9, an initial query (uncached entry) takes 244 ms. A subsequent cached answer takes 11 ms. On a different virtual server with a little more processing power, the time to resolve the address was 38 ms initially and 5 ms for subsequent cached responses. It seems like the processing power of the RPi is not that good for a fast DNS server.

Here is a table of my speed tests to resolve A RRs of different FQDNs:

Name Server FQDN Client Connection Initial time Subsequent time Ping time
RPi google.at 50/10 Mbit 192 ms 34 ms 32.9 ms
RPi google.at Gigabit 244 ms 11 ms 4.97 ms
vServer google.at 50/10 Mbit 60 ms 27 ms 25.2 ms
vServer google.at Gigabit 38 ms 5 ms 4.72 ms
RPi bundesregierung.de 50/10 Mbit 91 ms 35 ms 32.1 ms
vServer bundesregierung.de 50/10 Mbit 49 ms 27 ms 24.9 ms
RPi golem.de 50/10 Mbit 62 ms 36 ms 31.5 ms
vServer golem.de 50/10 Mbit 47 ms 26 ms 23.0 ms
8.8.8.8 twitter.com Gigabit 12 ms 12 ms 11.9 ms
RPi twitter.com Gigabit 266 ms 12 ms 9.38 ms
vServer twitter.com Gigabit 11 ms 5 ms 4.01 ms
8.8.8.8 denic.de Gigabit 12 ms 12 ms 11.8 ms
RPi denic.de Gigabit 57 ms 11 ms 9.50 ms
vServer denic.de Gigabit 17 ms 5 ms 4.05 ms
RPi blog.philippklaus.de Gigabit 873 ms 11 ms 9.50 ms
8.8.8.8 ipv4.[...] 50/10 Mbit 97 ms 34 ms 32.3 ms
RPi ipv4.[...] 50/10 Mbit 35 ms 35 ms 32.8 ms
vServer ipv4.[...] 50/10 Mbit 27 ms 27 ms 22.7 ms

The ping time is the avg of four ping rounds to the DNS server in question. The Server connection speed of the RPi and of the vServer was 100 Mbit in all tests.

The last three test entries contain a query for a domain being administered by the RPi&vServer. Thus the advantage of Google's 8.8.8.8 disappears.

Upating the entries

This can be done with nsupdate or with my Python based wrapper for nsupdate: ddns.py. See my previous blog post, Updating DNS Entries, for more details.

The above software is Python based. If you want to do update entries for low performance hardware such as routers running OpenWrt, you don't want to use Python but directly use nsupdate in a shell script. For more details, read the article DIY Dynamic DNS with OpenWrt & BIND and check out this startup script.

On your client you need the key files (.key and .private) generated in the dnssec-keygen step above.

Running your own What's My IP HTTP service

If you want a laptop or a device connected to the net via a consumer provider with changing IPs to update the DNS entries to their current external IP, you need to find out about that external IP first. To do so, there are a couple of services running on the web but you can also run your own.

I wrote a minimal Python-based server for this purpose, it's called WhatsMyIP and you can find it on Github.
Setting it up is described in the blog post Arch Linux – Deploying a Pure Python Web App. The script ddns.py, which was introduced in the section above, makes use of my deployment of that web app.

Setting up a secondary DNS on Arch Linux

BIND in Arch Linux' Wiki

pacman -S bind dnsutils

Add the following zone to /etc/named.conf:

// uncomment
listen-on-v6 { any; };

// you might want to allow recursion for your network:
allow-recursion { 141.2.0.0/16; 2001:a38::/32; };

// add the slave-zone:
zone "d.example.com" IN {
        type slave;
        masters { 38.68.84.19; };
        file "db.d.example.com";
};

Check your configuration with

named-checkconf

Start BIND9 with:

systemctl start named

Check that everything works as expected using systemctl status named, less /var/log/named.log, and by testing if the server responds to DNS requests. If everything works as expected, let BIND9 be started automatically on system boot:

systemctl enable named

Alternatives to Bind / Dynamic DNS server For The Home Network

Just for the sake of completeness, here are some other possibilities that I found on the web. I prefer Bind though:

Also check my older blog post Updating DNS Entries (with nsupdate or alternative implementations) – Run Your Own DDNS.