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.