README.md
dyndns.pl
Perl CGI-BIN script to handle Dynamic DNS updates through HTTP (e.g. from a router), updating DNS records through secure DNS update statements.
Version 1.0, latest version, documentation and bugtracker available on my GitLab instance
Copyright (c) 2013 - 2015 Frederik Lindenaar. free for distribution under the GNU License, see below
Introduction
This script provides a simple interface to allow Dynamic DNS updates for DNS zones. It is intended to be used for routers and (aDSL) modems to register their IP address by simply opening a URL (this is supported by many modern devices) but can also be used by end-users (either directly by using a client). Please bear in mind that this script suits my setup and still might have glitches, but so far turned out to be a quite stable solution for my needs and I use it in a production setup. In case you have any comments / questions or issues, please raise them through my GitLab instance so that all users benefit.
Setup
This script is to be executed as CGI-BIN script by a web server. As it is written in Perl, it requires that installed (which is pretty standard nowadays on all *nix platforms). This description covers the installation on Apache 2.4, which should be similar for other web servers, with ISC Bind v9. For performance reasons consider using the Apache mod_perl module for highly a volatile domain.
The setup of this solution consists of the following steps:
-
Ensure that the Perl modules CGI and Net::DNS are installed.
- on Debian/Ubunto linux this can be done by:
sudo apt-get install libcgi-pm-perl libnet-dns-perl
* or if you have cpan installed:
~~~
cpan CGI Net::DNS
~~~
Install the file
dyndns.pl
either in your cgi-bin directory or in a separate folderUpdate the configuration section at the top of the script to match your environment (see the section on configuration below). The least you need to change
$DNSServer
to point to your DNS server and you probably want to have a look at the$AllowDebugKey
(useful for getting things started but you want to set this to 'off' in production.-
To have a nicer URL (or in case the script is not installed in the web server's cgi-bin directory) add the following line to your Apache virtual host configuration (replacing
[INSTALL_DIR]
with the install directory):ScriptAlias /dyndns [INSTALL_DIR]/dyndns.pl
in case you have installed the script in a non-standard folder, you will also need the following to make this work on Apache 2.4 (again replacing
[INSTALL_DIR]
with the install directory):<Directory [INSTALL_DIR]/> AllowOverride None Options +ExecCGI -MultiViews -Indexes Require all granted </Directory>
reload apache with
/etc/init.d/apache reload
to make the script available at http://myserver.mydomain.tld/dyndns -
To setup your Bind nameserver, either update
named.conf
direcly or create a separate file (e.g.named.dyndns.conf
in the Bind configuration directory and include that in your setup with theinclude
directive (e.g.include "named.dyndns.conf";
). For a basic dynamic DNS setup a configuration like below is required:// Define the keys for DynDNS key "dyndns.mydomain.tld" { algorithm hmac-md5; secret "QdDJC7QVYmsCxgWoSAUmBg=="; }; key "siteuser" { algorithm hmac-md5; secret "R6Xkbn+FP85Hq3EDNmv+GQ=="; }; // Define the DDNS zone zone "dyndns.mydomain.tld" IN { type master; file "dyndns/db.dyndns.mydomain.tld"; // enable this for list and expire support // allow-transfer { 192.168.0.2; }; update-policy { grant dyndns.mydomain.tld zonesub ANY; grant siteuser name site.dyndns.mydomain.tld ANY; }; };
The above defines a domain zone file
dyndns/db.dyndns.mydomain.tld
with two signer/keys. siteuser only can updatesite.dyndns.mydomain.tld
while dyndns.mydomain.tld can update all entries in the domain (intended for expiry). If you intend to use expiry or want to be able to retrieve a list of all entries, comment out theallow-transfer
statement and update the IP adres to that of your web server.To seed these entries with fresh keys), use the following commands and copy the generated keys into the config file.
* to generate a new key *dyndns.mydomain.tld*:
~~~
ddns-confgen -a hmac-md5 -k dyndns.mydomain.tld -z dyndns.mydomain.tld
~~~
* generate the required configuration for *siteuser* (or any new user):
~~~
ddns-confgen -a hmac-md5 -k siteuser -s site.dyndns.mydomain.tld
~~~
-
Generate an initial zone file like the one below for the dyndns domain in the location specified in the config file above.
$TTL 3600 ; 1 hour @ IN SOA auth.dns.mydomain.tld. hostmaster.mydomain.tld. ( 2015051401 ; serial 43200 ; refresh (12 hours) 3600 ; retry (1 hour) 86400 ; expire (24 hours) 900 ; minimum (15 minutes) ) TXT "Dynamic DNS zone for mydomain.tld" site A 1.2.3.4
Please note that Bind will rewrite this file and you need to be careful with it. Entries do not need to exist initially, as long as the signer/key has access to a hostname, the entry can be created (so the only thing required to setup a new host is to register a signer/key).
If you do need to update the zone file to change entries, consider using the bind
nsupdate
command instead. If that is inconvenient, the following steps must be followed not to get our of sync with Bind's zone database (please note that when you have views this works slightly differently):
* execute the command `rndc freeze [zone]`
* edit the zone file for [zone]
* execute the command `rndc unfreeze [zone]`
-
Last step is to instruct bind to reload it's configuration (
rndc reload
) and test the setup. please see below how to invoke the script.URLs / checks to perform are:
* <http://myserver.mydomain.tld/dyndns/list?domain=dyndns.mydomain.tld>
to list the entries in the domain (requires zone transfer rights!)
* <http://myserver.mydomain.tld/dyndns/update?host=site.dyndns.mydomain.tld&user=siteuser&key=......>
to add/update a site and
* <http://myserver.mydomain.tld/dyndns/delete?host=site.dyndns.mydomain.tld&user=siteuser&key=......>
to delete (clear) it.
Please read the section below as well on the configuration and different modes (operations) available.
Configuration
At the top of the script is a "Configuration" section, which contains the configurable options of the scripts.
Parameter | Description |
---|---|
$DNSServer |
IP address of the DNS Server to send DNS update requests to |
$ExpandCNAMEs |
Max. CNAME lookups for $host (0 to disable), see below |
$AllowDebugKey |
Output debug log after result when debug parameter equals this value. Set to '' to always enable and to 'off' to disable debugging |
$AuthMode |
Defines how to authenticate DNS update requests, see below |
$StaticSigner |
Static signer ID to be used for AuthMode static or both
|
$StaticKey |
Static signing key to be used for AuthMode static or both
|
$RequireRR |
Require an existing DNS record of this type to allow updates. |
$ExpireAfter |
Expire time for registrations in minutes, hours, weeks or seconds. Format is number optionally followed by m, h, w, s (seconds is default). |
@ReplaceRR |
List of DNS Record types to remove (clear) as part of update. |
$UpdateTXT |
Add host TXT record during update with this text followed by a timestamp. Used for expiry (so don't change!), leave empty to not add this |
$DeleteTXT |
Set TXT record upon deletion with this text and a timestamp. |
Please note that the values must be correctly quoted, etc. not to break the script.
CNAME Support
The script supports using separate subdomain (e.g. dyndns.mydomain.tld) for dynamic DNS and CNAMEs to entries in that subdomain from another zone (e.g. mydomain.tld). The advantage of such a setup is that only one zone (SOA file) within the domain will have frequent updates (and hence requires a short TTL so prevent it from being cached) while the rest of the domain's zones can be cached.
The user does not have to notice this at all as script supports check whether
the host provided is a CNAME and if so, performs the request for the actual
hostname instead of the provided one. The value of $ExpandCNAMEs
determines
the maximum number of CNAME lookups supported (so nesting is allowed and this
limits the level of nesting to prevent loops).
To disable lookups for CNAME expansion, set $ExpandCNAMEs
to 0.
Authentication Modes
For signing DNS update requests sent to the DNS server the script supports 3 ways to obtain the signer and key:
AuthMode | Description |
---|---|
static | use only static authentication information from $StaticSigner and$StaticKey (and ignore authentication information provided in the request) |
remote | use only authentication information provided in the request |
both | use authentication information provided in the request (fields user and secret ) when provided, otherwise use static values from $StaticSigner and $StaticKey . Please note that this is checked per parameter |
Supported Operations
The script can perform the following operations (modes):
Mode | Description | Required Parameters | Optional Parameters |
---|---|---|---|
list | Show DDNS domain entries |
domain **
|
|
view | Show DDNS hostname entry | host |
|
update | Update/add a DDNS host |
host + auth.*
|
ipv4addr , ipv6addr
|
delete | Remove DDNS registration |
host + auth.*
|
|
expire | Expire registrations |
domain ** + auth.*
|
* Modes that change registrations require authentication, depending on the
value of $AuthMode
the parameters user
and secret
may be required
($AuthMode
remote) required or optional ($AuthMode
both)
** in case domain
is omitted, it will be determined using the host
parameter, if provided
Parameters
The script supports the following parameters (please see the table above for which is needed for what mode):
Parameter | Description |
---|---|
mode |
the action to perform (if not provided as part of the path name) |
domain |
domain for list/expire request, determined from host if ommitted |
host |
hostname to act on, expand CNAMEs max. $ExpandCNAMEs levels deep |
ip |
alias / shortcut for ipv4addr
|
ipv4addr |
The IPv4 address to register for the host (update mode only) * |
ipv6addr |
The IPv6 address to register for the host (update mode only) * |
user |
signer of the DNS Update, used for AuthMode remote and both
|
key |
key to sign the DNS Update, used for AuthMode remote and both
|
debug |
debug key, show debug information if this equals $AllowDebugKey
|
* in update mode, if ipv4addr
or ipv6addr
is not provided with the
request, the CGI variable $REMOTE_ADDR
(the client address), its value
will be used instead as IPv4/IPv6 address.
Invoking the script
The script is implemented using the perl CGI module so for testing purposes it can be called from the command line with parameters as arguments, i.e.
./dyndns.pl mode=expire domain=mydomain.tld debug=....
Which is quite handy for debugging purposes. Please note that the Perl CGI
library sets $REMOTE_ADDR
to 127.0.0.1 and that the output will always be
the HTML-based result.
The standard way to use the script is to place it in the cgi-bin folder your server, which allows it to be called as:
http://myserver.mydomain.tld/cgi-bin/dyndns.pl?mode=list&domain=mydomain.tld&debug=...
As per the setup instruction above, there are various ways to make the URL cleaner, i.e.
http://myserver.mydomain.tld/dyndns?mode=list&domain=mydomain.tld&debug=...
The script also supports include the mode variable as part of the location
(using and the CGI variable $PATH_INFO
to set the mode), i.e.
http://myserver.mydomain.tld/cgi-bin/dyndns.pl/list?domain=mydomain.tld&debug=...
When combining the setup would become:
http://myserver.mydomain.tld/dyndns/list?domain=mydomain.tld&debug=...
Which is how I use it.
Name Server Setup Requirements
As the script is only translating requests, depends heavily on the setup of the nameserver. The DNS server (obviously) needs to allow DNS updates. In addition to the setup described above, please note that:
- For the modes list and expire to work, the script needs to perform a DNS zone transfer (AXFR). This must be allowed for the host running the script.
- for each DDNS host, a signer and key must have the rights to change the entry (one signer/key can be setup to change multiple hosts).
- The expire mode requires a signer and key that can change all DDNS hosts within the domain.
- The script currently only supports HMAC-MD5 type keys (limitation of the used Perl Net::DNS library). The keys setup in the nameservers must therefore be of the same time or authentication won't work.
The solution scales reasonable well, although adding the keys to the nameserver configuration is still manual in my setup (but since it does not happen that often, it's no hassle). This setup has been tested against ISC Bind version 9.
License
This script, documentation and configration examples are free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This script, documenatation and configuration examples are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, download it from http://www.gnu.org/licenses/.