I’m succumbing to comments and queries on whether the PowerDNS DNS name server could be backed by a CouchDB database. It can.
I’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 aserial
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
launch=pipe
# Local IP addresses to which we bind
local-address=0.0.0.0
# The port on which we listen
local-port=9953
# Version of the pipe backend ABI
pipebackend-abi-version=2
# Name of co-process
pipe-command=/etc/powerdns/jpm/powercouch.pl
# Seconds to store packets in the PacketCache
cache-ttl=0
# Seconds to store packets in the PacketCache
query-cache-ttl=0
(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:
#!/usr/bin/perl
# 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 = 'http://127.0.0.1:5984/dns';
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";
<>;
exit;
}
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";
next;
}
# 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";
next;
}
if ($#{$v->{rows}} == -1) { # NXDOMAIN
print "END\n";
next;
}
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},
$s->{minimum});
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
;; ANSWER SECTION:
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.