Many network administrators have eased their workload by switching to using a DHCP server to configure client machines. In most cases, these machines do not run any services, so other machines never want to contact them, and so the lack of a corresponding entry in the domain name service is not a problem.

However, there are times when a machine requires a valid entry in the DNS. Traditionally, DNS has been set up by creating entries - called resource records - in text files called zone files. A typical address record for a host would look like this:

alpha        IN    A    10.0.0.52

This assumes that the host alpha has permanently been allocated the address 10.0.0.52. However, a major attraction of DHCP is that hosts do not own IP addresses; instead, they lease them, and if they do not renew the lease (due to being offline, switched off, or any other reason) then they address is returned to a pool for reuse, and may subsequently be issued to another machine. A user to tries to connect to alpha might find himself unknowingly connected to a different host altogether.

We need some way to dynamically update the zone data at the domain name server, since manually editing the zone files and reloading them is not practical. Fortunately BIND 8.2 and later support dynamic updates in a standard fashion. At the simplest level, this is extremely simple to set up, and quite practical for office networks in which a UNIX DNS supports Windows desktop and notebook workstations.

Dynamic DNS update is disabled by default, for obvious reasons. It can be turned on by creating an allow-update substatement, either in the options section of /etc/named.conf, or in a zone statement:

options {
        allow-query { 192.168.0.0/24; };
        alllow-transfer { 192.168.0.252; };
        allow-update { 192.168.0.0/24; };
        directory "/var/named";
}

This will allow hosts on the network 192.168.0.0 to make dynamic updates on this DNS.
If the DNS is a master for several zones, you might have to place the allow-update substatement inside only those zones where it is required:

zone "cullen.lesbell.com.au" {
        type master;
        file "db.cullen.lesbell.com.au";
        allow-update {192.168.168.0/24; };
};

If you have allowed updates for all zones in the options statement, you can override this or disable updates completely for any zone with the statement:

allow-update { none; };

Dynamic Updates for Windows Clients

For Windows XP client workstations, for example, open the Properties for a Local Area Connection, click on "Internet Protocol (TCP/IP", then click on properties, click on "Advanced...", click the DNS tab. With a DHCP server, the "DNS server addresses . . " list will probably be empty, as the DNS entries will be supplied by the DHCP server. Now check "Register this connection's addresses in DNS" near the bottom of the dialog, and optionally "Use this connection's DNS suffix in DNS registration", if using a notebook which might be connected to several different domains.
That's it. Now, as you boot the Windows clients, they will automatically update their entries in the domain name server's zone database. You will notice that the DNS zone files will change as it runs, since BIND will rewrite the files periodically. You will also find that each dynamically-updated zone has a corresponding .jnl (journal) file.

Dynamic Updates for Linux Clients with DHCP

Most Linux distributions allow configuration of interfaces by DHCP. Typically, they do this by placing an appropriate statement in the interface configuration file (for example, /etc/sysconfig/network-scripts/ifcfg-eth0) such as DYNAMIC=DHCP and not specifying the IP address, subnet mask, etc. Then, when the /etc/rc.d/init.d/network start command runs at system startup, the DHCP client program (dhchcd, pump or dhclient) is invoked to configure the interface.

But that's as far as it goes. In most cases, there is no attempt made to update the DNS. However, adding this capability is not difficult. Normally, when the DHCP client acquires an address on an interface, it will automatically run a script, if one is provided, and this can be used to in turn run the nsupdate command provided by the bind package.

For example, dhcpcd will automatically call a script called /etc/dhcpc/dhcpcd.exe. Most distributions do not provide such a script, but it is easy to write one. The dhcpcd.exe script will be passed three arguments:

  • The pathname of a file called -interface-name.info. This contains variables acquired from the DHCP server
  • The word "new", "up" or "down", to indicate whether the interface was brought up on a new address, brought up on the same address as previously, or brought down
  • A "-d" if dhcpcd itself was invoked with the "-d" debug option.

The nsupdate command can be run interactively, in which case it accepts commands on stdin (useful for debugging and experiment) or it can read its input from a file. The input consists of commands and their arguments, such as:

CommandMeans
server servername Send dynamic updates to the specified server (if none is specified, nsupdate sends updates to the master server of the correct zone).
update delete domain-name [ttl] [class] [type [data]] Deletes the corresponding resource record(s).
update add domain-name ttl [class] type data Adds the corresponding resource record


For example:

nsupdate
> update delete midgard.lesbell.com.au A
> update add midgard.lesbell.com.au 3600 IN A 203.35.202.158
> send

This will delete any pre-existing A resource record for midgard.lesbell.com.au, and then create a new one with TTL 3600 seconds and an address of 203.35.202.158.
The nsupdate command can easily be invoked from within a script which picks up the required values from the variables in the info file created by dhcpcd. Here is a simple example:

#!/bin/bash
# We get three parameters
# $1 is path to dhcpcd-<interface>.info file
# $2 us up | down | new
# $3 is "-d" if dhcpcd was started with -d debug flag

# define some local config variables here
MYDNS=fulbert.cullen.lesbell.com.au

# Source the info file
if [ ! -f $1 ]; then
    exit 1
fi
. $1

echo Our IP address is $IPADDR and hostname $HOSTNAME

case "$2" in
  "new")
    # Update the name server with our info
    nsupdate -d -k Kfreya2-fulbert.+157+37310.private <<-EOF
        server $MYDNS
        update delete $HOSTNAME A
        update add $HOSTNAME 3600 A $IPADDR
        send
EOF
    ;;
  "up")
    ;;
  "down")
    nsupdate -d -k Kfreya2-fulbert.157+37310.private <<-EOF
        server $MYDNS
        update delete $HOSTNAME A
        send
EOF
    ;;
  *)
    echo "Usage: $0 {up|down|new}"
    exit 1
    ;;
esac

However, there is a danger here. BIND will accept dynamic updates from any address in the range(s) specified in the allow-update substatement. IP addresses can be easily spoofed, especially for the UDP protocol which is used here. In addition, updates for an IP address will be accepted from any address in the range - not necessarily from the affected host itself. What is needed is some form of authentication, so that the DNS will know when it can trust dynamic update requests.

RFC 2137 outlines an approach to secure DNS updates based on public-key cryptography. However, there are two major objections to this approach: firstly, it is computationally intensive, and secondly it is complex and tricky to set up. But RFC 2845 introduces a simpler technique, based on signing the updates with a shared secret key. This approach will prove adequate for many environments.

Step 1. Create a shared key. You can do this manually, but a better way to get a proper key with the required degree of randomness is to use the dnssec-keygen utility. This is part of the BIND package, so it is probably not installed on the client computer, and you may have to perform this step at the DNS.

dnssec-keygen -a hmac-md5 -b 128 -n HOST host1-host2.

This will generate two files with long and complicated names (the ability to copy-and-paste the filenames between Xterm windows is helpful in subsequent steps).

Step 2. Copy the shared secret to both the DNS and the client machines. You can do this any way you want, but if you are concerned about security (and why else would you be using TSIGs?) you should do it using sftp, scp, by physically moving the files on floppy or by reading the value over the phone. On the client machine, save the two shared secret files in /etc/dhcpc/.

Step 3. Configure the server.

3.1 In the /etc/named.conf file, add a section as follows

key host1-host2. {
    algorithm hmac-md5;
    secret "xxxxxxxxxxxxxxxxxxxxxxxx";
} ;

In place of the x's between the quotes above, you should copy and paste the 24-character key. You can obtain this by catting the file Khost1-host2.+157+nnnnn.private and extracting the string from the line that begins "Key: ".

3.2 Either in the options section or the appropriate zone section, edit the allow-updates substatement to reference the key:

allow-update { key host1-host2. ; } ;

3.3 Save the file and restart BIND. The command to do this varies from system to system, but is often:

/etc/rc.d/init.d/named restart

4. Configure the client. Since the shared secret files are now in the /etc/dhcpc/ directory, all that remains to be done is to edit dhcpcd.exe script and add the argument:

-k host1-host2.+157+nnnnn.private

to the nsupdate command line.

Once the script has been saved, running dhcpd -k should release the current lease, and dhcpcd eth0 (or whatever) should obtain a new lease (probably for the same IP address) and should update the DNS zone file. Running tail -f /var/log/messages on the DNS machine should confirm this - you should see the client deleting an rrset and then the client adding an RR.

One further problem: Windows clients do not support the use of HMAC-MD5 keys. However, a binary package of BIND 9.2.1 for WindowsNT and Windows 2000 is available at http://www.isc.org, and this includes the nsupdate command.

Dynamic Updates with PPP

The same technique can be used with dial-up PPP connections - in fact, it's probably even more useful there. To get DDNS working with a client on a dial-up PPP connection, generate a private key and transfer it to the DNS's /etc/named.conf file as discussed above. Next, create a script which will invoke the nsupdate command. Most pppd implementations will call a script called /etc/ppp/ip-up; on Red Hat 9 and Red Hat Enterprise Linux this file is manipulated by the redhat-config-network command, and you should call your script /etc/ppp/ip-up.local as this will be called from /etc/ppp/ip-up. Make sure the script is executable (chmod +x /etc/ppp/ip-up*)

This script is passed various parameters (see man pppd for details). Most importantly, the fifth parameter is the new IP address of the machine, which can be used to update the DDNS. The script should look like this:

#!/bin/sh
MYDNS=bifrost.lesbell.com.au
HOSTNAME=mngdsys.subdomain.lesbell.com.au
nsupdate -d -k /etc/ppp/Kmngdsys-bifrost.+157+61098.private <<-EOF
  server $MYDNS
  update delete $HOSTNAME A
  update add $HOSTNAME 1800 A $4
  send
EOF

That's all that's required - once this is in place the client machine can drop the line, redial and reconnect on a different IP address and your DDNS will be updated correctly. This is a neat technique for managing remote systems on dial-up lines.

References

  • RFC 2136 - Dynamic Updates in the Domain Name System (DNS UPDATE)
  • RFC 2845 - Secret Key Transaction Authentication for DNS (TSIG)
  • RFC 2137 - Secure Domain Name System Dynamic Update