It is important for DNS records to exhibit certain types of consistency. In simple cases, where a host has only one name and only one address, the consistency requirements are obvious ... but in the general case, where a host can have multiple names and/or multiple addresses, the consistency requirements require more thoughtful and sophisticated handling.
Question #1: Suppose we have a host with multiple network interfaces. Should we use DNS to name the interfaces, name the host, or both?Answer #1: The best policy is to give a name to the host and to each of the interfaces. A good scheme for doing this is discussed in section 2.
Question #2: What about the reverse-DNS records?
Answer #2: It is important for the PTR records to exhibit F∘R consistency, as described in section 1.3.
Multi-homing is quite common, for a number of good reasons including:
It is worth setting up the DNS intelligently, because more and more email recipients are doing a reverse lookup on the source IP address followed by a forward lookup on the resulting name, and checking for consistency. (An example is mentioned in section 4.) If the DNS and rDNS records are not consistent, the recipient is likely to not accept the message, which is going to make the sender very unhappy. For this reason, most organizations are fastidious about the DNS records for their mail servers, even if they neglect their other servers. Here are some examples:
:; ./dns-consist www.ibm.com Failed: No PTR records: www.ibm.com ==> 129.42.56.216 --> () :; ./dns-consist -mx ibm.com name OK: ibm.com ==> 32.97.182.142 --> e2.ny.us.ibm.com. ==> 32.97.182.142 name OK: ibm.com ==> 32.97.182.143 --> e3.ny.us.ibm.com. ==> 32.97.182.143 name OK: ibm.com ==> 32.97.182.144 --> e4.ny.us.ibm.com. ==> 32.97.182.144 name OK: ibm.com ==> 32.97.182.145 --> e5.ny.us.ibm.com. ==> 32.97.182.145 name OK: ibm.com ==> 32.97.182.146 --> e6.ny.us.ibm.com. ==> 32.97.182.146 name OK: ibm.com ==> 32.97.110.149 --> e31.co.us.ibm.com. ==> 32.97.110.149 name OK: ibm.com ==> 32.97.110.150 --> e32.co.us.ibm.com. ==> 32.97.110.150 name OK: ibm.com ==> 32.97.110.151 --> e33.co.us.ibm.com. ==> 32.97.110.151 name OK: ibm.com ==> 32.97.110.152 --> e34.co.us.ibm.com. ==> 32.97.110.152 name OK: ibm.com ==> 32.97.110.153 --> e35.co.us.ibm.com. ==> 32.97.110.153 name OK: ibm.com ==> 32.97.182.141 --> e1.ny.us.ibm.com. ==> 32.97.182.141 Strong F(R(F())) consistency: ibm.com MX :; ./dns-consist www.intel.com Failed: No PTR records: www.intel.com ==> 174.76.227.94 --> () Failed: No PTR records: www.intel.com ==> 174.76.227.95 --> () :; ./dns-consist -mx intel.com name OK: intel.com ==> 134.134.136.24 --> mga09.intel.com. ==> 134.134.136.24 name OK: intel.com ==> 192.55.52.93 --> mga11.intel.com. ==> 192.55.52.93 name OK: intel.com ==> 143.182.124.37 --> mga14.intel.com. ==> 143.182.124.37 name OK: intel.com ==> 192.55.52.88 --> mga01.intel.com. ==> 192.55.52.88 name OK: intel.com ==> 134.134.136.20 --> mga02.intel.com. ==> 134.134.136.20 name OK: intel.com ==> 143.182.124.21 --> mga03.intel.com. ==> 143.182.124.21 Strong F(R(F())) consistency: intel.com MX |
For more on why F∘R consistency is important, see reference 1. Also note that the need for consistency is emphasized by RFC 1912 (reference 2), which says in part:
Make sure your PTR and A records match. For every IP address, there should be a matching PTR record in the in-addr.arpa domain. If a host is multi-homed, (more than one IP address) make sure that all IP addresses have a corresponding PTR record (not just the first one).
It must be emphasized that the records must match. It doesn’t suffice to have a PTR record that returns some random name. On a multi-homed host, it does not suffice to return a name that points to the wrong interface.
There are several types of consistency check that we could make. We can clarify the situation by introducing a little bit of notation.
Note that almost everything in this document applies equally well to IPv4 and IPv6. To emphasize this point, write A+records (with a + sign) as shorthand notation meaning A-records and/or AAAA-records.
We introduce the forward lookup operator F which maps a name to zero or more addresses, each specified by a DNS A+record:
| (1) |
Similarly the reverse lookup operator R maps an address to zero or more names, each specified by a DNS PTR-record:
| (2) |
Definition: We define the composition F∘R as follows:
Terminology note: F∘R is pronounced “F of R”. It can also be written F(R()). It is also sometimes called Forward-Confirmed reverse DNS (FCrDNS) as discussed in reference 1.
Definition: We say that a given address a exhibits strong F∘R consistency if for every i there exists some j such that F(R(a)[i])[j] = a.
We can define R∘F consistency in the analogous way. That is, given a name n, we require that for every i there exists some j such that R(F(n)[i])[j] = n.
Continuing down this road: Definition: We say that a name n exhibits strong F∘R∘F consistency if every address returned by F(n) exhibits strong F∘R consistency.
Note that F∘R∘F consistency is not sufficient to guarantee R∘F consistency; the pro-forma PTR records in section 2.2 provide a counterexample.
It is important to distinguish F∘R consistency from R∘F consistency. The former is far more important.
Remark: When we are worried about F∘R consistency, adding PTR-records can only increase the chances that the check will fail. In contrast, adding A+records can only increase the chances that the check will succeed.
Here is a little script to check DNS records for consistency. Given a numeric IP address, it checks for strong F∘R consistency. Given a hostname, it checks for strong F∘R∘F consistency.
#! /bin/bash if (($# == 0)) ; then echo "Script to test DNS records for consistency." echo "If given a numeric IP address, check for strong F(R()) consistency." echo "If given a hostname, check for strong F(R((F())) consistency." echo "Usage: $0 host [...]" echo "Options include:" echo " -MX look up MX records for hostnames that follow." echo " -A look up A records for hostnames that follow." echo " -AAAA look up AAAAA records for hostnames that follow." echo " -A+ look up both A and AAAAA records [default]." exit fi digMode="A AAAA" # Call as: a_records server host_ID modeSet # The server can be "--" to use the default DNS server # The host_ID can be a numeric IP address or a hostname # If numeric, we just return it. Trivial. # Otherwise, we return ALL the IP addresses associated # with that name. # The modeSet can be one of: "A" "AAAA" "MX" "NS" # or a combination such as "A AAAA" function a_records() { local server="$1" host="$2" modeSet="$3" srv="" : ${server:=--} if test "_$server" != "_--" ; then srv="@$server" fi if test -z "$modeSet" ; then modeSet="A AAAA"; fi for oneMode in $modeSet ; do local num="" cname="" line while read line ; do set xx $line ; shift if test "_$4" = "_A" -o "_$4" = "_AAAA" ; then num="$5" echo $num continue fi if test "_$4" = "_MX" ; then mxname="$6" #xx 1>&2 echo "Chasing MX $mxname" a_records "$server" $mxname "A AAAA" continue fi if test "_$4" = "_NS" ; then nsname="$5" #xx 1>&2 echo "Chasing NS $nsname" a_records "$server" $nsname "A AAAA" continue fi if test "_$4" = "_CNAME" ; then cname=$5 # dig does the chasing for us: #!!! 1>&2 echo "NOT Chasing CNAME $cname" continue fi 1>&2 echo "Unexpected response '$4' from forward lookup on '$host'" exit 2 done < <(dig +noall +answer $srv "$host" $oneMode) done # loop over modes (as in "A AAAA") } allArgsOK=1 for host in "$@" ; do # convert to numeric form: case $host in -a|-A) digMode=A; continue ;; -a+|-A+) digMode="A AAAA"; continue ;; -aaaa|-AAAA) digMode="AAAA"; continue ;; -m|-M|-mx|-MX) digMode=MX; continue ;; -*) 1>&2 echo "Unrecognized option: '$host'" ; exit 1;; esac is_numeric="" if test -z "${host%%[0-9.]*}" ; then nums=$host # already numeric is_numeric=1 showHost='' else nums=$( a_records "--" "$host" "$digMode" ) showHost="$host ==> " fi if test -z "$nums" ; then 1>&2 echo "Failed: No A records: ${showHost}()" continue fi if test -z "$modeSet" ; then modeSet="A AAAA"; fi for num in $nums ; do allNamesOK="" names=$( dig +short -x $num ) if test -z "$names" ; then 1>&2 echo "Failed: No PTR records: ${showHost}$num --> ()" allArgsOK="" else allNamesOK=1 fi for name in $names ; do addrs="" anyAddrMatch="" if test -n "$name" ; then for oneMode in $modeSet ; do addrs="$addrs $(dig +short $name $oneMode)" done for addr in $addrs; do if test "_$addr" = "_$num" ; then echo "name OK: ${showHost}$num --> $name ==> $addr" anyAddrMatch=1 break; fi done fi if test -z "$addrs" ; then addrs='()' fi if test -z "$anyAddrMatch" ; then 1>&2 echo "Failed: ${showHost}$num --> $name ==> $addrs " allArgsOK="" allNamesOK="" break fi done done if test -n "$allNamesOK" ; then if test -n "$is_numeric" ; then 1>&2 echo "Strong F(R()) consistency: $host" else 1>&2 echo "Strong F(R(F())) consistency: $host $digMode" fi fi done # loop over hosts if test -z "$allArgsOK" ; then exit 1; fi exit 0 |
As foreshadowed in section 1.1, in many cases it is convenient to name the host and name each of the interfaces. Here is an example, illustrating a convenient and systematic naming scheme.
category | name | address(es) | ||
interface | red.h.example.com | 10.99.10.1 | ||
interface | green.h.example.com | 10.99.20.1 | ||
interface | blue.h.example.com | 10.99.30.1 | ||
host | h.example.com | 10.99.10.1, 10.99.20.1, and 10.99.30.1 |
Equivalently, we can describe the naming scheme in terms of a matrix connecting names to addresses and vice versa:
10.99.10.1 | 10.99.20.1 | 10.99.30.1 | ||
red.h.example.com | ←↑ | · | · | |
green.h.example.com | · | ←↑ | · | |
blue.h.example.com | · | · | ←↑ | |
h.example.com | ←↑ | ←↑ | ←↑ |
As you can see by reading across the rows of the matrix, a lookup on an interface name returns just the address of that interface, whereas a lookup on the host name returns all of the host’s addresses. Conversely, as you can see by reading down the columns of the matrix, a reverse lookup on each address returns two names, namely the interface and the host.
:; dig +short red.h.example.com 10.99.10.1 :; dig +short green.h.example.com 10.99.20.1 :; dig +short blue.h.example.com 10.99.30.1 :; dig +short h.example.com 10.99.10.1 10.99.20.1 10.99.30.1 :; dig +short -x 10.99.10.1 h.example.com. red.h.example.com. :; dig +short -x 10.99.20.1 h.example.com. green.h.example.com. :; dig +short -x 10.99.30.1 h.example.com. blue.h.example.com. |
Note: In what follows, we assume you are using the ISC "bind" daemon (aka “named”) as your DNS server.
When a lookup returns multiple results, the default server behavior is to return them in a different order each time. This is probably what you want for forward lookups, especially if you want load-balancing, ... but probably not what you want for reverse lookups. Fortunately, there is an option to change this behavior. In the /etc/bind/named.conf.options file you can add a stanza that says:
rrset-order { type PTR order fixed; };
As a minor optimization, in rev/99.10 we arrange to return the interface name first, before the host name, because it is more specific. It is therefore less likely to confuse unsophisticated applications that only look at the first PTR-record returned and/or the first A+record returned.
Beware that on reverse lookups, dig appears to display the results in reverse order. That is, the last result printed by dig is the first result specified in the reverse zone file, rev/99.10. If all applications were coded properly it wouldn’t make any difference ... but given that not all applications know what to do with multiple A+records, not to mention multiple PTR-records, this behavior looks like bug-bait. By that I mean it magnifies the likelihood that a poorly written application will fail.
All of the A+records in question can be in the same zone file. That is, you can put them all in the example.com zone file if you want (rather than creating a separate file for h.example.com). Here are a working zone file and reverse zone file:
$ORIGIN . $TTL 259200 ; 3 days example.com IN SOA mail.example.com. admin.example.com. ( 2011000001 ; serial 600 ; refresh (10 minutes) 600 ; retry (10 minutes) 2419200 ; expire (4 weeks) 86400 ; minimum (1 day) ) NS ns.example.com. MX 10 mail.example.com. $ORIGIN example.com. $TTL 3600 ; 1 hour ns A 10.0.0.1 mail A 10.0.0.1 localhost A 127.0.0.1 h A 10.99.10.1 A 10.99.20.1 A 10.99.30.1 red.h A 10.99.10.1 green.h A 10.99.20.1 blue.h A 10.99.30.1 |
$TTL 86400 @ IN SOA ns.example.com. admin.example.com. ( 2011000001 ; Serial 28800 ; Refresh 14400 ; Retry 3600000 ; Expire 86400 ) ; Minimum NS ns.example.com. 1.10 PTR red.h.example.com. PTR h.example.com. 1.20 PTR green.h.example.com. PTR h.example.com. 1.30 PTR blue.h.example.com. PTR h.example.com. |
The solution described in section 2 is not the only reasonable solution.
10.99.10.1 | 10.99.20.1 | 10.99.30.1 | ||
ip10-99-10-1.isp.net | ←↑ | · | · | |
ip10-99-20-1.isp.net | · | ←↑ | · | |
ip10-99-30-1.isp.net | · | · | ←↑ | |
h.example.com | ↑ | ↑ | ↑ |
In this example, a forward lookup on the hostname h.example.com will return perfectly good addresses, but a reverse lookup on any of the addresses will not return the hostname. Instead it returns a pro-forma name that is rather uninformative. It doesn’t tell us much beyond the IP address, which we already knew.
Note that pro-forma PTR records of this type can be used to provide the addresses with strong F∘R consistency, which is important. (By way of contrast, the hostname in this example lacks any kind of R∘F consistency, which is ugly but not fatal.)
Further discussion of the options can be found in reference 3. Another similar opinion can be found in reference 4. Yet more discussion can be found in reference 5.
We now consider various ideas for dealing with DNS records for hosts and interfaces that go up and down. The “forward” part of this task can be handled by adding a script in /etc/network/if-up.d/. See section 3.1. The “reverse” part of this task is messier; see section 3.2.
There are some features in the DHCP daemons for dynamically updating DNS records, but they are nowhere good enough; see section 3.3.
It is possible to do a good job of updating the A+records by adding a script to /etc/network/if-up.d/.
There are multiple reasons why this is better than relying on the DHCP daemons to do it.
I have written a preliminary version of a script that does part of this job. Details coming soon.
The task of updating the reverse-DNS PTR-records is in some ways remarkably different from updating the forward-DNS A+records. For one thing, quite commonly the DNS server that owns the relevant PTR-records is different from the DNS server that owns the relevant A+records. Authorization to change the relevant A+records does not imply authorization to change the relevant PTR-records or vice versa.
It makes a certain amount of sense to expect the DHCP server to help upate the PTR-records. If it has the authority to give you the address, it ought to have the authority to attach arbitrary PTR-records to that address. (This stands in contrast to the A+records. As far as I can tell, it does not make sense to expect the DHCP server to do much with the A+records, except possibly pro-forma A+records of the kind discussed in section 2.2.)
There exists a 6to4 reverse zone authority. Some hints on how to use it can be found in reference 6. See also http://dyn.com/support/reverse-dns/?dyndns-redirect
Seel also reference 7.
There are features in the DHCP daemons that dynamically update the foward and reverse DNS records whenever a DHCP lease is granted or terminated. Alas, these daemons do not appear to be smart about multiple A+records, let alone multiple PTR-records. On top of that, there are other bugs.
In theory, part of the problem could be solved by adding the appropriate interface names to the /etc/dhcp3/dhclient.conf file. You might be tempted to try a configuration that looks like this:
interface "wlan0" { send host-name "blue.h"; } interface "eth0" { send host-name "green.h"; }
However, this fails miserably, due to some kind of bug in dhclient3. If you try it, any attempt to ifup one interface brings up the other also. Simiilarly, any attempt to ifdown one interface brings down the other also.
Even if we could get that to work, we would still be stuck with another problem, namely finding a way to collect all the interface addresses and create the A+records and PTR-records for the host h (as distinct from the individual interfaces *.h).
One way of attacking this second problem is to assign the host an IP address of its own, independent of any of the interface addresses, as illustrated in the table below. This address is attached to a dummy interface on the host. We are then left with a routing issue, namely a question of which interface to use to route traffic to the host address. Given that IPv4 addresses are somewhat scarce, this is not an ideal solution. On the other hand, sometimes an IPv4 address is available, sometimes an RFC1918 “private” address can be used, and sometime an IPv6 address can be used.
category | name | address(es) | ||
interface | red.h.example.com | 10.99.10.1 | ||
interface | green.h.example.com | 10.99.20.1 | ||
interface | blue.h.example.com | 10.99.30.1 | ||
host | h.example.com | 10.3.4.1 |
You might be tempted to try mapping the hostname h.example.com to multiple CNAMEs that point to the interfaces, but that doesn’t work. Only one CNAME is allowed. Fooey.
Another approach that might be worth trying is to tell the DHCP server daemon to not bother updating the DNS records, and let the client machine do it instead. Options include:
Beware of the following undocumented bugs and misfeatures:
send host-name "<hostname>";
that sends the system hostname. It would be nice if this were documented ... and even nicer if it were more general, so that we could say something like
send host-name "blue.<hostname>";
Alas, the generalized form does not appear to work.
/etc/dhcp3/dhclient.conf line 24: semicolon expected.
It appears the "do-forward-update false;" (without the s) works better. Sometimes.
I have observed that craigslist.org has a particularly paranoid mail system, and therefore makes a good test. If you have something listed on craigslist, check whether you can send mail to yourself via that route.
Thanks to Hugh Daniel for insightful help with this (and with many other things).