查看文章 |
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!! |
最近读者: