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.

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