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 example.com
:
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.
DDNS
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 1.2.3.4" 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.
Updating DDNS
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:password@ns1.ethitter.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.
Plain text usernames and passwords in the clear eh? Try harder
Well, if there was anything nefarious one could do, I might care a bit more, but the worst someone could do is change the DNS of an obscure zone for a minute or two before the client resets it.
That said, when I switched to my
dyndnsd-client
, I did move the daemon behind nginx so that I could switch to an HTTPS connection.I created a better way to update the
dyndnsd
daemon: http://ethitter.com/2017/12/a-better-dyndnsd-client/.