[Contents]
Copyright © 2011 jsd

Maintaining Consistency of DNS Records
(Especially for Multi-Homed Hosts)

1  Overview

1.1  Introduction

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 FR consistency, as described in section 1.3.

Multi-homing is quite common, for a number of good reasons including:

1.2  Some Motivation

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

dns-consist-example.out     (DL)

For more on why FR 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.

1.3  Types of Matching

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:

F(name)  addresses
             (1)

Similarly the reverse lookup operator R maps an address to zero or more names, each specified by a DNS PTR-record:

R(address)  names
             (2)

Definition: We define the composition FR as follows:

  1. Given an address a, first apply R to obtain a set of names.
  2. Consider one of those names, let’s say the ith one, which we denote R(a)[i].
  3. Apply F to that name to obtain a set of addresses. Consider one of those addresses, let’s say the jth one, which we denote by F(R(a)[i])[j].

Terminology note: FR 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 FR consistency if for every i there exists some j such that F(R(a)[i])[j] = a.

We can define RF 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 FRF consistency if every address returned by F(n) exhibits strong FR consistency.

Note that FRF consistency is not sufficient to guarantee RF consistency; the pro-forma PTR records in section 2.2 provide a counterexample.

It is important to distinguish FR consistency from RF consistency. The former is far more important.

Remark: When we are worried about FR 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 FR consistency. Given a hostname, it checks for strong FRF 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

dns-consist     (DL)

2  One Reasonable Solution

2.1  Bidirectional Consistency

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.110.99.20.110.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.


dig-fwd-rev.out     (DL)

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

zone/example.com     (DL)

$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.




rev/99.10     (DL)

2.2  Other Possibilities

The solution described in section 2 is not the only reasonable solution.

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.

3  Dynamic DNS

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.

3.1  Updating the A+Records

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.

3.2  Updating the PTR-Records

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.

3.3  Not-So-Good Approaches

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:

  1. The "hostname" option to the dhcp method as documented in connection with /etc/network/interfaces appears to have absolutely no effect on the dhcp3 client daemon. It is not even passed to /sbin/dhclient-script. If you want to specify a hostname, you have to do it in the /etc/dhcp3/dhclient.conf file.
  2. There appears to be an undocumented feature whereby the /etc/dhcp3/dhclient.conf file can include a statement of the form
           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.

  3. The documentation for dhclient.conf says we need to do "do-forward-updates false;" but that fails and gives misleading error messages:
       /etc/dhcp3/dhclient.conf line 24: semicolon expected.
    

    It appears the "do-forward-update false;" (without the s) works better. Sometimes.

4  Testing

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.

5  Acknowledgements and References

Thanks to Hugh Daniel for insightful help with this (and with many other things).

1.
Wikipedia article, “Forward-confirmed reverse DNS”
http://en.wikipedia.org/wiki/Forward_Confirmed_reverse_DNS
2.
D. Barr, “Common DNS Operational and Configuration Errors”
http://www.ietf.org/rfc/rfc1912.txt
3.
David Lindes, “DNS A Records”
http://www.sage.org/lists/sage-members-archive/1998/msg00687.html (skip down to where it says "multi-homed").
4.
Barry Margolin “Multihomed Name Servers”
https://lists.isc.org/pipermail/bind-users/2000-May/014463.html
5.
D. Brent Chapman and Elizabeth D. Zwicky, Building Internet Firewalls
http://www.c3.hu/docs/oreilly/tcpip/firewall/ch08_10.htm (section 8.10.4.2) and
http://www.c3.hu/docs/oreilly/tcpip/dnsbind/ch04_02.htm (sections 4.2.5 and 4.2.6).
6.
“Nick’s 6to4 page”
http://www.kfu.com/~nsayer/6to4/
7.
H. Eidnes, G. de Groot, P. Vixie, “Classless IN-ADDR.ARPA delegation”
http://www.ietf.org/rfc/rfc2317.txt
[Contents]
Copyright © 2011 jsd