NSD is a name server implementation developed and maintained by NLnet Labs in cooperation with RIPE. NSD is an authoritative-only DNS implementation, and is memory efficient, secure and fairly straightforward.
NSD can start up quickly, as all zone information is compiled into an efficient binary format prior the the NSD daemon reading it. A few of the root nameservers use NSD, as well as the .se ccTLD. NSD has a proven history of being robust and can meet the demands of the highest-traffic DNS requirements known.
You can read more about NSD over at the project page (http://www.nlnetlabs.nl/strace/bash -x that I had to indulge myself in. Both gooby and dolan are running a minimal package install of CentOS 6.3 x86_64.
Building and Installing NSD
The following steps were performed on both gooby and dolan. First, install the appropriate packages required to build NSD via yum:
|
1 |
# yum install -y make libstdc++-devel gcc-c++ openssl-devel |
Build and install NSD to /usr/local/nsd-<version>, and create a symlink from /usr/local/nsd. This presumes you’ve downloaded the source tarball to /usr/local/src:
|
1 2 3 4 5 6 7 |
# cd /usr/local/src # tar xzf nsd-3.2.14.tar.gz # cd nsd-3.2.14 # ./configure --prefix=/usr/local/nsd-3.2.14 # make # make install # ln -s /usr/local/nsd-3.2.14 /usr/local/nsd |
That’s all there is to it - now we must configure NSD.
Generating our TSIG Key
We will use TSIG to secure and encrypt our zone transfers. To generate a key suitable to use for TSIG, install the bind package via yum in order to provide the dnssec-keygen utility. I also install the bind-utils package to get access to useful utilities such as dig. I only did this on gooby but there would be no harm in installing the packages on both servers:
# yum install bind-utils bind
Check that dnssec-keygen is now installed:
|
1 2 |
# which dnssec-keygen /usr/sbin/dnssec-keygen |
Use dnssec-keygen to generate a HOST key, 160 bits long, using the HMAC-SHA1 algorithm. Make sure you do this in a secure directory as the key file will be created in the present working directory by default:
|
1 2 3 |
# cd /root # dnssec-keygen -a HMAC-SHA1 -b 160 -n HOST testtsig Ktesttsig.+161+18081 |
Note the key name that has been generated. Append .key to the end of this key name, and you have the filename of the key file generated. cat this file to see its contents:
|
1 2 |
# cat Ktesttsig.+161+18081.key testtsig. IN KEY 512 3 161 0915iGWHa1BQ12kkxD57/7fqcJ0= |
So - we will be using 0915iGWHa1BQ12kkxD57/7fqcJ0= as our secret when configuring secure zone transfers via TSIG. As long as both master and slave have the same key defined, and are using the same key for each zone transfer (i.e. master can encrypt using the key, slave can decrypt), all will work as expected.
Security: Creating chroot Jails and Users
As a security measure, we will configure our servers to drop privileges down to a non-privileged user after binding to port 53. As we are using a default installation of CentOS 6.3, disable iptables and set SELinux to permissive mode. For Production use, iptables should be configured to permit appropriate traffic rather than being disabled, with the appropriate SELinux policies applied. Perform all steps in this section on both nodes:
|
1 2 3 4 5 6 7 8 |
# iptables -L -n # service iptables stop # chkconfig iptables off # setenforce permissive # vi /etc/sysconfig/selinux ... SELINUX=permissive ... |
Add a user and group for NSD.
|
1 2 |
# groupadd -g 5252 nsd # useradd -M -d /dev/null -s /sbin/nologin -g nsd -u 5252 nsd |
Create the chroot directories:
|
1 2 3 |
# mkdir -p /var/chroot/nsd/zonefiles # mkdir -p /var/chroot/nsd/db/nsd # mkdir -p /var/chroot/nsd/dev |
And set appropriate ownership:
|
1 |
# chown -R nsd:nsd /var/chroot/nsd |
As we’re using CentOS 6.3, rsyslog must be configured to create a log socket at <NSD_BASE>/dev/log. To do this, create the following file:
|
1 2 3 |
# vi /etc/rsyslog.d/nsd.conf $ModLoad imuxsock.so $AddUnixListenSocket /var/chroot/nsd/dev/log |
And restart rsyslog:
|
1 |
# service rsyslog restart |
An ls will show that the socket has been created:
|
1 2 |
# ls -l /var/chroot/nsd/dev/log srw-rw-rw-. 1 root root 0 Feb 2 12:30 /var/chroot/nsd/dev/log |
NSD will now be able to generate syslog messages from within the chroot.
Starting NSD on the Master Server
Prior to configuring our Slave Server or loading any zones into the master, let’s first start NSD on the master and ensure it starts correctly. Create a basic nsd.conf as follows, under<NSD_BASE>/etc/nsd. In our case, we’re only doing this on gooby for now:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# vi /usr/local/nsd/etc/nsd/nsd.conf server: ip-address: 172.16.18.172 hide-version: yes chroot: "/var/chroot/nsd" username: nsd zonesdir: "/var/chroot/nsd/zonefiles" difffile: "/var/chroot/nsd/db/nsd/ixfr.db" xfrdfile: "/var/chroot/nsd/db/nsd/xfrd.state" pidfile: "/var/chroot/nsd/db/nsd/nsd.pid" database: "/var/chroot/nsd/db/nsd/nsd.db" key: name: tsig-testtsig algorithm: hmac-sha1 secret: "0915iGWHa1BQ12kkxD57/7fqcJ0=" |
Ensure that the files/directories specified in the zonesdir, difffile, xfrdfile, pidfile and database directives are accessible within the chroot (NSD is smart enough to remove the chroot path from these directives).
Next, start NSD using nsdc:
|
1 |
# /usr/local/nsd/sbin/nsdc start |
You should see a message such as the following in /var/log/messages:
|
1 |
Feb 4 13:42:21 gooby nsd[2610]: nsd started (NSD 3.2.14), pid 2610 |
If NSD has started correctly, proceed.
Creating and Loading a Zone
We will use example.com as per usual. First, create a zonefile at the appropriate location - in our case /var/chroot/nsd/zonefiles. If you have a large number of zonefiles, it is worth creating a directory tree under the zonefiles directory to aid in administration. Therefore, I’ll place my zonefiles in /var/chroot/nsd/zonefiles/<.
|
1 2 3 4 5 6 7 8 |
# mkdir /var/chroot/nsd/zonefiles/e # vi /var/chroot/nsd/zonefiles/e/db.example.com example.com. 86400 IN SOA ns1.example.com. hostmaster.example.com. 2013020201 10800 3600 604800 3600 example.com. 86400 IN NS ns1.example.com. example.com. 120 IN A 192.168.0.1 ns1.example.com. 120 IN A 192.168.0.1 mail.example.com. 120 IN A 192.168.0.1 example.com. 120 IN MX 25 mail.example.com. |
Next, add the appropriate zone entry to /usr/local/nsd/etc/nsd/nsd.:
|
1 2 3 4 5 6 7 8 |
# vi /usr/local/nsd/etc/nsd/nsd.conf ... zone: name: "example.com" zonefile: "e/db.example.com" notify: 172.16.18.169 tsig-testtsig provide-xfr: 172.16.18.169 tsig-testtsig outgoing-interface: 172.16.18.172 |
A few things to note here. We configure the zonefile directive relative to the zonesdir directive declared in the server section of nsd.conf. We also lock down notifies to be sent to dolan only (once we’ve configured it), and to be encrypted with our TSIG signature tsig-testtsig via the notify directive. Configure provide-xfr accordingly, to only allow dolan to AXFR from gooby. Also, as we’ve bound NSD to a specific interface on our host (as specified via the ip-address directive in the server section), outgoing-interface should be set to ensure notifies are sent out of the right interface.
Once your configuration and zonefile are in place, run nsdc rebuild to regenerate nsd.db - the binary configuration database. You can rebuild individual zonefiles, if required, with zonec - NSD calls this during a rebuild - observe:
|
1 2 3 4 5 |
# /usr/local/nsd/sbin/nsdc rebuild zonec: reading zone "example.com". zonec: processed 6 RRs in "example.com". zonec: done with no errors. |
Next, restart NSD.
|
1 |
# /usr/local/nsd/sbin/nsdc restart |
You should now be able to dig the SOA record for example.com, and receive a result:
|
1 2 |
# dig SOA example.com @172.16.18.172 +short ns1.example.com. hostmaster.example.com. 2013020201 10800 3600 604800 3600 |
If a zone has been added or removed from NSD, you need to run:
|
1 2 |
# /usr/local/nsd/sbin/nsdc rebuild # /usr/local/nsd/sbin/nsdc restart |
If you modify a zone, you need to run:
|
1 2 |
# /usr/local/nsd/sbin/nsdc rebuild # /usr/local/nsd/sbin/nsdc reload |
Configuring the Slave Server
We can now configure NSD on our slave server dolan as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# vi /usr/local/nsd/etc/nsd/nsd.conf server: ip-address: 0.0.0.0 hide-version: yes chroot: "/var/chroot/nsd" username: nsd zonesdir: "/var/chroot/nsd/zonefiles" difffile: "/var/chroot/nsd/db/nsd/ixfr.db" xfrdfile: "/var/chroot/nsd/db/nsd/xfrd.state" pidfile: "/var/chroot/nsd/db/nsd/nsd.pid" database: "/var/chroot/nsd/db/nsd/nsd.db" verbosity: 2 key: name: tsig-testtsig algorithm: hmac-sha1 secret: "0915iGWHa1BQ12kkxD57/7fqcJ0=" zone: name: "example.com" zonefile: "e/db.example.com" allow-notify: 172.16.18.172 tsig-testtsig allow-notify: 127.0.0.1 NOKEY request-xfr: 172.16.18.172 tsig-testtsig |
There are many things to note here. We bind to ALL interfaces on the server, using the ip-address: 0.0.0.0 directive. This is required, as an nsdc update (used to update all zones on the server) works by sending a notify to localhost. Therefore, we need to ensure that NSD is listening on localhost, and that an additional allow-notify statement is included to allow this with NOKEY (i.e. no TSIG key). The rest of the configuration is as per gooby, noting that we allow-notify from gooby only, and we only request-xfr from the master server - in this case also gooby.
Create the appropriate zonefile directory tree on the slave too. Using nsdc patch to write out the zonefiles from the journal will obviously fail if it can’t write to the appropriate directories:
|
1 2 |
# mkdir /var/chroot/nsd/zonefiles/e # chown nsd:nsd /var/choor/nsd/zonefiles/e |
Start NSD on the slave:
|
1 |
# /usr/local/nsd/sbin/nsdc start |
NSD should start, and transfer the zone from the master. This can be verified by taking a look at /var/log/messages:
|
1 |
Feb 4 13:17:48 dolan nsd[2023]: Zone example.com serial 0 is updated to 2013020601. |
Excellent. Make a change to the zone on the master, and update the serial:
|
1 2 3 4 |
gooby# vi /var/chroot/nsd/zonefiles/e/db.example.com ... gooby# /usr/local/nsd/sbin/nsdc rebuild gooby# /usr/local/nsd/sbin/nsdc reload |
On the slave, you should see the notify received, and the zone updated in /var/log/messages:
|
1 2 3 4 |
Feb 4 13:47:57 dolan nsd[2772]: Notify received and accepted, forward to xfrd Feb 4 13:47:57 dolan nsd[2771]: Handle incoming notify for zone example.com Feb 4 13:47:57 dolan nsd[2771]: xfrd: zone example.com written received XFR from 172.16.18.172 with serial 2013020603 to disk Feb 4 13:47:57 dolan nsd[2771]: xfrd: zone example.com committed "xfrd: zone example.com received update to serial 2013020603 at time 1359946077 from 172.16.18.172 in 1 parts TSIG verified with key tsig-testtsig" |
Excellent. The TSIG-verified zone transfer has worked.
You can run
|
1 |
# /usr/local/nsd/sbin/nsdc notify |
from the master to manually send notifies to the slave servers. You can also run
|
1 |
# /usr/local/nsd/sbin/nsdc update |
on the slave to manually initiate zone transfers.
Once the transfers have been committed, you can run nsdc patch to write them out to disk, for example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# /usr/local/nsd/sbin/nsdc patch reading database reading updates to database writing changed zones writing zone example.com to file e/db.example.com done zonec: reading zone "example.com". zonec: processed 7 RRs in "example.com". zonec: done with no errors. # cd /var/chroot/nsd/zonefiles # ls -arlt e total 16 drwxr-xr-x. 3 nsd nsd 4096 Feb 4 15:19 .. -rw-r--r--. 1 root root 540 Feb 4 15:20 db.example.com -rw-r--r--. 1 root root 522 Feb 4 15:20 db.example2.com drwxr-xr-x. 2 nsd nsd 4096 Feb 4 15:20 . |
The zonefiles have been written to disk as expected. This would have failed had we not created the appropriate directory structure.
Issues
During the course of my initial investigations into NSD, a few errors cropped up.
If you see a message like this on the slave …
|
1 |
Feb 2 12:34:16 dolan nsd[1944]: xfrd: zone example.com received error code REFUSED from 172.16.18.172 |
… and a message like this on the master
|
1 |
Feb 4 13:16:23 gooby nsd[6933]: query tsig unknown key/algorithm |
… this indicates that time is out of sync. Set correct time on both servers (here we can see that our servers were over 2 days apart), and the zone transfer will work as expected.
The following message:
|
1 |
Feb 2 12:32:42 dolan nsd[1906]: Could not tcp connect to 172.16.18.172: No route to host |
and this whilst debugging with nsdc notify
|
1 2 3 4 |
# /usr/local/nsd/sbin/nsdc notify Sending notify to slave servers... [1359944513] nsd-notify[7032]: warning: timeout (1 s) expired, retry notify to 172.16.18.169. [1359944515] nsd-notify[7032]: warning: timeout (2 s) expired, retry notify to 172.16.18.169. |
was caused by iptables running. Either disable iptables, or modify the ruleset accordingly, depending upon the Production status of the server.
The following update failure:
|
1 2 3 |
# /usr/local/nsd/sbin/nsdc update Sending notify to localhost to update secondary zones... nsdc: Could not send notify for slave zone example.com: not configured (with allow-notify: 127.0.0.1 or ::1) |
was caused by exactly what it said - I needed to allow-notify from localhost. I also had to ensure NSD was listening to localhost. This is not really what I wanted (I wanted to only bind to eth0), but from much time spent with bash -x, it seems to be all that NSD will allow (unless we hack the way the notifies are sent). Ensure your slaves all listen on localhost.
If you attempt to start NSD and receive permission denied errors when trying to bind to port 53, it is likely being caused by SELinux. Either define an appropriate SELinux ruleset, or set it to disabled or permissive, again depending upon the security requirements of the server.
Conclusion
This has been a very brief introduction and has really only just scraped the surface of what is a very mature and robust piece of technology. NSD is great for sites that require reliability, scalability and ease of administration - this is why various root nameservers and the .SE NS run it.
I’ll definitely be delving deeper into NSD over the coming months, and will be writing more articles around this and other DNS technologies.