DNS resolver rewritten, moved to proto/dns.c
authorMichael Brown <mcb30@etherboot.org>
Sat, 30 Apr 2005 14:50:33 +0000 (14:50 +0000)
committerMichael Brown <mcb30@etherboot.org>
Sat, 30 Apr 2005 14:50:33 +0000 (14:50 +0000)
src/core/dns_resolver.c [deleted file]
src/include/dns.h [new file with mode: 0644]
src/include/dns_resolver.h [deleted file]
src/proto/dns.c [new file with mode: 0644]

diff --git a/src/core/dns_resolver.c b/src/core/dns_resolver.c
deleted file mode 100644 (file)
index 52f24a4..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-/**************************************************************************
-*
-*    dns_resolver.c: Etherboot support for resolution of host/domain
-*    names in filename parameters
-*    Written 2004 by Anselm M. Hoffmeister
-*    <stockholm@users.sourceforge.net>
-*
-*    This program is free software; you can redistribute it and/or modify
-*    it under the terms of the GNU General Public License as published by
-*    the Free Software Foundation; either version 2 of the License, or
-*    (at your option) any later version.
-*
-*    This program is distributed in the hope that it will be useful,
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-*    GNU General Public License for more details.
-*
-*    You should have received a copy of the GNU General Public License
-*    along with this program; if not, write to the Free Software
-*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*
-*    This code is using nuts and bolts from throughout etherboot.
-*    It is a fresh implementation according to the DNS RFC, #1035
-*    
-*    $Revision$
-*    $Author$
-*    $Date$
-*
-*    REVISION HISTORY:
-*    ================
-*    2004-05-10 File created
-*    2004-05-19 First release to CVS
-*    2004-05-22 CNAME support first stage finished
-*    2004-05-24 First "stable" release to CVS
-*    2004-08-28 Improve readability, set recursion flag
-***************************************************************************/
-
-#ifdef DNS_RESOLVER
-#include "etherboot.h"
-#include "nic.h"
-#include "dns_resolver.h"
-
-#define        MAX_DNS_RETRIES 3
-#define        MAX_CNAME_RECURSION 0x30
-#undef DNSDEBUG
-
-int    donameresolution ( char * hostname, int hnlength, char * deststring );
-
-/*
- *     dns_resolver
- *     Function: Main function for name resolution - will be called by other
- *             parts of etherboot
- *     Param:  string filename (not containing proto prefix like "tftp://")
- *     Return: string filename, with hostname replaced by IP in dotted quad
- *             or NULL for resolver error
- *             In case no substitution is necessary, return will be = param
- *             The returned string, if not == input, will be temporary and
- *             probably be overwritten during the next call to dns_resolver
- *             The returned string may (or may not) only contain an IP
- *             address, or an IP followed by a ":" or "/", or be NULL(=error)
- */
-char * dns_resolver ( char * filename ) {
-       int     i = 0, j, k;
-       static char ipaddr[16] = { 0 };
-       // Search for "end of hostname" (which might be either ":" or "/")
-       for ( j = i; (filename[j] != ':') && (filename[j] != '/'); ++j ) {
-               // If no hostname delimiter was found, assume no name present
-               if ( filename[j] == 0 ) return  filename;
-       }
-       // Check if the filename is an IP, in which case, leave unchanged
-       k = j - i - 1;
-       while ( ( '.' == filename[i+k] ) ||
-               ( ( '0' <= filename[i+k] ) && ( '9' >= filename[i+k] ) ) ) {
-               --k;
-               if ( k < 0 ) return filename; // Only had nums and dots->IP
-       }
-       // Now that we know it's a full hostname, attempt to resolve
-       if ( donameresolution ( filename + i, j - i, ipaddr ) ) {
-               return  NULL;   // Error in resolving - Fatal.
-       }
-       // Return the dotted-quad IP which resulted
-       return  ipaddr;
-}
-
-/*
- *     await_dns
- *     Shall be called on any incoming packet during the resolution process
- *     (as is the case with all the other await_ functions in etherboot)
- *     Param:  as any await functions
- *     Return: see dns_resolver.h for constant return values + descriptions
- */
-static int await_dns (int ival, void *ptr,
-       unsigned short ptype __unused, struct iphdr *ip __unused,
-       struct udphdr *udp, struct tcphdr *tcp __unused) {
-       int     i, j, k;
-       unsigned char *p = (unsigned char *)udp + sizeof(struct udphdr);
-       // p is set to the beginning of the payload
-       unsigned char *q;
-       unsigned int querytype = QUERYTYPE_A;
-       if (  0 == udp  )       // Parser couldn't find UDP header
-               return RET_PACK_GARBAG; // Not a UDP packet
-       if (( UDP_PORT_DNS != ntohs (udp->src )) ||
-           ( UDP_PORT_DNS != ntohs (udp->dest)) )
-               // Neither source nor destination port is "53"
-               return RET_PACK_GARBAG; // UDP port wrong
-       if (( p[QINDEX_ID  ] != 0) ||
-           ( p[QINDEX_ID+1] != (ival & 0xff)))
-               // Checking if this packet has set (inside payload)
-               // the sequence identifier that we expect
-               return RET_PACK_GARBAG; // Not matching our request ID
-       if (( p[QINDEX_FLAGS  ] & QUERYFLAGS_MASK ) != QUERYFLAGS_WANT )
-               // We only accept responses to the query(ies) we sent
-               return  RET_PACK_GARBAG;        // Is not response=opcode <0>
-       querytype = (ival & 0xff00) >> 8;
-       if (((p[QINDEX_NUMANSW+1] + (p[QINDEX_NUMANSW+1]<<8)) == 0 ) ||
-            ( ERR_NOSUCHNAME == (p[QINDEX_FLAGS+1] & 0x0f) ) ) {
-               // Answer section has 0 entries, or "no such name" returned
-               if ( QUERYTYPE_A == querytype) {
-                       // It was an A type query, so we should try if there's
-                       // an alternative "CNAME" record available
-                       return  RET_RUN_CNAME_Q; // So try CNAME query next
-               } else if ( QUERYTYPE_CNAME == querytype) {
-                       // There's no CNAME either, so give up
-                       return  RET_NOSUCHNAME;
-               } else {
-                       // Anything else? No idea what. Should not happen.
-                       return  RET_NOSUCHNAME; // Bail out with error
-               }
-       }
-       if ( 0 != ( p[QINDEX_FLAGS+1] & 0x0f ) )
-               // The response packet's flag section tells us:
-               return  RET_NOSUCHNAME; // Another (unspecific) error occured
-       // Now we have an answer packet in response to our query. Next thing
-       // to do is to search the payload for the "answer section", as there is
-       // a question section first usually that needs to be skipped
-       // If question section was not repeated, that saves a lot of work :
-       if ( 0 >= (i = ((p[QINDEX_NUMQUEST] << 8) + p[QINDEX_NUMQUEST+1]))) {
-               q = p+ QINDEX_QUESTION; // No question section, just continue;
-       } else if ( i >= 2 ) {          // More than one query section? Error!
-               return  RET_NOSUCHNAME; // That's invalid for us anyway - 
-                                       // We only place one query at a time
-       } else {
-               // We have to skip through the question section first to
-               // find the beginning of the answer section
-               q = p + QINDEX_QUESTION;
-               while ( 0 != q[0] )
-                       q += q[0] + 1; // Skip through
-               q += 5; // Skip over end-\0 and query type section
-       }
-       // Now our pointer shows the beginning of the answer section
-       // So now move it past the (repeated) query string, we only
-       // want the answer
-       while ( 0 != q[0] ) {
-               if ( 0xc0 == ( q[0] & 0xc0 ) ) { // Pointer
-                       ++q;
-                       break;
-               }
-               q += q[0] + 1;
-       }
-       ++q;
-       // Now check wether it's an INET host address (resp. CNAME)?
-       // There seem to be nameservers out there (Bind 9, for example),
-       // that return CNAMEs when no As are there, e.g. in
-       //   testname.test.   IN CNAME othername.test.
-       // case, bind 9 would return the CNAME record on an A query.
-       // Accept this case as it saves a lot of work (an extra query)
-       if (( QUERYTYPE_CNAME == q[1] ) &&
-           ( QUERYTYPE_A     == querytype )) {
-               // A query, but CNAME response.
-               // Do simulate having done a CNAME query now and just assume
-               // the answer we are working on here is that of a CNAME query
-               // This works for single-depth CNAMEs fine.
-               // For deeper recursion, we won't parse the answer
-               // packet deeper but rely on a separate CNAME query
-               querytype = QUERYTYPE_CNAME;
-       }
-       // Now check wether the answer packet is of the expected type
-       // (remember, we just tweaked CNAME answers to A queries already)
-       if ( (q[0] != 0) ||
-            (q[1] !=   querytype) ||   // query type matches?
-            (q[2] != ((QUERYCLASS_INET & 0xff00) >> 8)) ||
-            (q[3] !=  (QUERYCLASS_INET & 0x00ff))) { // class IN response?
-               return  RET_DNSERROR;   // Should not happen. DNS server bad?
-       }
-       q += 8; // skip querytype/-class/ttl which are uninteresting now
-       if ( querytype == QUERYTYPE_A ) {
-               // So what we sent was an A query - expect an IPv4 address
-               // Check if datalength looks satisfactory
-               if ( ( 0 != q[0] ) || ( 4 != q[1] ) ) {
-                       // Data length is not 4 bytes
-                       return  RET_DNSERROR;
-               }
-               // Go to the IP address and copy it to the response buffer
-               p = ptr + QINDEX_STORE_A;
-               for ( i = 0; i < 4; ++i ) {
-                       p[i] = q[i+2];
-               }
-               return  RET_GOT_ADDR; // OK! Address RETURNED! VERY FINE!
-       } else if ( querytype == QUERYTYPE_CNAME ) { // CNAME QUERY
-               // The pointer "q" now stands on the "payload length" of the
-               // CNAME data [2 byte field]. We will have to check wether this
-               // name is referenced in a following response, which would save
-               // us further queries, or if it is standalone, which calls for
-               // making a separate query
-               // This statement above probably needs clarification. To help
-               // the reader understand what's going on, imagine the DNS
-               // answer to be 4-section: query(repeating what we sent to the
-               // DNS server), answer(like the CNAME record we wanted),
-               // additional (which might hold the A for the CNAME - esp.
-               // Bind9 seems to provide us with this info when we didn't
-               // query for it yet) and authoritative (the DNS server's info
-               // on who is responsible for that data). For compression's
-               // sake, instead of specifying the full hostname string all
-               // the time, one can instead specify something like "same as on
-               // payload offset 0x123" - if this happens here, we can check
-               // if that payload offset given here by coincidence is that of
-               // an additional "A" record which saves us sending a separate
-               // query.
-               // We will look if there's  a/ two or more answer sections
-               // AND  b/ the next is a reference to us.
-               p = (unsigned char *)udp + sizeof(struct udphdr);
-               i = q + 2 - p;
-               if ( p[7] > 1 ) { // More than one answer section
-                       // Check the next if it's a ref
-                       // For that, we have to locate the next. Do this with "q".
-                       p = q + (q[0] * 0x100) + q[1] + 2;
-                       if ( (p[0] == (0xc0 + ((i & 0xf00)/256))) &&
-                            (p[1] == (i & 0xff) ) &&
-                            (p[2] == ((QUERYTYPE_A     & 0xff00) >> 8)) &&
-                            (p[3] ==  (QUERYTYPE_A     & 0x00ff)) &&
-                            (p[4] == ((QUERYCLASS_INET & 0xff00) >> 8 )) &&
-                            (p[5] ==  (QUERYCLASS_INET & 0x00ff)) &&
-                            (p[10]== 0) &&             // Data length expected
-                            (p[11]== 4)) {             // to be <4> (IP-addr)
-                               // Behind that sections: TTL, data length(4)
-                               for ( i = 0; i < 4; ++i ) {
-                                   ((unsigned char*)ptr)[QINDEX_STORE_A+i] =
-                                           p[12+i];
-                               }
-                               return  RET_GOT_ADDR;
-                       }
-               }
-               // Reference not found, next query (A) needed (because CNAME
-               // queries usually return another hostname, not an IP address
-               // as we need it - that's the point in CNAME, anyway :-)
-               p = (unsigned char *)udp + sizeof(struct udphdr);
-#ifdef DNSDEBUG
-               printf ( " ->[");
-#endif
-               k = QINDEX_QUESTION;
-               i = (q-p) + 2;
-               // Compose the hostname that needs to be queried for
-               // This looks complicated (and is a little bit) because
-               // we might not be able to copy verbatim, but need to
-               // check wether the CNAME looks like
-               // "servername" "plus what offset 0x123 of the payload says"
-               // (this saves transfer bandwidth, which is why DNS allows
-               // this, but it makes programmers' lives more difficult)
-               while (p[i] != 0) {
-                       if ( (((unsigned char *)p)[i] & 0xc0) != 0 ) {
-                               i = ((p[i] & 0x3f) * 0x100) + p[i+1];
-                               continue;
-                       }
-                       ((unsigned char *)ptr)[k] = p[i];
-                       for ( j = i + 1; j <= i + p[i]; ++j ) {
-                               ((unsigned char *)ptr)[k+j-i] = p[j];
-#ifdef DNSDEBUG
-                               printf ( "%c", p[j] );
-#endif
-                       }
-                       k += ((unsigned char *)ptr)[k] + 1;
-                       i += p[i] + 1;
-#ifdef DNSDEBUG
-                       printf ( (p[i] ? ".": "") );
-#endif
-               }
-               ((unsigned char *)ptr)[k] = 0;
-#ifdef DNSDEBUG
-               printf ( "].." );
-#endif
-               // So we need to run another query, this time try to
-               // get an "A" record for the hostname that the last CNAME
-               // query pointed to
-               return  RET_RUN_NEXT_A;
-       }
-       // We only accept CNAME or A replies from the nameserver, every-
-       // thing else is of no use for us.
-       // Must be an invalid packet that the nameserver sent.
-       return  RET_DNSERROR;
-}
-
-int    chars_to_next_dot ( char * countfrom, int maxnum ) {
-       // Count the number of characters of this part of a hostname
-       int i;
-       for ( i = 1; i < maxnum; ++i ) {
-               if ( countfrom[i] == '.' ) return i;
-       }
-       return  maxnum;
-}
-
-/*
- *     donameresolution
- *     Function: Compose the initial query packet, handle answers until
- *             a/ an IP address is retrieved
- *             b/ too many CNAME references occured (max. MAX_DNS_RETRIES)
- *             c/ No matching record for A or CNAME can be found
- *     Param:  string hostname, length (hostname needs no \0-end-marker),
- *             string to which dotted-quad-IP shall be written
- *     Return: 0 for success, >0 for failure
- */
-int    donameresolution ( char * hostname, int hnlength, char * deststring ) {
-       unsigned char   querybuf[260+sizeof(struct iphdr)+sizeof(struct udphdr)];
-               // 256 for the DNS query payload, +4 for the result temporary
-       unsigned char   *query = &querybuf[sizeof(struct iphdr)+sizeof(struct udphdr)];
-               // Pointer to the payload
-       int     i, h = hnlength;
-       long    timeout;
-       int     retry, recursion;
-       // Setup the query data
-       query[QINDEX_ID  ]      = (QUERYIDENTIFIER & 0xff00) >> 8;
-       query[QINDEX_ID+1]      =  QUERYIDENTIFIER & 0xff;
-       query[QINDEX_FLAGS  ]   = (QUERYFLAGS & 0xff00) >> 8;
-       query[QINDEX_FLAGS+1]   =  QUERYFLAGS & 0xff;
-       query[QINDEX_NUMQUEST  ]= 0;
-       query[QINDEX_NUMQUEST+1]= 1; // 1 standard query to be sent
-       query[QINDEX_NUMANSW  ] = 0;
-       query[QINDEX_NUMANSW+1] = 0;
-       query[QINDEX_NUMAUTH  ] = 0;
-       query[QINDEX_NUMAUTH+1] = 0;
-       query[QINDEX_NUMADDIT  ]= 0;
-       query[QINDEX_NUMADDIT+1]= 0;
-       query[QINDEX_QUESTION]  = chars_to_next_dot(hostname,h);
-       if ( h > 236 ) return 1;        // Hostnames longer than 236 chars are refused.
-                                       // This is an arbitrary decision, SHOULD check
-                                       // what the RFCs say about that
-       for ( i = 0; i < h; ++i ) {
-               // Compose the query section's hostname - replacing dots (and
-               // preceding the string) with one-byte substring-length values
-               // (for the immediately following substring) - \0 terminated
-               query[QINDEX_QUESTION+i+1] = hostname[i];
-               if ( hostname[i] == '.' )
-                       query[QINDEX_QUESTION+i+1] = chars_to_next_dot(hostname + i + 1, h - i - 1);
-       }
-       query[QINDEX_QTYPE+h  ] = (QUERYTYPE_A & 0xff00) >> 8;
-       query[QINDEX_QTYPE+h+1] =  QUERYTYPE_A & 0xff;
-                                       // First try an A query, if that
-                                       // won't work, try CNAME
-       printf ( "Resolving hostname [" );
-       for ( i = 0; i < hnlength; ++i ) { printf ( "%c", hostname[i] ); }
-       printf ("]" );
-       for ( recursion = MAX_CNAME_RECURSION; recursion > 0; --recursion ) {
-               printf ( ".." );
-               // Try MAX_CNAME_RECURSION CNAME queries maximally, if then no
-               // A record is found, assume the hostname to not be resolvable
-               query[QINDEX_QUESTION+h+1] = 0; // Marks the end of
-                                               // the query string
-               query[QINDEX_QCLASS+h  ]= (QUERYCLASS_INET & 0xff00) >> 8;
-               query[QINDEX_QCLASS+h+1]=  QUERYCLASS_INET & 0xff;
-               rx_qdrain();    // Clear NIC packet buffer -
-                               // there won't be anything of interest *now*.
-               udp_transmit ( arptable[ARP_NAMESERVER].ipaddr.s_addr,
-                               UDP_PORT_DNS, UDP_PORT_DNS,
-                               hnlength + 18 + sizeof(struct iphdr) +
-                               sizeof(struct udphdr), querybuf );
-               // If no answer comes in in a certain period of time, retry
-               for (retry = 1; retry <= MAX_DNS_RETRIES; retry++) {
-                       timeout = rfc2131_sleep_interval(TIMEOUT, retry);
-                       i = await_reply ( await_dns,
-                               (query[QINDEX_QTYPE+h+1] << 8) +
-                               ((unsigned char *)query)[QINDEX_ID+1],
-                               query, timeout);
-                       // The parameters given are
-                       //      bits 15...8 of integer = Query type  (low bits)
-                       //      bits  7...0 of integer = Query index (low bits)
-                       //      query + QINDEX_STORE_A points to the place
-                       //      where A records shall be stored (4 bytes);
-                       //      query + 12(QINDEX_QUESTION) points to where
-                       //      a CNAME should go in case one is received
-                       if (i) break;
-               }
-               switch ( i ) {
-                 case  RET_GOT_ADDR:   // Address successfully retrieved
-                       sprintf( deststring, "%@",
-                                 (long)query[QINDEX_STORE_A  ]           +
-                               ( (long)query[QINDEX_STORE_A+1] * 256 ) +
-                               ( (long)query[QINDEX_STORE_A+2] * 65536 )+
-                               ( (long)query[QINDEX_STORE_A+3] * 16777216 ));
-                       printf ( " -> IP [%s]\n", deststring );
-                       return  RET_DNS_OK;
-                 case  RET_RUN_CNAME_Q: // No A record found, try CNAME
-                       query[QINDEX_QTYPE+h  ]=(QUERYTYPE_CNAME & 0xff00)>> 8;
-                       query[QINDEX_QTYPE+h+1]= QUERYTYPE_CNAME & 0xff;
-                       break;
-                 case  RET_RUN_NEXT_A:
-                       // Found a CNAME, now try A for the name it pointed to
-                       for ( i = 0; query[QINDEX_QUESTION+i] != 0;
-                                       i += query[QINDEX_QUESTION+i] + 1 ) {;}
-                       h = i - 1;
-                       query[QINDEX_QTYPE+h  ]=(QUERYTYPE_A & 0xff00)>> 8;
-                       query[QINDEX_QTYPE+h+1]= QUERYTYPE_A & 0xff;
-                       break;
-                 case  RET_CNAME_FAIL:
-                       // Neither A nor CNAME gave a usable result
-                       printf ("Host name cannot be resolved\n");
-                       return  RET_DNS_FAIL;
-                 case  RET_NOSUCHNAME:
-                 default:
-                       printf ( "Name resolution failed\n" );
-                       return  RET_DNS_FAIL;
-               }
-               query[QINDEX_ID  ] = 0; // We will probably never have more
-                                       // than 256 queries in one run
-               query[QINDEX_ID+1]++;
-       }
-       // To deep recursion
-       printf ( "CNAME recursion to deep - abort name resolver\n" );
-       return  RET_DNS_FAIL;
-}
-#endif                         /* DNS_RESOLVER */
diff --git a/src/include/dns.h b/src/include/dns.h
new file mode 100644 (file)
index 0000000..cd26a96
--- /dev/null
@@ -0,0 +1,81 @@
+#ifndef DNS_RESOLVER_H
+#define DNS_RESOLVER_H
+
+#include "stdint.h"
+#include "nic.h"
+#include "in.h"
+
+/*
+ * Constants
+ *
+ */
+
+#define DNS_TYPE_A             1
+#define DNS_TYPE_CNAME         5
+#define DNS_TYPE_ANY           255
+
+#define DNS_CLASS_IN           1
+#define DNS_CLASS_CS           2
+#define DNS_CLASS_CH           3
+#define DNS_CLASS_HS           4
+
+#define DNS_FLAG_QUERY         ( 0x00 << 15 )
+#define DNS_FLAG_RESPONSE      ( 0x01 << 15 )
+#define DNS_FLAG_QR(flags)     ( (flags) & ( 0x01 << 15 ) )
+#define DNS_FLAG_OPCODE_QUERY  ( 0x00 << 11 )
+#define DNS_FLAG_OPCODE_IQUERY ( 0x01 << 11 )
+#define DNS_FLAG_OPCODE_STATUS ( 0x02 << 11 )
+#define DNS_FLAG_OPCODE(flags) ( (flags) & ( 0x0f << 11 ) )
+#define DNS_FLAG_RD            ( 0x01 << 8 )
+#define DNS_FLAG_RA            ( 0x01 << 7 )
+#define DNS_FLAG_RCODE_OK      ( 0x00 << 0 )
+#define DNS_FLAG_RCODE_NX      ( 0x03 << 0 )
+#define DNS_FLAG_RCODE(flags)  ( (flags) & ( 0x0f << 0 ) )
+
+#define        DNS_UDP_PORT            53
+#define        DNS_MAX_RETRIES         3
+#define        DNS_MAX_CNAME_RECURSION 0x30
+
+/*
+ * DNS protocol structures
+ *
+ */
+struct dns_header {
+       uint16_t        id;
+       uint16_t        flags;
+       uint16_t        qdcount;
+       uint16_t        ancount;
+       uint16_t        nscount;
+       uint16_t        arcount;
+} __attribute__ (( packed ));
+
+struct dns_query_info {
+       uint16_t        qtype;
+       uint16_t        qclass;
+} __attribute__ (( packed ));
+
+struct dns_query {
+       struct iphdr    ip;
+       struct udphdr   udp;
+       struct dns_header dns;
+       char            payload[ 256 + sizeof ( struct dns_query_info ) ];
+} __attribute__ (( packed ));
+
+struct dns_rr_info {
+       uint16_t        type;
+       uint16_t        class;
+       uint16_t        ttl;
+       uint16_t        rdlength;
+} __attribute__ (( packed ));
+
+struct dns_rr_info_a {
+       struct dns_rr_info;
+       struct in_addr in_addr;
+} __attribute__ (( packed ));
+
+struct dns_rr_info_cname {
+       struct dns_rr_info;
+       char            cname[0];
+};
+
+#endif /* DNS_RESOLVER_H */
diff --git a/src/include/dns_resolver.h b/src/include/dns_resolver.h
deleted file mode 100644 (file)
index b04a976..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-// dns_resolver.h - #define statements for the DNS resolver
-// We only need A and CNAME queries (later possibly AAAA/A6?)
-#define        QUERYTYPE_A     1
-#define        QUERYTYPE_CNAME 5
-
-// We only query with INTERNET class (not CHAOS or whatever)
-#define        QUERYCLASS_INET 1
-
-// Our first query will have the identifier <1> (arbitrary -
-// remember however that (256 - QUERYIDENTIFIER)/2 > MAX_CNAME_RECURSION !!!
-#define        QUERYIDENTIFIER 1
-
-// Query flags are standard values here
-#define        QUERYFLAGS      0x0100
-#define        QUERYFLAGS_MASK 0xf8
-#define        QUERYFLAGS_WANT 0x80
-
-// Indices inside the byte array that holds DNS queries/answers
-#define        QINDEX_ID       0
-#define        QINDEX_FLAGS    2
-#define        QINDEX_NUMQUEST 4
-#define        QINDEX_NUMANSW  6
-#define        QINDEX_NUMAUTH  8
-#define        QINDEX_NUMADDIT 10
-#define        QINDEX_QUESTION 12
-#define        QINDEX_QTYPE    14
-#define        QINDEX_QCLASS   16
-#define        QINDEX_STORE_A  256
-
-// Constant UDP port number for DNS traffic
-#define        UDP_PORT_DNS    53
-
-// Return values that the package parser may give
-//     This packet was not for us (broadcast or whatever)
-#define        RET_PACK_GARBAG 0
-//     Retrieved an address - query finishes
-#define        RET_GOT_ADDR    1
-//     No A record for that hostname - try running a CNAME query
-#define        RET_RUN_CNAME_Q 2
-//     The CNAME query returned a valid hostname - run A query on that
-#define        RET_RUN_NEXT_A  3
-//     The CNAME query failed - stop resolving
-#define        RET_CNAME_FAIL  4
-//     We have a reliable input that claims that the hostname does not exist
-#define        RET_NOSUCHNAME  5
-//     The name server response is somehow bogus/can not be parsed -> Abort
-#define        RET_DNSERROR    6
-
-// Return values that the query engine may give
-//     DNS query succeeded, IP address delivered
-#define        RET_DNS_OK      0
-//     DNS query failed
-#define        RET_DNS_FAIL    1
-
-// Error codes the DNS server can send to us
-#define        ERR_NOSUCHNAME  3
-
diff --git a/src/proto/dns.c b/src/proto/dns.c
new file mode 100644 (file)
index 0000000..8615173
--- /dev/null
@@ -0,0 +1,333 @@
+/**************************************************************************
+*
+*    dns_resolver.c: Etherboot support for resolution of host/domain
+*    names in filename parameters
+*    Written 2004 by Anselm M. Hoffmeister
+*    <stockholm@users.sourceforge.net>
+*
+*    This program is free software; you can redistribute it and/or modify
+*    it under the terms of the GNU General Public License as published by
+*    the Free Software Foundation; either version 2 of the License, or
+*    (at your option) any later version.
+*
+*    This program is distributed in the hope that it will be useful,
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*    GNU General Public License for more details.
+*
+*    You should have received a copy of the GNU General Public License
+*    along with this program; if not, write to the Free Software
+*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+*    This code is using nuts and bolts from throughout etherboot.
+*    It is a fresh implementation according to the DNS RFC, #1035
+*    
+*    REVISION HISTORY:
+*    ================
+*    2004-05-10 File created
+*    2004-05-19 First release to CVS
+*    2004-05-22 CNAME support first stage finished
+*    2004-05-24 First "stable" release to CVS
+*    2004-08-28 Improve readability, set recursion flag
+*    2005-04-30 Tidied up to the point of being a complete rewrite (mcb30)
+***************************************************************************/
+
+#include "etherboot.h"
+#include "nic.h"
+#include "resolv.h"
+#include "dns.h"
+
+/*
+ *     await_dns
+ *     Shall be called on any incoming packet during the resolution process
+ *     (as is the case with all the other await_ functions in etherboot)
+ *     Param:  as any await functions
+ *
+ */
+static int await_dns ( int ival, void *ptr,
+                      unsigned short ptype __unused,
+                      struct iphdr *ip __unused,
+                      struct udphdr *udp, struct tcphdr *tcp __unused ) {
+       struct dns_header **header = ptr;
+
+       if ( ! udp )
+               return 0;
+       if ( ntohs ( udp->dest ) != ival )
+               return 0;
+       *header = ( struct dns_header * ) ( udp + 1 );
+       return 1;
+}
+
+/*
+ * Send a name server query and wait for a response.  Query is retried
+ * up to DNS_MAX_RETRIES times.  Returns a pointer to the answer
+ * packet, or NULL if no answer was received.
+ *
+ */
+struct dns_header * dns_query ( struct dns_query *query,
+                               unsigned int query_len, 
+                               struct sockaddr_in *nameserver ) {
+       long timeout;
+       int retry;
+       struct dns_header *reply;
+
+       for ( retry = 0 ; retry < DNS_MAX_RETRIES ; retry++ ) {
+               udp_transmit ( nameserver->sin_addr.s_addr,
+                              nameserver->sin_port, nameserver->sin_port,
+                              query_len, query );
+               timeout = rfc2131_sleep_interval ( TIMEOUT, retry );
+               if ( ! await_reply ( await_dns, nameserver->sin_port,
+                                    &reply, timeout ) )
+                       continue;
+               if ( reply->id != query->dns.id ) {
+                       DBG ( "DNS received unexpected reply ID %d "
+                             "(wanted %d)\n",
+                             ntohs ( reply->id ), ntohs ( query->dns.id ) );
+                       continue;
+               }
+               /* We have a valid reply! */
+               return reply;
+       }
+       return NULL;
+}
+
+/*
+ * Compare two DNS names to see if they are the same.  Takes
+ * compressed names in the reply into account (though the query name
+ * must be uncompressed).  Returns 0 for a match (for consistency with
+ * strcmp et al).
+ *
+ */
+static inline int dns_name_cmp ( const char *qname, const char *rname,
+                                struct dns_header *reply ) {
+       int i;
+       while ( 1 ) {
+               /* Obtain next section of rname */
+               while ( ( *rname ) & 0xc0 ) {
+                       rname = ( ( char * ) reply +
+                                 ( ntohs( *((uint16_t *)rname) ) & ~0xc000 ));
+               }
+               /* Check that lengths match */
+               if ( *rname != *qname )
+                       return 1;
+               /* If length is zero, we have reached the end */
+               if ( ! *qname )
+                       return 0;
+               /* Check that data matches */
+               for ( i = *qname + 1; i > 0 ; i-- ) {
+                       if ( *(rname++) != *(qname++) )
+                               return 1;
+               }
+       }
+}
+
+/*
+ * Skip over a DNS name, which may be compressed
+ *
+ */
+static inline const char * dns_skip_name ( const char *name ) {
+       while ( 1 ) {
+               if ( ! *name ) {
+                       /* End of name */
+                       return ( name + 1);
+               }
+               if ( *name & 0xc0 ) {
+                       /* Start of a compressed name */
+                       return ( name + 2 );
+               }
+               /* Uncompressed name portion */
+               name += *name + 1;
+       }
+}
+
+/*
+ * Find a Resource Record in a reply packet corresponding to our
+ * query.  Returns a pointer to the RR, or NULL if no answer found.
+ *
+ */
+static struct dns_rr_info * dns_find_rr ( struct dns_query *query,
+                                         struct dns_header *reply ) {
+       int i;
+       const char *p = ( ( char * ) reply ) + sizeof ( struct dns_header );
+
+       /* Skip over the questions section */
+       for ( i = ntohs ( reply->qdcount ) ; i > 0 ; i-- ) {
+               p = dns_skip_name ( p ) + sizeof ( struct dns_query_info );
+       }
+
+       /* Process the answers section */
+       for ( i = ntohs ( reply->ancount ) ; i > 0 ; i-- ) {
+               if ( dns_name_cmp ( query->payload, p, reply ) == 0 ) {
+                       return ( ( struct dns_rr_info * ) p );
+               }
+               p = dns_skip_name ( p );
+               p += ( sizeof ( struct dns_rr_info ) +
+                      ntohs ( ( ( struct dns_rr_info * ) p )->rdlength ) );
+       }
+
+       return NULL;
+}
+
+/*
+ * Convert a standard NUL-terminated string to a DNS query name,
+ * consisting of "<length>element" pairs.
+ *
+ * Returns a pointer to the character following the constructed DNS
+ * query name.
+ *
+ */
+static inline char * dns_make_name ( char *dest, const char *name ) {
+       char *length_byte = dest++;
+       char c;
+
+       while ( ( c = *(name++) ) ) {
+               if ( c == '.' ) {
+                       *length_byte = dest - length_byte - 1;
+                       length_byte = dest;
+               }
+               *(dest++) = c;
+       }
+       *length_byte = dest - length_byte - 1;
+       *(dest++) = '\0';
+       return dest;
+}
+
+/*
+ * Decompress a DNS name.
+ *
+ * Returns a pointer to the character following the decompressed DNS
+ * name.
+ *
+ */
+static inline char * dns_decompress_name ( char *dest, const char *name,
+                                          struct dns_header *header ) {
+       int i, len;
+
+       do {
+               /* Obtain next section of name */
+               while ( ( *name ) & 0xc0 ) {
+                       name = ( ( char * ) header +
+                                ( ntohs ( *((uint16_t *)name) ) & ~0xc000 ) );
+               }
+               /* Copy data */
+               len = *name;
+               for ( i = len + 1 ; i > 0 ; i-- ) {
+                       *(dest++) = *(name++);
+               }
+       } while ( len );
+       return dest;
+}
+
+/*
+ * Resolve a name using DNS
+ *
+ */
+static int dns_resolv ( struct in_addr *addr, const char *name ) {
+       struct dns_query query;
+       struct dns_query_info *query_info;
+       struct dns_header *reply;
+       struct dns_rr_info *rr_info;
+       struct sockaddr_in nameserver;
+       uint16_t qtype;
+       unsigned int recursion = 0;
+       unsigned int id = 1;
+
+       DBG ( "DNS resolving %s\n", name );
+
+       /* Set up the query data */
+       nameserver.sin_addr = arptable[ARP_NAMESERVER].ipaddr;
+       nameserver.sin_port = DNS_UDP_PORT;
+       memset ( &query, 0, sizeof ( query ) );
+       query.dns.flags = htons ( DNS_FLAG_QUERY | DNS_FLAG_OPCODE_QUERY |
+                                 DNS_FLAG_RD );
+       query.dns.qdcount = htons ( 1 );
+       query_info = ( void * ) dns_make_name ( query.payload, name );
+       query_info->qtype = htons ( DNS_TYPE_A );
+       query_info->qclass = htons ( DNS_CLASS_IN );
+
+       while ( 1 ) {
+               /* Transmit current query, wait for reply */
+               query.dns.id = htons ( id++ );
+               qtype = ntohs ( query_info->qtype );
+               reply = dns_query ( &query,
+                                   ( ( ( char * ) query_info )
+                                     + sizeof ( *query_info )
+                                     - ( ( char * ) &query ) ),
+                                   &nameserver );
+               if ( ! reply ) {
+                       DBG ( "DNS got no response from server %@ (port %d)\n",
+                             nameserver.sin_addr.s_addr,
+                             nameserver.sin_port );
+                       return 0;
+               }
+
+               /* Search through response for useful answers.  Do
+                * this multiple times, to take advantage of useful
+                * nameservers which send us e.g. the CNAME *and* the
+                * A record for the pointed-to name.
+                */
+               while ( ( rr_info = dns_find_rr ( &query, reply ) ) ) {
+                       switch ( ntohs ( rr_info->type ) ) {
+                       case DNS_TYPE_A: {
+                               /* Found the target A record */
+                               struct dns_rr_info_a *rr_info_a =
+                                       ( struct dns_rr_info_a * ) rr_info;
+                               *addr = rr_info_a->in_addr;
+                               DBG ( "DNS found address %@\n", addr->s_addr );
+                               return 1; }
+                       case DNS_TYPE_CNAME: {
+                               /* Found a CNAME record - update the query */
+                               struct dns_rr_info_cname *rr_info_cname =
+                                       ( struct dns_rr_info_cname * ) rr_info;
+                               char *cname = rr_info_cname->cname;
+
+                               DBG ( "DNS found CNAME\n" );
+                               query_info = ( void * )
+                                       dns_decompress_name ( query.payload,
+                                                             cname, reply );
+                               query_info->qtype = htons ( DNS_TYPE_A );
+                               query_info->qclass = htons ( DNS_CLASS_IN );
+
+                               if ( ++recursion > DNS_MAX_CNAME_RECURSION ) {
+                                       DBG ( "DNS recursion exceeded\n" );
+                                       return 0;
+                               }
+                               break; }
+                       default:
+                               DBG ( "DNS got unknown record type %d\n",
+                                     ntohs ( rr_info->type ) );
+                               return 0;
+                       }
+               }
+               
+               /* Determine what to do next based on the type of
+                * query we issued and the reponse we received
+                */
+               switch ( qtype ) {
+               case DNS_TYPE_A :
+                       /* We asked for an A record and got nothing;
+                        * try the CNAME.
+                        */
+                       DBG ( "DNS found no A record; trying CNAME\n" );
+                       query_info->qtype = htons ( DNS_TYPE_CNAME );
+                       break;
+               case DNS_TYPE_CNAME :
+                       /* We asked for a CNAME record.  If we didn't
+                        * get any response (i.e. the next A query
+                        * isn't already set up), then abort.
+                        */
+                       if ( query_info->qtype != htons ( DNS_TYPE_A ) ) {
+                               DBG ( "DNS found no CNAME record\n" );
+                               return 0;
+                       }
+                       break;
+               default:
+                       DBG ( "DNS internal error - inconsistent state\n" );
+               }
+       }
+}
+
+static struct resolver dns_resolver __resolver = {
+       .name = "DNS",
+       .resolv = dns_resolv,
+};