Table of Contents
This document is in the "I should write this down so other people can use it too" category. It works for me which does not mean for one second it should work for you (that's a bit of a disclaimer). If you have anything to add, suggestions how to make this work for different setups then please let me know. Corrections, updates are welcome. I can be mailed at <koos@kzdoos.xs4all.nl>
In June 2001 I got a subscription with a Dutch cablemodem provider, Casema. They (at that time) gave access using COM21 modems with PPPoE (PPP over Ethernet) as network protocol. All discussions about PPPoE aside, one disadvantage is that my IP address changes with every session. And it's something which looks like 12dyn219.com21.casema.net which is a bit hard to remember anyway. So, I wanted a name in my own domain to point at my cable modem.
Berkeley Internet Name Daemon (bind) version 8 added the option for "dynamic updates". This adds the option for hosts to update their own information in the nameserver. This could be very interesting but there is no good authentication mechanism (this has been fixed in bind 9). The only mechanism available is setting the IP numbers that can update a dns zone (the entire zone, not just one entry in the zone).
Stuff you need for my solution which I'll explain and describe :
Your own domain name. For the rest of this document I will use the
canonical example domain example.com.
One nameserver on the Internet with a fixed IP which you have ssh access to.
I decided to make the "dynamic" name a subdomain of the domain name for two reasons:
The dynamic stuff wouldn't affect the main zonefile which would therefore be safe from updates.
The dynamic stuff could run with exactly one nameserver where the main domain name needs at least two to be accepted.
The nameserver needs to be set up in such a way that I can log in using ssh
and run the dns update script and it needs to be on a fixed IP (so I can
delegate the cable zone to it and I know where to
reach it when the connection comes up). It does not have to be the
same nameserver as for the main server.
The main domain is example.com, the
dynamic zone is cable.example.com. The
zonefile for example.com has a pointer
for cable:
cable IN NS ns1.example.com.
I can have cable.example.com point directly at an IP since the 'zone top' can have MX, NS and A records just like any other record. The cable.example.com zone is set up to allow dynamic updates from localhost. The relevant part of named.conf:
zone "cable.example.com" {
type master;
file "dynamic/cable.example.com-zone";
allow-update {localhost;};
allow-query { any; };
};
The directory dynamic and the file
cable.example.com-zone are set up to be
writable for the named process (which does not run as root for
security reasons).
The special statement is the allow-update statement. The only host that can update this zone is localhost.
The contents of the zonefile is quite simple:
;BIND DUMP V8
$ORIGIN example.com.
cable 3600 IN SOA ns1.example.com. koos.example.com. (
20010661 3600 600 7200 3600 ) ;Cl=3
3600 IN NS ns1.example.com. ;Cl=3
3600 IN MX 10 mx.example.com. ;Cl=3
1800 IN A 213.17.82.219 ;Cl=3
Which is indeed the contents after it has been updated by named. The original contents was:
@ IN SOA ns1.example.com. koos.example.com. (
20010661 ; serial
3600 ; refresh
600 ; retry
7200 ; expire
3600 ; minimum
)
IN NS ns1.example.com.
IN MX 10 mx.example.com.
IN A 127.0.0.1
which might be a bit more readable for those who have configured nameservers before.
The interesting part is making the updates happen. There are two parts to this, the script to be run on the server and the call to that script on the client side.
This is a Perl script which is an adaptation of the script from Use Dynamic DNS for fun and profit!. I modified it slightly to allow me to update the zone top.
#!/usr/bin/perl -w
use strict;
use Net::DNS;
use vars qw ($zone $name $rr $res $query $update $ans $b);
if ($ARGV[0]){
$zone="cable.example.com";
$name="cable.example.com";
$res = new Net::DNS::Resolver;
$res->nameservers("127.0.0.1");
$query = $res->search($name);
if ($query){
print "Attempting to remove old entry: $name = ";
foreach $rr ($query->answer) {
next unless $rr->type eq "A";
print $rr->address, " ";
}
print "\n";
$update = new Net::DNS::Update($zone);
# Prerequisite (assumed) is that an A record must already exist.
$update->push("update",
$b = new Net::DNS::RR(Name => $name,
Type => "A",
Ttl => 0,
Class => "ANY",
Rdata => ""));
$res = new Net::DNS::Resolver;
$res->nameservers("127.0.0.1");
$ans = $res->send($update);
if (defined $ans) {
print "Return code: ", $ans->header->rcode, "\n";
if ($ans->header->rcode eq "NOERROR") {
print "Old entry removed successfully.\n";
} else {
print "Failed to remove old data!!!\n";
}
} else {
printf("Error: %s\n",$res->errorstring);
print "Failed to remove old data!!!!\n";
}
}
$update = new Net::DNS::Update($zone);
# NXRRSET - Prerequisite is that no A records exist for the name.
$update->push("pre", new Net::DNS::RR(Name => $name,
Class => "NONE",
Type => "A"));
# Add one A records for the name.
$update->push("update", new Net::DNS::RR(Name => $name,
Ttl => 1800,
Type => "A",
Address => $ARGV[0]));
$res = new Net::DNS::Resolver;
$res->nameservers("127.0.0.1");
$ans = $res->send($update);
if (defined $ans) {
print "Return code: ", $ans->header->rcode, "\n";
if ($ans->header->rcode eq "NOERROR") {
print "Success!\n";
} else {
print "Failure!\n";
}
} else {
printf("Error: %s\n",$res->errorstring);
print "Failure!\n";
}
}
This script simply tries to remove the current A record for cable.example.com (which is why the zone needed to be preloaded with an IP) and then loads the new one that was given as argument.
This script is available for download: dyndnsupdate perl script
The client (my system connected to the cablemodem) needs to do the call
at the right moment. The right moment being when the PPP connection is
up and has an ip number assigned. This is easy since pppd has the option
of running the ip-up script when the ip number is assigned. In Debian
Linux there is a /etc/ppp/ip-up.d/ directory for scripts to be
executed. I added a dyndns script to the directory with:
#!/bin/sh ssh -P dyndns@ns1.example.com "bin/updatedns $PPP_LOCAL"
The interesting part is that this has to happen as a passwordless action, so root on the ppp machine has a generated ssh public key which is in the authorized_keys of the dyndns account.
After you have this great name for your dynamic IP, you should still consider it "dynamic". This means the IP number can change and this influences the "quality" of the name.
You don't want to run your production, high-profile webserver on this. Suppose you lose the IP due to a glitch and the IP gets reassigned to the webserver of the kid down on the block sharing his "interesting images" collection.
You really don't want to point your MX records to dynamic names. When the IP gets reassigned and you don't change the A record (because for example your machine decides to hang during the day and you can't fix it until the evening) people will get bounces on mail telling them about "relaying denied" (hopefully) or the mail vanishes into a black hole.