Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 26 Nov 2002 16:15:57 -0500
From:      "Eric W. Bates" <ericx@vineyard.net>
To:        "Marcin Jessa" <yazzy@yazzy.org>
Cc:        <freebsd-isp@freebsd.org>
Subject:   Re: Dynamic DNS Server
Message-ID:  <06f701c29591$033fbae0$68c311cc@vineyard.net>
References:  <20021118231031.GA74664@yazzy.org>

next in thread | previous in thread | raw e-mail | index | archive | help
----- Original Message -----
From: "Marcin M. Jessa" <yazzy@yazzy.org>
To: <freebsd-isp@freebsd.org>
Sent: Monday, November 18, 2002 6:10 PM
Subject: Dynamic DNS Server


> Hi guys.
>
> I would like to set up a dynamic dns server.
> Do you know of any?
> The perfect sollution would be to make it to authenticate users from a
LDAP server but this it not nessesary.
> I'd also need windows and bsd/linux dyndns client apps that could talk to
my server as well.
>
> Thanks in advance.
> YazzY
>
> To Unsubscribe: send mail to majordomo@FreeBSD.org
> with "unsubscribe freebsd-isp" in the body of the message
>

This isn't overly generic; it's a client script; and I haven't finished
testing it; however, you might find this handy as a starting point.

This script hits a URL to determine it's IP address (this presumes the
machine is behind NAT and has no other way to find out the assigned IP).  If
the IP has changed, it composes a TSIG signed update message and sends it
off to the named server.

What's wrong:
I have to do some reading and experimentation about bind9 RR's.  I'm not
sure what is a reasonable TTL to submit with the A record with the new IP.
I don't know whether the name server will delete the record when the TTL
expires (in which case, the logic has to keep track of the expiry and push
updates out when they are due as well as when there is a change).  The
client doesn't have any way to know when the lease (if it is dhcp...  does
PPPoE provide expiry data?) is due to expire. Also: when ISC's dhcpd
performs dynamic updates, it submits TXT records along with the A and PTR
records.

The script still needs to be daemonized (probably a 5 minute wait loop).

Probably should have some logging.

The whole thing probably ought to be 180'ed and be re-written as a mod_perl
handler.  After which, the remote simply needs to query an authenticated
URL.

Let me know if this appears to have any value to you; I have to finish it in
the next week or so.

#!/bin/sh
# File:         whats-my-line
# Author:       Charlie Root, ericx@vineyard.net
# Date:         Thu Nov 21 18:49:12 2002
# Time-stamp:   <2002-11-21 19:32:58 ericx>
# Description:  Simple CGI to announce IP address of remote host
#
# $Id: whats-my-line,v 1.2 2002/11/22 00:33:36 ericx Exp $
# $Log: whats-my-line,v $
# Revision 1.2  2002/11/22 00:33:36  ericx
# Switched from perl to shell
#
# Revision 1.1  2002/11/22 00:00:21  ericx
# Hey! It works!
#

echo "Content-type: text/plain"
echo
echo "Remote IP = '${REMOTE_ADDR}'"



#!/usr/bin/perl5
# File:  dyn-dns-vni
# Author: Charlie Root, ericx@vineyard.net
# Date:  Wed Nov 20 10:34:52 2002
# Time-stamp: <2002-11-26 15:13:09 ericx>
# Description: DDNS (RFC 2136) script to push IP to VNI
#
# $Id: dyn-dns-vni,v 1.3 2002/11/22 03:16:23 ericx Exp ericx $
# $Log: dyn-dns-vni,v $
# Revision 1.3  2002/11/22 03:16:23  ericx
# Gathering the IP works
#
# Revision 1.2  2002/11/21 23:43:45  ericx
# &read_key works.
#
# Revision 1.1  2002/11/20 15:38:14  ericx
# Initial revision
#

use strict;
use Carp;
use Data::Dumper;
use LWP::UserAgent;
use File::Basename;
use Net::DNS;

use constant DEBUG        => 1;
use constant GET_URI      =>
    'https://www.your.host/cgi-bin/whats-my-line';
use constant LAST_IP_PATH => '/usr/local/share/dyn-dns-vni/last-ip';
use constant DOMAIN       => 'somedomain.com';
use constant NS           => 'ns1.your.host';
use constant TSIG_DIR     => '/etc/namedb/keys';
use constant TSIG_NAME    => 'some-ddns-key';

use vars qw($VERSION $PROG_NAME);

# Set up $VERSION from RCS marker
my @r = (q$Revision: 1.3 $ =~ /\d+/g);
$VERSION = sprintf "%d."."%02d" x $#r, @r;

{
    &main();
    exit 0;
}

sub main {
    my $my_ip;   # IP as reported by VNI CGI

    $my_ip = &get_ip();
    &carp("My IP appears to be: '$my_ip'") if DEBUG;

    # compare IP with stored IP
    exit 0 if &is_same_ip($my_ip);

    # if different, submit an update and exit with error condition if
    # not successful.
    exit 1 unless &submit_update($my_ip);

    # Ok, we're all good.  Write out the IP just successfully pushed
    # out for the next run.
    open(LAST_IP, '>'. LAST_IP_PATH) or
 &croak("Can't open ", LAST_IP_PATH, " for write.");
}

# Submit a GET requst to the VNI CGI and extract the IP number from
# the response as a dotted quad.
sub get_ip {
    my $useragent;  # LWP UserAgent
    my $response;  # response to GET request
    my $my_ip;   # IP as reported by VNI CGI

    # submit a GET request to whats-my-line
    $useragent = LWP::UserAgent->new;
    $useragent->agent(&basename($0). "/$VERSION");
    $response = $useragent->get(GET_URI);

    # confirm we got an answer
    &croak("GET of ". GET_URI. "failed:\n". $response->as_string())
 if ($response->is_error);

    # extract the VNI response and strip the string out of the
    # response
    $my_ip = $response->content();
    chomp $my_ip;
    &croak("Response content, '$my_ip',  does not match regexp")
 unless $my_ip =~ s/^.+'([\d\.]+)'$/$1/;

    return $my_ip;
}

# Given an IP as a param, compare it with the saved version on
# disk. Return true if the IP is the same.
sub is_same_ip {
    my $my_ip = shift;

    my $last_ip;

    # if we can't open the file, then the comparison is a failure
    open(LAST_IP, LAST_IP_PATH) or return undef;
    $last_ip = <LAST_IP>;
    chomp $last_ip;
    close LAST_IP;

    return $my_ip eq $last_ip;
}


# Push out a signed update request to the primary server at VNI. Spew
# errors on failure; but return only a success/fail boolean.
sub submit_update {
    my $new_ip = shift;

    my $key;   # TSIG key for DNSSec
    my $update;   # DNS Update object
    my $res;   # DNS Resolver object
    my $reply;   # DNS Reply object

    $key = &read_key(TSIG_NAME) or
 &croak("Can't read the key named: '". TSIG_NAME. "'.");
    &carp("key = '$key'") if DEBUG;

    # Create the update packet.
    $update = Net::DNS::Update->new(DOMAIN);

    # Add an A record for this host.
    # Have to suss the TTL data.  86400 = 24 hr.
    $update->push("update", rr_add("fw.some.host. 86400 A $new_ip"));
    $update->push("update", rr_add("mail.some.host. 86400 A $new_ip"));

    # not clear whether pushing an MX is appropriate (probably unnecessary)
    $update->push("update", rr_add(DOMAIN. " MX 10 mail.some.host"));

    # Sign the update
    $update->sign_tsig(TSIG_NAME, $key);

    # Send the update to the zone's primary master.
    $res = Net::DNS::Resolver->new();
    $res->nameservers(NS);
    $reply = $res->send($update);

    # Did it work?
    if (defined $reply) {
 if ($reply->header->rcode eq "NOERROR") {
     return 1;
 } else {
     &carp("DNS Update failed: ". $reply->header->rcode);
 }
    } else {
 &carp("Update failed: ", $res->errorstring);
    }
}

# open up one of the 2 files created by dnskeygen in the TSIG
# dir. This is a little krufty in that the file name algorithm inserts
# digits in a way which I do not know how to reproduce.  At this
# location the digits are '+157+00000' consistently; but I have seen
# other patterns.
sub read_key {
    my $key_name = shift; # name of key

    my $key_file_name;  # name of private file listing key

    # Find the key file corresponding to the keyname
    opendir(KEY_DIR, TSIG_DIR) or &croak("Cannot open TSIG directory.");
    $key_file_name = (grep {/^K$key_name\.\+\d{3}\+\d{5}\.private$/}
             readdir(KEY_DIR))[0];
    closedir(KEY_DIR);

    open(KEY_FILE, TSIG_DIR. "/$key_file_name") or
 &croak("Cannot open key file, '$key_file_name'.");
    while (<KEY_FILE>) {
 next unless /^Key: (.*)$/;
 return $1;  # success
    }
    return undef;  # failure
}

=head1 VERSION

$Id: dyn-dns-vni,v 1.3 2002/11/22 03:16:23 ericx Exp ericx $

=head1 AUTHOR

Eric W. Bates

=cut


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-isp" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?06f701c29591$033fbae0$68c311cc>