BIND nsupdate

Here's how to set up BIND to accept a dynamic update from a particular host. We do this at Freelock to provide dynamic DNS service for our clients who need to be able to connect to an office machine on a dynamic IP address.

There are two sides to this: Server side, and Client side. We use a symmetrical key to authenticate the client request before allowing an update. Both sides have the same key--the client stores it in a .private file, the server in the BIND configuration. This key should be kept confidential. It is possible to use assymetrical keys, but that involves reconfiguring the zone for dns security. See http://ops.ietf.org/dns/dynupd/secure-ddns-howto.html for more on this--look for sig[0] signatures.

Server side


This assumes we already have a BIND server set up and handling the zones correctly.
  1. Generate a key using dns-keygen. Use the host name for the key name:

[root@foraker root]# dnssec-keygen -a HMAC-MD5 -b 512 -n HOST hunter.freelock.lan.
Khunter.freelock.lan.+157+33318
[root@foraker root]#
[root@foraker root]# cat Khunter.freelock.lan.+157+33318.private
Private-key-format: v1.2
Algorithm: 157 (HMAC_MD5)
Key: GY/VSVYP0SsinjUoycawYhUJlgdiFgKnIItGDfIlqbZfafsHJLm238PxApCH48pPk9tGkt/bs9ctiII+FRsCgA==
[root@foraker root]#


In /etc/named.conf


1. Near the top of the file, define the key that the client will use. Name it with the name of the host, so that you can easily assign the right key to the right zone. This name can also be used to automatically limit what records the key is allowed to update, but currently we specify this manually. Copy the key generated from the dnssec-keygen output files:

key hunter.freelock.lan. {
algorithm hmac-md5;
secret "GY/VSVYP0SsinjUoycawYhUJlgdiFgKnIItGDfIlqbZfafsHJLm238PxApCH48pPk9tGkt/bs9ctiII+FRsCgA==";
};

  1. In the zone definition, add an update-policy for the key:
    zone "freelock.lan" {
    type master;
    file "freelock.lan.hosts";
    update-policy {
    grant hunter.freelock.lan. self hunter.freelock.lan.;
    };
    };

  2. Reload BIND.

Client Side


1. Copy key files to server. Check ownership and permissions--should be no more than 0600, and owned by the owner of the update script. At Freelock, we've set up an updatedomain.php script to look up the effective IP address by hitting our public web server (so this runs behind a firewall), and run this from cron every 15 minutes.
  1. Create a test action file (nsupdate.action):
    server ns.freelock.lan
    zone freelock.lan
    update delete hunter.freelock.lan. A
    update add hunter.freelock.lan. 86400 A 10.10.10.1
    show
    send

  2. Test the action:
    nsupdate -k Khunter.freelock.lan.+157+33318.private -v nsupdate.action

  3. Now set up the script!

PHP-based Dynamic DNS update script

Here's a recent version of a PHP CLI script that does the trick:
  1. !/usr/bin/php
<?php
// These are the settings that change for each server.

// first, the host name to update dynamically.
define(HOST, 'hunter.freelock.lan');

// The zone to update.
define(NAME_ZONE, 'freelock.lan');

// The authoritative name server for this zone.
define(NAME_SERVER, 'baker.freelock.lan');

// The file that contains the key that allows us to update...
define(NS_KEY_FILE, 'Khunter.freelock.com.+157+33318.private');

// How long should DNS servers cache this record?
define(TIMEOUT, '3600');

// Address to notify on IP address change
$ip_email = 'notice@freelock.com';

// settings below shouldn't need to change much, as long as this
// is stored in the same place (we use /var/www/conf).
/* note: uptime code stripped out of this file--requires db */
// URL that returns our public IP address so this works behind a firewall
define(EZ_CHECK, 'http://baker.freelock.com/up_ip.php');

// set to directory where this script lives.
chdir('/var/www/conf');
// temporary files so we know what happened last time--these should be writable
// by whatever process is running this script, and relative to the directory
// just specified.
define(EZ_FILE, 'status/last_router_ip');
define(NS_ACTION_FILE, 'status/nsupdate.action');


// get our IP address
if ($fp=@fopen(EZ_CHECK,'r')) {
$err = socket_get_status($fp);
if ($err['timed_out']) {
$text = EZ_CHECK . " TIMED OUT\N";
$get_error=true;
}
else
{
$text = trim(fread($fp, 2048));
if (ereg("^([1-2]?[0-9]?[0-9]\.){3}[1-2]?[0-9]?[0-9]$", $text))
{ $currentIP = $text;
$get_error = false;
}
else
{
$get_error = true;
}
}
fclose($fp);
}
else
{
$get_error=true;
$text = EZ_CHECK ." NOT OPENED\N";
}
if (!$get_error)
{
// now we compare to our cached file
if ($oldIP = @file(EZ_FILE))
{
if ($currentIP != trim($oldIP[0]))
{
//IP address has changed
$changed=true;
}
//no else... no changes made... drop down to rest of script.
                    }<br/>
                    else //file didn't exist<br/>
                    {<br/>
                    $changed=true;<br/>
                            }<br/>

}
            if ($changed && !$get_error)<br/>
            {<br/>
                    // now do the actual update. First, create an action file:<br/>
                    $fp = fopen(NS_ACTION_FILE,'w');<br/>
                    fwrite($fp, 'server '.NAME_SERVER.'<br/>

zone '.NAME_ZONE.'
update delete '.HOST.'. A
update add '.HOST.'. '.TIMEOUT.' A '.$currentIP.'
show
send
');
fclose($fp);
$keyfilename = NS_KEY_FILE;
$nsaction = NS_ACTION_FILE;
$out = nsupdate -k $keyfilename -v $nsaction;

              $fp = fopen(EZ_FILE, 'w');<br/>
              fwrite($fp, $currentIP);<br/>
              fclose($fp);<br/>
            mail($ip_email, $host .' IP Address Change', $currentIP. ' -now<br/>

' . $oldIP[0] . ' -before
' . $out);

            }<br/>

?>