A slightly less simple Dynamic DNS server
|So earlier in the week, I posted up the source of ddserver.pl, which is a Dynamic DNS server which I’m using to track a machine with a rapidly-changing IP out on the internet[1].
I’ve updated that post with a bit more detail about how to use it, so check it again if you tried to install it and get stymied by the lack of documentation supplied (it now has some sample apache config and the contents of the reload cronjob).
This entry is about some additional features that make the script slightly less simple, but slightly more useful, if this is the sort of thing that you think is useful.
Round-tripping zone files
So if you run ddserver.pl
from a clean installation, and create a new A
record for your computer called ‘test
‘ in the ‘example.com
‘ zone, you’ll get a bind file created in /etc/bind/dynamic/db.example.com
that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ;
; BIND data file for example.com zone
; this file is automatically generated by ddserver.pl.
;
$TTL 604800
@ IN SOA ns1.example.com. soaContact.example.com (
2013071001 ; (YYYYMMDDNN) Serial INCREMENT THIS EVERY TIME FILE CHANGES
; (triggers zone transfer if secondary configured)
7200 ; Refresh
86400 ; Retry
2419200 ; Expire
7200 ) ; Negative Cache TTL
;
example.com. 7200 IN NS ns1.example.com.
example.com. 7200 IN NS ns2.example.com.
;
; === START OF GENERATED CONFIGURATION
test 300 IN A 1.2.3.4
; === END OF GENERATED CONFIGURATION
; if you are manually modifying this file, anything added before or after
; the automatically generated markers above should be preserved.
; Except for the serial number, of course, because that's special. |
If you’re running your own bind server, you probably have a raft of other DNS records for your domain, which are more fixed than the records being maintained by ddserver.pl
.
ddserver.pl
supports maintaining these manually by modifying the contents of the zone file directly, since only the text between the
; === START OF GENERATED CONFIGURATION
and
; === END OF GENERATED CONFIGURATION
markers is modified by the script during subsequent updates.
This means that other entries, formatting and comments are kept intact whenever the ddserver.pl
script reads and writes the file, meaning you can roundtrip the file without discarding the non-machine-readable information.
Which is nice if your zone files are filled with crap that needs a reference to a change request someone raised in 1998.
Templates
While this is all well and good, manual changes are still, well, manual.
The templating mechanism allows you to deploy changes using whatever system you use to deploy configuration file changes (such as puppet, chef, sprinkle, stackful, salt[2], or my personal favourite, vmaint), and have those changes merged into the dynamic data already stored in the file.
Say I wanted to add a TXT
record to the domain to prove to google analytics that I own the thing and wanted to make graphs showing the 30 hits or so a month that I get. I could either update the /etc/bind/dynamic/db.example.com
file directly, restart bind, and this would work, or I could create/update a template file (in /etc/bind/dynamic/db.example.com.template
) which looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | ;
; BIND data file for example.com zone
; this file is automatically generated by ddserver.pl and db.example.com.template.
;
$TTL 604800
@ IN SOA ns1.example.com. soaContact.example.com (
2013071001 ; (YYYYMMDDNN) Serial INCREMENT THIS EVERY TIME FILE CHANGES
; (triggers zone transfer if secondary configured)
7200 ; Refresh
86400 ; Retry
2419200 ; Expire
7200 ) ; Negative Cache TTL
;
example.com. 7200 IN NS ns1.example.com.
example.com. 7200 IN NS ns2.example.com.
example.com. 7200 IN TXT "google-site-verification=cLF9jhJ75wc3ITzLVr6tAH4ipXFX9_KqbETj3HgMoR8"
;
; === START OF GENERATED CONFIGURATION
<%= $bindrecords %>
; === END OF GENERATED CONFIGURATION
; if you are manually modifying this file, anything added before or after
; the automatically generated markers above should be preserved.
; although I recommend that you use the template file instead, because that
; will take precedence the next time someone merges this thing |
perform a NOCHG
update using the commandline with the forcetemplate
parameter set to yes
, and the zonefile would be updated to reflect the template, whilst still preserving the dynamic records maintained by ddserver.pl
; i.e. something like:
1 2 3 4 5 | perl /var/www/dyndns.example.com/cgi-bin/ddserver.pl /update? \ username=admin password=admin \ hostname=dummy.example.com \ myip=NOCHG \ forcetemplate=yes |
The upshot of all this is that you can now ensure that the non-dynamic portions of the zonefile can be tracked via source control and deployed using some vaguely deterministic manner.
Propagating updates
In the example ddserver.json
configuration file (which in this example would be located at /var/www/dyndns.example.com/cgi-bin/ddserver.json
) contained in the previous blog post, the nameservers
block appears as:
78 79 80 81 82 | /* the nameservers for these zones (array) */ "nameservers" : [ "ns1.example.com", "ns2.example.com" ] |
If you despise axfr for some reason, this can be enhanced to include a URL for each nameserver, thusly:
78 79 80 81 82 | /* the nameservers for these zones (hash) */ "nameservers" : { "ns1.example.com" : { "dyndns" : "http://ns1.example.com/ddserver.pl" }, "ns2.example.com" : { "dyndns" : "http://ns2.example.com/ddserver.pl" } } |
which will cause the ddserver.pl
script to send any changes through to all other nameservers for your organisation. (The ddserver.pl
script needs to be set up on these nameservers for this to work).
You’d probably use HTTPS as well, I’d imagine.
To prevent the script from sending changes to itself, you will need an extra configuration file in the directory holding the ddserver.pl
script called ddserver-hostname.txt
, containing the host name of that nameserver; i.e. something like this in /var/www/dyndns.example.com/cgi-bin/ddserver-hostname.txt
:
1 | ns1.example.com |
You’d have thought that the script could work out it’s own hostname by itself, and you’d probably be right, but you’d also sometimes be wrong.
Keeping it in a separate file also means that every nameserver can have identical ddserver.json
configuration files as well, which is nice.
If you don’t have multiple nameservers, then ignore this whole section.
Anything else?
You should probably be aware that ddserver.pl
:
- doesn’t perform locking around file updates
- doesn’t yet support support dynamic AAAA records if you’re using ipv6
- doesn’t support the mx parameters of the dyndns update protocol
- doesn’t actually support the zoneedit update protocol properly either
- hasn’t really been tested at all, and probably therefore has bugs and as-yet-undiscovered security holes like you wouldn’t believe
- during normal execution, and more importantly, when it breaks, there’s a logfile created (in the sample
ddserver.json
configuration file this is set to/var/log/ddserver.log
).If there’s no logfile, it probably means the user that the webserver is running under (usually
www-data
) doesn’t have access to the file, so you’d need to do something like this:sudo /bin/bash -c 'touch /var/log/ddserver.log; chown www-data:www-data /var/log/ddserver.log; chmod 664 /var/log/ddserver.log'
Which also reminds me… your
/etc/bind/dynamic
directory should have group write permissions, and you should chgrp the folder towww-data
, so something like this:sudo /bin/bash -c 'mkdir /etc/bind/dynamic; chown bind:www-data /etc/bind/dynamic; chmod 775 /etc/bind/dynamic'
, although for some reason this isn’t what I’ve got running here.
I may update this bit later, some time after I convert this all into a debian package.
Possibly.
It’s very long for a bullet point.
Also remember that unless you remembered to shorten the TTL (time-to-live) value just before you changed the IP address, which you can’t actually do unless you know when your IP is going to change or have a time machine, then any changes made to IP addresses will take a while to be visible to the outside world. I think the default is something like five minutes.
So is that it?
If you download any time in the next ten years, I’ll throw in a free Dynamic DNS client for Windows.
[1] well, it changes about once every 24 hours, but that’s more rapid than, say, not at all.
[2] bonus marks if you can work out why this site is called randomnoun.
Hi Greg,
Thanks for sharing your tutorial and scripts which appealed to me because they are robust due to the fact that your script does not interfere with the static name resolution within the same server! Impressive!
However, I am getting errors as of below when trying to run your script in the chrooted bind in Debian 9 aka Stretch (https://wiki.debian.org/Bind9#Bind_Chroot) which complained that ddserver.json file did not exist whereas it did as of below:
# perl /var/www/dyndns.MYDOMAIN.TLD/cgi-bin/ddserver.pl /update? username=admin password=admin hostname=erl.MYDOMAIN.TLD myip=NOCHG forcetemplate=yes
Error in ddserver.pl script on ns2.MYDOMAIN.TLD: Could not open ddserver.json: No such file or directory at /var/www/dyndns.MYDOMAIN.TLD/cgi-bin/ddserver.pl line 234.
# ls -la /var/www/dyndns.MYDOMAIN.TLD/cgi-bin/
total 284
drwxr-xr-x 2 bind www-data 4096 Jul 19 23:20 .
drwxr-xr-x 3 bind www-data 4096 Jul 19 20:57 ..
-rw-r–r– 1 bind www-data 22 Jul 19 22:00 ddserver-hostname.txt
-rw-r–r– 1 bind www-data 3088 Jul 19 23:09 ddserver.json
-rw-r–r– 1 bind www-data 273671 Jul 19 20:57 ddserver.pl
# grep -Rn “ddserver.json” /var/www/dyndns.MYDOMAIN.TLD/cgi-bin/
/var/www/dyndns.MYDOMAIN.TLD/cgi-bin/ddserver.pl:94:Configure ddserver.json, which I will describe in detail somewhere
/var/www/dyndns.MYDOMAIN.TLD/cgi-bin/ddserver.pl:234: open (INPUT, “ddserver.json”) || die “Could not open ddserver.json: $!”;
/var/www/dyndns.MYDOMAIN.TLD/cgi-bin/ddserver.json:3: ddserver.json
Any inputs appreciated!