I need to have a DNS server which returns resource records (RR) depending on the location of the requesting machine. Such servers exist, but the difficulty in my case, is that I have to provide these services on a private IP network, and that I therefore cannot use Geo data such as that offered by Maxmind. Apart from setting up a BIND nameserver with views (which by the way break all existing zones, as they too have to be set up as views), I could have attempted to create a custom sdb backend, but that would mean a very high investment in time and testing. An alternative would also be the Pipe backend of PowerDNS or the use of its geo backend, used by Wikimedia for load balancing, but I don’t want to touch our running system and add a backend. A very interesting and tremendously flexible solution is the Stanford::DNSserver, which is a DNS server framework written in Perl. A possibly similar project exists in form of the perl geo dns server, but I haven’t otherwise looked at that. After downloading the tarball and performing the usual perl Makefile.PL; make incantation, I launched the supplied sample, and sent some queries to it with dig. Delightful to see what an answer to a TXT query can be:

    $ dig -p 5300 @localhost dir.jpm.passwd.my.net. txt
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21158
    ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;dir.jpm.passwd.my.net.         IN      TXT
    
    ;; ANSWER SECTION:
    dir.jpm.passwd.my.net.  60      IN      TXT     "/home/users/jpm"

Now, if that isn’t flexible, then what is? The rest of course is easy, and I’m including my code here to show the full server. There isn’t much to be proud of in this code, because Stanford::DNSserver does it all; I’m only responsible for an if or two. ;-)

#!/usr/bin/perl

use FindBin;
use lib "$FindBin::Bin";

use Socket;
use Sys::Hostname;
use Stanford::DNS;
use Stanford::DNSserver;

my $domain = 'example.com';

$ns = new Stanford::DNSserver (
#   listen_on => ["localhost"],
#   listen_on => ["127.0.0.1"],
   port      =>            53,
   defttl    =>            60,
   debug     =>             1,
   daemon    =>          "no",
   pidfile   => "example.pid",
#   chroot    =>    "/var/tmp",
#   run_as    =>      "nobody",
   logfunc   => sub { print shift,"\n"                },
   loopfunc  => sub { print "weeeee ... ", `date`     },
   exitfunc  => sub { print "good-bye cruel world!\n" }
   );

$ns->add_static("geo.$domain",
         T_SOA, rr_SOA(hostname(), "hostmaster.$domain",
                   time, 3600, 3600, 86400, 0));

$ns->add_dynamic("geo.$domain" => \&handle_geo_request);

$ns->answer_queries();

sub handle_geo_request {
    my ($domain, $host, $qtype, $qclass, $dm, $from) = @_;

    print "DOMAIN=[$domain], HOST=[$host], QT=[$qtype] FROM=[$from]\n";

    if ($qtype == T_A) {
       if ($from =~ /^10\.1/) {
          $entry = unpack('N', inet_aton('10.0.128.101'));
       } elsif ($from =~ /^10\.2\./) {
          $entry = unpack('N', inet_aton('10.2.1.11'));
       } elsif ($from =~ /^10\.4\./) {
          $entry = unpack('N', inet_aton('10.4.1.198'));
       } else {
          $entry = unpack('N', inet_aton('10.0.128.104'));
       }
       $dm->{answer} .= dns_answer(QPTR, T_A, C_IN, 34, rr_A($entry));
       $dm->{ancount} += 1;
    } elsif ($qtype == T_TXT) {
       $data = "No RR for host=$host qtype=$qtype from client=$from";
       $dm->{answer} .= dns_answer(QPTR, T_TXT, C_IN, 1, rr_TXT($data));
       $dm->{ancount} += 1;
    } else {
        $dm->{rcode} = NXDOMAIN;
    }
}

And before you ask: if you launch this with perl –I blib/lib ./geodns.pl you have a fully functional non–recursive name server. A simple delegation from BIND to the address of the machine on which this server is running, integrates it into our DNS environment.

Mail, Linux, and DNS :: 02 Apr 2007 :: e-mail