summaryrefslogtreecommitdiffstats
path: root/src/net
diff options
context:
space:
mode:
authorMichael Brown2007-01-15 18:31:35 +0100
committerMichael Brown2007-01-15 18:31:35 +0100
commit9af12d5fba1483ec5e95ac302b3f5c5aeb3662c1 (patch)
tree3e97aec4cb3500383fdfda5e6027be5411c972a1 /src/net
parentUpdate TFTP and FTP to take the same temporary URI scheme as HTTP (diff)
downloadipxe-9af12d5fba1483ec5e95ac302b3f5c5aeb3662c1.tar.gz
ipxe-9af12d5fba1483ec5e95ac302b3f5c5aeb3662c1.tar.xz
ipxe-9af12d5fba1483ec5e95ac302b3f5c5aeb3662c1.zip
A working DNS resolver (not yet tied in to anything)
Diffstat (limited to 'src/net')
-rw-r--r--src/net/udp/dns.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/net/udp/dns.c b/src/net/udp/dns.c
new file mode 100644
index 000000000..17075d68e
--- /dev/null
+++ b/src/net/udp/dns.c
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * Portions copyright (C) 2004 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 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.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <gpxe/async.h>
+#include <gpxe/udp.h>
+#include <gpxe/dhcp.h>
+#include <gpxe/dns.h>
+
+/** @file
+ *
+ * DNS protocol
+ *
+ */
+
+/**
+ * Compare DNS reply name against the query name from the original request
+ *
+ * @v dns DNS request
+ * @v reply DNS reply
+ * @v rname Reply name
+ * @ret zero Names match
+ * @ret non-zero Names do not match
+ */
+static int dns_name_cmp ( struct dns_request *dns, struct dns_header *reply,
+ const char *rname ) {
+ const char *qname = dns->query.payload;
+ 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 (possibly compressed) DNS name
+ *
+ * @v name DNS name
+ * @ret name Next DNS name
+ */
+static 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 an RR in a reply packet corresponding to our query
+ *
+ * @v dns DNS request
+ * @v reply DNS reply
+ * @ret rr DNS RR, or NULL if not found
+ */
+static union dns_rr_info * dns_find_rr ( struct dns_request *dns,
+ struct dns_header *reply ) {
+ int i, cmp;
+ const char *p = ( ( char * ) reply ) + sizeof ( struct dns_header );
+ union dns_rr_info *rr_info;
+
+ /* 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-- ) {
+ cmp = dns_name_cmp ( dns, reply, p );
+ p = dns_skip_name ( p );
+ rr_info = ( ( union dns_rr_info * ) p );
+ if ( cmp == 0 )
+ return rr_info;
+ p += ( sizeof ( rr_info->common ) +
+ ntohs ( rr_info->common.rdlength ) );
+ }
+
+ return NULL;
+}
+
+/**
+ * Convert a standard NUL-terminated string to a DNS name
+ *
+ * @v string Name as a NUL-terminated string
+ * @v buf Buffer in which to place DNS name
+ * @ret next Byte following constructed DNS name
+ *
+ * DNS names consist of "<length>element" pairs.
+ */
+static char * dns_make_name ( const char *string, char *buf ) {
+ char *length_byte = buf++;
+ char c;
+
+ while ( ( c = *(string++) ) ) {
+ if ( c == '.' ) {
+ *length_byte = buf - length_byte - 1;
+ length_byte = buf;
+ }
+ *(buf++) = c;
+ }
+ *length_byte = buf - length_byte - 1;
+ *(buf++) = '\0';
+ return buf;
+}
+
+/**
+ * Convert an uncompressed DNS name to a NUL-terminated string
+ *
+ * @v name DNS name
+ * @ret string NUL-terminated string
+ *
+ * Produce a printable version of a DNS name. Used only for debugging.
+ */
+static inline char * dns_unmake_name ( char *name ) {
+ char *p;
+ unsigned int len;
+
+ p = name;
+ while ( ( len = *p ) ) {
+ *(p++) = '.';
+ p += len;
+ }
+
+ return name + 1;
+}
+
+/**
+ * Decompress a DNS name
+ *
+ * @v reply DNS replay
+ * @v name DNS name
+ * @v buf Buffer into which to decompress DNS name
+ * @ret next Byte following decompressed DNS name
+ */
+static char * dns_decompress_name ( struct dns_header *reply,
+ const char *name, char *buf ) {
+ int i, len;
+
+ do {
+ /* Obtain next section of name */
+ while ( ( *name ) & 0xc0 ) {
+ name = ( ( char * ) reply +
+ ( ntohs ( *((uint16_t *)name) ) & ~0xc000 ) );
+ }
+ /* Copy data */
+ len = *name;
+ for ( i = len + 1 ; i > 0 ; i-- ) {
+ *(buf++) = *(name++);
+ }
+ } while ( len );
+ return buf;
+}
+
+/**
+ * Mark DNS request as complete
+ *
+ * @v dns DNS request
+ * @v rc Return status code
+ */
+static void dns_done ( struct dns_request *dns, int rc ) {
+
+ /* Stop the retry timer */
+ stop_timer ( &dns->timer );
+
+ /* Close UDP connection */
+ udp_close ( &dns->udp );
+
+ /* Mark async operation as complete */
+ async_done ( &dns->async, rc );
+}
+
+/**
+ * Send next packet in DNS request
+ *
+ * @v dns DNS request
+ */
+static void dns_send_packet ( struct dns_request *dns ) {
+ static unsigned int qid = 0;
+
+ /* Increment query ID */
+ dns->query.dns.id = htons ( ++qid );
+
+ DBGC ( dns, "DNS %p sending query ID %d\n", dns, qid );
+
+ /* Start retransmission timer */
+ start_timer ( &dns->timer );
+
+ /* Send the data */
+ udp_send ( &dns->udp, &dns->query,
+ ( ( ( void * ) dns->qinfo ) - ( ( void * ) &dns->query )
+ + sizeof ( dns->qinfo ) ) );
+}
+
+/**
+ * Handle DNS retransmission timer expiry
+ *
+ * @v timer Retry timer
+ * @v fail Failure indicator
+ */
+static void dns_timer_expired ( struct retry_timer *timer, int fail ) {
+ struct dns_request *dns =
+ container_of ( timer, struct dns_request, timer );
+
+ if ( fail ) {
+ dns_done ( dns, -ETIMEDOUT );
+ } else {
+ dns_send_packet ( dns );
+ }
+}
+
+/**
+ * Receive new data
+ *
+ * @v udp UDP connection
+ * @v data Received data
+ * @v len Length of received data
+ * @v st_src Partially-filled source address
+ * @v st_dest Partially-filled destination address
+ */
+static int dns_newdata ( struct udp_connection *conn, void *data, size_t len,
+ struct sockaddr_tcpip *st_src __unused,
+ struct sockaddr_tcpip *st_dest __unused ) {
+ struct dns_request *dns =
+ container_of ( conn, struct dns_request, udp );
+ struct dns_header *reply = data;
+ union dns_rr_info *rr_info;
+ struct sockaddr_in *sin;
+ unsigned int qtype = dns->qinfo->qtype;
+
+ /* Sanity check */
+ if ( len < sizeof ( *reply ) ) {
+ DBGC ( dns, "DNS %p received underlength packet length %zd\n",
+ dns, len );
+ return -EINVAL;
+ }
+
+ /* Check reply ID matches query ID */
+ if ( reply->id != dns->query.dns.id ) {
+ DBGC ( dns, "DNS %p received unexpected reply ID %d "
+ "(wanted %d)\n", dns, ntohs ( reply->id ),
+ ntohs ( dns->query.dns.id ) );
+ return -EINVAL;
+ }
+
+ DBGC ( dns, "DNS %p received reply ID %d\n", dns, ntohs ( reply->id ));
+
+ /* Stop the retry timer. After this point, each code path
+ * must either restart the timer by calling dns_send_packet(),
+ * or mark the DNS operation as complete by calling
+ * dns_done()
+ */
+ stop_timer ( &dns->timer );
+
+ /* 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 ( dns, reply ) ) ) {
+ switch ( rr_info->common.type ) {
+
+ case htons ( DNS_TYPE_A ):
+
+ /* Found the target A record */
+ DBGC ( dns, "DNS %p found address %s\n",
+ dns, inet_ntoa ( rr_info->a.in_addr ) );
+ sin = ( struct sockaddr_in * ) dns->st;
+ sin->sin_family = AF_INET;
+ sin->sin_addr = rr_info->a.in_addr;
+
+ /* Mark operation as complete */
+ dns_done ( dns, 0 );
+ return 0;
+
+ case htons ( DNS_TYPE_CNAME ):
+
+ /* Found a CNAME record; update query and recurse */
+ DBGC ( dns, "DNS %p found CNAME\n", dns );
+ dns->qinfo = ( void * ) dns_decompress_name ( reply,
+ rr_info->cname.cname,
+ dns->query.payload );
+ dns->qinfo->qtype = htons ( DNS_TYPE_A );
+ dns->qinfo->qclass = htons ( DNS_CLASS_IN );
+
+ /* Terminate the operation if we recurse too far */
+ if ( ++dns->recursion > DNS_MAX_CNAME_RECURSION ) {
+ DBGC ( dns, "DNS %p recursion exceeded\n",
+ dns );
+ dns_done ( dns, -ELOOP );
+ return 0;
+ }
+ break;
+
+ default:
+ DBGC ( dns, "DNS %p got unknown record type %d\n",
+ dns, ntohs ( rr_info->common.type ) );
+ break;
+ }
+ }
+
+ /* Determine what to do next based on the type of query we
+ * issued and the reponse we received
+ */
+ switch ( qtype ) {
+
+ case htons ( DNS_TYPE_A ):
+ /* We asked for an A record and got nothing;
+ * try the CNAME.
+ */
+ DBGC ( dns, "DNS %p found no A record; trying CNAME\n", dns );
+ dns->qinfo->qtype = htons ( DNS_TYPE_CNAME );
+ dns_send_packet ( dns );
+ return 0;
+
+ case htons ( DNS_TYPE_CNAME ):
+ /* We asked for a CNAME record. If we got a response
+ * (i.e. if the next A query is already set up), then
+ * issue it, otherwise abort.
+ */
+ if ( dns->qinfo->qtype == htons ( DNS_TYPE_A ) ) {
+ dns_send_packet ( dns );
+ return 0;
+ } else {
+ DBGC ( dns, "DNS %p found no CNAME record\n", dns );
+ dns_done ( dns, -ENXIO );
+ return 0;
+ }
+
+ default:
+ assert ( 0 );
+ dns_done ( dns, -EINVAL );
+ return 0;
+ }
+}
+
+/** DNS UDP operations */
+struct udp_operations dns_udp_operations = {
+ .newdata = dns_newdata,
+};
+
+/**
+ * Reap asynchronous operation
+ *
+ * @v async Asynchronous operation
+ */
+static void dns_reap ( struct async *async ) {
+ struct dns_request *dns =
+ container_of ( async, struct dns_request, async );
+
+ free ( dns );
+}
+
+/** DNS asynchronous operations */
+static struct async_operations dns_async_operations = {
+ .reap = dns_reap,
+};
+
+/**
+ * Resolve name using DNS
+ *
+ */
+int dns_resolv ( const char *name, struct sockaddr_tcpip *st,
+ struct async *parent ) {
+ struct dns_request *dns;
+ union {
+ struct sockaddr_tcpip st;
+ struct sockaddr_in sin;
+ } nameserver;
+ int rc;
+
+ /* Allocate DNS structure */
+ dns = malloc ( sizeof ( *dns ) );
+ if ( ! dns ) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ memset ( dns, 0, sizeof ( *dns ) );
+ dns->st = st;
+ dns->timer.expired = dns_timer_expired;
+ dns->udp.udp_op = &dns_udp_operations;
+
+ /* Create query */
+ dns->query.dns.flags = htons ( DNS_FLAG_QUERY | DNS_FLAG_OPCODE_QUERY |
+ DNS_FLAG_RD );
+ dns->query.dns.qdcount = htons ( 1 );
+ dns->qinfo = ( void * ) dns_make_name ( name, dns->query.payload );
+ dns->qinfo->qtype = htons ( DNS_TYPE_A );
+ dns->qinfo->qclass = htons ( DNS_CLASS_IN );
+
+ /* Open UDP connection */
+ memset ( &nameserver, 0, sizeof ( nameserver ) );
+ nameserver.sin.sin_family = AF_INET;
+ nameserver.sin.sin_port = htons ( DNS_PORT );
+#warning "DHCP-DNS hack"
+ struct dhcp_option *option =
+ find_global_dhcp_option ( DHCP_DNS_SERVERS );
+ if ( ! option ) {
+ DBGC ( dns, "DNS %p no name servers\n", dns );
+ rc = -ENXIO;
+ goto err;
+ }
+ dhcp_ipv4_option ( option, &nameserver.sin.sin_addr );
+ DBGC ( dns, "DNS %p using nameserver %s\n", dns,
+ inet_ntoa ( nameserver.sin.sin_addr ) );
+ udp_connect ( &dns->udp, &nameserver.st );
+ if ( ( rc = udp_open ( &dns->udp, 0 ) ) != 0 )
+ goto err;
+
+ dns_send_packet ( dns );
+
+ async_init ( &dns->async, &dns_async_operations, parent );
+ return 0;
+
+ err:
+ DBGC ( dns, "DNS %p could not create request: %s\n",
+ dns, strerror ( rc ) );
+ free ( dns );
+ return rc;
+}