Alternative DNS ServersDuring the course of last week I was thinking that CouchDB would make a rather lovely back-end to a DNS server: its built-in replication would make for a pretty good multi-master setup. And yes, I know: there are lots of those, but even so, I was thinking about it. (If you are new to CouchDB, I've made a reference card you might enjoy.)

And what do you know? I wasn't alone. The post CouchDB, DNS and Scaling the Cloud hit me right in the eye, and while waiting for the issues with my Internet connection to be sorted out (that's another story) I thought I'd give it a whack. Nothing hard-core mind you, but just a proof of concept.

Here are the results:

$ dig @127.0.0.1 couch.example
    ;; ANSWER SECTION:
    couch.example.      60  IN  A   192.168.1.20
    
    $ dig @127.0.0.1 www.couch.example 
    
    ;; ANSWER SECTION:
    www.couch.example.  3600    IN  A   192.168.1.17
    www.couch.example.  3600    IN  A   172.10.4.5
    www.couch.example.  3600    IN  A   10.0.0.2
    
    ;; Query time: 57 msec
    
    $ dig @127.0.0.1 www.couch.example txt
    
    ;; ANSWER SECTION:
    www.couch.example.  3600    IN  TXT "it is now really"
    www.couch.example.  3600    IN  TXT "time to relax"
    
    ;; Query time: 55 msec

Nothing spectacular there, until you see the source of the data: An address record (A) would look a bit like this (note the multivalued addresses)

{
       "_id" : "bd4b48f65792a45224ad406ff0068c68",
       "_rev" : "1-2a0ce2bebca4a86e692800346bc96f51",
       "zone" : "couch.example",
       "name" : "www",
       "type" : "a",
       "addr" : [
          "192.168.1.17",
          "172.10.4.5",
          "10.0.0.2"
       ]
    }

and a corresponding TXT resource record is

{
       "_id" : "bd4b48f65792a45224ad406ff0069ce6",
       "_rev" : "1-e2f056558a2202e1c814e1c1b8cb2b83"
       "zone" : "couch.example",
       "name" : "www",
       "type" : "txt",
       "txt" : [
          "it is now really",
          "time to relax"
       ],
    }

The quick and dirty Perl program is based on the excellent Stanford::DNSserver, which I discuss together with lots of other name servers, in my book Alternative DNS Servers.

#!/usr/bin/perl
    
    use strict;
    use IO::Socket;
    use Sys::Syslog;
    use Sys::Hostname;
    use Stanford::DNS;
    use Stanford::DNSserver;
    use Net::CouchDB;
    use Data::Dump qw(pp);
    
    my $myname = 'home.mens.de';
    my $tld     = 'couch.example';
    my $ttl     = 3600;
    
    my $couch = Net::CouchDB->new('http://127.0.0.1:5984');
    my $db = $couch->db('dns');
    my $design = $db->document('_design/dns');
    my $view = $design->view('fqdnrr');
    
    my %querytypes = (
        '1'  => 'a',
        '16' => 'txt',
        '255'    => 'any',
        );
    
    my $ns = new Stanford::DNSserver (
        listen_on => ["127.0.0.1"],
        port      =>        53,
        defttl    =>        60,
        debug     =>         1,
        daemon    =>      "no",
        pidfile   => "/tmp/example.pid",
        logfunc   => sub { print shift; print "\n" },
        exitfunc  => sub {
                print "Bye!\n";
                });
    
    # Add static answers for SOA, NS, and A
    $ns->add_static("$tld",
         T_SOA, rr_SOA($myname, "hostmaster.$tld",
               time, 3600, 3600, 86400, 0));
    $ns->add_static("$tld", T_NS, rr_NS($myname));
    $ns->add_static("$tld", T_TXT, rr_TXT('time to relax'));
    
    my $myaddr = inet_ntoa((gethostbyname($myname))[4]);
    $ns->add_static("$tld", T_A,
        rr_A(unpack('N', inet_aton($myaddr))));
    
    # Set up handler for dynamic requests
    $ns->add_dynamic("$tld" => \&userreq);
    
    # Start serving answers...
    $ns->answer_queries();
    
    sub userreq {
        my ($domain, $host, $qtype, $qclass, $dm, $from) = @_;
    
        my $fqdnrr = $host.'.'.$domain.'.'.$querytypes{$qtype};
    
        print "DOMAIN=[$domain], HOST=[$host], QT=[$qtype] FROM=[$from]\n";
    
        print "Search for $fqdnrr\n";
        my $rs = $view->search({ key => $fqdnrr });
    
        my $cursor = $rs->first;
        my $doc;
    
        if (!defined($cursor)) {
            $dm->{rcode} = NXDOMAIN;
        return;
        }
    
        print pp($cursor->{row}{id}), "\n";
        if (!defined($doc = $db->document($cursor->{row}{id}))) {
            $dm->{rcode} = NXDOMAIN;
        return;
        }
    
        # print pp($doc),"\n";
        
        $dm->{rcode} = NOERROR;
    
    
        if ($qtype == T_A || $qtype == T_ANY) {
    
            for my $ip (@{$doc->{addr}}) {
                # push each IP back into Stanford::'s reply
                my $entry = unpack('N', inet_aton($ip));
                $dm->{answer} .= dns_answer(QPTR, T_A, C_IN, $ttl, rr_A($entry));
                $dm->{ancount} += 1;
            }
        }
    
        if ($qtype == T_TXT || $qtype == T_ANY) {
    
            for my $txt (@{$doc->{txt}}) {
    
                $dm->{answer} .= dns_answer(QPTR, T_TXT, C_IN,
                    $ttl, rr_TXT($txt));
                $dm->{ancount} += 1;
        }
        }
    
        # If no answers available, return NXDOMAIN
    
        if (! $dm->{ancount} ) {
            $dm->{rcode} = NXDOMAIN;
        }
    }

The design is wrong because this won't support queries for ANY, but that is rather easily fixed. Update 2010-05-03: this continues...

Flattr this
DNS, dnsbook, CouchDB, and NoSQL :: 30 Apr 2010 :: e-mail

Comments

blog comments powered by Disqus