百度空间 | 百度首页 
 
查看文章
 
DNS and the resolver function 2
2009-07-03 19:32


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!!

类别:运维 | 添加到搜藏 | 浏览() | 评论 (0)
 
最近读者:
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu