A dead simple Dynamic DNS server

I tend to loathe people using terms like ‘simple’ and ‘advanced’ when used to describe software products, because it assumes a level of context in which you have any idea what the author is talking about.

Everyone knows what DNS is, though, so I’m going to stick with simple here .

What’s Dynamic DNS?

There used to be this service called ‘DynDNS‘ which allowed you to roam the internet using whatever IP address your ADSL or 3G service provider doled out to you, and DynDNS would bind your IP to a subdomain in one of their dozen or so domain names. This would let you refer to your machine as ‘shinybitofplastic.example.com‘, where ‘shinybitofplastic‘ was specific to your machine, and ‘example.com‘ was one of DynDNS’s domains.

Humans tend to think of this as a improvement on IP addresses, which look like this: 198.51.100.113, and aren’t nearly as easy to memorise or brand.

(example.com isn’t one of DynDNS’s domains, that’s just an example. DynDNS used to have incredibly vague domain names like podzone.org or is-an-artist.com, that people presumably wouldn’t mind sharing with millions of other people).

This made it marginally easier trying to serve up files from your machine to the outside world, so other people could use the relatively fixed DNS name rather than a constantly changing IP address to visit your landing website containing bootlegged Brittney Spears MP3s, or your dropbox-like SMB share, or a VPN to your missile launch site, or whatever it is that you do.

Doesn’t DynDNS do that for free?

DynDNS recently started charging for their service, so not any more they don’t.

You’re in luck, though, if this is the sort of thing you expect people to give away for free, because I took it upon myself to write up a replacement dynamic DNS server, that does pretty much the same thing as DynDNS, and a couple of other things which I’ll write up in a later blog post.

You can read the source code, if you like.

The canonical DynDNS client is called ddclient, so I’ve decided to call this thing ddserver.pl .

Where do I download this ddserver.pl of which you speak ?

You can download this excellent free alternative to DynDNS here:

ddserver
git@github.com:randomnoun/ddserver.git

Click on the big grey button with the silhouette of a corporate mascot on it, download the script, shove it on a server out in The Cloud somewhere that’s running bind and apache, and Bob’s your uncle, whatever that means.

What does it look like?

It doesn’t really look like anything, since it typically runs in the background.

Since that’s a bit boring it also includes a web interface, which looks like this:

The sample above is configured to respond to requests using the URL dyndns.example.com, as well as a couple of other domains (click the down-arrow button at the top of the frame to see the others).

You’d replace the .example.com and other domains with your own list of domains in the ddserver.conf configuration file. There’s a zoneedit-compatible interface in there as well, but let’s ignore that for now.

Configuration

What you’ll want to do is:

  • Buy a domain name (optional). I use CrazyDomains, because their prices are, well, crazy.
    You don’t want to order ‘domain certification’, or the ‘dns upgrade’, or the ‘privacy protection’, or the ‘domain promotion’, or ‘web hosting’ or the ’email hosting’, or any other of the value-added services they provide, because you’re going to do all that. Although you can if you want to. It’s your money.
    It should cost you about $3 for a .com if you can think of a name that no-one else has thought of in the past 20 years or so. I’d start with your highly original hotmail or twitter account name, and then try bustaname when that doesn’t work.
  • Install a bind server somewhere in the cloud, or on one of your hypervisors if you’re running this at home or on the boat.
  • Set up a web server on the same host as the bind server to serve up the various ddserver.pl interfaces. Apache or lighttpd are reasonable choices.
  • Create a virtual web host in the web server for the ddserver.pl dynamic DNS server.
  • Copy ddserver.pl into the virtual web host
  • Update the ddserver.json configuration file for ddserver.pl
  • Make sure ddserver.pl has permissions to read/write to the places it needs to read/write to
  • Update the bind configuration
  • Create a cron job to reload bind whenever it needs to be reloaded

More detailed information is in the sourcecode documentation, or at least, it will be, once I’ve written it up.

ddserver.json

The settings for the server are configured using the ddserver.json file, which sits in the same directory as the script.

It looks like this (there’s a description that follows):

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*
 
  ddserver.json
 
  Configuration file for ddserver.pl
 
  See  http://www.randomnoun.com/wp/2013/07/08/a-dead-simple-dynamic-dns-server/
  for a brief description of the syntax of this file.
 
*/
{
  "syntax" : "ddserver.pl-1.0",
  "revision" : "$Id$",
  "config" : {
 
    /* a contact address that the server includes in HTTP requests and the HTML interface */
    "serverAdmin" : "youremail@example.com",
 
    /* all URLs below must be mapped to the ddserver.pl script. They are functionally equivalent. */
    "interfaces" : {
      "html"     : "http://www.example.com/cgi-bin/ddserver.pl",
      "dyndns"   : "http://dyndns.example.com/nic",
      "zoneedit" : "http://zoneedit.example.com"
    },
 
    "logfile" : "/var/log/ddserver.log",
 
    /* the restartfile is created if settings have changed. 
       a cronjob will detect this and cause bind to reload its configuration files.
     */
    "restartfile" : "/etc/bind/dynamic/.bind_restart",
 
    /* filesystem location of zonefiles, templates and backups.
 
       "{zone}" here will be replaced with a value from the config.domain() array below.
       (the zone is the bit of the domain name that you probably paid someone money for. 
       e.g. if {zone} = ".com" then you probably shouldn't be using this script). 
 
       If a bindZoneTemplate file exists, then that will be used instead of the built in 
       template for new zones, or when the 'forcetemplate' parameter is supplied.
     */ 
    "bindZoneFile" : "/etc/bind/dynamic/db.{zone}",
    "bindZoneTemplateFile" : "/etc/bind/dynamic/db.{zone}.template",
    "bindZoneBackupFile" : "/etc/bind/dynamic.old/db.{zone}.{serial}",
 
    /* these things go into the generated DNS records */    
    "defaultTTL" : "300",
 
    /* these values are used to populate values in the default template SOA record
       if a zone-specific template doesn't exist.
     */
    "defaultSOA" : {
      "soaContact" : "soacontact@example.com",
      "authoritativeNameserver" : "ns1.example.com"
    },
 
    /* the first HTTP header below that exists will be used to override request IP address 
       the myip parameter overrides both header value and request IP address
     */  
    "proxyHeaders" : [ "X-Fowarded-For", "Z-Forwarded-For" ],
 
    /* could have fancier ACLs here, e.g.
         multiple username, passwords
         username/password(s) per domain
         username/password(s) for admin console
         your choice of user directory, database, grouping mechanism etc
         your choice of hashing algorithm, certs, securid, dna sequence etc 
    */
    "username" : "admin",
    "password" : "admin",
    "domains" : [
      "example.com",
      "specificgeneralisations.com",
      "generalspecifics.com",
      "mycompanyname.com"
    ],
 
    /* the nameservers for these zones (array) */
    "nameservers" : [ 
        "ns1.example.com",
        "ns2.example.com"
    ]
  }
}

The configuration file is structured as a JSON Object, with the following keys:

  • syntax: this value defines the version of the syntax of the configuration file. This is currently set to ‘ddserver.pl-1.0’, but may change if I ever decide to make it a bit more fancy (e.g. more sophisticated ACLs, that sort of thing).
  • revision: whatever marker your source control system uses to embed file revisions. I use CVS, because I am old, so for me this is $Id$.
    Don’t worry about the structure of the marker, it’s not parsed by the script.
    You are keeping all your environment files under source control, aren’t you ?

  • config: the main configuration object; this is a hashmap with the following keys:
    • serverAdmin: an email address used to identify the administrator of this script to people using the HTML interface. You should probably set it to be your own email address. It is also included in HTTP requests generated by the script to help identify people when things go pear-shaped.
    • interfaces: the various URLs that are configured for this server. These should all be aliased to the ddserver.pl script (see the sample apache config file). There are three interfaces:
      • html: the html browser interface. This URL is used for POST destinations, and is the relative base for CSS/JS/IMG references.
      • dyndns: the API that everyone will actually use; this value is used in the example dyndns URLs in the web interface
      • zoneedit: another programmatic API; this value is used in the example zoneedit URLs in the web interface
    • logfile: the name of the logfile that records any operations made to the bind nameserver. This file must be writable by the www-data user (or whatever user your web server is running as)
    • restartfile: the restartfile is created if settings have changed. A cronjob will detect this and cause bind to reload its configuration files.
    • bindZoneFile: the location on the filesystem to read/write bind zone files. The text {zone} is replaced by the name of the zone being updated. e.g. /etc/bind/dynamic/db.{zone} will read/write the /etc/bind/dynamic/db.example.com file for the example.com zone.
    • bindZoneTemplateFile: Similarly, the location on the filesystem where the template file for a zone is kept. The text {zone} is replaced by the name of the zone being created. If a template file for the zone does not exist, than an internal one is used instead.
    • bindZoneBackupFile: The location on the filesystem where backup files are kept of modified zone files. The text {zone} is replaced by the name of the zone being updated, and the text {serial} is replaced by the serial number of the zone file being backed up; e.g. /etc/bind/dynamic.old/db.{zone}.{serial} will create backup files that look similar to /etc/bind/dynamic.old/db.example.com.2013070801
    • defaultTTL: The default TTL (time-to-live) of created A records, in seconds.
    • defaultSOA: Default settings used in the internal template file for new zones. It contains the following keys:
      • soaContact: The email address for the administrator of this zone. (This will be modified by the script to be in the bizarre format required by the DNS standard).
      • authoritativeNameserver: The primary master nameserver for this domain. You probably want something like ns1.example.com.
    • proxyHeaders: A list of HTTP headers that are searched for when attempting to determine the user’s IP address. You only need to update these options if you have a forward proxy configured around your web server. Which you probably don’t. The IP used for an update will be either:
      • The value of the ‘myip’ HTTP parameter, if present, otherwise
      • The value of the first proxyHeader HTTP header that exists, otherwise
      • The IP address of the HTTP request, as reported by the web server
    • username: username required for any update operations
    • password: password required for any update operations (in plaintext)
    • domains: a list of domains, representing the zones that can be updated by this script. The first zone that matches will be used.
      Normally this would just be a list of the domains that you own, but it also means that you can have multi-level subdomains configured (like.this.kind.of.thing.example.com) with different levels in different zone files, if that’s the sort of thing that you want to do .
    • nameservers: an array of name servers, which will be converted into a set of NS records for new zones. If you’ve got more than one nameserver set up, you probably want something like ns1.example.com, ns2.example.com.
      If this object is a hash rather than an array, then the script will attempt to propagate changes to secondary/tertiary/etc nameservers, but don’t worry about this for now.

Apache/lighttpd configuration

In /etc/apache2/sites-available/dyndns.example.com, you probably want something 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
25
26
27
28
# This file defines the apache configuration for the dyndns.example.com domain
# (change example.com to your own domain name, of course)
<VirtualHost *:80>
        ServerName dyndns.example.com       # dyndns interface
        ServerAlias ns1.example.com         # nameserver-specific dyndns interface
        ServerAlias zoneedit.example.com    # zoneedit interface
        ServerAlias dynamic.example.com     # html interface
 
        ServerAdmin youremail@example.com
 
        DocumentRoot /var/www/dyndns.example.com/
        <Directory /var/www/dyndns.example.com/>
                Options FollowSymLinks MultiViews +ExecCGI
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>
 
        LogLevel warn
        ErrorLog /var/log/apache2/dyndns.example.com/error.log
        CustomLog /var/log/apache2/dyndns.example.com/access.log combined
        ServerSignature On
 
        AddHandler cgi-script .pl
        RewriteEngine on
        RewriteRule ^/$ /cgi-bin/ddserver.pl [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
        RewriteRule ^/nic(|/|/.*)$ /cgi-bin/ddserver.pl$1 [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</VirtualHost>

and then create a symlink to enable it via

ln -s /etc/apache2/sites-available/dyndns.example.com /etc/apache2/sites-enabled/dyndns.example.com

and restart your apache server using whatever incantation normally does that for your combination of operating system and cloud deployment software.

If you’re using lighttpd instead of apache, then you’ll need to add something like this to your lighttpd.conf file:

1
2
# ddserver.pl rewrites
url.rewrite-once = ( "^/nic/(.*)$" => "/cgi-bin/ddserver.pl/$1" )

Cron

You’ll also need this file in /etc/cron.d/ddserver:

1
2
3
4
5
6
7
8
#
# cron-jobs for ddserver
#
 
MAILTO=root
 
# Every minute, check to see if the bind server should be reloaded
*/1 * * * *     root if [ -f /etc/bind/dynamic/.bind_restart ]; then /etc/init.d/bind9 reload >/dev/null; rm -f /etc/bind/dynamic/.bind_restart; fi

And that should probably get the thing barely running.

It’s not very modular, is it ?

Well, the whole thing fits into just one perl file, so no, not really.

It is nice, though, to create a tiny script that does one thing reasonably well, rather than creating an overengineered Java behemoth riddled with the usual AbstractRefreshableConfigApplicationContext objects.

Why isn’t it slightly less simple?

Here you go.

What about this other ddserver I just found on Sourceforge?

What, you mean this one? Yes, well, I didn’t notice that before. That’s a FastCGI SAPI module written in C that delegates to nsupdate though (thereby clobbering your zonefiles), which may or may not be what you want.

Does it work on Windows™?

It might, I haven’t tried.

Although if you’re running bind on Windows, then you’re a braver man than I.

Do you ever get tired of asking yourself rhetorical questions?

Never.

Help! I have a computer without a keyboard and/or have issues typing words that machines take literally, for various definitions of the word ‘literally’

No problem, you could pay me slightly less what DynDNS is charging, and I’ll set it up for you.

They’re charging $25/year so let’s say $20. Bargain.

If you’re a government entity, or work in a company that has just three capital letters in its name however, you should take advantage of the temporary gov/tla pricing tier discounts, which reduce the standard price from the usual $2.2 million dollars to a low $1.8 million dollars. So you’re actually saving money.

If you’re interested in either of the above offers, put your email address into my soon-to-be-launched namingwords.com site here.

If you do try downloading and installing this yourself, any thoughts, opinions or suggestions are appreciated.


Updated 2013-07-10: Added the apache, lighttpd and cron sections
Updated 2021-02-27: It’s on github


    90% of software is about hiding complexity anyway; I can almost ask Joe EverydayUser to open firefox and view a webpage without him having to understand the 9.2 MLOC that allows him to do that.
    Note to self: petition ICANN for a ‘.stop’ global TLD, and then sell domain names based on telegraph code mneumonics.
    I can’t do this in real life, of course, because he wouldn’t know how to install the thing.


13 Comments

Add a Comment

Your email address will not be published. Required fields are marked *