At the beginning of the year, I wrote about using
nsd3 to run my own nameservers: “Authoritative DNS with redundancy, using
nsd and Debian Wheezy“. That post focused on the public-facing benefits of running my own nameservers, notably the flexibility it gives me with regard to record types and update frequency.
As I’ve added more and more services to the Raspberry Pis running on our home network, the flexibility I have has demonstrated another benefit: assigning a domain name to the network’s ever-changing IP address. Time Warner doesn’t offer static IPs for consumer accounts, which presents a challenge to using our router’s native VPN functionality. To make it convenient to connect to our home network from afar, I’ve employed an open-source script and a custom DNS zone to provide dynamic DNS via my own servers.
Creating the dynamic zone
It’s easiest to start by choosing an obscure subdomain, and delegating its DNS back to the server you’re already running. This creates a second zone file in your DNS server, specific to the subdomain, and ensures that the dynamic DNS feature doesn’t interfere with existing records.
For example, if you were hosting
example.com and wanted to use
vpn.example.com for dynamic DNS, the following record would be added to the zone for
vpn IN NS ns1.example.com.
A second record, using
vpn.exmaple.com as its origin, can now be created by the DDNS script configured next.
Since our home network’s IP can change anytime the modem reconnects, DDNS, or dynamic DNS, is used to update a consistent DNS entry with a variable IP. Doing so allows users to, in our example, connect to
vpn.example.com regardless of your home network’s IP. Synchronizing the local network’s IP with the DNS server involves two pieces.
First, something on the DNS server must be able to receive a new home IP and update a DNS zone accordingly. For this, I’ve leveraged
dnydnsd, a small Ruby daemon that happens to support my existing DNS server,
nsd: https://github.com/cmur2/dyndnsd. It listens on the same ports that DynDNS, the creator of the Dynamic DNS protocol, uses, making it a drop-in replacement for Dyn’s service. For use with
nsd, I have the following configuration:
host: "0.0.0.0" port: "8245" # the DynDNS.com alternative HTTP port # user: "nobody" # group: "nogroup" logfile: "/var/log/dyndnsd.log" db: "/etc/dyndnsd/db.json" domain: "vpn.example.com" updater: name: "command_with_bind_zone" params: # make sure to register zone file in your nsd.conf zone_file: "/etc/nsd3/zones/vpn.example.com.zone" # fake DNS update (discards NSD stats) command: "nsdc rebuild; nsdc reload; nsdc notify" ttl: "5m" dns: "ns1.ethitter.com." email_addr: "hostmaster.ethitter.com." # specify additional raw BIND-style zone content # here: an A record for ap.example.org itself # additional_zone_content: "@ IN A 188.8.131.52" users: user: password: password hosts: - home.vpn.example.com
The above ensures that
home.vpn.example.com maps to whatever IP Time Warner assigns me, but doesn’t do anything to synchronize IP changes from the local network. Again, this was only the first step.
Second, something on the local network must send its public IP periodically for the daemon to know of changes.
I’m fortunate to have two Raspberry Pis on our network, which with the benefit of
curl are each able to keep our dynamic DNS updated. To both devices’ crontab, I’ve added the following:
*/5 * * * * curl --silent "http://user:email@example.com:8245/nic/update?hostname=home.vpn.example.com" > /var/log/dyndnsd-ping 2>&1
Every five minutes, the above sends the home network’s IP address to the DNS server. If the value changes, the
dyndnsd daemon updates the nsd zone and fires the
command specified above to update nsd and its slaves. From outside of our network,
home.vpn.example.com will always map back to our home router, and whatever services it exposes to the world.