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.
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:
You'll write the shell script much faster than you'll write the C program.
If you are not comfortable with DNS, you can work out the muck in your program's logic with a quick shell script prototype. When you finally write the C program, you can focus on the additional control you have with C, rather than spending your time reworking the basic functionality.
You might find out that the shell script version does your task well enough so that you don't have to write the C program after all. Not only is the coding time shorter, but shell scripts are easier to maintain if you stick with them for the long run.
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.
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.
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.