“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 messages is quite straightforward; you don’t have to deal with ASN.1[*] at all, as you do with SNMP. And you have nifty library routines to make parsing DNS messages easy. We’ve included portions of RFC 1035 in Appendix A. However, you might find it handy to have a copy of RFC 1035 to look at as we go through this chapter; at least have a copy of it nearby when you write your own DNS programs.
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 or dig. There are good reasons to start with a shell script:
You can write the shell script much faster than you can write a C program.
If you’re not comfortable with DNS, you can work out the details of 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. And not only is it quicker to write shell scripts, but they’re easier to maintain if you stick with them for the long run.
If you prefer Perl over plain old shell programming, you can use Perl instead. 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 need a problem to solve. Let’s suppose you want your network management system to watch over your primary master and slave nameservers. You want it to notify you of several problems: a nameserver that isn’t running (it might have died), a nameserver that is not authoritative for a zone it is supposed to be authoritative for (the config file or zone datafile might have been messed up), or a nameserver that has fallen behind in updating its zone data (the primary master’s serial number might have been decreased accidentally).
Each problem is easily detectable. If a nameserver is not running on a host, the host sends back an ICMP port unreachable message. You can find this out with either a query tool or the resolver routines. Checking whether a nameserver is authoritative for a zone is easy: ask it for the zone’s SOA record. If the answer is nonauthoritative or the nameserver does not have the SOA record, there’s a problem. You’ll have to ask for the SOA record in a nonrecursive query so that the nameserver 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 nameservers for that zone, and then queries each nameserver for the SOA record for the zone. The response will show whether the nameserver is authoritative, and it will show the zone’s serial number. If there is no response, the program needs to determine if there’s even a nameserver running on the host. Once you write this program, you should run it on each zone you want to watch over. Since this program looks up the nameservers (by looking up the NS records for the zone), we assume that you have listed all your nameservers in NS records in your zone data. If that’s not the case, you will have to change this program to read a list of nameservers from the command line.
Let’s write the basic program as a shell script that uses nslookup. First, we 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 nameservers are supposed to be authoritative for the zone, both when the server is authoritative for the zone that contains 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 nameserver 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
Then, findout what the response looks like when the nameserver 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 domain names of the nameservers by looking for the lines that contain nameserver and saving the last field. When the nameserver wasn’t 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 zone that contains the SOA record and when it isn’t. We turn off recurse so the nameserver doesn’t go off and query an authoritative nameserver 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 nameserver is not authoritative and 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
Then, find out what the response looks like when the nameserver 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 = 1995 refresh = 3600 (1H) retry = 900 (15M) expire = 3600000 (5w6d16h) minimum ttl = 21600 (6H)
When the nameserver was not authoritative for the zone, it returned references to other nameservers. If the nameserver had previously looked up the SOA record and cached it, the nameserver would have returned the SOA record and said that it was nonauthoritative. We need to check for both cases. When the nameserver 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 nameserver is running on a host. We’ll change servers to a host that does not normally run a nameserver 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 nameservers 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 cases, the error message was written to stderr.[*] We can use 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 zone exit 1 fi ZONE=$1 # # Use nslookup to discover the name servers for this zone ($1). # Use awk to grab the name server's domain 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 $ZONE | 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 temp 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 domain 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 $ZONE 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 $ZONE 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 domain 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 1995
STRAWB.MIT.EDU has serial number 1995
W20NS.MIT.EDU has serial number 1995
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—you’ll have to write a C program. We’ll do that later in this chapter.
Before writing any code, though, you need to be familiar with the DNS message format and the resolver library routines. In the shell script we just wrote, nslookup parsed the DNS message. In a C program, though, you have to do the parsing. Let’s start this section on programming by looking at the DNS message format.
You’ve seen the DNS message format before, in Chapter 12. It looks like this:
Header section
Question section
Answer section
Authority section
Additional section
The format of the header section is described in RFC 1035 on pages 26–28, and in Appendix A of this book. It looks like this:
query identification number (2 octets) query response (1 bit) opcode (4 bits) authoritative answer (1 bit) truncation (1 bit) recursion desired (1 bit) recursion available (1 bit) reserved (3 bits) response code (4 bits) question count (2 octets) answer record count (2 octets) name server record count (2 octets) additional record count (2 octets)
You’ll also find opcode, response code, type, and class values defined in arpa/nameser.h, as well as routines to extract this information from a message. We’ll discuss these routines, part of the nameserver library, shortly.
The question section is described on pages 28–29 of RFC 1035. It looks like this:
domain name (variable length) query type (2 octets) query class (2 octets)
The answer, authority, and additional sections are described on pages 29–30 of RFC 1035. These sections comprise some number of resource records that look like this:
domain name (variable length) type (2 octets) class (2 octets) TTL (4 octets) resource data length (2 octets) resource data (variable length)
The header section contains a count of how many of these resource records are in each section.
As you can see, the names stored in the DNS message are of variable length. Unlike C, DNS does not store the names as null-terminated strings. Domain names are stored as a series of length/value pairs ending with an octet of 0. Each label in a domain name is composed of a length octet and a label. A name like venera.isi.edu is stored as:
6 venera 3 isi 3 edu 0
You can imagine how much of a DNS message could be devoted to storing names. The developers of DNS recognized this and came up with a simple way to compress domain names.
Often, an entire domain name or, at least, the trailing labels of a domain name match a name already stored in the message. Domain name compression eliminates the repetition of domain names by storing a pointer to the earlier occurrence of the name instead of inserting the name again. Here is how it works. Suppose a response message already contains the name venera.isi.edu. If the name vaxa.isi.edu is added to the response, the label vaxa is stored, and then a pointer to the earlier occurrence of isi.edu is added. So how are these pointers implemented?
The first two bits of the length octet indicate whether a length/label pair or a pointer to a length/label pair follows. If the first two bits are zeros, then the length and label follow. As you may remember from way back in Chapter 2, a label is limited to 63 characters. That’s because the length field has only the remaining six bits for the length of the label—enough to represent the lengths 0-63. If the first two bits of the length octet are ones, then what follows is not a length but a pointer. The pointer is the last 6 bits of the length octet and the next octet—14 bits in total. The pointer is an offset from the start of the DNS message. Now, when vaxa.isi.edu is compressed into a buffer containing only venera.isi.edu, this is what results:
byte offset: 0 123456 7 890 1 234 5 6 7890 1 2 -------------+--------------+-------- pkt contents: 6 venera 3 isi 3 edu 0 4 vaxa 0xC0 7
The 0xC0 is a byte with the high two bits ones and the rest of the bits zeros. Since the high two bits are ones, this is a pointer instead of a length. The pointer value is 7: the last six bits of the first octet are zeros and the second octet is 7. At offset seven in this buffer, you find the rest of the domain name that begins with vaxa, which is isi.edu.
In this example, we only showed the compression in two domain names in a buffer, not a whole DNS message. A DNS message would have had a header as well as other fields. This example is intended only to give you an idea of how the domain name compression works. Now the good news: you don’t really need to care how names are compressed as long as the library routines do it properly. What you do need to know is how parsing a DNS response message can get messed up if you are off by one byte. For example, try to expand the name starting with byte two instead of byte one. You’ll discover that “v” doesn’t make a very good length octet or pointer.
The resolver library contains the routines that you need to write your application. You’ll use these routines to generate queries. You’ll use the nameserver library routines, explained next, to parse the response.
In case you’re wondering why we’re not using the BIND 9 resolver routines in our code, BIND 9 includes library routines to perform lots of powerful DNS functions, but they’re oriented toward the BIND 9 nameserver’s needs and are very complicated to use, we’re told. BIND 9 includes the BIND 8 resolver in lib/bind/resolv and we will continue to use that for now. A program linked against the BIND 8 library routines will work just fine with a BIND 9 nameserver.
Here are the header files you must include:
#include <sys/types.h> #include <netinet/in.h> #include <arpa/nameser.h> #include <resolv.h>
Now let’s look at the resolver library routines.
extern int h_errno; int herror(const char *s)
herror is a routine like perror, except that it prints out a string based on the value of the external variable h_errno instead of errno. The only argument is:
A string used to identify the error message. If a string s is supplied, it is printed first, followed by “:” and a string based on the value of h_errno.
Here are the possible values of h_errno:
The domain name does not exist. The return code in the nameserver response was NXDOMAIN.
Either the nameserver is not running, or the nameserver returned SERVFAIL.
Either the domain name could not be compressed because
it was an invalid domain name (e.g., a name missing a
label—..movie.edu
) or
the nameserver returned FORMERR, NOTIMP, or REFUSED.
The domain name exists, but there is no data of the requested type.
There was a library error unrelated to the network or name service. Instead, see errno for the problem description.
int res_init(void)
res_init
reads
resolv.conf and initializes a data structure
called _res (more about that later). All the
previously discussed routines will call
res_init
if they detect that it hasn’t
been called previously. Or you can call it on your own; this is
useful if you want to change some of the defaults before calling the
first resolver library routine. If there are any lines in
resolv.conf that
res_init
doesn’t understand, it ignores
them. res_init
always returns 0, even if
the manpage reserves the right to return -1.
int res_mkquery(int op, const char *dname, int class, int type, const u_char *data, int datalen, const u_char *newrr, u_char *buf, int buflen)
res_mkquery
creates the query
message. It fills in all the header fields, compresses the domain
name into the question section, and fills in the other question
fields.
The dname, class, and
type arguments are the same as for
res_search
and
res_query
. The remaining arguments
are:
The “operation” to be performed. This is normally QUERY, but it can be IQUERY (inverse query). However, as we’ve explained before, IQUERY is seldom used. BIND versions 4.9.4 and later, by default, do not even support IQUERY.
A buffer containing the data for inverse queries. It is NULL when op is QUERY.
The size of the data buffer. If data is NULL, then datalen is 0.
A buffer used for the dynamic update code (covered in Chapter 10). Unless you are playing with this feature, it is always NULL.
A buffer in which res_mkquery
places the query message. It should be PACKETSZ or larger,
like the answer buffer in
res_search
and
res_query
.
The size of the buf buffer (e.g., PACKETSZ).
res_mkquery
returns the size of the
query message or -1 if there was an error.
int res_query(const char *dname, int class, int type, u_char *answer, int anslen)
res_query
is one of the “mid-level”
resolver routines. It does all the real work in looking up the
domain name: it makes a query message by calling
res_mkquery
, sends the query by calling
res_send
, and looks at enough of the
response to determine whether your question was answered. In many
cases, res_query
is called by
res_search
, which just feeds it the
different domain names to look up. As you’d expect, these two
functions have the same arguments.
res_query
returns the size of the
response, or it fills in h_errno and returns -1
if there was an error or the answer count was 0.
int res_search(const char *dname, int class, int type, u_char *answer, int anslen)
res_search
is the “highest level”
resolver routine, and is called by
gethostbyname.
res_search
applies the search algorithm
to the domain name passed to it. That is, it takes the domain name
it receives (dname), “completes” the name (if
it’s not fully qualified) by adding the various domain names from
the resolver search list, and calls
res_query
until it receives a successful
response, indicating that it found a valid, fully qualified domain
name. In addition to implementing the search algorithm,
res_search
looks in the file referenced
by your HOSTALIASES environment variable. (The HOSTALIASES variable
was described in Chapter 6.) So it
also takes care of any “private” host aliases you might have.
res_search
returns the size of the
response or fills in h_errno and returns -1 if
there was an error or the answer count is 0.
(h_errno is like errno,
but for DNS lookups.)
Therefore, the only parameter that’s really of interest to
res_search
is dname;
the others are just passed through to
res_query
and the other resolver
routines. The other arguments are:
The class of the data you’re looking up. This is almost always the constant C_IN, the Internet class. The class constants are defined in arpa/nameser.h.
The type of data you’re looking up. Again, this is a constant defined in arpa/ nameser.h. A typical value would be T_NS to retrieve a nameserver record, or T_MX to retrieve an MX record.
A buffer in which res_search
will place the response message. Its size should be at least
PACKETSZ (from arpa/nameser.h)
bytes.
The size of the answer buffer (e.g., PACKETSZ).
res_search
returns the size of the
response or -1 if there was an error.
int res_send(const u_char *msg, int msglen, u_char *answer, int anslen)
res_send
implements the retry
algorithm. It sends the query message, msg, in
a UDP datagram, but it can also send it over a TCP stream. The
response message is stored in answer. This
routine, of all the resolver routines, is the only one to use black
magic (unless you know all about connected datagram sockets). You’ve
seen these arguments before in the other resolver routines:
The buffer containing the DNS query message
The size of the message
The buffer in which to store the DNS response message
The size of the answer message
res_send
returns the size of the
response or -1 if there was an error. If this routine returns -1 and
errno is ECONNREFUSED, then there is no
nameserver running on the target nameserver host.
You can look at errno to see if it is
ECONNREFUSED after calling res_search
or
res_query
.
(res_search
calls
res_query
, which calls
res_send
.) If you want to check
errno after calling
res_query
, clear
errno first. That way, you know the current
call to res_send
was the one that set
errno. However, you don’t have to clear
errno before calling res_search.
res_search
clears errno itself
before calling res_query
.
Each resolver routine (i.e., each routine whose name starts with res_) uses a common data structure called _res. You can change the behavior of the resolver routines by changing _res. If you want to change the number of times res_send retries a query, you can change the value of the retry field. If you want to turn off the resolver search algorithm, you turn off the RES_DNSRCH bit from the options mask. You’ll find the all-important _res structure in resolv.h:
struct _ _res_state { int retrans; /* retransmission time interval */ int retry; /* number of times to retransmit */ u_long options; /* option flags - see below. */ int nscount; /* number of name servers */ struct sockaddr_in nsaddr_list[MAXNS]; /* address of name server */ #define nsaddr nsaddr_list[0] /* for backward compatibility */ u_short id; /* current packet id */ char *dnsrch[MAXDNSRCH+1]; /* components of domain to search */ char defdname[MAXDNAME]; /* default domain */ u_long pfcode; /* RES_PRF_ flags - see below. */ unsigned ndots:4; /* threshold for initial abs. query */ unsigned nsort:4; /* number of elements in sort_list[] */ char unused[3]; struct { struct in_addr addr; /* address to sort on */ u_int32_t mask; } sort_list[MAXRESOLVSORT]; };
The options field is a simple bit mask of the enabled options. To turn on a feature, turn on the corresponding bit in the options field. Bit masks for each of the options are defined in resolv.h; the options are:
If this bit is on, res_init has been called.
This bit causes resolver debugging messages to be printed—if the resolver routines were compiled with DEBUG, that is. Off is the default.
Requires the answer to be authoritative, not from a nameserver’s cache. It’s too bad this isn’t implemented, as it would be a useful feature. Given the BIND resolver’s design, this feature would have to be implemented in the nameserver, and it’s not.
Query the primary master nameserver only—again, not implemented.
Turn on this bit if you’d like the resolver to make its queries over a virtual circuit (TCP) connection instead of with UDP datagrams. As you might guess, there is a performance penalty for setting up and tearing down a TCP connection. Off is the default.
If you are making your queries over a TCP connection, turning on this bit causes the connection to be left open, so you can use it to query the same remote nameserver again. Otherwise, the connection is torn down after the query has been answered. Off is the default.
If the nameserver response has the truncation bit set, then the default resolver behavior is to retry the query using TCP. If this bit is turned on, the truncation bit in the response message is ignored, and the query is not retried using TCP. Off is the default.
The default behavior for the BIND resolver is to send recursive queries. Turning off this bit turns off the “recursion desired” bit in the query message. On is the default.
The default behavior for the BIND resolver is to append the local domain name to any domain name that does not have a dot in it. Turning off this bit turns off appending the local domain name. On is the default.
The default behavior for the BIND resolver is to append each element of the search list to a domain name that does not end in a dot. Turning off this bit turns off the search list function. On is the default.
The default behavior for a 4.9.3 or later BIND resolver is to ignore answers from nameservers that were not queried. Turning on this bit disables this security check. Off (i.e., security check on) is the default.
The default behavior for a 4.9.3 or later BIND resolver is to ignore answers in which the question section of the response does not match the question section of the original query. Turning on this bit disables this security check. Off (i.e., security check on) is the default.
The default behavior for the BIND resolver is to use aliases defined in the file specified by the user’s HOSTALIASES environment variable. Turning on this bit disables the HOSTALIASES feature for 4.9.3 and later BIND resolvers. Previous resolvers did not allow this feature to be disabled. Off is the default.
Tells the resolver to return IPv6 addresses (in addition to IPv4 addresses) to the gethostbyname function.
Normally, a resolver that sends repeated queries always queries the first nameserver in resolv.conf first. With RES_ROTATE set, a BIND 8.2 or later resolver sends its first query to the first nameserver in resolv.conf, its second to the second nameserver, and so on. (See the options rotate directive in Chapter 6 for details.) The default is not to rotate nameservers.
Since BIND 4.9.4, resolvers have checked the domain names in responses to make sure they conform to the naming guidelines described in Chapter 4. BIND 8.2 resolvers offer the option of turning off the name-checking mechanism. Off (i.e., name check on) is the default.
This option tells a BIND 8.2 or later resolver not to strip the TSIG record from a signed DNS message. This way, the application that called the resolver can examine it.
“Blast” all recursive servers by sending queries to them simultaneously. Not implemented yet.
This isn’t a single option, but rather a combination of the RES_RECURSE, RES_DEFNAMES, and RES_DNSRCH options, all of which are on by default. You normally won’t need to set RES_DEFAULT explicitly; it’s set for you when you call res_init.
The nameserver library contains routines you need to parse response messages. Here are the header files you must include:
#include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/nameser.h> #include <resolv.h>
Following are the nameserver library routines.
u_int ns_get16(const u_char *cp) void ns_put16(u_int s, u_char *cp)
The DNS messages have fields that are unsigned short integer
(type, class, and data length, to name a few).
ns_get16
returns a 16-bit integer pointed
to by cp, and
ns_put16
assigns the 16-bit value of
s to the location pointed to by
cp.
u_long ns_get32(const u_char *cp) void ns_put32(u_long l, u_char *cp)
These routines are like their 16-bit counterparts except that they deal with a 32-bit integer instead of a 16-bit integer. The TTL (time to live) field of a resource record is a 32-bit integer.
int ns_initparse(const u_char *msg, int msglen, ns_msg *handle)
ns_initparse
is the first routine
you must call before you use the other nameserver library routines.
ns_initparse
fills in the data structure
pointed to by handle, which is a parameter
passed to other routines. The arguments are:
A pointer to the beginning of the response message buffer
The size of the message buffer
A pointer to a data structure filled in by
ns_initparse
ns_initparse
returns 0 on success
and -1 if it fails to parse the message buffer.
const u_char *ns_msg_base(ns_msg handle) const u_char *ns_msg_end(ns_msg handle) int ns_msg_size(ns_msg handle)
These routines return a pointer to the start of the message, a
pointer to the end of the message, and the size of the message. They
return the data you passed into
ns_initparse
. The only argument
is:
A data structure filled in by
ns_initparse
u_int16_t ns_msg_count(ns_msg handle, ns_sect section)
ns_msg_count
returns a counter from
the header section of the response message. Its arguments
are:
A data structure filled in by
ns_initparse
An enumerated type that can have the following values:
ns_s_qd /* Query: Question section */ ns_s_zn /* Update: Zone section */ ns_s_an /* Query: Answer section */ ns_s_pr /* Update: Prerequisite section */ ns_s_ns /* Query: Name Server section */ ns_s_ud /* Update: Update section */ ns_s_ar /* Query|Update: Additional records section */
u_int16_t ns_msg_get_flag(ns_msg handle, ns_flag flag)
ns_msg _ get_ flag
returns the
“flag” fields from the header section of the response message. Its
arguments are:
A data structure filled in by
ns_initparse
An enumerated type that can have the following values:
ns_f_qr /* Question/Response */ ns_f_opcode /* Operation Code */ ns_f_aa /* Authoritative Answer */ ns_f_tc /* Truncation Occurred */ ns_f_rd /* Recursion Desired */ ns_f_ra /* Recursion Available */ ns_f_z /* Must Be Zero */ ns_f_ad /* Authentic Data (DNSSEC) */ ns_f_cd /* Checking Disabled (DNSSEC) */ ns_f_rcode /* Response Code */ ns_f_max
u_int16_t ns_msg_id(ns_msg handle)
ns_msg _id
returns the
identification from the header section (described earlier) of the
response message. The only argument is:
A data structure filled in by
ns_initparse
int ns_name_compress(const char *exp_dn, u_char *comp_dn, size_t length, const u_char **dnptrs, const u_char **lastdnptr)
ns_name_compress
compresses a
domain name. You won’t normally call this routine yourself—you’ll
let res_mkquery
do it for you. However,
if you need to compress a name for some reason, this is the tool to
do it. The arguments are:
The “expanded” domain name that you supply—i.e., a normal, null-terminated string containing a fully qualified domain name.
The place where
ns_name_compress
will store the
compressed domain name.
The size of the comp_dn buffer.
An array of pointers to previously compressed domain names. dnptrs[0] points to the beginning of the message; the list ends with a NULL pointer. After you’ve initialized dnptrs[0] to the beginning of the message and dnptrs[1] to NULL, dn_comp updates the list each time you call it.
A pointer to the end of the dnptrs
array. ns_name_compress
needs to
know where the end of the array is so it doesn’t overrun
it.
If you want to use this routine, look at how it is used in the
BIND source in src/lib/resolv/res_mkquery.c
(BIND 8) or res/res_mkquery.c (BIND 4). It’s
often easier to see how to use a routine from an example than from
an explanation. ns_name_compress
returns
the size of the compressed name or -1 if there was an error.
int ns_name_skip(const u_char **ptrptr, const u_char *eom)
ns_name_skip
is like
ns_name_uncompress
, but instead of
uncompressing the name, it just skips over it. The arguments
are:
A pointer to a pointer to the name to skip over. The original pointer is advanced past the name.
A pointer to the first byte after the message. It is
used to make sure that ns_name_skip
doesn’t go past the end of the message.
ns_name_skip
returns 0 if
successful. It returns -1 if it fails to uncompress the name.
int ns_name_uncompress(const u_char *msg, const u_char *eomorig, const u_char *comp_dn, char *exp_dn, size_t length)
ns_name_uncompress
expands a
“compressed” domain name. You’ll use this routine if you parse a
nameserver response message, as we do in
check_soa, the C program that follows. The
arguments are:
A pointer to the beginning of your response message.
A pointer to the first byte after the message. It is
used to make sure that
ns_name_uncompress
doesn’t go past
the end of the message.
A pointer to the compressed domain name within the message.
The place where
ns_name_uncompress
will store the
expanded name. You should always allocate an array of MAXDNAME
characters for the expanded name.
The size of the exp_dn buffer.
ns_name_uncompress
returns the size
of the compressed name or -1 if there was an error. You might wonder
why ns_name_uncompress
returns the size
of the compressed name, not the size of the
expanded name. It does this because when you
call ns_name_uncompress
, you are parsing
a DNS message and need to know how much space the compressed name
occupied in the message so that you can skip over it.
int ns_parserr(ns_msg *handle, ns_sect section, int rrnum, ns_rr *rr)
ns_parserr
extracts information
about a response record and stores it in rr,
which is a parameter passed to other nameserver libarary routines.
The arguments are:
A pointer to a data structure filled in by
ns_initparse
.
The same parameter described in ns_msg
_count
.
A resource record number for the resource records in this section. Resource records start numbering at 0. ns_msg _count tells you how many resource records are in this section.
A pointer to a data structure to be initialized.
ns_parserr
returns 0 on success and
-1 if it fails to parse the response buffer.
char *ns_rr_name(ns_rr rr) u_int16_t ns_rr_type(ns_rr rr) u_int16_t ns_rr_class(ns_rr rr) u_int32_t ns_rr_ttl(ns_rr rr) u_int16_t ns_rr_rdlen(ns_rr rr) const u_char *ns_rr_rdata(ns_rr rr)
These routines return individual fields from a response record. Their only argument is:
A data structure filled in by
ns_parserr
The easiest way to learn how to parse a DNS message is to look at code that already does it. Assuming that you have the BIND source code, the best file to look through is src/lib/resolv/res_debug.c (BIND 8) or lib/bind/resolv/res_debug.c (BIND 8 resolver in the BIND 9 distribution). (If you’re really determined to use BIND 9, you might have to read almost 3,000 lines of lib/dns/message.c.) res_debug.c contains fp_query (or res_pquery in BIND 8.2 and later), the function that prints out the DNS messages in the nameserver debugging output. Our sample program traces its parentage to code from this file.
You won’t always want to parse the DNS response manually. An intermediate way to parse the response is to call p_query, which calls fp_query, to print out the DNS message. Then use Perl or awk to grab what you need. Cricket has been known to wimp out in this way.
Let’s now look at a C program to solve the same problem for which we wrote a shell script earlier.
Here are the header files that are needed, the declarations for external variables, and the declarations of functions. Notice that we use both h_errno (for the resolver routines) and errno. We limit this program to checking 20 nameservers. You’ll rarely see a zone with more than 10 nameservers, so an upper limit of 20 should suffice.
/**************************************************************** * check_soa -- Retrieve the SOA record from each name server * * for a given zone and print out the serial number. * * * * usage: check_soa zone * * * * The following errors are reported: * * o There is no address for a server. * * o There is no server running on this host. * * o There was no response from a server. * * o The server is not authoritative for the zone. * * o The response had an error response code. * * o The response had more than one answer. * * o The response answer did not contain an SOA record. * * o The expansion of a compressed domain name failed. * ****************************************************************/ /* Various header files */ #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <errno.h> #include <arpa/nameser.h> #include <resolv.h> /* Error variables */ extern int h_errno; /* for resolver errors */ extern int errno; /* general system errors */ /* Our own routines; code included later in this chapter */ void nsError( ); /* report resolver errors */ void findNameServers( ); /* find a zone's name servers */ void addNameServers( ); /* add name servers to our list */ void queryNameServers( ); /* grab SOA records from servers */ void returnCodeError( ); /* report response message errors */ /* Maximum number of name servers we will check */ #define MAX_NS 20
The main body of the program is small. We have an array of string pointers, nsList, to store the names of the nameservers for the zone. We call the resolver function res_init to initialize the _res structure. It wasn’t necessary for this program to call res_init explicitly because it would have been called by the first resolver routine that used the _res structure. However, if we had wanted to modify the value of any of the _res fields before calling the first resolver routine, we would have made the modifications right after calling res_init. Next, the program calls findNameServers to find all the nameservers for the zone referenced in argv[1] and to store them in nsList. Last, the program calls queryNameServers to query each nameserver in nsList for the SOA record for the zone:
main(argc, argv) int argc; char *argv[]; { char *nsList[MAX_NS]; /* list of name servers */ int nsNum = 0; /* number of name servers in list */ /* sanity check: one (and only one) argument? */ if(argc != 2){ (void) fprintf(stderr, "usage: %s zone ", argv[0]); exit(1); } (void) res_init( ); /* * Find the name servers for the zone. * The name servers are written into nsList. */ findNameServers(argv[1], nsList, &nsNum); /* * Query each name server for the zone's SOA record. * The name servers are read from nsList. */ queryNameServers(argv[1], nsList, nsNum); exit(0); }
The routine findNameServers follows. This routine queries the local nameserver for the NS records for the zone. It then calls addNameServers to parse the response message and store away all the nameservers it finds. The header files, arpa/nameser.h and resolv.h, contain declarations we make extensive use of:
/**************************************************************** * findNameServers -- find all of the name servers for the * * given zone and store their names in nsList. nsNum is * * the number of servers in the nsList array. * ****************************************************************/ void findNameServers(domain, nsList, nsNum) char *domain; char *nsList[]; int *nsNum; { union { HEADER hdr; /* defined in resolv.h */ u_char buf[NS_PACKETSZ]; /* defined in arpa/nameser.h */ } response; /* response buffers */ int responseLen; /* buffer length */ ns_msg handle; /* handle for response message */ /* * Look up the NS records for the given domain name. * We expect the domain name to be a fully qualified, so * we use res_query( ). If we'd wanted the resolver search * algorithm, we would have used res_search( ) instead. */ if((responseLen = res_query(domain, /* the zone we care about */ ns_c_in, /* Internet class records */ ns_t_ns, /* Look up name server records*/ (u_char *)&response, /*response buffer*/ sizeof(response))) /*buffer size */ < 0){ /*If negative */ nsError(h_errno, domain); /* report the error */ exit(1); /* and quit */ } /* * Initialize a handle to this response. The handle will * be used later to extract information from the response. */ if (ns_initparse(response.buf, responseLen, &handle) < 0) { fprintf(stderr, "ns_initparse: %s ", strerror(errno)); return; } /* * Create a list of name servers from the response. * NS records may be in the answer section and/or in the * authority section depending on the DNS implementation. * Walk through both. The name server addresses may be in * the additional records section, but we will ignore them * since it is much easier to call gethostbyname( ) later * than to parse and store the addresses here. */ /* * Add the name servers from the answer section. */ addNameServers(nsList, nsNum, handle, ns_s_an); /* * Add the name servers from the authority section. */ addNameServers(nsList, nsNum, handle, ns_s_ns); } /**************************************************************** * addNameServers -- Look at the resource records from a * * section. Save the names of all name servers. * ****************************************************************/ void addNameServers(nsList, nsNum, handle, section) char *nsList[]; int *nsNum; ns_msg handle; ns_sect section; { int rrnum; /* resource record number */ ns_rr rr; /* expanded resource record */ int i, dup; /* misc variables */ /* * Look at all the resource records in this section. */ for(rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) { /* * Expand the resource record number rrnum into rr. */ if (ns_parserr(&handle, section, rrnum, &rr)) { fprintf(stderr, "ns_parserr: %s ", strerror(errno)); } /* * If the record type is NS, save the name of the * name server. */ if (ns_rr_type(rr) == ns_t_ns) { /* * Allocate storage for the name. Like any good * programmer should, we test malloc's return value, * and quit if it fails. */ nsList[*nsNum] = (char *) malloc (MAXDNAME); if(nsList[*nsNum] == NULL){ (void) fprintf(stderr, "malloc failed "); exit(1); } /* Expand the name server's domain name */ if (ns_name_uncompress( ns_msg_base(handle),/* Start of the message */ ns_msg_end(handle), /* End of the message */ ns_rr_rdata(rr), /* Position in the message */ nsList[*nsNum], /* Result */ MAXDNAME) /* Size of nsList buffer */ < 0) { /* Negative: error */ (void) fprintf(stderr, "ns_name_uncompress failed "); exit(1); } /* * Check the domain name we've just unpacked and add it to * the list of name servers if it is not a duplicate. * If it is a duplicate, just ignore it. */ for(i = 0, dup=0; (i < *nsNum) && !dup; i++) dup = !strcasecmp(nsList[i], nsList[*nsNum]); if(dup) free(nsList[*nsNum]); else (*nsNum)++; } } }
Notice that we don’t explicitly check for finding zero nameserver records. We don’t need to check because res_query flags that case as an error; it returns -1 and sets herrno to NO_DATA. If res_query returns -1, we call our own routine, nsError, to print out an error string from h_errno instead of using herror. The herror routine isn’t a good fit for our program because its messages assume you are looking up address data (e.g., if h_ errno is NO_DATA, the error message is “No address associated with name”).
The next routine queries each nameserver that we’ve found for an SOA record. In this routine, we change the value of several of the _res structure fields. By changing the nsaddr_list field, we change which nameserver res_send queries. We disable the search list by turning off bits in the options field; all the domain names that this program handles are fully qualified:
/****************************************************************** * queryNameServers -- Query each of the name servers in nsList * * for the SOA record of the given zone. Report any * * errors encountered (e.g., a name server not running or * * the response not being an authoritative response). If * * there are no errors, print out the serial number for the zone. * ******************************************************************/ void queryNameServers(domain, nsList, nsNum) char *domain; char *nsList[]; int nsNum; { union { HEADER hdr; /* defined in resolv.h */ u_char buf[NS_PACKETSZ]; /* defined in arpa/nameser.h */ } query, response; /* query and response buffers */ int responseLen, queryLen; /* buffer lengths */ u_char *cp; /* character pointer to parse DNS message */ struct in_addr saveNsAddr[MAXNS]; /* addrs saved from _res */ int nsCount; /* count of addresses saved from _res */ struct hostent *host; /* structure for looking up ns addr */ int i; /* counter variable */ ns_msg handle; /* handle for response message */ ns_rr rr; /* expanded resource record */ /* * Save the _res name server list since * we will need to restore it later. */ nsCount = _res.nscount; for(i = 0; i < nsCount; i++) saveNsAddr[i] = _res.nsaddr_list[i].sin_addr; /* * Turn off the search algorithm and turn off appending * the local domain name before we call gethostbyname( ); * the name server's domain names will be fully qualified. */ _res.options &= ~(RES_DNSRCH | RES_DEFNAMES); /* * Query each name server for the zone's SOA record. */ for(nsNum-- ; nsNum >= 0; nsNum--){ /* * First, we have to get the IP address of every name server. * So far, all we have are domain names. We use gethostbyname( ) * to get the addresses, rather than anything fancy. * But first, we have to restore certain values in _res * because _res affects gethostbyname( ). (We altered * _res in the previous iteration through the loop.) * * We can't just call res_init( ) again to restore * these values since some of the _res fields are * initialized when the variable is declared, not when * res_init( ) is called. */ _res.options |= RES_RECURSE; /* recursion on (default) */ _res.retry = 4; /* 4 retries (default) */ _res.nscount = nsCount; /* original name servers */ for(i = 0; i < nsCount; i++) _res.nsaddr_list[i].sin_addr = saveNsAddr[i]; /* Look up the name server's address */ host = gethostbyname(nsList[nsNum]); if (host == NULL) { (void) fprintf(stderr,"There is no address for %s ", nsList[nsNum]); continue; /* nsNum for-loop */ } /* * Now get ready for the real fun. host contains IP * addresses for the name server we're testing. * Store the first address for host in the _res * structure. Soon, we'll look up the SOA record... */ (void) memcpy((void *)&_res.nsaddr_list[0].sin_addr, (void *)host->h_addr_list[0], (size_t)host->h_length); _res.nscount = 1; /* * Turn off recursion. We don't want the name server * querying another server for the SOA record; this name * server ought to be authoritative for this data. */ _res.options &= ~RES_RECURSE; /* * Reduce the number of retries. We may be checking * several name servers, so we don't want to wait too * long for any one server. With two retries and only * one address to query, we'll wait at most 15 seconds. */ _res.retry = 2; /* * We want to see the response code in the next * response, so we must make the query message and * send it ourselves instead of having res_query( ) * do it for us. If res_query( ) returned -1, there * might not be a response to look at. * * There is no need to check for res_mkquery( ) * returning -1. If the compression was going to * fail, it would have failed when we called * res_query( ) earlier with this domain name. */ queryLen = res_mkquery( ns_o_query, /* regular query */ domain, /* the zone to look up */ ns_c_in, /* Internet type */ ns_t_soa, /* look up an SOA record */ (u_char *)NULL, /* always NULL */ 0, /* length of NULL */ (u_char *)NULL, /* always NULL */ (u_char *)&query,/* buffer for the query */ sizeof(query)); /* size of the buffer */ /* * Send the query message. If there is no name server * running on the target host, res_send( ) returns -1 * and errno is ECONNREFUSED. First, clear out errno. */ errno = 0; if((responseLen = res_send((u_char *)&query,/* the query */ queryLen, /* true length*/ (u_char *)&response,/*buffer */ sizeof(response))) /*buf size*/ < 0){ /* error */ if(errno == ECONNREFUSED) { /* no server on the host */ (void) fprintf(stderr, "There is no name server running on %s ", nsList[nsNum]); } else { /* anything else: no response */ (void) fprintf(stderr, "There was no response from %s ", nsList[nsNum]); } continue; /* nsNum for-loop */ } /* * Initialize a handle to this response. The handle will * be used later to extract information from the response. */ if (ns_initparse(response.buf, responseLen, &handle) < 0) { fprintf(stderr, "ns_initparse: %s ", strerror(errno)); return; } /* * If the response reports an error, issue a message * and proceed to the next server in the list. */ if(ns_msg_getflag(handle, ns_f_rcode) != ns_r_noerror){ returnCodeError(ns_msg_getflag(handle, ns_f_rcode), nsList[nsNum]); continue; /* nsNum for-loop */ } /* * Did we receive an authoritative response? Check the * authoritative answer bit. If this name server isn't * authoritative, report it, and go on to the next server. */ if(!ns_msg_getflag(handle, ns_f_aa)){ (void) fprintf(stderr, "%s is not authoritative for %s ", nsList[nsNum], domain); continue; /* nsNum for-loop */ } /* * The response should only contain one answer; if more, * report the error, and proceed to the next server. */ if(ns_msg_count(handle, ns_s_an) != 1){ (void) fprintf(stderr, "%s: expected 1 answer, got %d ", nsList[nsNum], ns_msg_count(handle, ns_s_an)); continue; /* nsNum for-loop */ } /* * Expand the answer section record number 0 into rr. */ if (ns_parserr(&handle, ns_s_an, 0, &rr)) { if (errno != ENODEV){ fprintf(stderr, "ns_parserr: %s ", strerror(errno)); } } /* * We asked for an SOA record; if we got something else, * report the error and proceed to the next server. */ if (ns_rr_type(rr) != ns_t_soa) { (void) fprintf(stderr, "%s: expected answer type %d, got %d ", nsList[nsNum], ns_t_soa, ns_rr_type(rr)); continue; /* nsNum for-loop */ } /* * Set cp to point the the SOA record. */ cp = (u_char *)ns_rr_rdata(rr); /* * Skip the SOA origin and mail address, which we don't * care about. Both are standard "compressed names." */ ns_name_skip(&cp, ns_msg_end(handle)); ns_name_skip(&cp, ns_msg_end(handle)); /* cp now points to the serial number; print it. */ (void) printf("%s has serial number %d ", nsList[nsNum], ns_get32(cp)); } /* end of nsNum for-loop */ }
Notice that we use recursive queries when we call gethostbyname, but nonrecursive queries when we look up the SOA record. gethostbyname may need to query other nameservers to find the host’s address. But we don’t want the nameserver querying another server when we ask it for the SOA record; it’s supposed to be authoritative for this zone, after all. Allowing the nameserver to ask another server for the SOA record would defeat the error check.
The next two routines print out error messages:
/**************************************************************** * nsError -- Print an error message from h_errno for a failure * * looking up NS records. res_query( ) converts the DNS * * message return code to a smaller list of errors and * * places the error value in h_errno. There is a routine * * called herror( ) for printing out strings from h_errno * * like perror( ) does for errno. Unfortunately, the * * herror( ) messages assume you are looking up address * * records for hosts. In this program, we are looking up * * NS records for zones, so we need our own list of error * * strings. * ****************************************************************/ void nsError(error, domain) int error; char *domain; { switch(error){ case HOST_NOT_FOUND: (void) fprintf(stderr, "Unknown zone: %s ", domain); break; case NO_DATA: (void) fprintf(stderr, "No NS records for %s ", domain); break; case TRY_AGAIN: (void) fprintf(stderr, "No response for NS query "); break; default: (void) fprintf(stderr, "Unexpected error "); break; } } /**************************************************************** * returnCodeError -- print out an error message from a DNS * * response return code. * ****************************************************************/ void returnCodeError(rcode, nameserver) ns_rcode rcode; char *nameserver; { (void) fprintf(stderr, "%s: ", nameserver); switch(rcode){ case ns_r_formerr: (void) fprintf(stderr, "FORMERR response "); break; case ns_r_servfail: (void) fprintf(stderr, "SERVFAIL response "); break; case ns_r_nxdomain: (void) fprintf(stderr, "NXDOMAIN response "); break; case ns_r_notimpl: (void) fprintf(stderr, "NOTIMP response "); break; case ns_r_refused: (void) fprintf(stderr, "REFUSED response "); break; default: (void) fprintf(stderr, "unexpected return code "); break; } }
To compile this program using the resolver and nameserver routines in libc:
%cc -o check_soa check_soa.c
Or, if you’ve newly compiled the BIND code as we describe in Appendix C and want to use the latest header files and resolver library:
%cc -o check_soa -I/usr/local/src/bind/src/include
check_soa.c /usr/local/src/bind/src/lib/libbind.a
Here is what the output looks like:
%check_soa mit.edu
BITSY.MIT.EDU has serial number 1995
W20NS.MIT.EDU has serial number 1995
STRAWB.MIT.EDU has serial number 1995
If you look back at the shell script output, it looks the same, except that the shell script’s output is sorted by the nameserver’s name. What you can’t see is that the C program ran much faster.
If using the shell to parse nslookup’s output seems too awkward and writing a C program seems too complicated, consider writing your program in Perl using the Net::DNS module written by Michael Fuhr. You’ll find the package at http://www.perl.com/CPAN-local/modules/by-module/Net/Net-DNS-0.12.tar.gz.
Net::DNS treats resolvers, DNS messages, sections of DNS messages, and individual resource records as objects and provides methods for setting or querying each object’s attributes. We’ll examine each object type first, then give a Perl version of our check_soa program.
Before making any queries, you must first create a resolver object:
$res = new Net::DNS::Resolver;
Resolver objects are initialized from your resolv.conf file, but you can change the default settings by making calls to the object’s methods. Many of the methods described in the Net::DNS::Resolver manual page correspond to fields and options in the _res structure described earlier in this chapter. For example, if you want to set the number of times the resolver tries each query before timing out, you can call the $res->retry method:
$res->retry(2);
To make a query, call one of the following methods:
$res->search $res->query $res->send
These methods behave like the res_search, res_query, and res_send library functions described in the C programming section, though they take fewer arguments. You must provide a domain name, and you can optionally provide a record type and class (the default behavior is to query for A records in the IN class). These methods return Net::DNS::Packet objects, which we’ll describe next. Here are a few examples:
$packet = $res->search("terminator"); $packet = $res->query("movie.edu", "MX"); $packet = $res->send("version.bind", "TXT", "CH");
Resolver queries return Net::DNS::Packet objects, whose methods you can use to access the header, question, answer, authority, and additional sections of a DNS message:
$header = $packet->header; @question = $packet->question; @answer = $packet->answer; @authority = $packet->authority; @additional = $packet->additional;
DNS message headers are returned as Net::DNS::Header objects. The methods described in the Net::DNS::Header manual page correspond to the header fields described in RFC 1035 and in the HEADER structure used in C programs. For example, if you want to find out if this is an authoritative answer, call the $header->aa method:
if ($header->aa) { print "answer is authoritative "; } else { print "answer is not authoritative "; }
The question section of a DNS message is returned as a list of Net::DNS::Question objects. You can find the name, type, and class of a question object with the following methods:
$question->qname $question->qtype $question->qclass
The answer, authority, and additional sections of a DNS message are returned as lists of Net::DNS::RR objects. You can find the name, type, class, and TTL of an RR object with the following methods:
$rr->name $rr->type $rr->class $rr->ttl
Each record type is a subclass of Net::DNS::RR and has its own type-specific methods. Here’s an example that shows how to get the preference and mail exchanger out of an MX record:
$preference = $rr->preference; $exchanger = $rr->exchange;
Now that we’ve described the objects Net::DNS uses, let’s look at how to use them in a complete program. We’ve rewritten check_soa in Perl:
#!/usr/local/bin/perl -w use Net::DNS; #---------------------------------------------------------------------- # Get the zone from the command line. #---------------------------------------------------------------------- die "Usage: check_soa zone " unless @ARGV == 1; $domain = $ARGV[0]; #---------------------------------------------------------------------- # Find all the name servers for the zone. #---------------------------------------------------------------------- $res = new Net::DNS::Resolver; $res->defnames(0); $res->retry(2); $ns_req = $res->query($domain, "NS"); die "No name servers found for $domain: ", $res->errorstring, " " unless defined($ns_req) and ($ns_req->header->ancount > 0); @nameservers = grep { $_->type eq "NS" } $ns_req->answer; #---------------------------------------------------------------------- # Check the SOA record on each name server. #---------------------------------------------------------------------- $| = 1; $res->recurse(0); foreach $nsrr (@nameservers) { #------------------------------------------------------------------ # Set the resolver to query this name server. #------------------------------------------------------------------ $ns = $nsrr->nsdname; print "$ns "; unless ($res->nameservers($ns)) { warn ": can't find address: ", $res->errorstring, " "; next; } #------------------------------------------------------------------ # Get the SOA record. #------------------------------------------------------------------ $soa_req = $res->send($domain, "SOA"); unless (defined($soa_req)) { warn ": ", $res->errorstring, " "; next; } #------------------------------------------------------------------ # Is this name server authoritative for the zone? #------------------------------------------------------------------ unless ($soa_req->header->aa) { warn "is not authoritative for $domain "; next; } #------------------------------------------------------------------ # We should have received exactly one answer. #------------------------------------------------------------------ unless ($soa_req->header->ancount == 1) { warn ": expected 1 answer, got ", $soa_req->header->ancount, " "; next; } #------------------------------------------------------------------ # Did we receive an SOA record? #------------------------------------------------------------------ unless (($soa_req->answer)[0]->type eq "SOA") { warn ": expected SOA, got ", ($soa_req->answer)[0]->type, " "; next; } #------------------------------------------------------------------ # Print the serial number. #------------------------------------------------------------------ print "has serial number ", ($soa_req->answer)[0]->serial, " "; }
Now that you’ve seen how to write a DNS program using a shell script, a Perl script, and C code, you should be able to write one of your own using the language that best fits your situation.