DNS & BIND

DNS & BINDSearch this book
Previous: 13.6 Problem SymptomsChapter 14Next: 14.2 C Programming with the Resolver Library Routines
 

14. Programming with the Resolver and Name Server Library Routines

Contents:
Shell Script Programming with nslookup
C Programming with the Resolver Library Routines
Perl Programming with Net::DNS

"I know what you're thinking about," said Tweedledum; "but it isn't so, nohow."

"Contrariwise," continued Tweedledee, "if it was so, it might be; and if it were so, it would be; but as it isn't, it ain't. That's logic."

I bet you think resolver programming is hard. Contrariwise! It isn't very hard, really. The format of DNS packets is quite straightforward - you don't have to deal with ASN.1[1] at all, as you have to do with SNMP. And you have nifty library routines to make parsing DNS packets easy. We've included portions of RFC 1035 in Appendix A, DNS Message Format and Resource Records. However, you might find it handy to have a copy of RFC 1035 to look at while we are going through this chapter; at least have a copy of it nearby when you write your own DNS program.

[1] ASN.1 stands for Abstract Syntax Notation. ASN.1 is a language for encoding object types, accepted as an international standard by the International Organization for Standardization.

14.1 Shell Script Programming with nslookup

Before you go off and write a C program to do your DNS chore, you should write the program as a shell script using nslookup. There are good reasons to start with a shell script:

If you prefer Perl over plain old shell programming, you can do that too. At the end of this chapter, we'll show you how to use the Perl Net::DNS module written by Michael Fuhr.

14.1.1 A Typical Problem

Before you write a program, you have to have a problem to solve. Let's suppose you want your network management system to watch over your primary and secondary name servers. You want it to notify you of several problems: a name server that is not running (it might have died), a name server that is not authoritative for a domain that it is supposed to be authoritative for (the boot file might have been messed up), or a name server that has fallen behind in updating its data (the primary's serial number might have been decreased accidentally).

Each of these problems is easily detectable. If a name server is not running on a host, the host will send back an ICMP port unreachable message. You can find this out with both nslookup and the resolver routines. Checking if a name server is authoritative for a domain is easy: ask it for the domain's SOA record. If the answer is nonauthoritative, or the name server does not have the SOA record, there is a problem. You'll have to ask for the SOA record in a nonrecursive query so that the name server doesn't go off and look up the SOA record from another server. Once you have the SOA record, you can extract the serial number.

14.1.2 Solving This Problem with a Script

This problem requires a program that takes the domain name of a zone as an argument, looks up the name servers for that zone, and then queries each of those name servers for the SOA record for the zone. The response will show if the server is authoritative, and it will show the zone's serial number. If there is no response, the program needs to determine if a name server is even running on the host. Once this program is written, it needs to be called for every zone you want to watch over. Since this program looks up the name servers (by looking up the NS records for the zone), we assume that you have listed all your name servers in NS records in your zone data. If that is not the case, then you will have to change this program to take a list of name servers from the command line.

Let's write the basic program as a shell script that uses nslookup. First, we must figure out what the output of nslookup looks like, so that we can parse it with UNIX tools. We'll look up NS records to find out which servers are supposed to be authoritative for a zone, both when the server is authoritative for the NS records and when it isn't:

% nslookup
Default Server:  relay.hp.com
Address:  15.255.152.2

> set type=ns

Find out what the response looks like when the server is not authoritative for the NS records:

> mit.edu.
Server:  relay.hp.com
Address:  15.255.152.2

Non-authoritative answer:
mit.edu nameserver = STRAWB.MIT.EDU
mit.edu nameserver = W20NS.MIT.EDU
mit.edu nameserver = BITSY.MIT.EDU

Authoritative answers can be found from:
MIT.EDU nameserver = STRAWB.MIT.EDU
MIT.EDU nameserver = W20NS.MIT.EDU
MIT.EDU nameserver = BITSY.MIT.EDU
STRAWB.MIT.EDU  internet address = 18.71.0.151
W20NS.MIT.EDU   internet address = 18.70.0.160
BITSY.MIT.EDU   internet address = 18.72.0.3

Find out what the response looks like when the server is authoritative for the NS records:

> server strawb.mit.edu.
Default Server:  strawb.mit.edu
Address:  18.71.0.151

> mit.edu.
Server:  strawb.mit.edu
Address:  18.71.0.151

mit.edu nameserver = BITSY.MIT.EDU
mit.edu nameserver = STRAWB.MIT.EDU
mit.edu nameserver = W20NS.MIT.EDU
BITSY.MIT.EDU   internet address = 18.72.0.3
STRAWB.MIT.EDU  internet address = 18.71.0.151
W20NS.MIT.EDU   internet address = 18.70.0.160

You can see from this output that we can grab the name server names by looking for the lines that contain nameserver and saving the last field. When the server was not authoritative for the NS records, it printed them twice, so we'll have to weed out duplicates.

Next, we look up the SOA record for the zone, both when the server is authoritative for the SOA record and when it isn't. We turn off recurse so the name server doesn't go off and query an authoritative name server for the SOA:

% nslookup
Default Server:  relay.hp.com
Address:  15.255.152.2

> set type=soa
> set norecurse

Find out what the response looks like when the server does not have the SOA record:

> mit.edu.
Server:  relay.hp.com
Address:  15.255.152.2

Authoritative answers can be found from:
MIT.EDU nameserver = STRAWB.MIT.EDU
MIT.EDU nameserver = W20NS.MIT.EDU
MIT.EDU nameserver = BITSY.MIT.EDU
STRAWB.MIT.EDU  internet address = 18.71.0.151
W20NS.MIT.EDU   internet address = 18.70.0.160
BITSY.MIT.EDU   internet address = 18.72.0.3

Find out what the response looks like when the server is authoritative for the zone:

> server strawb.mit.edu.
Default Server:  strawb.mit.edu
Address:  18.71.0.151

> mit.edu.
Server:  strawb.mit.edu
Address:  18.71.0.151

mit.edu
        origin = BITSY.MIT.EDU
        mail addr = NETWORK-REQUEST.BITSY.MIT.EDU
        serial = 378
        refresh = 3600 (1 hour)
        retry   = 300 (5 mins)
        expire  = 3600000 (41 days 16 hours)
        minimum ttl = 21600 (6 hours)

When the name server was not authoritative for the zone, it returned references to other name servers. If the name server had previously looked up the SOA record and cached it, the name server would have returned the SOA record and said it was "non-authoritative." We need to check for both cases. When the name server returns the SOA record and it is authoritative, we can grab the serial number from the line that contains serial.

Now, we need to see what nslookup returns when no name server is running on a host. We'll change servers to a host that does not normally run a name server and look up an SOA record:

% nslookup
Default Server:  relay.hp.com
Address:  15.255.152.2

> server galt.cs.purdue.edu.
Default Server:  galt.cs.purdue.edu
Address:  128.10.2.39

> set type=soa
> mit.edu.
Server:  galt.cs.purdue.edu
Address:  128.10.2.39

*** galt.cs.purdue.edu can't find mit.edu.: No response from server

Last, we need to see what nslookup returns if a host is not responding. We can test this by switching servers to an unused IP address on our LAN:

% nslookup
Default Server:  relay.hp.com
Address:  15.255.152.2

> server 15.255.152.100
Default Server:  [15.255.152.100]
Address:  15.255.152.100

> set type=soa
> mit.edu.
Server:  [15.255.152.100]
Address:  15.255.152.100

*** Request to [15.255.152.100] timed-out

In the last two error cases, the error message was written to stderr. We can make use of that fact when writing our shell script. Now we are ready to compose the shell script. We'll call it check_soa:

#!/bin/sh
if test "$1" = ""
then
    echo usage: $0 domain
    exit 1
fi
DOMAIN=$1
#
# Use nslookup to discover the nameservers for this domain ($1).
# Use awk to grab the name server names from the nameserver lines.
# (The names are always in the last field.)  Use sort -u to weed out
# duplicates; we don't actually care about collation.
#
SERVERS=`nslookup -type=ns $DOMAIN |\
                 awk '/nameserver/ {print $NF}' | sort -u`
if test "$SERVERS" = ""
then
    #
    # Didn't find any servers.  Just quit silently; nslookup will
    # have detected this error and printed a message.  That will
    # suffice.
    #
    exit 1
fi
#
# Check each server's SOA serial number.  The output from
# nslookup is saved in two tmp files: nso.$$ (standard output)
# and nse.$$ (standard error).  These files are rewritten on
# every iteration.  Turn off defname and search since we
# should be dealing with fully qualified names.
#
# NOTE: this loop is rather long; don't be fooled.
#
for i in $SERVERS
do
  nslookup >/tmp/nso.$$ 2>/tmp/nse.$$ <<-EOF
    server $i
    set nosearch
    set nodefname
    set norecurse
    set q=soa
    $DOMAIN
EOF
  #
  # Does this response indicate that the current server ($i) is
  # authoritative?  The server is NOT authoritative if (a) the
  # response says so, or (b) the response tells you to find
  # authoritative info elsewhere.
  #
  if egrep "Non-authoritative|Authoritative answers can be" \
                                          /tmp/nso.$$ >/dev/null
  then
    echo $i is not authoritative for $DOMAIN
    continue
  fi
  #
  # We know the server is authoritative; extract the serial number.
  #
  SERIAL=`cat /tmp/nso.$$ | grep serial | sed -e "s/.*= //"`
  if test "$SERIAL" = ""
  then
    #
    # We get here if SERIAL is null.  In this case, there should
    # be an error message from nslookup; so cat the "standard
    # error" file.
    #
    cat /tmp/nse.$$
  else
    #
    # Report the server's name and its serial number.
    #
    echo $i has serial number $SERIAL
  fi
done  # end of the "for" loop
#
# Delete the temporary files.
#
rm -f /tmp/nso.$$ /tmp/nse.$$

Here is what the output looks like:

% check_soa mit.edu
BITSY.MIT.EDU has serial number 378
STRAWB.MIT.EDU has serial number 378
W20NS.MIT.EDU has serial number 378

If you are pressed for time, this short tool will solve your problem, and you can go on to other work. If you find that you are checking lots of zones and that this tool is too slow, you'll want to convert it to a C program. Also, if you want more control over the error messages - rather than relying on nslookup for error messages - then you'll have to write a C program. We'll do just that, later on in this chapter.


Previous: 13.6 Problem SymptomsDNS & BINDNext: 14.2 C Programming with the Resolver Library Routines
13.6 Problem SymptomsBook Index14.2 C Programming with the Resolver Library Routines