DNS and the resolver function

Programming the resolver library

 

by

Lord Soth

 

All rights reserved to the Immortal Descendants

Visit us on the web at : http://www.ImmortalDescendants.org/

 

 

Contents:

 

1.1  System/Reader requirements.

                1.2  Introduction.

2.1  The normal DNS functions.

3.1  The resolver functions.

                3.2  A DNS message format (basic)

                3.3  Example code: doing MX queries.

 

1.1  - System/Reader Requirements:

 

Any UNIX type operating system (OS).

Or, if you really must, WinSock32, but

it's not that great of an implementation

of TCPIP  :p

The reader requirements is to have a good

understanding of UNIX (nix) network programming.

I'ts not required to know Berkley sockets or any other interface like X/Open,

but it will be useful in understanding the entire scheme.

 

Introduction

 

OK. So you've been wondering what this essay is really about.

This text is meant for network programmers that wish to understand a little more

about the workings of the DNS interface in the nix environment, and most probably

in WinSock as well (but with different headers, etc..)

The problem lies in the fact that there aren't that many functions to deal with

DNS queries of any kind. There are the two basic functions to do an A query and

a PTR query. One asks the DNS to convert an IP address to a host name,

which is a human readable string (with dots of course heh), and the other is a

request to the DNS server to convert an address (host name) to an IP address,

which is basically a 4 byte value.

There are a few more functions to deal with DNS but those functions don't really

give the programmer all the functionality he or she might need in the program.

One such example is given here and is taken from my own personal experience.

Performing MX queries to a DNS server.

The MX resource record (RR) contained in the DNS configuration files let the DNS

server know which host (hostname+IP) is handling mail exchange for the domain.

This is a very important RR because any one wishing to send Email to a domain,

has to know to which host to issue the commands and send the data.

After we'll review the resolver functions and the DNS message format, I'll give

an example of how to perform MX queries.

The idea behind this text was that not many people know how to operate the

resolver functions properly, if at all. This is mostly because of lack of

documentation about DNS. A programmer can only find the funtion's prototypes

in the manual page, but it still doesn't help understand what possible values

can pass to the function, and what is the form of the reply.

After we've stated this, let's start.

 

2.1 - The normal DNS functions (gethostbyname, gethostbyaddr)

 

Let's begin at the beginning. We'll now explore the wonderful and exciting world

of performing A and PTR queries (ok maybe it's not THAT exciting, so sue me hehe)

The two functions that are basically responsible for doing these tasks for us

are:

 

struct hostent *gethostbyname( const char *hostname);

 

struct hostent *gethostbyaddr( const char *addr, size_t len, int family);

 

 

Both of these functions return a valid pointer to a hostent structure containing

the response, or NULL if there was an error of some kind.

Notice one VERY IMPORTANT thing about the gethostbyaddr function. In the docs,

the function prototype if wrong. The first argument to the function is NOT a

pointer to a string (char *), but is actually a pointer to an in_addr structure.

So the prototype should be:

 

struct hostent *gethostbyaddr( struct in_addr *addr, size_t len, int family);

 

One more thing to notice is that the required header file for these functions is

<netdb.h>

You can basically figure out which header is needed by using the manual page for

the function.

 

OK, now let's describe these functions. The gethostbyname function converts a

hostname (A query) to an IP adderss. The gethostbyaddr function does the opposite

and returns the hostname for a specified IP address.

The specifics of how it's done is not important, although in the next few

sections we'll talk a little about the functions that are used to perform DNS

queries.

For now, we only need to know the reply type for those functions.

The reply is a pointer to a static hostent structure that is declared inside the

resolver library. This of course pops a problem: What if we call any of the

functions in a multithreaded application ? After all, they both share the same

reply memory area!

Before we go into that, let's talk a little about the reply itself, the hostent

structure.

The structure is defined as follows:

 

struct hostent {

                char *h_name;                      // official (canonial) name of the host

                char **h_aliases                  // pointer to an array of pointers to alias names (if any)

                int h_addrtype;                    // host address type: AF_INET (IPv4) or AF_INET6 (IPv6)

                int n_length;                         // length of address (4 for IPv4, 16 for IPv6)

                char **h_addr_list;             // pointer to an array of pointers with IPv4/6 addresses

};

#define   h_addr   h_addr_list[0]

 

So the important thing for us is when we call gethostbyname for example, we expect

to receive the IP address of the host.

To access the member variable that contains the IP address we'll use the following

syntax, assuming pHost is a pointer to a hostent struct (declared somewhere in our

prog):

 

pHost->h_addr_list[0]

 

This points to a small segment of memory that contains the first IP address for the

host, and hence, the one that we require. Just remember, it's in network byte order

and should be converted to the presentation form for printing, for example:

 

printf("\nThe returned IP address is: %s\n", inet_ntoa( pHost->h_addr_list[0]) );

 

The above line will convert the network byte ordered IP address to the presentation

form. There is also the reverse function, inet_aton.

In any case, getting back to our discussion, we now can see which part of the

hostent structure contains the IP address. Now let's say we want to call

gethostbyaddr and print the returned canonial name, which is accessed as follows:

 

pHost->h_name

 

Simple, isn't it ? Well, YES! IT IS!

So the printing would be as follows:

 

printf("\nThe returned canonial name is : %s\n", pHost->h_name);

 

And that is basically all we need to know about the most basic DNS functions.

If we're not coding a multithreaded application, we can safely use these functions.

Those two functions are non-reentrant because they use a static hostent pointer,

which is shared between functions. If we were to call them at once, one will

overwrite the other's data.

But for multiprocessed applications, which contain independent processes, this is

not a problem.

For those cases that do pose a problem to us, there are versions of these functions

that are reentrant, and there is also the very good all-in-wonder function

getaddrinfo.

We'll not dwelve into it, but it's basically based on the same guidelines of thses

two functions. It just packs all the info into one place and it also handles

multiple IPs and both TCP and UDP.

By presenting these two basic functions we now know how to do A and PTR queries.

The A resource record is used to map between host names and IP addresses.

The PTR resource record is used to map between IP addresses and host name.

 

But we have other types of records, such as MX, NS, etc.. What will we do then ?

The answer, although we don't like to hear it, is to write our own function

to do the job, or alternatively, use someone else's library. There are not many

and you'll have to put your trust in someone else's hands.

 

The resolver, which is the portion that does all the DNS resolving for the

program, is actually a part of the application, and it's linked at link time

by the compiler. The resolver functions are used to do all the querying, even

inside the gethostbyname and gethostbyaddr functions. Those same resolver

functions are what we'll have to use in order to do our own query.

 

The next section talks about the resolver functions (the important ones anyway).

 

3.1 - The resolver functions

 

The lowest level functions that we can use to perform DNS queries are called the

resolver functions. The normal set of functions looks like res_xxx where xxx is

a name. Those however are not the only resolver functions, other names exist.

The usable functions are the functions that are listed in the manual page for

the resolver (man resolver).

I'll now describe some of the most important of them:

 

int res_query( char *host, int class, int type, u_char *answer, size_t anslen);

int res_search( char *host, int class, int type, u_char *answer, size_t anslen);

int dn_expand( u_char *answer, u_char *endofanswer, u_char *ptr, char *buffer, int buflen);

int dn_skipname( u_char *ptr, u_char *eom);

 

res_query - This function performs a DNS query of the specified class and type,

for the domain (host) and puts the response in the answer buffer of size anslen

bytes.

This function is basically the function that does a real normal query on it's own.

In the next section we'll see how the DNS message looks like and it's important

to know that this function creates a DNS question message and sends it to the

DNS server of the specified host.

 

res_search - Does the same as res_query but considers some of the flags that can

be set in the _res.options member of the _res structure (which is defined in

the <arpa/nameser.h> header file). Typical flags are a flag to operate using

default domain names (any domain that doesn't end with a period will have the

default domain name appended to it), and another useful flag is to tell the

server to recursively seek the answer, going to other servers as well.

IMO the best idea is to use the res_search function. It has the most chance of

succeeding, and it requires less care on our part.

 

dn_skipname - This function assumes the unsigned char pointer (ptr) is a

pointer inside the answer buffer in which the value that is stored is a name

pointer (we'll get to that part soon).

It needs the eom pointer to know it's not out of the memory location of the

answer buffer (VERY IMPORTANT!!).

It then measures the name pointer's length and returns it. This is useful

for, well, you guessed it, skipping names :)

 

dn_expand - This function needs the answer address, a pointer to the end

of the answer buffer, a pointer inside the buffer that should point to

a name pointer, a buffer for storing the decompressed name, and the length

of that last buffer.

This function will decompressed the compressed name pointed to by the

pointer at the address in which ptr is pointing to.

The decompressed name will be put in (buffer) and will not exceed buflen

in size.

 

Now the questions are popping. What are all those buffers, answers, classes,

types and the other things ??

You're probably asking yourself these questions for a very good reason. here

lies the resolver function's problem. This info is not documented in the

programming manual. You can find bits of information in different places.

For example, I found that the class and type parameters can be any one of

the values that are defined in the <arpa/nameser.h> file.

This header file contains definitions for the resolver library.

The usual class (actually, this class is used almost exclusively) is the

C_IN class. This class tells the resolver that the medium we're operating

on is the ARPA Internet. This is not trivial as the resolver was designed

by the guys at Berkley to operate on other networks as well, not just

IP driven networks, theoretically.

The type parameter will be the type of query needed. For an MX query for

example, we'll specify a type of T_MX.

Again, all these are written in the header file.

How did I find this header file ?

 

Well, I've searched the header files using the following command to see

what comes up:

 

fgrep 'T_MX' $(find /usr/include -name '*.h')

 

This command line uses the find command to search for any files with an

extension of h (header files) and it feeds those as arguments to the

fgrep command which looks inside each for the pattern (T_MX in this case).

Notice that this is different from piping because pipes link the stdout

of a program to the stdin of another program. What we've done here is

to use the output of one program as command arguments for another program.

If we were to pipe the find stdout to the fgrep stdin, fgrep will look

inside the data it receives for T_MX, but the data it receives is a list

of files to look into them!

So after I've explained this little shell programming trivial trick, let's

move on.

Other information is still required by us to get a picture of what is happening

with those resolver functions.

The only thing left for us to figure out is how the answer looks like. This

should also explain what is all this compressing/decompressing we see in there.

 

Let me save you the trouble of looking into other people's sources to figure

this one out. The answer buffer is filled with a DNS message, which is no more

than a DNS packet (including it's own header).

So the resolver functions do the query and return the DNS packet after the

kernel stripped the IP and UDP/TCP headers of the packet (not looking at any

additional info put in by the interface. An Ethernet interface would make

all the headers+data into a thing called a Frame).

The last thing for us to do is to translate the returned packet into what we

need to use in our application.

For this end we'll need to know about the DNS message format. My biggest

source for information on this is the book TCPIP Illustrated Vol. 1, by

W. Richard Stevens.

It's a comprehensive overview of networking and protocols, including DNS.

There are of course a few loose ends in this book, as it can't cover

everything, but it covers a LOT.

The rest you can find in the various RFCs. An example to this will be given

although we won't actually look into any RFCs.

And so, the next section describes the DNS message format briefly, and we'll

focus on one type of response when we get to the MX query section.

Maybe one other thing worth mentioning about the resolver library is that

you can't just include the <resolv.h> file in your program and expect it to

work. It wasn't designed to be one of the libraries that gets linked by the

compiler by default. Hence you'll need to add the -lresolv switch to your

compilation command (in your Makefile or not), so that the compiler will link

the necessary libraries for you to use in your code.

 

3.2 - A DNS message format

 

In the last section we came to see that we now only need to know the message

format in order to perform a query. In fact, our situation is even more

simplified by the fact that we don't actually have to create any kind of DNS

message. This is a bit vague, since in the last section I've stated we need

to know the format of the message so we can get the information we need from

it.

The point is that DNS messages can contain questions and answers at the same

time, and because the res_xxx function we used to make a query actually performs

the creation and sending of a DNS question to a DNS server, we only need to deal

with the answer buffer.

The information dealing with what fields exist in the question buffer is

therefore not our concern. I could have written them all here of course, but

that would just be copying it from TCPIP Illustrated, and therefore I decided

not to include them.

What I will have to include is the message itself without going into detail

about the question portion, and it looks as follows.

Notice this is a bits/bytes diagram. The lower you go, the bigger the offset

is in the buffer. Each block represents an element and the bits at the top

let you figure out the size of each element.

 

 

0                                   15  31                                     32

------------------------------------------------------------------

       identification           |                    flags        

------------------------------------------------------------------

  number of questions  |   number of answer RRs 

------------------------------------------------------------------                       

num. of authority RRs  |    num. of additional RRs

------------------------------------------------------------------

                                questions

------------------------------------------------------------------

                                 answers

------------------------------------------------------------------

                                authority

------------------------------------------------------------------

                     additional information

------------------------------------------------------------------

 

Of all those fields, the important are the number of questions, number of

answers and the answers section itself.

The number of questions will be used to skip the question portion of the

DNS message. The DNS server replies with the original questions themselves

and so we'll need to know how many of them to skip.

The number of answers is trivial. We need to know how many answers the server

has given to our query.

The last part is the most important one. In the answer section lies the real

information we need from the server. The format of that information differs

between types of queries and it's up to us to know which type of answer it

is and act accordingly.

For that end, I've included the diagram for the answer RR portion of the DNS

message.

 

0                                   15  31                                     32

------------------------------------------------------------------

|                               domain name                                |

------------------------------------------------------------------

|              type                   |                    class                |

------------------------------------------------------------------

|                         Time To Live (TTL)                           |

------------------------------------------------------------------

| resource data length   |                                             |

-------------------------------                                              |

|                             resource data                                 |

------------------------------------------------------------------

 

Let's now describe each field:

 

Domain Name - The compressed default domain name of the domain we queried.

Believe it or not, I discovered this the hard way, while studying sendmail's

source code. At first I didn't look at this diagram that I had in TCPIP

Illustrated and I figured this is the answer itself, only it didn't make sense

because I knew the REAL answer to the MX query I was trying to make. This

should not be surprising as I'm the one who configured my LAN's DNS server.

The domain name field is a 32 bit pointer field. DNS uses a compression scheme

on which it stores names in the data block of the message, and it stores

pointers to these names in the various fields (such as this one). Any name that

repeats itself will be replaced by the pointer itself, which will save room

in the target header. The functions that convert from a pointer to a name and

backwards are the dn_comp and dn_expand functions.

We'll stick with the dn_expand function as it is the one we need to decypher

the response message.

 

Type - This is a 16 bit field describing the type of query answer. The various

types are located inside the <arpa/nameser.h> header file, as I've stated

before. This field designates what kind of answer it is. This is a very

important point to consider. If several questions were issued in the same

message, to the DNS server, it'll reply with several types of answers. Therefore

we have to check the answer type (this field) in order to know what to do with

it.

 

Class - The class of medium we use for the message. This is usually C_IN, which

states the ARPA Internet.

 

TTL - This is the Time To Live value of the packet. As you may or may not know,

the TTL is the "timer" that keeps a packet alive on the net. The designers of

IP made this so packets won't float around forever on the net. If for example,

a TCP connection tries to resend it's packet (say a timeout value for and

acknowledgement has elapsed), there would be more than one instance of the

packet on the net. Basically the TTL is a hop counter, not a real timer.

Whenever a packet hops from one router/machine to another, that machine's

kernel decreases 1 from the TTL. A router/machine that sees a packet with a TTL

of 1 will discard the packet and send an ICMP message packet back to the

original sender of the packet, so the later will know to resend it.

This and other really neat tricks are what makes TCP a great protocol, however

DNS mainly uses UDP, unless the size of the info is too large.

For those interested, the TCPIP Illustrated Vol. 1 book describes the UDP and

TCP protocols in a remarkable way for just one book, I suggest reading it.

 

Resource data length - Naturally, the length of the resource data itself, in

bytes. Notice that the data buffer of an answer starts right after this field,

which is a 16 bit field.

 

We've looked at a fairly basic way into the DNS message format, yet there are

other issues with it. One of which is the data format of the answer itself.

We've seen that the answer starts right after the resource data length field,

yet we don't have any information about the format of the actual data.

For these ends, even TCPIP Illustrated will not help. For that we'll need to

go and look for the DNS RFCs and search for the data format.

Luckily in the MXRR example I've included we won't need it since the format

is fairly simple. If I understood it from source files, so can anyone.

 

With that in mind, we have to write code that will traverse the answer buffers,

look into each, extract the necessary information and so forth.

I'll give a brief description of an MX query in the next section, along with

the source code for a function that does the magic.

 

3.3 - Example code: performing MX queries to a DNS server

 

Alright, this is the last part of this essay, and arguably the most important

one, since it contains the source for an MX query function.

Basically, one can learn everything he needs to know from the source itself,

without looking at any books. However, it took me several hours to figure

everything out, for exampkle.

 

To perform an MX query using the res_search function we'll need to declare

a few things, then call it in the following manner:

 

res_search( host, C_IN, T_MX, (u_char *) &answer, sizeof( answer));

 

The various parameters are:

 

host - a string or a pointer to a string (the same thing really) that contains

the domain name to query. Notice that with the res_search function you can

specify just the leftmost part of the domain, if you query your domain, and

the full domain name if you query other domains.

 

C_IN - The class value, ARPA Internet.

T_MX - The type of query, in this case MX query.

 

&answer - A pointer to an unsigned char buffer that will contain the answer

which is the entire response message itself.

The answer buffer is usually defined and then declared as follows:

 

typedef union {

                HEADER               hdr;                         // defines a DNS answer header

                u_char buffer[MAXSIZE];                 // define a buffer of MAXSIZE bytes

} qbuf;

 

This will declare a union type containing a header and a buffer. The header

we define is a header defined in the <arpa/nameser.h> header file. This header

contains various flags and information, some of which are important to us,

such as the number of answers and questions.

To declare such a union type:

 

qbuf answer;

 

Will of course declare a qbuf type of union named answer.

I would suggest the MAXSIZE value be compatible with the BIND max packet size.

Since BIND is the default DNS distribution (and just so you know it's doing

the job quite admireably if I may say so), it is the best way to insure

compatibility in the response size. BIND defines the max packet size as 8192

bytes.

 

sizeof( answer) - This should really be trivial to any C programmer :)

 

Assuming the function succeeded, that is, the return value is the amount of

bytes written to the answer buffer (-1 if an error occured), we can start

the data extraction.

First we'll need to skip the 12 byte DNS header, then skip all the questions.

Once we do that we'll reach the answers portion of the message, and that is the

part we are usually interested in.

In an MX answer we have first the domain name to extract to a temporary location

and then to skip it, the type field to check (have to be T_MX for us to use it)

and skipping the class field altogether.

Then we can also skip the size of the resource if we so desire and then we reach

the real answer itself. Note that each answer has all the following fields of

course, because each answer can be of a different type and class and length.

When we reach the MX answer itself, the first 16 bit are the priority of the

reply. If you know a little DNS you'll know you can specify more than one MX

host to handle mail in your domain. Each has a priority. The priority is a 16

bit value and the lower it is, the more prefered the host is.

So this field is naturally quite important for us. We'll extract it and after

it comes the pointer to the reply itself. For the pointer we'll again use

dn_expand and store the answer in our buffer or some other field.

I've found that the sendmail's implementation of the MX query function is the

best I've seen so I've decided to write my function based on their code.

 

And without further delay, I present to you 2 parts of codes. The first are

things to put in your header file in preparation of the function itself, and

the second is the function's source itself. Enjoy.

 

Stuff to put in header file/s

-------------------------------------------------------------------------------------------------------------------

#ifndef MAXPACKET                        // make sure a maximum packet size is declared by BIND

#define MAXPACKET 8192              // BIND maximum packet size

#endif

#define MAXMXHOSTS 5                // max number of MX records returned

#define MXBUFFERSIZE (128 * MAXMXHOSTS)

#ifndef HFIXEDSZ                              // make sure header size is declared

#define HFIXEDSZ 12

#endif

 

// definitions of return codes for your function can go in here..

 

// define custom data types

typedef union

{

  HEADER hdr;                                     // define a header structure

  u_char qbuf[MAXPACKET];          // define a query buffer

} mxquery;

 

The function code itself. It is declared external so you can call it from different source fils.

---------------------------------------------------------------------------------------------------------------------------

 

extern int GetMXRecord( char *dname, char **mxhosts, u_short *mxprefs, char *mxbuf)

{

  // a function that queries a DNS server for MX records for the specified host

  // host - string containing host name

  // mxhosts - a pointer to an array of pointers each pointing to an MX record (in the same buffer, for efficiency)

  // mxprefs - a pointer to an array of unsigned shorts that specify the preferances of each MX host

  // mxbuf - a pointer to an allocated buffer that will contain all the host names (each name's pointer is in the mxhosts array)

 

  u_char *end, *puChar;                     // pointers to end of message, generic u_char pointer.

  int i, j, n, nmx;

  char *pByte;                                       // generic char pointer

  HEADER *hp;                                    // points to a header struct

  mxquery answer;                                // declare an mxquey buffer

  int answers, questions, buflen;

  u_short pref, type, *pPrefs;

  struct hostent *h;                              // for the A record, if needed

 

  // check pointers

  if ( mxprefs == NULL)

    return ( MX_NOBUFFERMEM);

  if ( mxhosts == NULL)

    return ( MX_NOBUFFERMEM);

  if ( mxbuf == NULL)

    return ( MX_NOBUFFERMEM);

 

  // make query

  errno=0;

  n = res_query( dname, C_IN, T_MX, (u_char *) &answer, sizeof( answer));

  if ( n < 0)

    {

      // handle error conditions

      switch( h_errno)

                {

                case NO_DATA:

                case NO_RECOVERY:

                  // no MX RRs, try the A record..

                  h = gethostbyname( dname);

                  if ( h != NULL)

                    {

                      // returned a resolved result, store

                      if ( h->h_name != NULL)

                                {

                                  if ( strlen( h->h_name) != 0)

                                    snprintf( mxbuf, MXBUFFERSIZE-1, h->h_name);

                                }

                      else

                                snprintf( mxbuf, MXBUFFERSIZE-1, dname);

                      // set the arrays

                      nmx=0;

                      mxprefs[nmx]=0;

                      mxhosts[nmx]=mxbuf;

                      nmx++;

                      return( nmx);

                    }

                  return( MX_NODATA);

                  break;

                case TRY_AGAIN:

                case -1:

                  // couldn't connect or temp failure

                  return( MX_TEMPFAIL);

                  break;

                default:

                  return( MX_UNKNOWNERROR);

                  break;

                }

      // who knows what happened

      return( MX_UNKNOWNERROR);

    }

  // make sure we don't exceed buffer length

  if ( n > sizeof( answer))

    n = sizeof( answer);

 

  // skip the question portion of the DNS packet

  hp = (HEADER *) &answer;

  puChar = (u_char *) &answer + HFIXEDSZ;                // point after the header

  end = (u_char *) &answer + n;                                        // point right after the entire answer

  pPrefs = mxprefs;                                                               // initialize the pointer to the array of preferences

 

  for( questions = ntohs((u_short)hp->qdcount) ; questions > 0 ; questions--, puChar += n+QFIXEDSZ)

    {

      // loop on question count (taken from header), and skip them one by one

      if ( (n = dn_skipname(puChar, end)) < 0)

                {

                  // couldn't account for a question portion in the packet.

                  return ( MX_QUERROR);

                }

    }

  // initialize and start decompressing the answer

  nmx = 0;

  buflen = MXBUFFERSIZE-1;

  pByte = mxbuf;                                                                   // point to start of mx hosts string buffer

  ZeroMemory( mxbuf, MXBUFFERSIZE);

  ZeroMemory( mxhosts, MAXMXHOSTS * 4);

  ZeroMemory( pPrefs, MAXMXHOSTS * sizeof(u_short));

  answers = ntohs((u_short)hp->ancount);                     // number of answers

 

  while( (--answers >= 0) && (puChar < end) && (nmx < MAXMXHOSTS-1) )

    {

      // puChar constantly changes (moves forward in the answer buffer)

      // pByte points to the mx buffer, moves forward after we stored a host name

 

     // decompress the domain's default host name into the buffer so we can skip it and check if it's an     

     // MXhost

      if ( (n = dn_expand( (u_char *) &answer, end, puChar, (char *)pByte, buflen)) < 0)

                break;                                                     // error in answer

     

      puChar += n;                                                   // skip the name, go to its attributes

      GETSHORT( type, puChar);                         // get the type and move forward

      puChar += INT16SZ + INT32SZ;                 // skip the class and TTL portion of the answer RR

      GETSHORT( n, puChar);                              // get the resource size and move on

      if ( type != T_MX)

                {

                  // type of record is somehow NOT an MX record, move on

                  puChar += n;

                  continue;

                }

      GETSHORT( pref, puChar);                          // get the preference of the RR and move on

      if ( (n = dn_expand( (u_char *) &answer, end, puChar, (char *)pByte, buflen)) < 0)   // expand the MXRR

                break;                                                     // error in decompression

      puChar += n;

 

      // store it's attributes

      pPrefs[nmx] = pref;

      mxhosts[nmx] = pByte;

      nmx++;

      n = strlen( pByte);

      pByte += n+1;                                 // make sure it's null terminated, notice the buffer was set to 0 initially

      buflen -= n+1;           

    }

  // in case the records aren't sorted, bubble sort them

  for( i=0 ; i < nmx ; i++)

    for( j=i+1 ; j < nmx ; j++)

      if ( pPrefs[i] > pPrefs[j])

                {

                  int temp;

                  char *temp2;

                 

                  temp = pPrefs[i];

                  pPrefs[i] = pPrefs[j];

                  pPrefs[j] = temp;

                  temp2 = mxhosts[i];

                  mxhosts[i] = mxhosts[j];

                  mxhosts[j] = temp2;            

                }

  // remove duplicates

  for( i=0 ; i < nmx-1 ; i++)

    {

      if ( strcasecmp( mxhosts[i], mxhosts[i+1]) != 0)

                continue;

      else

                {

                  // found a duplicate

                  for( j=i+1 ; j < nmx ; j++)

                    {

                      mxhosts[j] = mxhosts[j+1];

                      pPrefs[j] = pPrefs[j+1];

                    }

                  nmx--;                                  // 1 less MX record

                }

    }

  // all done, bug out

  return nmx;

}

-------------------------------------------------------------------------------------------------------

 

The ZeroMemory function

-------------------------------------------------------------------------------------------------------

extern int ZeroMemory( void *ptr, int size)

{

  // zeroes out a memory buffer

  if ( memset( ptr, 0, size) < 0)

    return -1;

  else return 1;

}

-------------------------------------------------------------------------------------------------------

 

Comments

 

Take a look at the code. At first let's talk some about the parameters to this function.

The host param is understood I hope. The mxhosts param is a pointer to an array

of char pointers. So in the caller code you'll have to declare something along

the following lines:

 

char *mxhosts[MAXMXHOSTS];

 

You should then pass this to the function. You should pass the address of the

first pointer of course, which is &mxhosts[0].

These pointers will at the end point to a big buffer that will contain all the

answers. You should also declared such a buffer in the following manner:

 

char mxbuf[MXBUFFERSIZE];

 

The buffer size is 128 times the maximum number of MX hosts wanted.

The pointers to specific parts in this buffers will be set in the GetMXRecord

function.

You might be asking yourself why this weird looking scheme. Well, while I was

going over sendmail's source for this function I realized the efficiency

in this model. If all the answers are located in the mxbuf, and you naturally

may want to sort the answers so that the lowest priority answer is the first,

you can declare pointers to each answer, and it would be much easier to sort

an array of pointers against a uniform buffer with several answers each

different in size.

Look at the bubble sort part of the function, also very trivial to code, but

it appears like this in the sendmail source file as well.

The mxprefs array is an array of unsigned shorts you need to pass so that

the function can fill in the priorities of each answer.

You will usually declare it in the called routine and pass it like this:

 

u_short prefs[MAXMXHOSTS];

 

Then you should pass a pointer to the beginning of this array: &prefs[0].

 

Another thing to notice in this function is the GETSHORT macro. This macro

gets the short value pointer to by a pointer that points to some memory.

The short type can be different in size accross platform therefore it's

very important to use such a macro that is defined in the standard library

of the OS. The most important thing about this macro is that it also advances

the pointer to after the short value, so you can continue from there.

 

The last two parts are the bubble sorter that will sort the pointers according

to each's preference value, and the part that removes any duplicates from

the list (you may never know).

It's important to note that a DNS server might contain MX resource records that

have the same priority. In that case, you can either leave it like it is, or

you can try to somehow differentiate between them so you'll know which one to

put first. Sendmail has a function to generate a weight value for each name

so that it'll know which one to put in first. I consider this to be redundant

because with both the same priority, it doesn't really matter which one is

first. After all, that is the point of the priority field!

IMO this should be left untouched.

Also notice the error handling of the res_search call. You can define your own

error codes to return. You don't have to rely on mine. Note that if you really

want to dig into this, the sendmail's source contains quite an extensive

error controlling, for what end, who knows..

 

Last but not least, the function returns the number of MX hosts it found, and

this value can be used to iterate through the array of pointers (mxhosts)

by the caller routine.

 

Summary

 

I hope I showed you something you were interested in knowing with this essay.

The negotiation with DNS servers is one such subject that doesn't contain

much documentation, and IMO those that do deal with DNS interaction don't

contain nearly enough information about the subject.

That is it for today, let's hope some one will benefit from this little

tutorial of mine.

For any comments/questions/suggestions/bug reports, I can be contacted at:

 

lordsoth8@hotmail.com

lordsoth@immortaldescendants.org

or via ICQ # : 5178515

 

Lord Soth

 

17/11/2000

 

Greetings:

 

Greetings go to ALL the Immortal Descendants members, all the +HCU members, the +Sandman (I owe him special thanks , since because of him I am what I am today), The Snake (my dear friend), The Hobgoblin (just love that guy :), Lazarus (love him too, loved the Laz_Calc.. hehe), and Jeff of course, all those that visit the Newbies Forum, +Fravia, +Frog's print, +tsehp, DQ, _mammon, and all those gods of knowledge that roam the cyberspace and occasionally rear their heads either at +fravia's forum or the newbies forum...

Of course, Ghirrizibo (not sure how to type it) for his wonderful IceDump util, and many other I probably forgot, if I did, I apologize!!