I'm succumbing to comments and queries on whether the PowerDNS DNS name server could be backed by a CouchDB database. It can.

Alternative DNS ServersI'm not going to delve too deeply into PowerDNS here because I discuss it very thoroughly in chapter 6 of my book Alternative DNS Servers. So, in other words, if you're not familiar with PowerDNS, I'll recommend that as study material.

Faithful readers will recall, that I implemented a first DNS server with a CouchDB database as a proof of concept. After a complete redesign of the document model I had an implementation of a standalone authoritative DNS server with a CouchDB backend. This third iteration I discuss here takes that same model and the same data and creates a so-called pipe back-end for PowerDNS.

Now, the pipe back-end is a simple shell, Perl, or whatever program which reads queries given to it by PowerDNS from stdin and issues replies on stdout. As such, it is a very useful back-end for prototyping. Unfortunately, and as is to be expected, the result is not terribly fast; this is no limitation of PowerDNS, but rather of the shuffling of data that occurs between PowerDNS and the back-end's program.

The pipe backend is a PowerDNS native type only; it cannot be a master or a slave, nor does it have autoserial capability, where a serial number is automatically incremented. Well, the good news is that this CouchDB version has all of that:

  • The SOA serial number is incremented automatically when the JSON document in CouchDB is modified. (If you don't want this to happen, add a serial field to your zone document.)
  • CouchDB being what it is, this backend can replicate! Master, slave, whatever you like. ;-) (But admittedly, this is not DNS data AXFR-type replication.)

I'm using the following pdns.conf:

# Which backends to launch and order to query them in
    # Local IP addresses to which we bind
    # The port on which we listen
    # Version of the pipe backend ABI
    # Name of co-process
    # Seconds to store packets in the PacketCache
    # Seconds to store packets in the PacketCache

(For testing purposes, leave the two caching values at 0 for the time being.)

The powercouch.pl program is the pipe-back-end's program:

    # powercouch.pl (C)2010 by Jan-Piet Mens
    # demo pipe back-end for PowerDNS on CouchDB
    use strict;
    use IO::Socket;
    use AnyEvent::CouchDB;
    use Data::Dump qw(pp);
    my $uri = '';
    my $db = couchdb($uri);
    $|=1;   # disable buffering
    chomp(my $line = <>);
    # Start of pipe "protocol". See
    # http://doc.powerdns.com/backends-detail.html
    unless($line eq "HELO\t2") {
        print "FAIL\n";
    print "OK\tHere is $0. Relax.\n";
    while(chomp($line = <>))
        my @pq;
        my $v;
        my ($type,$qname,$qclass,$qtype,$id,$client,$ip) = @pq = split(/\t/, $line);
        print "LOG\treceived: $line\n";
        if ($#pq < 6) {
            print "LOG\tPowerDNS sent unparseable line\n";
            print "FAIL\n";
        # Check whether conn to CouchDB is still alive, and if not,
        # re-establish.
        eval {
            my $info = $db->info()->recv;
            # print pp($info),"\n";
        if ($@) {
            $db = couchdb($uri);
        my @keys = ($qname, lc $qtype);
        eval {
            $v = $db->view('dns/rrq', { key => [ @keys ] })->recv
        if ($@) {
            print "LOG\tCouchDB replies: $_: $@\n";
            print "FAIL\n";
        if ($#{$v->{rows}} == -1) { # NXDOMAIN
            print "END\n";
        foreach my $r (@{$v->{rows}}) {
            my $rr = $r->{value};
            my $ttl = $rr->{ttl};
            my $type = uc $rr->{type};
            # print pp($rr),"\n";
            if (($qtype == 'SOA' || $qtype == 'ANY') && $rr->{type} eq 'soa') {
                my $s = $rr->{data};
                my $reply = sprintf("%s %s %d %d %d %d %d",
                    $s->{mname}, $s->{rname}, $s->{serial},
                        $s->{refresh}, $s->{retry}, $s->{expire},
                reply($qname, $qclass, $id, $ttl, $type, $reply);
            if (($qtype == 'NS' || $qtype == 'ANY') && $rr->{type} eq 'ns') {
                reply($qname, $qclass, $id, $ttl, $type, $rr->{data});
            if (($qtype == 'A' || $qtype == 'ANY') && $rr->{type} eq 'a') {
                for my $ip (@{$rr->{data}}) {
                    reply($qname, $qclass, $id, $ttl, $type, $ip);
            if (($qtype == 'CNAME' || $qtype == 'ANY') && $rr->{type} eq 'cname') {
                reply($qname, $qclass, $id, $ttl, $type, $rr->{data});
            if (($qtype == 'TXT' || $qtype == 'ANY') && $rr->{type} eq 'txt') {
                for my $txt (@{$rr->{data}}) {
                    reply($qname, $qclass, $id, $ttl, $type, $txt);
            if (($qtype == 'PTR' || $qtype == 'ANY') && $rr->{type} eq 'ptr') {
                reply($qname, $qclass, $id, $ttl, 'PTR', $rr->{data});
            # AXFR not implemented
        print "END\n";
    sub reply {
        my ($qname, $qclass, $id, $ttl, $qtype, $rr) = @_;
        print "DATA\t$qname\t$qclass\t$qtype\t$ttl\t$id\t$rr\n";
        print "LOG\treturning $rr for $qname/$qtype\n";

Note that I'm using exactly the same CouchDB database and design that I used in the standalone version. You'll notice that the two programs are quite similar, the differences being in the way the standalone version replies to Stanford::DNSserver and the PowerDNS version simply prints its results to stdout for PowerDNS to then parse.

The result works.

$ dig example.org txt
    example.org.   1801  IN   TXT     "Relax"
    example.org.   1801  IN   TXT     "powered by"
    example.org.   1801  IN   TXT     "CouchDB and PowerDNS"
    ;; Query time: 67 msec

It isn't fast, but it works. You can, however, make it blindingly fast by tuning the cache values for PowerDNS as hinted to above. Read about it here. :-)

Update: and finally, if you are interested, I've implemented a BIND SDB back- end with CouchDB.

Flattr this
DNS, Database, dnsbook, CouchDB, powerdns, and NoSQL :: 05 May 2010 :: e-mail


blog comments powered by Disqus