Managing DNS (from Red Hat Linux 7.2 Unleashed)

By Abhijit Menon-Sen <>

This chapter was commissioned by SAMS Publishing for "Red Hat Linux 7.2 Unleashed" in 2001. They graciously allowed me to reproduce the text here (with minor edits).

Introduction

It is often convenient to refer to networked computers by name rather than by IP address, and various translation mechanisms have been devised to make this possible. The DNS (Domain Name System) is one such method, now used almost universally on the Internet; this chapter introduces DNS concepts and practice using BIND (Berkeley Internet Name Domain), the de facto standard DNS software for UNIX.

Hostnames are merely a convenience for users. Communication with other computers still requires knowledge of their IP addresses, and to allow hosts to be referred to by name, it must be possible to translate a name into an equivalent IP address. This process is called name resolution, and is usually performed by software known as a resolver. Since it is a very common operation, whatever translation method we use must be very fast and reliable.

Hostname to address mappings were once maintained by the SRI (Stanford Research Institute) in the hosts.txt file, each line of which contained the name and address of a host. Anyone could obtain a copy of this file via FTP and let their resolver use it locally. This scheme worked well when there were only a few machines, but it quickly grew impractical as more and more people began connecting to the Internet.

A lot of bandwidth was wasted in keeping the ever-growing hosts.txt file synchronised between the increasing number of hosts. Name resolution was progressively slowed down, since the resolver took longer to search the list of hosts each time. Changes to the database took forever to make and propagate, since the SRI was inundated by requests for additions and changes.

DNS is designed to address these problems, and to provide a consistent, portable namespace for network resources. Its database is maintained in a distributed fashion, to accommodate its size and the need for frequent updates. Performance and bandwidth utilisation are improved by the extensive use of local caches. Authority over portions of the database is delegated to people who are able and willing to maintain them in a timely manner, so that updates are no longer constrained by the schedules of a central authority.

DNS is a simple but delicate system which is vital to today's Internet. Errors may manifest themselves in far from obvious ways, long after the changes which caused them were made, often leading to unacceptable and embarrassing service disruptions. An understanding of the concepts and processes involved will help to make sure that your experiences as a DNS admin are pleasant ones.

DNS concepts

We begin with a look at the ideas behind DNS, independent of the details of the software used to implement it. An understanding at this level is invaluable in avoiding the majority of problems, and in diagnosing and quickly solving the ones which do occur. In the following overview, I avoid several small details in the protocol, because they are not very relevant to the everyday tasks of a DNS administrator. If you need more information, consult the DNS standards, especially RFC 1034. (The RFCs related to DNS are distributed with BIND. Red Hat 7.2 installs them in /usr/share/doc/bind-9.1.3/rfc/.)

The domain namespace is structured as a tree. Each domain is a node in the tree, and has a name. For every node, there are "Resource Records" (RRs), each of which stores a single fact about the domain (Who owns it? What is its IP address?). Domains may have any number of children, or subdomains. The root of the tree is a domain named "." (like the "/" root directory in a filesystem).

The resource records belonging to a domain each store a different type of information. For example, "A" (Address) records store the IP address associated with a name. NS (Name Server) records name an authoritative name server for a domain. Some other common RRs are MX (Mail Exchanger), SOA (Start of Authority), and PTR (Pointer). They are discussed later.

Every node has a unique name which specifies its position in the tree, just as every file has a unique path from the root directory to itself. That is, one starts with the root domain ".", and prepends to it each name in the path, using a dot to separate the names. The root domain has children named "com.", "org.", "net.", "de.", and so on. They, in turn, have children named "ibm.com", "wiw.org.", and "gmx.de". In general, a fully-qualified domain name (FQDN) like "foo.example.com." is like the path "/com/example/foo". (Notice how the trailing dot in an FQDN is often omitted.)

Information about the structure of the tree, and the associated resource records, is stored by programs called name servers. Every domain, has an authoritative name server which holds a complete local copy of the data for the domain (and its administrators are responsible for maintaining the data). A name server may also cache information about parts of the tree for which they have no authority. For administrative convenience, name servers may delegate authority over certain subdomains to other, independently maintained, name servers.

The authoritative name server for a zone knows about the name servers to which authority over subdomains has been delegated. It may refer queries about the delegated zones to those name servers. So, we can always find authoritative data for a domain by following the chain of delegations of authority from "." (the root domain) until we reach an authoritative name server for the domain. This is what gives DNS its distributed tree structure.

Users of DNS need not be aware of these details. To them, the namespace is just a single tree, any part of which they can request information about. The task of finding the requested RRs from the resource set for a domain is left to programs called resolvers. Resolvers are aware of the distributed structure of the database. They know how to contact the root name servers (which are authoritative for the root domain), and how to follow the chain of delegations until they find an authoritative name server which can give them the information they are looking for.

At the risk of stretching the analogy too far, one can think of domains as directories in a filesystem, and resource records as files in these directories. The delegation of authority over subdomains is like having an NFS filesystem mounted under a subdirectory: requests for files under that directory would go to the NFS server, rather than this filesystem. The resolver's job is to start from the root directory and walk down the directory tree (following mount points) until they reach the directory which contains the files they are interested in. (For efficiency, they can then cache the information they find for some time.) This process is examined in detail below.

In practice, there are several authoritative name servers for a domain. One of them is the master (or primary) name server, where the domain's data is held. The others are known as slave (or secondary) name servers, and they hold automatically updated copies of the master data. Both the master and the slaves serve the same information, so it doesn't matter which one a resolver asks. The distinction between master and slave is made purely for reasons of reliability, to ensure that the failure of a single name server does not result in the loss of authoritative data for the domain. As a bonus, this redundancy also distributes the network load between several hosts, so that no one name server is overwhelmed with requests for authoritative information.

(As a DNS administrator, it is your responsibility to ensure that your name servers provide sufficient redundancy for your zones. Your slaves should be far away from the master, so that power failures, network outages, and other catastrophes do not affect your name service.)

Despite these precautions, the load on DNS servers would be crushing, without the extensive use of local caches. As mentioned before, name servers are allowed to cache the results of queries and intermediate referrals for some time, so that they can serve repeated requests for data without referring to the source each time. If they didn't do this, root name servers, and the name servers for other popular zones, would be contacted by clients all over the world for every name lookup, wasting a huge amount of resources.

Name resolution in practice

Let us see what happens behind the scenes when a Web browser issues a request for the IP address of www.ibm.com. Usually, the request is sent to a local name server, which resolves the name, stores the result in its cache, and returns the IP address. We will mimic the actions of our resolver by using the incredibly useful dig utility to follow the chain of delegations between zones until we find the A record we are looking for. (Since most name servers would follow the delegations for us, we use the "+norec" dig parameter to turn off recursion. That is, if the name server does not know how to answer our query, it will not issue further queries on its own.)

We randomly select one of the thirteen root name servers (ranging from a.root-servers.net to m.root-servers.net; we picked e), and ask what it knows about an A record for www.ibm.com:

$ dig @e.root-servers.net www.ibm.com A +norec

; <<>> DiG 9.1.3 <<>> @e.root-servers.net www.ibm.com A +norec
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52356
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 13

;; QUESTION SECTION:
;www.ibm.com.                   IN      A

;; AUTHORITY SECTION:
com.                    172800  IN      NS      A.GTLD-SERVERS.NET.
com.                    172800  IN      NS      G.GTLD-SERVERS.NET.
com.                    172800  IN      NS      H.GTLD-SERVERS.NET.
com.                    172800  IN      NS      C.GTLD-SERVERS.NET.
com.                    172800  IN      NS      I.GTLD-SERVERS.NET.
com.                    172800  IN      NS      B.GTLD-SERVERS.NET.
com.                    172800  IN      NS      D.GTLD-SERVERS.NET.
com.                    172800  IN      NS      L.GTLD-SERVERS.NET.
com.                    172800  IN      NS      F.GTLD-SERVERS.NET.
com.                    172800  IN      NS      J.GTLD-SERVERS.NET.
com.                    172800  IN      NS      K.GTLD-SERVERS.NET.
com.                    172800  IN      NS      E.GTLD-SERVERS.NET.
com.                    172800  IN      NS      M.GTLD-SERVERS.NET.

;; ADDITIONAL SECTION:
A.GTLD-SERVERS.NET.     172800  IN      A       192.5.6.30
G.GTLD-SERVERS.NET.     172800  IN      A       192.42.93.30
H.GTLD-SERVERS.NET.     172800  IN      A       192.54.112.30
C.GTLD-SERVERS.NET.     172800  IN      A       192.26.92.30
I.GTLD-SERVERS.NET.     172800  IN      A       192.36.144.133
B.GTLD-SERVERS.NET.     172800  IN      A       192.33.14.30
D.GTLD-SERVERS.NET.     172800  IN      A       192.31.80.30
L.GTLD-SERVERS.NET.     172800  IN      A       192.41.162.30
F.GTLD-SERVERS.NET.     172800  IN      A       192.35.51.30
J.GTLD-SERVERS.NET.     172800  IN      A       210.132.100.101
K.GTLD-SERVERS.NET.     172800  IN      A       213.177.194.5
E.GTLD-SERVERS.NET.     172800  IN      A       192.12.94.30
M.GTLD-SERVERS.NET.     172800  IN      A       202.153.114.101

;; Query time: 819 msec
;; SERVER: 192.203.230.10#53(e.root-servers.net)
;; WHEN: Wed Sep 26 10:05:08 2001
;; MSG SIZE  rcvd: 461

The "QUERY: 1, ANSWER: 0" in the response means that e.root-servers.net did not know the answer to our question. It does know the authoritative name servers for the "com" TLD, and it refers our query to them in the "AUTHORITY" section. (Not too long ago, all the root-servers.net name servers were themselves authoritative for the com TLD, but additional delegations were recently introduced.)

The resolver's next step would be to select one of these listed servers at random, to use the IP addresses mentioned in the "ADDITIONAL" section of the response to connect to the server, and repeat the question. This is what we do now, having chosen i.gtld-servers.net:

; <<>> DiG 9.1.3 <<>> @i.gtld-servers.net www.ibm.com A +norec
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61562
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 5

;; QUESTION SECTION:
;www.ibm.com.                   IN      A

;; AUTHORITY SECTION:
ibm.com.                172800  IN      NS      INTERNET-SERVER.ZURICH.ibm.com.
ibm.com.                172800  IN      NS      NS.WATSON.ibm.com.
ibm.com.                172800  IN      NS      NS.ERS.ibm.com.
ibm.com.                172800  IN      NS      NS.ALMADEN.ibm.com.
ibm.com.                172800  IN      NS      NS.AUSTIN.ibm.com.

;; ADDITIONAL SECTION:
INTERNET-SERVER.ZURICH.ibm.com. 172800 IN A     195.212.119.252
NS.WATSON.ibm.com.      172800  IN      A       198.81.209.2
NS.ERS.ibm.com.         172800  IN      A       204.146.173.35
NS.ALMADEN.ibm.com.     172800  IN      A       198.4.83.35
NS.AUSTIN.ibm.com.      172800  IN      A       192.35.232.34

;; Query time: 8337 msec
;; SERVER: 192.36.144.133#53(i.gtld-servers.net)
;; WHEN: Wed Sep 26 10:06:46 2001
;; MSG SIZE  rcvd: 240

Still 0 ANSWERs, but we are clearly getting closer. The response lists the names and IP addresses of five authoritative name servers for the "ibm.com" domain. (Notice the abnormally large query time. This tells us that our choice of i.gtld-servers.net was, for some reason, a poor one. Intelligent resolvers remember this fact, and would pick a different server in future. BIND only does this for the root servers, though.)

We choose NS.WATSON.ibm.com, and repeat our question:

$ dig @NS.WATSON.ibm.com www.ibm.com A +norec

; <<>> DiG 9.1.3 <<>> @NS.WATSON.ibm.com www.ibm.com A +norec
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32287
;; flags: qr aa ra; QUERY: 1, ANSWER: 4, AUTHORITY: 5, ADDITIONAL: 5

;; QUESTION SECTION:
;www.ibm.com.                   IN      A

;; ANSWER SECTION:
www.ibm.com.            1800    IN      A       129.42.18.99
www.ibm.com.            1800    IN      A       129.42.19.99
www.ibm.com.            1800    IN      A       129.42.16.99
www.ibm.com.            1800    IN      A       129.42.17.99

;; AUTHORITY SECTION:
ibm.com.                600     IN      NS      ns.watson.ibm.com.
ibm.com.                600     IN      NS      ns.austin.ibm.com.
ibm.com.                600     IN      NS      ns.almaden.ibm.com.
ibm.com.                600     IN      NS      ns.ers.ibm.com.
ibm.com.                600     IN      NS      internet-server.zurich.ibm.com.

;; ADDITIONAL SECTION:
ns.watson.ibm.com.      600     IN      A       198.81.209.2
ns.austin.ibm.com.      86400   IN      A       192.35.232.34
ns.almaden.ibm.com.     86400   IN      A       198.4.83.35
ns.ers.ibm.com.         259200  IN      A       204.146.173.35
internet-server.zurich.ibm.com. 1800 IN A       195.212.119.252

;; Query time: 441 msec
;; SERVER: 198.81.209.2#53(NS.WATSON.ibm.com)
;; WHEN: Wed Sep 26 10:08:21 2001
;; MSG SIZE  rcvd: 304

NS.WATSON.ibm.com knew the answer to our question, and for the first time, the response contains an "ANSWER" section which lists four A records for www.ibm.com. Most resolvers pick one at random and return it to the program which initiated the name resolution. This concludes our search.

Reverse resolution

Given an IP address, it is often necessary to find the name associated with it (while writing web server logs, for example). This process is known as reverse resolution, and is accomplished with the help of an elegant subterfuge. The problem is that IP addresses (like filenames) are "backwards" from the DNS point of view. Since we can only associate RRs with DNS names, we must find a way to write an IP address, with its left-to-right hierarchy (129.42.18.99 belongs to 129.*), as a DNS name, with a right-to-left hierarchy (ibm.com belongs to com).

We do this by reversing the order of the octets in the address, and then appending ".in-addr.arpa" (a domain used exclusively to support reverse lookups) to the result. For example, 129.42.18.99 would be written as 99.18.32.129.in-addr.arpa. PTR (Pointer) records associated with this special name would then tell us the real name of the host to which the IP belongs.

We can look for PTR records in the usual fashion, by following a chain of delegations from a root server. We examine this process briefly by resolving 203.200.109.66 (which is one of the dialup IP addresses which my ISP assigns to its customers). We ask a root name server about 66.109.200.203.in-addr.arpa:

$ dig @a.root-servers.net 66.109.200.203.in-addr.arpa PTR +norec

; <<>> DiG 8.2 <<>> @a.root-servers.net 66.109.200.203.in-addr.arpa PTR +norec
; (1 server found)
;; res options: init defnam dnsrch
;; got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17284
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 3
;; QUERY SECTION:
;;      66.109.200.203.in-addr.arpa, type = PTR, class = IN

;; AUTHORITY SECTION:
203.in-addr.arpa.       1D IN NS        SVC00.APNIC.NET.
203.in-addr.arpa.       1D IN NS        NS.APNIC.NET.
203.in-addr.arpa.       1D IN NS        NS.TELSTRA.NET.
203.in-addr.arpa.       1D IN NS        NS.RIPE.NET.

;; ADDITIONAL SECTION:
SVC00.APNIC.NET.        2D IN A         202.12.28.131
NS.APNIC.NET.           2D IN A         203.37.255.97
NS.RIPE.NET.            2D IN A         193.0.0.193

;; Total query time: 432 msec
;; FROM: lustre to SERVER: a.root-servers.net  198.41.0.4
;; WHEN: Sun Sep 23 02:10:19 2001
;; MSG SIZE  sent: 45  rcvd: 186

Notice that this output is from an old version of dig (one shipped with BIND 8) which I happened to have installed. There are minor differences in output format, notably that the "QUESTION" section is missing, and that time intervals are specified in days and hours rather than seconds.

Continuing with NS.TELSTRA.NET, we have:

$ dig @NS.TELSTRA.NET 66.109.200.203.in-addr.arpa PTR +norec

; <<>> DiG 8.2 <<>> @NS.TELSTRA.NET 66.109.200.203.in-addr.arpa PTR
+norec
; (1 server found)
;; res options: init defnam dnsrch
;; got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11519
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2
;; QUERY SECTION:
;;      66.109.200.203.in-addr.arpa, type = PTR, class = IN

;; AUTHORITY SECTION:
200.203.in-addr.arpa.   4D IN NS        ns3.vsnl.com.
200.203.in-addr.arpa.   4D IN NS        dns.vsnl.net.in.

;; ADDITIONAL SECTION:
ns3.vsnl.com.           19h48m53s IN A  203.197.12.42
dns.vsnl.net.in.        3h30m22s IN A   202.54.1.30

;; Total query time: 723 msec
;; FROM: lustre to SERVER: NS.TELSTRA.NET  203.50.0.137
;; WHEN: Sun Sep 23 02:11:51 2001
;; MSG SIZE  sent: 45  rcvd: 132

And then:

$ dig @ns3.vsnl.com 66.109.200.203.in-addr.arpa PTR +norec

; <<>> DiG 8.2 <<>> @ns3.vsnl.com 66.109.200.203.in-addr.arpa PTR +norec
; (1 server found)
;; res options: init defnam dnsrch
;; got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 65340
;; flags: qr aa ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUERY SECTION:
;;      66.109.200.203.in-addr.arpa, type = PTR, class = IN

;; AUTHORITY SECTION:
200.203.in-addr.arpa.   1D IN SOA       dns.vsnl.net.in.
                                        helpdesk.giasbm01.vsnl.net.in. (
                                        200001053       ; serial
                                        1D              ; refresh
                                        2H              ; retry
                                        4w2d            ; expiry
                                        4D )            ; minimum


;; Total query time: 233 msec
;; FROM: lustre to SERVER: ns3.vsnl.com  203.197.12.42
;; WHEN: Sun Sep 23 02:15:07 2001
;; MSG SIZE  sent: 45  rcvd: 114

What happened here?

If you've been reading the responses carefully so far, you would have noticed that the status has always been "NOERROR", but this one has a status of "NXDOMAIN" (Nonexistent Domain), and the "flags" section has the "aa" (Authoritative Answer) flag. This means that the name we are looking for is known not to exist. Notice the difference between this and earlier responses. The response from the root name servers did not have the aa flag set, and said "We don't know about this name, ask somebody else". The authoritative answer from ns3.vsnl.com says "I know that this name does not exist, and you might as well stop looking."

The administrators at vsnl.net.in have clearly not bothered to set up PTR records for their dialup IP address pool. If they had, the response would have looked like the ones we've seen before, and included a PTR record. As it is, the response lists the SOA record for zone (including the email address of the domain contact, helpdesk@giasbm01.vsnl.net.in, should we choose to complain about broken reverse resolution).

What did the resolver learn?

We have a list of the names and addresses of the authoritative name servers for the com TLD. In future, if we are asked to resolve, say www.mcp.com, we can direct our query to gtld-servers.net from the start, instead of wasting one query on a root server. However, the NS records have an expiry time of "2D", so we must throw away the information once it is older than two days.

Similarly, we know the authoritative name servers for ibm.com, for use in queries involving ibm.com during the next two days. For instance, a web browser might ask for the IP address of commerce.ibm.com when a user clicks on a link on the IBM web page. We can save two queries by asking NS.WATSON.ibm.com again. Of course, we also have the four A records for www.ibm.com in our cache, and we can return one of them if the browser asks us to resolve the name again.

All of this information (including that gleaned from the reverse resolution process) can be cached until expiry and used to speed up further queries.

We have also learned some things which a resolver cannot remember or make use of. We can guess from the names, for instance, that the DNS administrators at IBM have, as recommended, delegated their DNS service to servers which are distant both geographically and network-wise. We can see that IBM runs four web servers on the same network, perhaps to gracefully handle the load. We know that the DNS administrators at VSNL (a large ISP) aren't as conscientious as they could be, since they have only two name servers for their entire domain, and don't have correct reverse mappings.

DNS is endlessly fascinating, and a few hours of playing with dig is well rewarded, both in terms of learning interesting things, and because familiarity with dig queries and responses is very useful in debugging problems with your own DNS setup.

BIND

BIND is the de facto standard DNS software suite for UNIX. It contains a nameserver daemon (named) which answers DNS queries, a resolver library which allows programs to make such queries, and some utility programs. It is maintained by the ISC (Internet Software Consortium), whose web site for it is at http://www.isc.org/bind/.

There are three major versions of BIND in common use today: 4, 8, and 9. The use of BIND 4 is now strongly discouraged (due to numerous security vulnerabilities and other bugs), and will not be discussed here. BIND 8, with many new features and bugfixes, is now quite widely deployed. It is actively maintained, but still vulnerable to a variety of attacks.

This chapter discusses the use of BIND 9, which is now shipped with Red Hat Linux. BIND 9 was rewritten from scratch, in an attempt to make the code more robust and leave behind the problems inherent in the old code. It is compliant with new DNS standards, and is claimed to represent a substantial improvement in features, performance, and security.

BIND has often been criticised for its recurrent security problems, but few alternatives exist. djbdns is the only one which seems ready for production at the moment. Its use is not discussed here.

At the time of writing, BIND 9.1.3 is the latest version. Installing it is just a matter of installing the bind and bind-utils RPMs shipped with the Red Hat 7.2 distribution. The former contains named and a wealth of BIND documentation, while the latter contains, among other things, the invaluable dig(1) utility. Of course, you may choose to compile BIND yourself, in which case you can download the source distribution from the ISC's web site and follow the build instructions therein.

Once you install the RPMs, the following directories are of special interest. (If you installed from source, the files will be in the locations you specified at configure time, with the default being directories under /usr/local/.)

/etc/                           Configuration files.
/usr/bin/                       dig, host, nslookup, nsupdate.
/usr/sbin/                      named, rndc, and various support programs.
/usr/share/doc/bind-9.1.3/      BIND documentation.
/usr/share/man/                 Manual pages.
/var/named/*                    Zone files.

Basic configuration

We develop a minimal name server configuration below, and then expand it as necessary to provide useful DNS service. The components which must be configured are named (the nameserver daemon) and rndc (a control utility which permits various interactions with a running named); often, it is also necessary to configure the resolver software, as discussed later.

rndc.conf

rndc uses a TCP connection (on port 953) to communicate with named; for authentication, it uses cryptographic keys to digitally sign commands before sending them over the network to named. The configuration file, /etc/rndc.conf by default, must specify a server to talk to, and the corresponding key (which must be recognised by named) to use while talking to it.

The only authentication mechanism currently supported is the use of a secret, encrypted with the HMAC-MD5 algorithm, and shared between rndc and named. The easiest way to generate a key is to use the dnssec-keygen utility. Here, we are asking it to generate a 128-bit HMAC-MD5 user key named rndc:

$ dnssec-keygen -a hmac-md5 -b 128 -n user rndc
Krndc.+157+14529

$ cat Krndc.+157+14529.private
Private-key-format: v1.2
Algorithm: 157 (HMAC_MD5)
Key: mKKd2FiHMFe1JqXl/z4cfw==

Two files are created, with .key and .private extensions respectively. The "Key: " line in the .private file tells us the secret that rndc and named need to share ("mKKd2FiHMFe1JqXl/z4cfw=="). Once we have this, we can proceed to set up the very simple /etc/rndc.conf:

# Use the key named "rndc" when talking to the name server "localhost."
server localhost {
    key                 rndc;
};

# Here are the details about the key named "rndc."
key rndc {
    algorithm           HMAC-MD5;
    secret              "mKKd2FiHMFe1JqXl/z4cfw==";
};

# Defaults.
options {
    default-server      localhost;
    default-key         rndc;
};

The file needs to have three sections: a "server" section which defines a name server ("localhost") and specifies a key ("rndc") to be used while communicating with it. The corresponding "key" section contains some details about the key, as generated by dnssec-keygen. When we set up named, we need to tell it the same information about the key, so that it can authenticate requests made by rndc. The third section, "options," just sets up reasonable defaults (because the file may list multiple servers and keys). Should you need it, the rndc(8) and rndc.conf(5) manual pages contain more information.

named.conf

Our next task is to configure named itself. Its single configuration file (/etc/named.conf) has syntax very similar to rndc.conf, but only a small subset of its many available configuration directives, essential to the configuration of a functional name server, are described here. For a more exhaustive reference, consult the BIND 9 ARM (Administrator Reference Manual; it is distributed with BIND and Red Hat 7.2 installs it under /usr/share/doc/bind-9.1.3/arm/).

Only two sections are absolutely necessary: the "options" section must tell named where the zone files are kept, and named must know where to find the root zone ("."). We also set up a "controls" section to allow suitably authenticated commands from rndc to be accepted. Since clients (notably nslookup) often depend on resolving the name server's IP, we set up the 0.0.127.in-addr.arpa reverse zone as well.

We start with a configuration file like this:

options {
    # This is where zone files are kept.
    directory           "/var/named";
};

# Allow rndc running on localhost to send us commands.
controls {
    inet 127.0.0.1
        allow { 127.0.0.1; }
        keys { rndc; };
};

# The same information as specified in rndc.conf
key rndc {
    algorithm           hmac-md5;
    secret              "mKKd2FiHMFe1JqXl/z4cfw==";
};

# Information about the root zone.
zone "." {
    type                hint;
    file                "root.hints";
};

# Lots of software depends on being able to resolve 127.0.0.1
zone "0.0.127.in-addr.arpa" {
    type                master;
    file                "rev/127.0.0";
};

The "options" section is where most of the interesting directives go. For now, we will content ourselves with specifying the directory in which named should look for zone files (as named in other sections of the file). Other options will be introduced as required along the way.

Next, we instruct named to accepts commands from an authenticated rndc. We add a "key" section, identical to the one from rndc.conf, and the "controls" section saying that rndc will be connecting from localhost and using the specified key. (You can specify more than one IP address in the allow list, or use an access control list as described in the Security section).

The "." zone tells named about the root name servers, whose names and addresses are contained in the root.hints file. This information is used to decide which root name server to consult initially (the decision is frequently revised based on the server's response time). Although the hints file can be obtained via FTP, the recommended, network-friendly way to keep it synchronised is to use dig. We ask a root name server (it doesn't matter which one) for the NS records of ".", and use the dig output directly to create /var/named/root.hints:

# dig @j.root-servers.net. . ns > /var/named/root.hints
# cat /var/named/root.hints
; <<>> DiG 8.2 <<>> @j.root-servers.net . ns
; (1 server found)
;; res options: init recurs defnam dnsrch
;; got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6
;; flags: qr aa rd; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 13
;; QUERY SECTION:
;; ., type = NS, class = IN

;; ANSWER SECTION:
.                  6D IN NS        H.ROOT-SERVERS.NET.
.                  6D IN NS        C.ROOT-SERVERS.NET.
.                  6D IN NS        G.ROOT-SERVERS.NET.
.                  6D IN NS        F.ROOT-SERVERS.NET.
.                  6D IN NS        B.ROOT-SERVERS.NET.
.                  6D IN NS        J.ROOT-SERVERS.NET.
.                  6D IN NS        K.ROOT-SERVERS.NET.
.                  6D IN NS        L.ROOT-SERVERS.NET.
.                  6D IN NS        M.ROOT-SERVERS.NET.
.                  6D IN NS        I.ROOT-SERVERS.NET.
.                  6D IN NS        E.ROOT-SERVERS.NET.
.                  6D IN NS        D.ROOT-SERVERS.NET.
.                  6D IN NS        A.ROOT-SERVERS.NET.

;; ADDITIONAL SECTION:
H.ROOT-SERVERS.NET.        5w6d16h IN A    128.63.2.53
C.ROOT-SERVERS.NET.        5w6d16h IN A    192.33.4.12
G.ROOT-SERVERS.NET.        5w6d16h IN A    192.112.36.4
F.ROOT-SERVERS.NET.        5w6d16h IN A    192.5.5.241
B.ROOT-SERVERS.NET.        5w6d16h IN A    128.9.0.107
J.ROOT-SERVERS.NET.        5w6d16h IN A    198.41.0.10
K.ROOT-SERVERS.NET.        5w6d16h IN A    193.0.14.129
L.ROOT-SERVERS.NET.        5w6d16h IN A    198.32.64.12
M.ROOT-SERVERS.NET.        5w6d16h IN A    202.12.27.33
I.ROOT-SERVERS.NET.        5w6d16h IN A    192.36.148.17
E.ROOT-SERVERS.NET.        5w6d16h IN A    192.203.230.10
D.ROOT-SERVERS.NET.        5w6d16h IN A    128.8.10.90
A.ROOT-SERVERS.NET.        5w6d16h IN A    198.41.0.4

;; Total query time: 4489 msec
;; FROM: lustre to SERVER: j.root-servers.net  198.41.0.10
;; WHEN: Mon Sep 10 04:18:26 2001
;; MSG SIZE  sent: 17  rcvd: 436

The zone file

The "zone 0.0.127.in-addr.arpa" section in named.conf says that we are a master name server for that zone, and that the zone data is in the file 127.0.0. Before we examine our first real zone file in detail, let us look at the general format of a resource record specification:

    name        TTL     class   type    data

Here, "name" is the DNS name with which this record is associated. In a zone file, names ending with a "." are fully qualified, while others are relative to the name of the zone. In the zone example.com, "foo" refers to the fully-qualified name "foo.example.com.". The special name "@" is a short form for the name of the zone itself. If the name is omitted, the last specified name is used again.

The TTL (Time To Live) field is a number which specifies the time for which the record may be cached. This is explained in greater detail in the discussion of the SOA record below. If it is omitted, the default TTL for the zone is assumed. TTL values are usually in seconds, but (since BIND 8) you can append an "m" for minutes, "h" for hours, or "d" for days.

BIND supports different record classes, but for all practical purposes, the only important class is IN, for Internet. If no class is explicitly specified, a default value of IN is assumed; to save a little typing, we don't mention the class in any of the zone files we write here.

The "type" field is mandatory, and names the RR in use, such as A, NS, MX, or SOA. (We will only use a few of the existing RRs here. Consult the DNS standards for a complete list.) The "data" field (or fields) contains data specific to this type of record. The appropriate syntax will be introduced as we examine the use of each RR in turn.

Here is the zone file (/var/named/rev/127.0.0) for the 0.0.127.in-addr.arpa zone:

$TTL 2D
@       SOA     localhost. hostmaster.example.com. (
                        2001090101  ; Serial
                        24h         ; Refresh
                        2h          ; Retry
                        3600000     ; Expire (1000h)
                        1h)         ; Minimum TTL
        NS      localhost.
1       PTR     localhost.

The $TTL directive, which should begin every zone file, sets the default minimum time-to-live for the zone to 2 days. (This is discussed further in the section describing the SOA record).

The SOA record

The second line uses the special "@" name that we saw earlier. Here, it stands for "0.0.127.in-addr.arpa", to which the SOA (Start of Authority) record belongs. The rest of the fields (continued on the next few lines, until the closing parenthesis) contain SOA-specific data.

The first data field in the SOA record is the fully-qualified name of the master name server for the domain. The second field is the email address of the contact person for the zone. It is written as a DNS name by replacing the "@" sign with a "."; "foo@example.com" would be written as "foo.example.com." (Note the trailing ".") Don't use an address like "a.b@example.com", since it is written as a.b.example.com., and will later be misinterpreted as a@b.example.com. It is important to ensure that mail to this address is frequently read, for it is used to report DNS setup problems and other potentially useful information.

The next several numeric fields specify various characteristics of this zone. It is important to configure these values correctly, and hence to understand them all. As shown in the comments (note that zone file comments aren't the same syntax as named.conf comments), the fields are: serial number, refresh interval, retry time, expire period, and minimum TTL.

Serial numbers are 32-bit quantities which can hold values between 0 and 4,294,967,295 (2^32-1). Every time the zone data is changed, the serial number must be incremented. This change serves as a signal to slaves that they need to transfer the contents of the zone again. It is conventional to assign serial numbers in the format YYYYMMDDnn, i.e. the date of the change and a two-digit revision number (e.g. 2001090101). For changes made on the same day, you increment only the revision (this assumes, reasonably, that you don't make more than 99 changes to a zone in one day); for changes on the next, the date is changed and the revision number starts from 01 again.

The refresh interval specifies how often a slave server should check if the master data has been updated. It has been set to 24 hours here, but if the zone changes often, the value should be lower. (Slaves may reload the zone much sooner if they and the master both support the DNS NOTIFY mechanism. Most DNS software does.) The retry time is relevant only when a slave fails to contact the master after the refresh time has elapsed. It specifies how long it should wait before trying again (it is set to 2 hours here).

If the slave is consistently unable to contact the master for the length of the expire period (usually due to some catastrophic failure), it discards the zone data it already has and stops answering queries for the zone. Thus, the expire period should be long enough to allow for the recovery of the master name server. It has repeatedly been shown that a value of one or two weeks is too short. 1000 hours (about 6 weeks) is accepted as a good default.

The TTL (Time to Live) fields deserve more explanation. Every RR has a TTL, which specifies how long it may be cached before the origin of the data must be consulted again. If the RR definition does not specify a TTL explicitly, the default TTL (set by the $TTL directive) is used instead. This allows individual RRs to override the default TTL as required.

The SOA TTL, the last numeric field in the SOA record, is now used to determine how long negative responses (NXDOMAIN) should be cached (i.e., if a query results in an NXDOMAIN response, that fact is cached for as long as indicated by the SOA TTL). Older versions of BIND used the SOA minimum TTL to set the default TTL, but BIND 9 no longer does so. The default TTL of 2 days and SOA TTL of 1 hour is recommended for cache friendliness.

The values used above are good defaults for zones which do not change often. You may have to adjust them a bit for zones with very different requirements, in which case http://www.ripe.net/docs/ripe-203.html is recommended reading.

Other records

The next two lines in the zone file create NS and a PTR records. The NS record has no explicit name specified, so it uses the last one, which is the @ of the SOA record. Thus, the name server for 0.0.127.in-addr.arpa is defined to be localhost. The PTR record has the name "1" which, when qualified, becomes "1.0.0.127.in-addr.arpa" (which is how you write the address 127.0.0.1 as a DNS name), and the name "localhost." (We shall see some of the numerous other RR types when we later configure our name server to be authoritative for a real domain.)

Logging

We now have all the elements of a minimal functioning DNS server, but before we experiment further, some extra logging will allow us to see exactly what named is doing. Log options are configured in a "logging" section in named.conf, and the various options are described in detail in the BIND 9 ARM.

All log messages go to one or more channels, each of which can write messages to the syslog, to an ordinary file, to stderr, or to "null" (that is, discard the message). There are categories of messages, such as those generated while parsing configuration files, those caused by OS errors, and so on. Your logging statement must define some channels, and associate them with the categories of messages that you wish to see.

BIND logging is very flexible, but complicated, so we'll only examine a simple log configuration here. If you need anything significantly more complicated, refer to the ARM. The following addition to your named.conf will set up a channel called custom which writes timestamped messages to a file, and send messages in the listed categories to it.

logging {
    channel custom {
        file "/tmp/named.log";  # Where to send messages.
        print-time yes;         # Print timestamps?
        print-category yes;     # Print message category?
    };

    category config       { custom; };     # Configuration files
    category notify       { custom; };     # NOTIFY messages
    category dnssec       { custom; };     # TSIG messages
    category general      { custom; };     # Miscellaneous
    category security     { custom; };     # Security messages
    category xfer-out     { custom; };     # Zone transfers
    category lame-servers { custom; };
};

Keeping track of logs is especially important because syntax errors often cause BIND to reject a zone and not answer queries for it, causing your server to become "lame" (i.e., not authoritative for a zone for which it is supposed to be).

Resolver configuration

The last thing we need to do before running BIND is to set up the local resolver software. This involves configuring three files: /etc/hosts, /etc/resolv.conf, and /etc/nsswitch.conf.

To avoid gratuitous network traffic, most UNIX resolvers still use a hosts.txt-like file named /etc/hosts to store the names and addresses of commonly used hosts. Each line in this file contains an IP address and a list of names for the host. Add entries to this file for any hosts you wish to be able to resolve independent of DNS.

/etc/resolv.conf specifies the addresses of preferred name servers, and a list of domains relative to which unqualified names will be resolved. A name server is specified with a line of the form "nameserver 1.2.3.4" (where 1.2.3.4 is the address of the name server). You can use multiple nameserver lines (usually up to 3). You can also use a "search" line to specify a list of domains to search for unqualified names. A search line like "search example.com example.net" would cause the resolver to attempt to resolve the unqualified name "xyz", first as xyz.example.com, and, if that fails, as xyz.example.net. Don't use too many domains in the search list, because it slows down resolution.

A "hosts: files dns" line in /etc/nsswitch.conf will cause the resolver to consult /etc/hosts before using the DNS during the course of a name lookup. This allows you to override the DNS by making temporary changes to /etc/hosts, which is especially useful during network testing. (Older resolvers may require an "order hosts, bind" line in the /etc/host.conf file instead.)

Running named

Finally! You can now start named with "/etc/rc.d/init.d/named start". You should see messages similar to the ones below in the syslog (or according to the logging configuration you have set up).

Sep 26 09:47:12 ns1 named[30507]: starting BIND 9.1.3
Sep 26 09:47:12 ns1 named[30507]: using 1 CPU
Sep 26 09:47:12 ns1 named[30509]: loading configuration from '/etc/named.conf'
Sep 26 09:47:12 ns1 named[30509]: listening on IPv4 interface lo, 127.0.0.1#53
Sep 26 09:47:12 ns1 named[30509]: command channel listening on 127.0.0.1#953
Sep 26 09:47:12 ns1 named[30509]: running

You can interact with this instance of named with rndc. Running rndc without arguments displays a list of available commands, including the ability to reload or refresh zones, dump statistics and the database to disk, toggle query logging, and stop the server. (Unfortunately, rndc does not yet implement all the commands which were supported by ndc, the control program shipped with earlier versions of BIND.)

You should now be able to resolve 1.0.0.127.in-addr.arpa locally (try "dig @localhost 1.0.0.127.in-addr.arpa PTR +norec"), and other names via recursive resolution. If you cannot, there is something wrong, and you should read the Troubleshooting section below to diagnose and correct your problem before proceeding further. Remember to read the logs!

A real domain

Let us expand this minimal configuration into one which performs useful name service for a real domain. Suppose that we own (i.e., our ISP has assigned to us) the IP addresses in the 192.0.2.0/29 range (which has 6 usable addresses: 192.0.2.1-6), and that we want to serve authoritative data for the domain example.com. A friend has agreed to configure her name server (192.0.2.96), to be a slave for the domain, as well as a backup mail server. In return, she wants the foo.example.com subdomain delegated to her own name servers.

Forward zone

First, we must introduce the zone to named.conf:

zone "example.com" {
    type master;
    file "example.com";
};

And create the zone file:


$TTL 2D
@       SOA     ns1.example.com. hostmaster.example.com. (
                        2001090101  ; Serial
                        24h         ; Refresh
                        2h          ; Retry
                        3600000     ; Expire (1000h)
                        1h)         ; Minimum TTL
        NS      ns1.example.com.
        NS      ns2.example.com.
        MX 5    mx1.example.com.
        MX 10   mx2.example.com.
        A       192.0.2.1

; Addresses
ns1     A       192.0.2.1          ; Name servers
ns2     A       192.0.2.96
mx1     A       192.0.2.2          ; Mail servers
mx2     A       192.0.2.96
www     A       192.0.2.3          ; Web servers
dev     A       192.0.2.4
work    A       192.0.2.5          ; Workstations
play    A       192.0.2.6

; Delegations
foo     NS      dns1.foo.example.com.
foo     NS      dns2.foo.example.com.
dns1.foo A      192.0.2.96
dns2.foo A      192.0.2.1

The SOA record is similar to the one we saw before. (Note that the next five records all use the implicit name "@", short for "example.com".)

The two NS records define ns1.example.com (our own server, 192.0.2.1) and ns2.example.com (our friend's server, 192.0.2.96) as authoritative name servers for example.com.

The MX (Mail Exchanger) records each specify a mail server for the zone. An MX RR takes two arguments: a priority number, and the name of a host. In delivering mail addressed to example.com, the listed MXes are tried in increasing order of priority. In this case, mx1.example.com (our own machine, 192.0.2.2) has the lowest priority, and is always tried first. If the attempt to deliver mail to mx1 fails (for whatever reason), the next listed MX, mx2.example.com (our friend's server) is tried.

The A record says that the address of example.com is 192.0.2.1, and the next few lines specify addresses for other hosts in the zone: our name servers ns1 and ns2, mail servers mx1 and mx2, two web servers, and two workstations.

Next, we add NS records to delegate authority over the foo.example.com domain to dns1 and dns2.foo.example.com. The A records for dns1 and dns2 are known as glue records, and they enable resolvers to find the address of the authoritative name servers, so that they can continue the query. (If we were using dig, the NS records for dns1 and dns2 would be listed in the AUTHORITY section of the response, while the ADDITIONAL section would contain their addresses.)

Notice that dns2.foo.example.com is 192.0.2.1, our own name server. We are acting as a slave for the foo.example.com zone, and must configure named accordingly. We introduce the zone as a slave in named.conf, and specify the address of the master name server:

zone "foo.example.com" {
    type slave;
    file "foo.example.com";
    masters {
        192.0.2.96;
    };
};

Similarly, our friend must configure 192.0.2.96, which is a master for foo.example.com, and a slave for example.com. (She must also configure her server to accept mail addressed to example.com. Usually, mx2 would just queue the mail until it could be delivered to mx1.)

Reverse zone

Let us pretend that we live in a perfect world, and that our highly competent ISP has successfully delegated authority of our reverse zone to us, and we must set up named to handle reverse resolution as well. There is nothing particularly different between this and the reverse zone we set up for 0.0.127.in-addr.arpa. There is, however, one new issue we must address. What is our zone named?

DNS can only delegate authority at the "." in domain names. This means that one can set up reverse zones for the whole of a class A, B, or C network, since they are divided at octet boundaries in the IP address. This is clearly unsuitable for classless subnets like ours, since the divisions are not at octet boundaries, but in the middle of an octet. In other words, our network can't be described as x.* (class A), x.y.* (class B), or x.y.z.* (class C). The latter comes closest, but includes several addresses (like 192.0.2.22) which do not belong to our tiny 192.0.2.0/29 network. To set up a reverse zone for our network, we must resort to the use of classless delegation (described in RFC 2317).

The ISP, which is authoritative for the 2.0.192.in-addr.arpa zone, must either maintain your reverse zone for you, or add the following records into its zone file:

1          CNAME   1.1-6
2          CNAME   2.1-6
3          CNAME   3.1-6
4          CNAME   4.1-6
5          CNAME   5.1-6
6          CNAME   6.1-6

1-6        NS      192.0.2.1
1-6        NS      192.0.2.96

The first CNAME record says that 1.2.0.192.in-addr.arpa is an alias for 1.1-6.2.0.192.in-addr.arpa. (The others are similar. We don't have CNAME records for network and broadcast addresses 0 and 7, because they don't need to resolve.) Resolvers already know how to follow CNAME aliases while resolving names. When they ask about the 1-6 domain, they find the NS records defined above, and continue with their query by asking our name server about 1.1-6.2.0.192.in-addr.arpa.

So we must set up a zone file for 1-6.2.0.192.in-addr.arpa. Apart from the peculiar name, this zone file is similar in every respect to the reverse zone we set up earlier, and should contain 6 PTR records (apart from the SOA and NS records). Note that we make 192.0.2.96 (ns2) a slave for the reverse zone as well, so the administrator must add a suitable zone statement to named.conf for it.

In the real world, however, you may have to wait for months for your ISP to get the reverse delegation right, and your reverse zone will remain broken until it does.

Registering the domain

You now have a working DNS setup, but external resolvers cannot see it, because there is no chain of delegations from the root name servers to yours. You need to create this chain by "registering" the domain; that is, by paying the appropriate registration fees to an authority known as a registrar, who then delegates authority over the chosen zone to your name servers.

There is nothing magical about what a registrar does. It has authority over a certain portion of the DNS database (say, the "com." TLD), and, for a fee, it delegates authority over a subdomain ("example.com") to you. This delegation is accomplished by the same mechanisms that were explained above in our delegation of foo.example.com.

http://www.iana.org/domain-names.htm contains a list of all the TLDs and the corresponding registrars (of which there are now several). The procedure and fees for registering a domain vary wildly between them. Visit the web site of the registrar in question and follow the procedures outlined there. After wading through the required amounts of red tape, your domain should be visible to the rest of the world.

Congratulations. Your job as a DNS administrator has just begun.

Troubleshooting

There is a lot of good material about finding and fixing DNS errors. The DNSRD Tricks and Tips and the RFC 1912, entitled "Common DNS Operational and Configuration Errors," which discusses a number of these problems at length.

Delegation problems

Your zone must be delegated to the name servers authoritative for them, either by the root name servers, or the parents of the zone in question. The lack of proper delegation can lead to problems ranging from the name service for your domain being entirely dysfunctional, to some networks being unable to use it. These are usually a problem only in the initial stages of setting up a domain, when the delegations have not propagated widely yet. As discussed at length earlier, you can use dig to follow delegation chains and find the point at which problems occur. Tools like dnswalk may also be useful.

The opposite situation is also problematic. When a name server is listed as being authoritative for a zone, but is in fact not authoritative (it has not been configured to be a master for the zone), it is called a lame server, and the situation is a lame delegation. Unfortunately, lame delegations are very common on the Internet, either temporarily because of moving domains around, or (especially in the case of reverse zones), more permanent configuration errors which are never detected because of a lack of attention to detail.

If your registrar's bills for your domain aren't promptly paid, they may discontinue the delegation of authority for your zone. If this happens (and the whois record for your domain will usually mention this), the best thing to do is quickly pay the registrar and ask them to renew the delegation. It's better to not let it happen, though, since such changes can take a relatively long time to make and propagate.

Reverse lookup problems

Reverse lookup problems are often very hard to diagnose, because they manifest themselves as failures in systems other than DNS. Many security sensitive services perform reverse lookups on the originating host for all incoming connections, and deny the connection if the query fails.

Even if reverse resolution succeeds, many servers may reject connections from your host if your A and PTR records do not match (that is, the PTR record for a particular IP address refers to a name, and the A record for that name refers to a different IP address). They perform a double lookup to verify that the PTR and A records match to eliminate spoofing attacks. Maintain your reverse zones carefully at all times.

Delegation problems are a frequent source of woe. Unfortunately, many ISPs appear unable to understand, configure or delegate reverse zones. In such cases, you often have little choice but to try and tell them what to do to fix the problem, and if they refuse to listen, find a new ISP (or live with broken DNS).

Another typical symptom of failing reverse lookups is an abnormally long delay on connection attempts. This happens when the server's query for a PTR record is not answered and times out (often due to network problems, or the name server being down). This can be baffling to diagnose, but you should suspect DNS problems whenever you hear questions like "Hey! Why is telnet taking so long to connect?"

Serial numbers

Serial numbers are very important to the correct operation of slave servers. An increase in the serial number of a zone causes slaves to reload the zone and update their local cache. A very common mistake is forgetting to increment the serial number after a change to the zone data. Secondary name servers will not reload the zone, and will continue to serve old data. If you suspect that the data on the master and slave servers are out of sync, you can use dig to view the SOA record for the zone on each server ("dig @master domain SOA" and "dig @slave domain SOA") and compare the serial numbers in the responses.

Another common problem is setting the serial number to an incorrect value, either too small or too large. A too-small serial number causes slaves to think that they possess a more up-to-date copy of the zone data, but this is easily corrected by increasing the serial number as necessary. A too-large serial number is more problematic, and needs more elaborate measures to fix.

Serial number comparisons are defined in such a way that if a serial number, when subtracted from another with no overflow correction, results in a positive number, the second number is "newer" than the first, and a zone transfer is required. (See RFC 1982 "Serial Number Arithmetic" for details.) You can exploit this property by temporarily setting the serial number to 2^32 (4,294,967,296), waiting for all the slaves to reload the zone, and then setting it to the correct number.

Zone files

The commonest error in zone data is to forget that names in a zone file are relative, not to the root, but to the origin of the zone. Writing "www.example.com" in the zone file for example.com, and expecting it to be fully qualified, causes names like "www.example.com.example.com" to show up in the DNS. You should either write "www", which is qualified to the correct "www.example.com", or write "www.example.com." with the trailing period, to indicate that the name is fully qualified.

The SOA record should be treated with care. It should contain (as the first field) the domain name of the master server (not a CNAME), and a contact address (with the @ replaced by a ".") to report problems to. Mail sent to this address should be read frequently. The other fields should contain sensible values for your zone, and the serial number should be correctly incremented after each change.

As discussed earlier, A and PTR records should always match, that is, the A record pointed to by a PTR should point back to the address of the PTR record. Remember to quote the two arguments of HINFO records if they contain any whitespace. Avoid the use of CNAME records for MX, NS, and SOA records.

In general, after making changes to zone data, it is a good idea to reload named and examine the logs for any errors which cause named to complain or reject the zone. Even better, you could use one of the verification tools like dnswalk, discussed briefly below.

Tools

http://www.dns.net/dnsrd/tools.html maintains an extensive list of programs which can help you debug and prevent DNS problems. BIND itself includes the always-useful dig program, as well as named-checkconf (to check /etc/named.conf for syntax errors) and named-checkzone (to do the same for zone files).

I also specially recommend dnswalk and nslint. dnswalk is a Perl script which scans the DNS setup of a given domain for problems. It should be used in conjunction with RFC 1912, which explains most of the problems it detects. nslint, like the analogous lint utility for C programs, searches for common BIND and zone file configuration errors.

The occasional use of these programs, especially after non-trivial zone changes, helps to keep your DNS configuration healthy and trouble-free.

Security

Security considerations are of vital importance to DNS administrators, because DNS is itself not a very secure protocol, and because a number of successful attacks against BIND have been found over the years. The most important defence is to keep abreast of developments in security circles and act on them promptly. The BugTraq mailing list, hosted by Securityfocus, and the SANS Institute are good places to start.

DNS is especially vulnerable to attacks known as poisoning and spoofing. Poisoning refers to the placing of incorrect data into the DNS database, which then spreads to clients and caches across the world, potentially causing hundreds of thousands of people to unwittingly use the bad data. Although DNS poisoning can occur because of carelessness, it has serious implications when performed deliberately. What if someone set up a clone of a common web site, redirected users to it by DNS poisoning, and then asked them for their credit card numbers? Spoofing is the practice of forging network packets, and making name servers believe that they are receiving a valid answer to a query is one of the ways malicious poisoning can be performed.

BIND has often been criticised as being very insecure, and though the recent versions are greatly improved in this regard, DNS administrators today must take several precautions to ensure that its use is adequately protected from attacks. Of course, it is important to always run the latest recommended version of BIND.

UNIX security considerations

The very first thing to do is to configure the environment BIND runs in to use all the security mechanisms available to it through the operating system to its advantage. named should run with as few privileges as it needs to function. Even if an attacker manages to exploit a security hole in BIND, the effects of the break-in can be minimised if named is running as nobody rather than root. Of course, named needs to be started as root (because it needs to bind to port 53), but it can be instructed to switch to a given user and group with the -u and -g command line options.

Starting named with a command like "named -u nobody -g nogroup" is highly recommended. Remember, however, that if you run multiple services as nobody, the risks of a compromise are no longer negligible. In such a situation, it is best to create separate accounts for each service, and use them for nothing else.

You can also use the chroot feature of UNIX to isolate named into its own part of the filesystem. If correctly configured, such a jail will restrict attackers -- if they manage to break in -- to a part of the filesystem which contains little of value. It is important to remember than a chroot jail is not a panacea, and does not eliminate the need for other defensive measures. (Programs which use chroot but do not take other precautions as well have been shown to be insecure. BIND does, however, take such precautions.)

In order for a chroot environment to work properly, you need to set up a directory which contains everything which BIND needs to run. It is recommended that you start with a working configuration of BIND, and then create a directory, say /usr/local/bind, and copy over the files it needs into subdirectories under that one. For instance, you will need to copy the binaries, some system libraries, the configuration files, and so on. Consult the BIND documentation for details about exactly which files you need.

Once your chroot environment is set up, you can start named with the "-t /usr/local/bind" option (combined with the -u and -g options) to instruct it to chroot to the directory you have set up.

You may also want to keep track of resource usage. named manages a cache of DNS data which can potentially grow very large; it will also happily hog CPU and bandwidth, making your server unusable. This is something which can be exploited by clever attackers, but you can configure BIND to set resource limits. Several such options in the named.conf file are available, including "datasize", which limits the maximum size of the data segment (and thus the cache). One downside of this approach is that named might be killed by the kernel if it exceeds these limits, which means you have to run it in a loop which restarts it if it dies (or run it from /etc/inittab).

DNS security considerations

There are several configuration options for named which can make it more resistant to various potential attacks. The most common ones are briefly described below. For more detailed discussions of the syntax and use of these options, refer to the BIND 9 documentation.

ACLs (Access Control Lists)

Specifying network and IP addresses multiple times in a configuration file is tedious and error-prone. BIND allows you to define ACLs (Access Control Lists), which are named collections of network and IP addresses, to ease the task of assigning permissions.

Four predefined ACLs exist: "any", which matches anything; "none", which matches nothing; "localhost", which matches all the network interfaces local to your name server, and "localnets", which matches any network directly attached to a local interface. In addition, you can define your own lists in named.conf, containing as many network and IP addresses as you wish, using the "acl" command as shown:

acl trusted {
    192.0.2.0/29;          // Our own network is OK.
    localhost;             // And so is localhost.
    !192.0.2.33/29;        // But not this range.
};

As shown, you can use a "!" to negate members in an ACL. Once defined, you can use these ACLs in allow-query, allow-transfer, allow-recursion and similar options, as discussed below.

Queries

As mentioned before, most name servers will perform recursive resolution for any queries it receives unless it is specifically configured not to do so (which behaviour we suppressed by using the "dig +norec"). It so happens that explicitly denying recursion is a good idea because, by repeatedly fetching data from a number of unknown and untrusted name servers, it makes your installation vulnerable to DNS poisoning.

Recursive queries can be disabled by adding a "recursion no" statement to the "options" section of named.conf. It may still be desirable to allow recursive queries from some trusted hosts, however, and this can be accomplished by the use of an "allow-recursion" statement. This excerpt would would configure named to disallow recursion for all but the listed hosts:

options {
    ...

    recursion no;
    allow-recursion {
        192.0.2.0/29;
        localnets;         // Trust our local networks.
        localhost;         // And ourselves.
    };
};

You can choose to be still more restrictive and allow only selected hosts to query your name server at all, by using the allow-query statement (with syntax similar to allow-recursion, as described above). Of course, this is not desirable if your server is authoritative for a zone. In that case, you will have to explicitly "allow-query { all; }" in the configuration section of each zone for which you wish to serve authoritative data.

Allow only known slave servers to perform zone transfers from your server. Not only do zone transfers consume a lot of resources (they require a named-xfer process to be forked each time) and provide an avenue for denial of service attacks, there have been remote exploits via buffer overflows in named-xfer which allow attackers to gain root privileges on the compromised system. To prevent this, add sections like the following to all your zone definitions:

zone "example.com" {
    ...

    allow-transfer {
        192.0.2.96;        // Known slave.
        localhost;         // Often required for testing.
    };
};

Despite all this, it may be necessary to single out a few troublesome hosts for special treatment. The "server" and "blackhole" statements in named.conf can be used to tell named about known sources of poisoned information or attack attempts. For instance, if the host 203.122.154.1 is repeatedly trying to attack the server, the following addition to the "options" section of named.conf will cause our server to ignore traffic from that address. Of course, one can specify multiple addresses and networks in the blackhole list.

options {
    ...

    blackhole {
        203.122.154.1;
    };
};

For a known source of bad data, one can do something like the following to cause your name server to simply stop asking the listed server any questions. This is different from adding a host to the blackhole list. A server marked as bogus will never be sent queries, but it can still ask us questions. A blackholed host is simply ignored altogether.

server bogus.info.example.com {
    bogus yes;
};

The AUS-CERT advisory AL-1999.004, which discusses denial of service attacks against DNS servers, discusses various ways of restricting access to name servers, and is a highly recommended read. Among other things, it recommends the most restrictive configuration possible, and the permanent blackholing of some addresses known to be popular sources of spoofed requests and answers. It is a good idea to add the ACL below to the blackhole list of all your servers.

/* These are known fake source addresses. */
acl "bogon" {
    0.0.0.0/8;     # Null address
    1.0.0.0/8;     # IANA reserved, popular fakes
    2.0.0.0/8;
    192.0.2.0/24;  # Test address
    224.0.0.0/3;   # Multicast addresses

    /* RFC 1918 addresses may be fake too. Don't list these if you
       use them internally. */
    10.0.0.0/8;
    172.16.0.0/12;
    192.168.0.0/16;
};

DNSSEC

DNSSEC, a set of security extensions to the DNS protocol, provides data integrity and authentication by using cryptographic digital signatures. It provides for the storage of public keys in the DNS and their use for verifying transactions. DNSSEC is still not widely deployed, but BIND 9 does support it for inter-server transactions (zone transfers, NOTIFY, recursive queries, dynamic updates). It is worth configuring the TSIG (Transaction Signature) mechanism if your slaves also run BIND 9. I briefly discuss using TSIG for authenticated zone transfers here.

The first thing to do is to use dnssec-keygen, as we did with rndc, to generate a shared secret key. This key is stored on both the master and slave servers. As before, we extract the "Key:" data from the ".private" file. The following command creates a 512-bit host key named transfer:

$ dnssec-keygen -a hmac-md5 -b 512 -n host transfer

Next, we set up matching key statements in the named.conf for both the master and slave servers (exactly as we did for rndc and named earlier). Remember not to transfer the secret key from one machine to the other over an insecure channel. Use ssh, secure FTP, or something similar. Remember also that the shared secrets should not be stored in world readable files. The statements, identical on both machines, would look something like this:

key transfer {
    algorithm "hmac-md5";
    secret "...";          # Key from .private file
};

Finally, we set up a server statement on the master, to instruct it to use the key we just created when communicating with the slave, and enable authenticated zone transfers with the appropriate allow-transfer directives:

server 192.0.2.96 {
    key { transfer; };
};

The BIND 9 ARM contains more information on TSIG configuration and DNSSEC support in BIND.

Split DNS

BIND is often run on firewalls, both to act as a proxy for resolvers inside the network, and to serve authoritative data for some zones. In such situations, many people prefer to avoid exposing more details of their private network configuration via DNS than is unavoidable (though there is some debate about whether or not this is actually useful). The outside world should only see information they are explicitly allowed access to, while internal hosts are allowed access to other data. This kind of setup is called "split DNS."

The best way to do this used to be to run two separately configured instances of named on internal and external interfaces, and forward selected traffic from the former to the latter. BIND 9 provides an elegant and convenient solution to the problem with its support for views. Suppose you have a set of zones which you wish to expose to the outside world, and another set which you wish to allow hosts on your network to see. You can accomplish this with a configuration like the following:

acl private {
    localhost;
    192.168.0.0/24;     # Define your internal network suitably.
};

view private_zones {
    match { private; };
    recursion yes;      # Recursive resolution for internal hosts.

    zone internal.zone {
        # Zone statements;
    };

    # More internal zones.
};

view public_zones {
    match { any; }
    recursion no;

    zone external.zone {
        # Zone statements;
    };

    # More external zones.
};

On a somewhat related note, you may wish to configure internal hosts running named to forward all queries to the firewall, and never try to resolve queries themselves. The "forward only" and "forwarders" options in named.conf do this (forwarders specifies a list of IP addresses of the name servers to forward queries to).

The BIND 9 ARM discusses several details of running BIND in a secure split-DNS configuration.

References

I recommend the book "The Concise Guide to DNS and BIND", by Nicolai Langfeldt, for in-depth discussion of both theoretical and operational aspects of DNS administration.