I'm in an environment in which a number of outward-facing authoritative DNS servers are fed by hidden primary servers; nothing special. What is perhaps a bit special is that all these servers are hand-crafted: zone definitions and BIND configurations are lovingly hand-edited, and as such, there is little control over which zones are actually valid, which of the servers are correctly configured, etc.
What I need on the "outside" DNS servers is a list of zones which have been configured on the hidden primaries, so that I can automate verification: are all zones configured, are any missing, etc. But there's a problem here: I'm not the copy-and-paste type of person: I want delivery of this zone list automated.
What makes this a bit tricky is that the only communications channel available to me for transporting data between the outward-facing servers and the internal hidden primaries or vice versa, is the DNS ports: port 53 UDP and TCP, so I thought I'd just zone-transfer a list of zones out in its own zone. Crazy but easy. :-) I'm going to show you how I'm solving the problem. First of all, let me show you what I'm talking about.
On the left of the following diagram is a hidden primary BIND name server. On the right, is a set of firewalls (the thick red line) and then come the outward-facing name servers. (Their brand is irrelevant; it suffices that they are capable of transferring zones from the left-hand-side name server via the DNS zone transfer (AXFR) protocol.)

What I need is a list of zones configured on the hidden primary servers (seen on the left in
the diagram above). I could use the usual Unix toolbox to parse named.conf
and its included files, but I thought I'd rather query BIND itself, through
its statistics server.
You may know that BIND 9.5 introduced a built-in
statistics server you enable when building the server. The statistics
server provides a large variety of data in XML format you can retrieve
directly via HTTP. The host and port on which the statistics server listens
and access controls to it are configured in named.conf. For example, the
statements
acl "trusted" {
192.168.1.0/24; // local
127.0.0.1;
};
...
statistics-channels {
inet * port 8053 allow { trusted; };
};
tell BIND to accept HTTP connections to its statistics server at any
of the system interfaces on port 8053 for the addresses in the "trusted"
ACL.
In fact if I point my Web browser at the host:port combination configured
for BIND (which for me would be http://127.0.0.1:8053/) I'll see a nicely
(oh, my eyes) formatted display of the data. This is a tiny excerpt showing
some of my configured zones:

(The formatted output you see above in the
browser is created by an XSL style sheet which is also provided by the
BIND name server -- look closely at the second line of what is output in the
example below.) The plain XML looks different of course; the first few lines
of what I get when I retrieve the XML with an invocation of curl -s
http://127.0.0.1:8053/ look like this:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/bind9.xsl"?>
<isc version="1.0">
<bind>
<statistics version="2.2">
<views>
<view>
<name>internal</name>
<zones>
<zone>
<name>temp.aa/IN/internal</name>
<rdataclass>IN</rdataclass>
<serial>42401</serial>
<counters>
<Requestv4>0</Requestv4>
<Requestv6>0</Requestv6>
......
What I need from the XML is a list of zones, which is easier said than done. In BIND zones may be contained in views, and I may have the same zone in two (or more) different views. So, what I really want is a list which contains the zone name, the view name, the class and the zone's current SOA serial number.
I first tried parsing the XML produced by the BIND statistics server with Perl, but that was much too slow for me, so I created bzl, a small and very fast libxml2-enabled C program that grabs the XML statistics directly via HTTP from BIND, uses Xpath to extract the zones and prints those out one per line. An example output is:
42401 temp.aa IN internal
0 0.IN-ADDR.ARPA IN internal
0 127.IN-ADDR.ARPA IN internal
0 254.169.IN-ADDR.ARPA IN internal
0 2.0.192.IN-ADDR.ARPA IN internal
0 100.51.198.IN-ADDR.ARPA IN internal
0 113.0.203.IN-ADDR.ARPA IN internal
0 255.255.255.255.IN-ADDR.ARPA IN internal
0 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA IN internal
0 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA IN internal
0 8.B.D.0.1.0.0.2.IP6.ARPA IN internal
0 D.F.IP6.ARPA IN internal
0 8.E.F.IP6.ARPA IN internal
0 9.E.F.IP6.ARPA IN internal
0 A.E.F.IP6.ARPA IN internal
0 B.E.F.IP6.ARPA IN internal
- foo.bar IN internal
1287679338 bzl IN internal
17 example.net IN external
2001013101 bind CH extern-chaos
0 authors.bind CH
0 hostname.bind CH
0 version.bind CH
0 id.server CH
Note how BIND's internal automatically configured zones usually have
an SOA serial number of 0. Also note, the zone foo.bar has a dash for the
serial number -- the zone can't be loaded because it's an SDB zone and
it's back-end isn't with me in the hotel (although it should be).
The rest is trivial: a Perl program called makezonefile.pl periodically takes that output, filters out the zones I'm not interested in, and creates a normal RFC 1035 zone file.
$TTL 1H
; generated by ./makezonefile.pl (bzl) on Thu Oct 21 20:26:30 2010
;
;
@ IN SOA localhost. jpmens.no.where. 1287681990 1H 1H 1W 60
@ IN NS localhost.
temp.aa IN TXT "42401"
foo.bar IN TXT "-"
bzl IN TXT "1287681967"
example.net IN TXT "17"
bzl-nzones IN TXT "4"
Each zone name retrieved by bzl becomes an unqualified domain name.
The value of the TXT resource record is the
zone's SOA serial retrieved from the XML out of the statistics server; it is
simply for informational purposes, but I could even use it for monitoring. An
additional resource record called bzl-nzones is added to the zone file: it
contains the number of zones listed in this zone file.
And now? I said: the rest is trivial. I configure my hidden primary server to serve this zone to particular hosts only, of course:
zone "bzl" in {
type master;
file "master/bzl-zonefile.db";
allow-transfer { 10.0.0.1; };
allow-query { ... };
also-notify { 10.0.0.1; };
};
And I configure the zone as a slave zone on the outward-facing name servers, taking particular care to protect the zone's content. (Make sure you restrict in a view or via an ACL which clients may query the zone!) Let me show you how the zone will be transferred onto a slave:
$ dig @192.168.1.20 bzl axfr
bzl. 3600 IN SOA localhost. jpmens.no.where. 1287682762 3600 3600 604800 60
bzl. 3600 IN NS localhost.
temp.aa.bzl. 3600 IN TXT "42401"
foo.bar.bzl. 3600 IN TXT "-"
bzl.bzl. 3600 IN TXT "1287681990"
bzl-nzones.bzl. 3600 IN TXT "4"
example.net.bzl. 3600 IN TXT "17"
bzl. 3600 IN SOA localhost. jpmens.no.where. 1287682762 3600 3600 604800 60
A cron entry on the hidden primary creates the zone file and reloads the name server. If the zone has changed, BIND will notify its slave(s) which then re-transfer the zone.
0 * * * * /usr/local/sbin/makezonefile.pl && /usr/sbin/rndc reload bzl
Thanks to the BIND statistics server I have an easy way of listing zones configured in a BIND name server, and wrapping a few bits around that data, solves a problem I would have had difficulty solving otherwise.
Further reading:
Comments
blog comments powered by Disqus