Running a service which adds new hostnames to the internet requires a Dynamic DNS service. The uplift-bind package provides the client side of service interface for a Dynamic DNS based on ISC BIND DNS (or any other DNS service which honors RFC 2136 and 3007 dynamic update queries).
If you’re running a self-contained service for development or testing, you’ll need to have a local DNS service capable of accepting updates and responding to queries for your test zones while passing all other requests through to your external DNS servers.
For some simple unit testing you may instead want to have a completely isolated DNS service which can be initialized, updated, destroyed and reset without interfering with system operation.
This document describes how to configure DNS in both ways for different modes of testing. The first section describes and integrated service and the second describes an isolated one.
The initial steps assume that the host has a static IP address. Additional steps to configure a system with an IP address provided by DHCP follow.
1. Integrated Local DNS Service
This procedure will add a local named service which will accept updates using RFC compliant queries. A tool in bind-utils named nsupdate can be used to send updates for testing. The uplift-bind Ruby module uses rubygem-dnsruby to do the same thing.
For this procedure, we’re going to use a target domain of example.com. This domain is set aside by RFC 2606 for just this purpose. When it is done you will be able to query the example.com domain and get local answers. You will also be able to update the contents of the local example.com domain. Modify any instance of example.com in the procedure below if you wish to use another domain for your configuration.
Nearly all of the commands below must be run as root or using sudo(8).
All other queries will be forwarded to the normal external DNS service.
1.1. Install BIND
You should just be able to request the bind package. The bind-utils package is needed for verification and testing.
$ yum install bind bind-utils
1.2. Generate DNS Update Keys
There are several good sites that go into details about how to create DNSSEC keys. See the references section. For now I’m just going to give one example.
Note that dnssec-keygen needs a source of "entropy". If it appears to hang, log onto the host with another session and type or execute a few commands until enough entropy has been generated to complete the key generation.
$ dnssec-keygen -a HMAC-MD5 -b 512 -n USER example.com.
If this step hangs, you probably don’t have enough entropy on your system. You can solve this in one of two ways:
The first method will give you potentially weaker keys. The second
requires you to type a lot or generate a lot of network traffic until
the entropy pool (in |
The key generation process (dnssec-keygen
) will produce two files who’s names look something like this:
Kexample.com.+157+30572.key Kexample.com.+157+30572.private
The key string is in both files but you can extract it most easily like this:
$ grep Key: Kexample.com.*.private | cut -d' ' -f 2
(assuming you only have one private key file in your current working directory)
Copy the files to /var/named for safe keeping. We’ll need the key
string later for configuring /etc/named.conf
1.3. Create Initial Test Zone Files
DNS updates have to be sent to a specific zone. You need to have the
example.com zone configured into your local named as a dynamic zone.
There is a sample initial example.com.db
file included in the
examples directory.
One thing to note is that the example has the default TTL value set to 1 second. This is to avoid testing errors caused by caching. If the TTL is larger then changes will not be reflected until the record times out. As it is, delete test queries should wait 2 seconds after the delete operation completes to be sure to get a correct answer. You would never use this value in production.
1.4. Configure Named
The master configuration file for ISC BIND named is /etc/named.conf
.
There are several significant settings in that file which bear pointing out.
1.4.1. Forwarding
The local server must be set to forward only. While recursion is enabled, it will not be used. Requests for zones which are not locally authoritative (basically everything but example.com) will be forwarded to the upstream DNS server.
The forwarders clause in the configuration will be included from a
file named /var/named/forwarders.conf
. This will allow you to update
the forwarders without editing the named.conf itself. This will
become important if you’re getting your primary IP address from DHCP.
The forwarders section consists of the forwarders keyword and a block
of semi-colon terminated IP addresses. These addresses should be the
addresses you would normally have in your /etc/resolv.conf
nameserver
list.
forwarders { <ip address 1> [ ; <ip address N ]... ; } ;
For now you can create /var/named/forwarders.conf
by hand.
1.4.2. Update Keys
Each dynamic zone requires an associated update key. The nameserver and client each have a copy of the same key. The keys have an id and a value. The id is the string provided at the end of the dnssec-keygen(8) command above and the value is the string we extracted from the K*.private file.
The sample named.conf file includes the key configuration from a file
called example.com.key
.
include "example.com.key";
The key file contains the key definition section which looks like this:
key example.com { algorithm HMAC-MD5 ; secret "<key string>" ; } ;
Substitute the key string from the private key file generated earlier
and place the key file in /var/named/example.com.key
.
1.4.3. Test Zones
The test zones are set in the /etc/named.conf
file with a zone
section. The zone file itself was described above. Here we specify the
type of zone, the zone file location and the fact that it can be
updated using the key included in the previous section.
The sample named.conf
has a zone section for the example.com zone:
zone "example.com" IN { type master; file "dynamic/example.com.db"; allow-update { key example.com ; } ; };
1.5. Start named
Service
First, you want to test that the configuration files are valid and free of typos. You can start a named manually and observe the startup using the -g option:
$ /usr/sbin/named -g
If there are any errors, check the log output and the contents of /var/log/messages
for syntax and configuration errors.
When you’re satisfied that the configuration is correct, interrupt the
named with CTRL-C
and start it as a proper service:
$ service named start
1.6. Test Local Queries
Once the named is running you can check that it is responding to
queries. The tools for that are in the bind-utils RPM. Install that
if you haven’t yet. The two tools for testing ordinary queries are
dig(1)
and host(1)
. Dig gives more detailed output and does not use
the domain or search lines from /etc/resolv.conf
. You have to provide
fully qualified domain names for queries. Host gives more compact
simple output and does use resolv.conf
to complete partial names.
Because the resolv.conf does not yet have 127.0.0.1 as the first nameserver you have to specify the nameserver on the query command line.
$ dig @127.0.0.1 example.com soa
or
$ host -t soa example.com 127.0.0.1
Those will show the Start Of Authority records for the example.com zone. They should reflect the values in your local zone. Compare them to the values you get from the same query on a host using normal DNS.
1.7. Test Remote Queries
With forwarders configured, you should also be able to get responses for zones outside your test zone.
$ dig @127.0.0.1 icann.org a
or
$ host -t a icann.org 127.0.0.1
These should complete promptly and show the normal IP address values.
1.8. Test Updates
Update testing uses another tool from bind-utils named nsupdate(1). nsupdate takes its input from standard input. It also requires the key for authentication. You can test adding a record like this: (note the extra spaces in the indentation of the example)
nsupdate -k /var/named/example.com.key <<EOF server 127.0.0.1 update add testaddr.example.com 1 A 192.168.254.254 send quit EOF
Following successful completion of that command you should be able to query with dig(1) or host(1) and verify that the new record is there.
$ dig @127.0.0.1 testhost.example.com a
1.9. Enable named
Service
When you’re satisfied that the service is running and responding correctly you can enable the system service so that it restarts on reboot:
$ chkconfig named on
1.10. Set Resolver Order
The final step to integrating the local named is to make it the first nameserver in your resolver list. Once this is done all queries will go to the local named first by default.
Add the following line to your /etc/resolv.conf
file before any
other nameserver lines:
nameserver 127.0.0.1
2. DHCP Updates
If your network interface is set statically, you can stop here. Otherwise, read on. |
For a self-contained service which would be typical for testing, or
for a virtual host environment like EC2 it is possible that the DNS
host will get its IP address and DNS information from DHCP. In that
case each time the host renews its DHCP lease it will overwrite the
/etc/resolv.conf
file. It may also change its upstream nameserver
list. If that happens, the forwarders list for the named must also
change.
dhclient is the daemon that maintains DHCP controlled interfaces. It has hooks which can be used to run scripts triggered on lease renewals. However in RHEL and Fedora distributions the current default manager for interfaces is NetworkManager. NetworkManager is designed mostly for mobile device users and does not seem to provide access to the kinds of control hooks that dhclient does.
To provide the control needed we’re doing to disable NetworkManager and let interface control fall back to the more primitive network service and the dhclient daemon.
Note that the forwarders update below will fail if you have SELinux enabled. If you’re running with SELinux disabled skip down past the SELinux instructions.
If you consider it safe you can temporarily suspend SELinux and re-enable it later.
$ setenforce 0
2.1. SELinux
If you don’t have SELinux enabled, you can skip to the next section. But you should really, really be using SELinux! |
If you are running your host with SELinux enabled then the dhclient
service will not have permission to write any file which the named
service has permission to read. You will need to extend the SELinux
policy to allow the dhclient-up-hooks script to write the
/var/named/forwarders.conf
file and make it readable.
To compile and load the new policy you will need the selinux-policy and policycoreutils RPMs installed. If you have SELinux enabled you will certainly already have the selinux-policy package. You may still need to install policycoreutils.
$ yum install selinux-policy policycoreutils
The examples directory contains two files which define a policy update that does just that:
dhcpnamedforward.te dhcpnamedforward.fc
The first is a set of new policy rules. The second defines the
default label for the /var/named/forwarders.conf
file so that the
rules will apply.
2.1.1. Compiling the Policy Module
Copy the policy files to /usr/share/selinux/packages
.
Compile the policy module:
$ cd /usr/share/selinux/packages $ make -f /usr/share/selinux/devel/Makefile
This will generate two additional files.
dhcpnamedforward.if (1) dhcpnamedforward.pp (2)
1 | The .if file is an empty "interface" template and can be ignored. |
2 | The .pp file is the compiled policy. This is what gets loaded. |
2.1.2. Installing the Policy Module
To load the policy module use semodule(8)
$ semodule -i /usr/share/selinux/packages/dhcpnamedforward.pp
This could take a couple of minutes. When it completes, check that the module is installed:
$ semodule -l | grep dhcpnamedforward
At this point SELinux should allow the dhclient-up-hooks script to
write /var/named/forwarders.conf
and the named service to read it.
2.2. Disable NetworkManager Service
Attempt to disable NetworkManager service:
$ chkconfig NetworkManager off
If you get any errors it’s likely that you don’t have NetworkManager installed and life is good.
Then enable the generic "network" service
$ chkconfig network on
And change any interfaces that think they’re controlled by NetworkManager and change them over:
$ grep -l NM_CONTROLLED /etc/sysconfig/network-scripts/ifcfg-* | xargs perl -p -i -e '/NM_CONTROLLED/ && s/yes/no/i'
2.3. dhclient
Config (Resolver Prefix)
As noted earlier, dhclient
will rewrite the /etc/resolv.conf
file each
time it renews the DHCP lease. You can configure it to put a value
before the other nameserver lines. Create a file named
/etc/dhcp/dhclient.conf
and put this in it:
# prepend localhost for DNS lookup in dev and test prepend domain-name-servers 127.0.0.1 ;
2.4. dhclient
Hooks (Update Forwarders)
dhclient
also has the capability to run a script when an interface
comes up. If you place a Bourne shell script at
/etc/dhcp/dhclient-up-hooks
and make sure it’s readable and
executable then it will be sourced when any interface renews its
lease.
The dhclient-up-hooks script in the directory which contains this
README will create a file named /var/named/forwarders.conf
on lease
renew.
2.5. named.conf
: Include Forwarders
If you followed the instructions initially your /etc/named.conf file already includes the /var/named/forwarders.conf to set the forwarders list. If not, do it now.
2.6. Renewing the Interface
Now if you force the external interface to renew (do this while logged in via serial console!) you should be able to watch the forwarders be updated and the named reloaded to get the update
$ service network restart
If you get an error or you don’t see the timestamp change on
/var/named/forwarders.conf
then check the execute bit on
/etc/dhcp/dhclient-up-hooks
.
3. Isolated Local DNS Service
This section describes how to create a small self-contained DNS service suitable for testing dynamic DNS operations.
The idea is to run a local DNS server as a non-root user on a non-standard port for testing purposes. This configuration will not forward requests and will not interact with regular system DNS lookups.
3.2. Create a Workspace
Create a space to run the local service
$ mkdir ~/ddns
Create a space for temporary files and logs
# mkdir ~/ddns/tmp
3.3. Populate the Workspace
Copy the stock/default named configuration files
$ cd ~/ddns $ sudo cp /etc/named.* . $ sudo cp /var/named/named.{ca,empty,localhost,loopback} . $ sudo chown `id -u`:`id -g` *
Comment out IPv6 root servers (unless you have IPv6 configured):
$ sed -i -e '/AAAA/ && s/^/;;/' named.*
3.4. Enable Secure Updates
Generate update keys: may need enough randomness. Log in and type stuff:
$ dnssec-keygen -a HMAC-MD5 -b 512 -n USER example.com
Extract the key value:
$ perl -n -e '/Key: / && s/Key: // && print' Kexample.com.*.private
example.com.key:
key example.com { algorithm HMAC-MD5; secret "H6NDDnTbNpcBrUM5c4BJtohyK2uuZ5Oi6jxg3ME+RJsNl5Wl2B87oL12 YxWUR3Gp7FdZQojTKBSfs5ZjghYxGw=="; };
3.5. Create the Test Configuration File
This file is a limited configuration. It runs on a non-standard high-numbered port. It runs from a single directory and stores run-time files in a temporary directory so they can be cleaned up and repopulated easily
// named.conf options { listen-on port 10053 { 127.0.0.1; }; directory "."; allow-query { localhost; }; recursion no; pid-file "tmp/named.pid"; session-keyfile "tmp/named.session.key"; managed-keys-directory "tmp"; }; // disable remote controls controls {}; logging { channel default_debug { //file "data/named.run"; file "tmp/named.log"; severity dynamic; }; }; include "named.rfc1912.zones"; // // Local customization // include "example.com.key"; zone "example.com" IN { type master; file "tmp/example.com.db"; allow-update { key example.com ; }; };
3.6. Zone Files
Create the template zone files in the main directory. You will copy them to the tmp directory for test runs. Changes to the running service will cause changes to the zone files.
; initial data for testing DDNS using BIND $ORIGIN . $TTL 1 ; 1 seconds (for testing only) example.com IN SOA ns1.example.com. hostmaster.example.com. ( 2011112904 ; serial 60 ; refresh (1 minute) 15 ; retry (15 seconds) 1800 ; expire (30 minutes) 10 ; minimum (10 seconds) ) NS ns1.example.com. MX 10 mail.example.com. $ORIGIN example.com. mail A 127.0.0.1 master A 192.168.1.1 ns1 A 127.0.0.1 node A 192.168.1.10 ; test records testns1 TXT "reserved namespace testns1" ;testns2 TXT "to be added by tests" testns3 TXT "reserved to add apps" testns4 TXT "reserved to delete apps" testapp4-testns4 CNAME node.example.com.
3.7. Testing
Go to the ddns working directory:
$ cd ~/ddns
Clear the tmp directory
$ rm -f tmp/*
Copy the initial zone files
$ for FILE in *.init ; do cp $FILE tmp/`basename $FILE .init` ; done
Start the named
; log to stdout, no fork:
$ /usr/sbin/named -c named.conf -g
Add an A record and try to retrieve it:
nsupdate -y HMAC-MD5:example.com:`perl -n -e '/secret "([^"]+)"/ && print $1;' example.com.key` <<EOF server localhost 10053 update add foo.example.com 1 A 192.168.1.2 send EOF
Check the logs for the entry record:
$ grep foo tmp/named.log
Check that the named returns the new record
$ dig -p 10053 @localhost foo.example.com
Stop the named
instance:
$ kill `cat tmp/named.pid`
4. References
-
RFC 2136: Dynamic Updates in the Domain Name System (DNS UPDATE)
-
RFC 2606: Reserved Top Level DNS Names
-
RFC 3007: Secure Domain Name System (DNS) Dynamic Update
-
Article: Painless Dynamic DNS Copyright © 2008 Jeff Garzik
-
Article: Painless DDNS part 2: the server Copyright © 2008 Jeff Garzik
-
A step-by-step guide to building a new SELinux policy module Dan Walsh, Copyright © 2012 Red Hat, Inc.