http://qs321.pair.com?node_id=1079497

I'm using DynDNS.org, but recently they got more obnoxious with their (free!) service, and being a cheapskate who doesn't want to be annoyed, I set up my own dynamic DNS update with my vhost at Hosteurope. I have set up ns.example.com as the DNS server for the dyn.example.com zone with the Hosteurope DNS and run my own DNS server on my vhost to serve entries for *.dyn.example.com. The key setup is done according to the many dynamic DNS articles.

The below program reads the external IP address from my UPnP enabled gateway, a FritzBox. It then sends the signed DNS update packet to my DNS server.

The setup is not particularly secure, as the key can be used to update any dynamic IP address in the dyn.example.com zone. But for my purpose of having one name map to a dynamic IP address, that's just enough.

#!/usr/bin/perl -w use strict; use vars qw($VERSION); use Net::UPnP::ControlPoint; use Net::UPnP::GW::Gateway; use Net::DNS '0.74'; # Earlier versions had bugs in the TSIG handling use Getopt::Long; use Pod::Usage; $VERSION= '0.01'; GetOptions( 'v|verbose' => \my $verbose, 'k|key:s' => \my $key, 'n|key-name:s' => \my $key_name, 'f|key-file:s' => \my $keyfile, 's|server:s' => \my $server, 'h|hostname:s' => \my $hostname, 'z|zone:s' => \my $zone, 't|ttl:s' => \my $ttl, 'force' => \my $force, 'help' => \my $help, 'man' => \my $man, ) or pod2usage(2); pod2usage(1) if $help; pod2usage(-verbose => 2) if $man; pod2usage("$0: No hostname to update." unless $hostname; sub status { print "@_\n" if $verbose; }; $ttl||= 600; if( ! $zone) { ($zone=$hostname)=~ s!^\w+\.!!; status("Assuming DNS zone is $zone"); }; my $upnp= Net::UPnP::ControlPoint->new(); my $current_address= ''; my $resolver= Net::DNS::Resolver->new(); # Find the relevant nameserver for the zone if( ! $server ) { my $query= $resolver->search($zone,'NS'); if( $query ) { for my $rr (grep { 'NS' eq $_->type } ($query->answer)) { $server= $rr->nsdname; status "Nameserver for zone '$zone' is '$server'."; }; } else { die "Couldn't find a nameserver for zone '$zone'.\n"; }; }; $resolver->nameservers($server); status "Looking up IP for '$hostname'"; my $query= $resolver->search($hostname); if(! $query) { my $err= $resolver->errorstring(); if( 'NXDOMAIN' ne $err ) { die "Lookup of '$hostname' failed: $err\n"; } else { status "Hostname '$hostname' was not found."; }; } else { for my $rr (grep { 'A' eq $_->type } ($query->answer)) { $current_address= $rr->address; status "Hostname '$hostname' resolves to IP address '$current_ +address'"; }; }; status "Searching local network for UPnP-enabled gateways"; my @devices= $upnp->search( st => 'urn:schemas-upnp-org:device:Interne +tGatewayDevice:1', mx => 3 ); foreach my $dev (@devices) { my $type= $dev->getdevicetype; my $gw= Net::UPnP::GW::Gateway->new; $gw->setdevice( $dev ); my $ip_address= $gw->getexternalipaddress; status sprintf "Gateway '%s' has IP address %s", $dev->getfriendly +name, $ip_address; # XXX Do a name lookup and do an early exit if we don't need to up +date if( $current_address eq $ip_address ) { if( $force ) { status "Current IP address and address in DNS are identica +l, but --force is in effect"; } else { status "Current IP address and address in DNS are identica +l, skipping update"; exit 0; }; }; my $update = Net::DNS::Update->new($zone); $update->push( update => rr_del("$hostname. A") ); $update->push( update => rr_add("$hostname. 600 A $ip_address") ); if( $keyfile ) { status "Signing from key file $keyfile"; $update->sign_tsig( $keyfile ); } else { status "Signing from command line with key named $key_name"; $update->sign_tsig( $key_name, $key ); }; status "Updating IP for $hostname on $server to $ip_address"; status $update->string if $verbose; my $reply= $resolver->send( $update ); if( $reply ) { if( 'NOERROR' eq $reply->header->rcode) { status "Success"; } else { die "Update failed: " . $reply->header->rcode . "\n"; }; } else { die "Update failed: " . $resolver->errorstring . "\n"; }; }; __END__ =head1 NAME update-wan-ip - dynamic DNS update from an UPnP enabled gateway =head1 SYNOPSIS update-wan-ip [options] update-wan-ip -h HOSTNAME -k KEYFILE update-wan-ip -h home.dyn.example.com -k /root/Kdyn-example-com+157+ +12345.key Options: --hostname, -h --key-file, -f --key, -k --key-name, -n --force --verbose --help --man =head1 OPTIONS =over 4 =item B<--hostname>, B<-h> --hostname home.dyn.example.com Set the hostname you want to update. =item B<--key-file>, B<-k> --key-file /root/Kdyn-example-com+157+12345.key Name of the keyfile that contains the key for the DNS updates. Note that the filename also carries the name of the DNS key. This is i +mportant and must match the name of the DNS key in your DNS server. =item B<--key-name>, B<-n> --key-name dyn-example-com Name of the key that is used for the DNS update. If you don't use a keyfile, for example while testing the setup, you can specify the name of the key through this switch. This does not override a name given through a keyfile. =item B<--key>, B<-k> --key AAANN3...== The key that is used for the DNS update. If you don't use a keyfile, for example while testing the setup, you can specify the key through this switch. =back =head1 TROUBLESHOOTING Test with the C<nsupdate> command whether DNS updates work at all: nsupdate -y key-name:key server ns.example.com zone dyn.example.com update add home.dyn.example.com 600 A 127.0.0.1 show send Check that both machines have the same time. If not, install C<ntp> on both machines. =cut

Replies are listed 'Best First'.
Re: My own dynamic DNS setup
by Theodore (Friar) on Apr 11, 2014 at 13:01 UTC
    And here is my own perverted pair of scripts, with IPv6/AAAA support :-)