14.2 C Programming with the Resolver Library RoutinesBefore writing any code, though, you need to be familiar with the DNS packet format and the resolver library routines. In the shell script we just wrote, nslookup parsed the DNS packet. In a C program, you have to do the parsing. Let's start this section on programming by looking at the DNS packet format. 14.2.1 DNS Packet FormatYou've seen the DNS packet format before, in Chapter 11, nslookup . It looks like this:
The format of the header section is described in RFC 1035 on pages 26-28 and in Appendix A . 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 response. We'll discuss these routines, the name server 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. 14.2.2 Domain Name StorageAs you can see, the names stored in the DNS packet 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 zero. 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 packet could be devoted to storing names. The DNS authors recognized this, and came up with a simple way to compress domain names. 14.2.3 Domain Name CompressionOften an entire domain name or, at least, the trailing labels of a domain name match a name already stored in the response. 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 packet 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 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 zero, then the length and label follow. As you may have read elsewhere, 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 six bits of the length octet and the next octet - 14 bits total. The pointer is an offset from the start of the DNS packet. 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 seven - the last six bits of the first octet are zeros and the second octet is seven. At offset seven in this buffer, you find the rest of the vaxa domain name: isi.edu . In this example, we only showed compressing two names in a buffer, not a whole DNS packet. A DNS packet would have had a header as well as other fields. This example is intended to give you only an idea of how the domain name compression works. Now the good news: you don't have to care how names are compressed, as long as the library routines do it properly. What you need to know is how parsing a DNS response 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. 14.2.4 The Resolver Library RoutinesThe resolver library contains the routines that you need to write your application. You'll use these routines to generate queries. You'll use the name server library routines, explained next, to parse the response. Here are the header files you must include: #include <sys/types.h> #include <netinet/in.h> #include <arpa/nameser.h> #include <resolv.h> These are the resolver library routines: int res_search(const char *dname, int class, int type, u_char *answer, int anslen)
res_search
is the "highest" level resolver
routine. It is called by
gethostbyname
.
res_search
implements
the search algorithm on 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 "extensions"
from the resolver search list, and calls
res_query
until
it receives a successful response, indicating that it found a valid,
fully qualified name. In addition to implementing the search algorithm,
res_search
looks in the file referenced by
your Therefore, the only parameter that's really of interest to res_search is dname ; the others are just passed to res_query and the other resolver routines. The other arguments are:
res_search returns the size of the response, 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 "midlevel" resolver routines. It does all the real work in looking up the domain name; it makes a query packet by calling res_mkquery , sends the query by calling res_send , and looks at enough of the response to determine if 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 zero. 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 packet. 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:
res_mkquery returns the size of the query packet, 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 packet, msg , in a UDP packet, but it can also send it over a TCP stream. The response packet 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:
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 name server running on the target name server 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 , then 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 . int res_init(void) res_init reads resolv.conf and initializes a data structure called _res (more about that later). All of 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 zero, even if the manpage reserves the right to return -1. 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:
Here are the possible values of h_errno :
14.2.5 The _res StructureEach of the resolver routines (i.e., each routine whose name starts with res_ ) makes use of 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:
14.2.6 The Name Server Library RoutinesThe name server library contains routines you need to parse response packets. 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> Here are the name server library routines: int ns_init_parse(const u_char *msg, int msglen, ns_msg *handle) ns_init_parse is the first routine you must call before you use the other name server library routines. ns_init_parse fills in the data structure pointed to by handle , which is a parameter passed to other routines. The arguments are:
ns_init_parse returns zero on success and -1 when it fails to parse the response 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 response, a pointer to the end of the response, and the size of the response. They are returning data you passed into ns_init_parse . The only argument is:
u_int16_t ns_msg_id(ns_msg handle) ns_msg_id returns the identification from the header section (described earlier) of the response packet. The only argument is:
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 packet. Its arguments are:
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_rcode /* Response Code */ 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 packet. Its arguments are:
ns_s_qd /* Question section */ ns_s_an /* Answer section */ ns_s_ns /* Name Server section */ ns_s_ar /* Additional records sectiona */ 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 . rr is a parameter passed to other name server libarary routines. The arguments are:
ns_parserr returns zero on success and -1 when 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:
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:
If you want to use this routine, look at how it is used in res/res_mkquery.c from the BIND source. 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_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 will use this routine if you parse a name server response, as we do in the example that follows. The arguments are:
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 packet and need to know how much space the compressed name took in the packet so that you can skip over it. 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:
ns_name_skip returns zero if successful. It returns -1 when it fails to uncompress the name. u_int ns_get16(const u_char *cp) void ns_put16(u_int s, u_char *cp) The DNS packets 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 . 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 field (time to live) of a resource record is a 32-bit integer. 14.2.7 Parsing DNS ResponsesThe easiest way to learn how to parse a DNS packet is to look at code that already does it. Assuming that you have the DNS source code, the best file to look through is res/res_debug.c ( BIND 4) or src/lib/resolv/res_debug.c ( BIND 8). This file has fp_query , the routine that prints out the DNS packets in the name server 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 packet. Then use basic UNIX tools, like Perl or awk , to grab what you need. Cricket has been known to wimp out this way. 14.2.8 A Sample Program: check_soaHere is a C program to solve the same problem that we wrote a shell script for earlier: /**************************************************************** * check_soa -- Retrieve the SOA record from each name server * * for a given domain and print out the serial number. * * * * usage: check_soa domain * * * * 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 domain. * * 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 domain's name servers */ void addNameServers(); /* add name servers to our list */ void queryNameServers(); /* grab SOA records from servers */ void returnCodeError(); /* report response packet errors */ /* Maximum number of name servers we will check */ #define MAX _ NS 20 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've set a limit of 20 name servers that this program will check. You will rarely see a zone with more than ten name servers, so an upper limit of 20 should suffice: 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 domain\n", argv[0]); exit(1); } (void) res_init(); /* * Find the name servers for the domain. * The name servers are written into nsList. */ findNameServers(argv[1], nsList, &nsNum); /* * Query each name server for the domain's SOA record. * The name servers are read from nsList. */ queryNameServers(argv[1], nsList, nsNum); exit(0); } The main body of the program is small. We have an array of string pointers, nsList , to store the names of the name servers 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, since 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 name servers for the zone referenced in argv[1] and to store them in nsList . Last, the program calls queryNameServers to query each of the name servers in nsList for the SOA record for the zone. The routine findNameServers follows. This routine queries the local name server for the NS records for the zone. It then calls addNameServers to parse the response packet and store away all the name servers 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 domain 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 packet */ /* * Look up the NS records for the given domain name. * We expect the domain to be a fully qualified name, so * we use res_query(). If we wanted the resolver search * algorithm, we would have used res_search() instead. */ if((responseLen = res_query(domain, /* the domain 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\n", 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\n", 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\n"); exit(1); } /* Expand the name server's name */ if (ns_name_uncompress( ns_msg_base(handle),/* Start of the packet */ ns_msg_end(handle), /* End of the packet */ ns_rr_rdata(rr), /* Position in the packet*/ nsList[*nsNum], /* Result */ MAXDNAME ) /* Size of nsList buffer */ < 0) { /* Negative: error */ (void) fprintf(stderr, "ns_name_uncompress failed\n"); exit(1); } /* * Check the name we've just unpacked and add it to * the list of 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 name server 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 name server 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 server res_send queries. We disable the search list by turning off bits in the options field - all the names that this program handles are fully qualified: /**************************************************************** * queryNameServers -- Query each of the name servers in nsList * * for the SOA record of the given domain. 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 * * domain. * ****************************************************************/ 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 packet */ 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 packet */ 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 default domain before we call gethostbyname(); the * name server names will be fully qualified. */ _res.options &= ~( RES _ DNSRCH | RES _ DEFNAMES ); /* * Query each name server for an SOA record. */ for(nsNum-- ; nsNum >= 0; nsNum--){ /* * First, we have to get the IP address of every server. * So far, all we have are 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\n", 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 packet 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 domain 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 packet. 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\n", nsList[nsNum]); } else { /* anything else: no response */ (void) fprintf(stderr, "There was no response from %s\n", 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\n", 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 the 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\n", 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\n", 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\n", 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\n", 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\n", nsList[nsNum], ns_get32(cp)); } /* end of nsNum for-loop */ } Notice that we use recursive queries when we call gethostbyname , but use nonrecursive queries when we look up the SOA record. gethostbyname may need to query other servers to find the host's address. But we don't want the name server querying another server when we ask it for the SOA record - it's supposed to be authoritative for this zone, after all. Allowing the name server 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 * * packet 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 domains, 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 domain: %s\n", domain); break; case NO_DATA: (void) fprintf(stderr, "No NS records for %s\n", domain); break; case TRY_AGAIN: (void) fprintf(stderr, "No response for NS query\n"); break; default: (void) fprintf(stderr, "Unexpected error\n"); 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\n"); break; case ns_r_servfail: (void) fprintf(stderr, "SERVFAIL response\n"); break; case ns_r_nxdomain: (void) fprintf(stderr, "NXDOMAIN response\n"); break; case ns_r_notimpl: (void) fprintf(stderr, "NOTIMP response\n"); break; case ns_r_refused: (void) fprintf(stderr, "REFUSED response\n"); break; default: (void) fprintf(stderr, "unexpected return code\n"); break; } } To compile this program using the resolver and name server routines in libc : % Or, if you've newly ported the BIND code as we describe in Appendix B, Compiling and Installing BIND on a Sun , and want to use the latest header files and resolver library: % Here is what the output looks like: % If you look back at the shell script output, it looks the same, except that the shell script's output is sorted by the name server's name. What you can't see is that the C program ran much faster. |
|