Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / net / udp / dns.c
diff --git a/qemu/roms/ipxe/src/net/udp/dns.c b/qemu/roms/ipxe/src/net/udp/dns.c
new file mode 100644 (file)
index 0000000..fffe6e6
--- /dev/null
@@ -0,0 +1,1152 @@
+/*
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/xfer.h>
+#include <ipxe/open.h>
+#include <ipxe/resolv.h>
+#include <ipxe/retry.h>
+#include <ipxe/tcpip.h>
+#include <ipxe/settings.h>
+#include <ipxe/features.h>
+#include <ipxe/dhcp.h>
+#include <ipxe/dhcpv6.h>
+#include <ipxe/dns.h>
+
+/** @file
+ *
+ * DNS protocol
+ *
+ */
+
+FEATURE ( FEATURE_PROTOCOL, "DNS", DHCP_EB_FEATURE_DNS, 1 );
+
+/* Disambiguate the various error causes */
+#define ENXIO_NO_RECORD __einfo_error ( EINFO_ENXIO_NO_RECORD )
+#define EINFO_ENXIO_NO_RECORD \
+       __einfo_uniqify ( EINFO_ENXIO, 0x01, "DNS name does not exist" )
+#define ENXIO_NO_NAMESERVER __einfo_error ( EINFO_ENXIO_NO_NAMESERVER )
+#define EINFO_ENXIO_NO_NAMESERVER \
+       __einfo_uniqify ( EINFO_ENXIO, 0x02, "No DNS servers available" )
+
+/** The DNS server */
+static union {
+       struct sockaddr sa;
+       struct sockaddr_tcpip st;
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+} nameserver = {
+       .st = {
+               .st_port = htons ( DNS_PORT ),
+       },
+};
+
+/** The DNS search list */
+static struct dns_name dns_search;
+
+/**
+ * Encode a DNS name using RFC1035 encoding
+ *
+ * @v string           DNS name as a string
+ * @v name             DNS name to fill in
+ * @ret len            Length of DNS name, or negative error
+ */
+int dns_encode ( const char *string, struct dns_name *name ) {
+       uint8_t *start = ( name->data + name->offset );
+       uint8_t *end = ( name->data + name->len );
+       uint8_t *dst = start;
+       size_t len = 0;
+       char c;
+
+       /* Encode name */
+       while ( ( c = *(string++) ) ) {
+
+               /* Handle '.' separators */
+               if ( c == '.' ) {
+
+                       /* Reject consecutive '.' */
+                       if ( ( len == 0 ) && ( dst != start ) )
+                               return -EINVAL;
+
+                       /* Terminate if this is the trailing '.' */
+                       if ( *string == '\0' )
+                               break;
+
+                       /* Reject initial non-terminating '.' */
+                       if ( len == 0 )
+                               return -EINVAL;
+
+                       /* Reset length */
+                       len = 0;
+
+               } else {
+
+                       /* Increment length */
+                       len++;
+
+                       /* Check for overflow */
+                       if ( len > DNS_MAX_LABEL_LEN )
+                               return -EINVAL;
+               }
+
+               /* Copy byte, update length */
+               if ( ++dst < end ) {
+                       *dst = c;
+                       dst[-len] = len;
+               }
+       }
+
+       /* Add terminating root marker */
+       if ( len )
+               dst++;
+       if ( dst < end )
+               *dst = '\0';
+       dst++;
+
+       return ( dst - start );
+}
+
+/**
+ * Find start of valid label within an RFC1035-encoded DNS name
+ *
+ * @v name             DNS name
+ * @v offset           Current offset
+ * @ret offset         Offset of label, or negative error
+ */
+static int dns_label ( struct dns_name *name, size_t offset ) {
+       const uint8_t *byte;
+       const uint16_t *word;
+       size_t len;
+       size_t ptr;
+
+       while ( 1 ) {
+
+               /* Fail if we have overrun the DNS name */
+               if ( ( offset + sizeof ( *byte) ) > name->len )
+                       return -EINVAL;
+               byte = ( name->data + offset );
+
+               /* Follow compression pointer, if applicable */
+               if ( DNS_IS_COMPRESSED ( *byte ) ) {
+
+                       /* Fail if we have overrun the DNS name */
+                       if ( ( offset + sizeof ( *word ) ) > name->len )
+                               return -EINVAL;
+                       word = ( name->data + offset );
+
+                       /* Extract pointer to new offset */
+                       ptr = DNS_COMPRESSED_OFFSET ( ntohs ( *word ) );
+
+                       /* Fail if pointer does not point backwards.
+                        * (This guarantees termination of the
+                        * function.)
+                        */
+                       if ( ptr >= offset )
+                               return -EINVAL;
+
+                       /* Continue from new offset */
+                       offset = ptr;
+                       continue;
+               }
+
+               /* Fail if we have overrun the DNS name */
+               len = *byte;
+               if ( ( offset + sizeof ( *byte ) + len ) > name->len )
+                       return -EINVAL;
+
+               /* We have a valid label */
+               return offset;
+       }
+}
+
+/**
+ * Decode RFC1035-encoded DNS name
+ *
+ * @v name             DNS name
+ * @v data             Output buffer
+ * @v len              Length of output buffer
+ * @ret len            Length of decoded DNS name, or negative error
+ */
+int dns_decode ( struct dns_name *name, char *data, size_t len ) {
+       unsigned int recursion_limit = name->len; /* Generous upper bound */
+       int offset = name->offset;
+       const uint8_t *label;
+       size_t decoded_len = 0;
+       size_t label_len;
+       size_t copy_len;
+
+       while ( recursion_limit-- ) {
+
+               /* Find valid DNS label */
+               offset = dns_label ( name, offset );
+               if ( offset < 0 )
+                       return offset;
+
+               /* Terminate if we have reached the root */
+               label = ( name->data + offset );
+               label_len = *(label++);
+               if ( label_len == 0 ) {
+                       if ( decoded_len < len )
+                               *data = '\0';
+                       return decoded_len;
+               }
+
+               /* Prepend '.' if applicable */
+               if ( decoded_len && ( decoded_len++ < len ) )
+                       *(data++) = '.';
+
+               /* Copy label to output buffer */
+               copy_len = ( ( decoded_len < len ) ? ( len - decoded_len ) : 0);
+               if ( copy_len > label_len )
+                       copy_len = label_len;
+               memcpy ( data, label, copy_len );
+               data += copy_len;
+               decoded_len += label_len;
+
+               /* Move to next label */
+               offset += ( sizeof ( *label ) + label_len );
+       }
+
+       /* Recursion limit exceeded */
+       return -EINVAL;
+}
+
+/**
+ * Compare DNS names for equality
+ *
+ * @v first            First DNS name
+ * @v second           Second DNS name
+ * @ret rc             Return status code
+ */
+int dns_compare ( struct dns_name *first, struct dns_name *second ) {
+       unsigned int recursion_limit = first->len; /* Generous upper bound */
+       int first_offset = first->offset;
+       int second_offset = second->offset;
+       const uint8_t *first_label;
+       const uint8_t *second_label;
+       size_t label_len;
+       size_t len;
+
+       while ( recursion_limit-- ) {
+
+               /* Find valid DNS labels */
+               first_offset = dns_label ( first, first_offset );
+               if ( first_offset < 0 )
+                       return first_offset;
+               second_offset = dns_label ( second, second_offset );
+               if ( second_offset < 0 )
+                       return second_offset;
+
+               /* Compare label lengths */
+               first_label = ( first->data + first_offset );
+               second_label = ( second->data + second_offset );
+               label_len = *(first_label++);
+               if ( label_len != *(second_label++) )
+                       return -ENOENT;
+               len = ( sizeof ( *first_label ) + label_len );
+
+               /* Terminate if we have reached the root */
+               if ( label_len == 0 )
+                       return 0;
+
+               /* Compare label contents (case-insensitively) */
+               while ( label_len-- ) {
+                       if ( tolower ( *(first_label++) ) !=
+                            tolower ( *(second_label++) ) )
+                               return -ENOENT;
+               }
+
+               /* Move to next labels */
+               first_offset += len;
+               second_offset += len;
+       }
+
+       /* Recursion limit exceeded */
+       return -EINVAL;
+}
+
+/**
+ * Copy a DNS name
+ *
+ * @v src              Source DNS name
+ * @v dst              Destination DNS name
+ * @ret len            Length of copied DNS name, or negative error
+ */
+int dns_copy ( struct dns_name *src, struct dns_name *dst ) {
+       unsigned int recursion_limit = src->len; /* Generous upper bound */
+       int src_offset = src->offset;
+       size_t dst_offset = dst->offset;
+       const uint8_t *label;
+       size_t label_len;
+       size_t copy_len;
+       size_t len;
+
+       while ( recursion_limit-- ) {
+
+               /* Find valid DNS label */
+               src_offset = dns_label ( src, src_offset );
+               if ( src_offset < 0 )
+                       return src_offset;
+
+               /* Copy as an uncompressed label */
+               label = ( src->data + src_offset );
+               label_len = *label;
+               len = ( sizeof ( *label ) + label_len );
+               copy_len = ( ( dst_offset < dst->len ) ?
+                            ( dst->len - dst_offset ) : 0 );
+               if ( copy_len > len )
+                       copy_len = len;
+               memcpy ( ( dst->data + dst_offset ), label, copy_len );
+               src_offset += len;
+               dst_offset += len;
+
+               /* Terminate if we have reached the root */
+               if ( label_len == 0 )
+                       return ( dst_offset - dst->offset );
+       }
+
+       /* Recursion limit exceeded */
+       return -EINVAL;
+}
+
+/**
+ * Skip RFC1035-encoded DNS name
+ *
+ * @v name             DNS name
+ * @ret offset         Offset to next name, or negative error
+ */
+int dns_skip ( struct dns_name *name ) {
+       unsigned int recursion_limit = name->len; /* Generous upper bound */
+       int offset = name->offset;
+       int prev_offset;
+       const uint8_t *label;
+       size_t label_len;
+
+       while ( recursion_limit-- ) {
+
+               /* Find valid DNS label */
+               prev_offset = offset;
+               offset = dns_label ( name, prev_offset );
+               if ( offset < 0 )
+                       return offset;
+
+               /* Terminate if we have reached a compression pointer */
+               if ( offset != prev_offset )
+                       return ( prev_offset + sizeof ( uint16_t ) );
+
+               /* Skip this label */
+               label = ( name->data + offset );
+               label_len = *label;
+               offset += ( sizeof ( *label ) + label_len );
+
+               /* Terminate if we have reached the root */
+               if ( label_len == 0 )
+                       return offset;
+       }
+
+       /* Recursion limit exceeded */
+       return -EINVAL;
+}
+
+/**
+ * Skip RFC1035-encoded DNS name in search list
+ *
+ * @v name             DNS name
+ * @ret offset         Offset to next non-empty name, or negative error
+ */
+static int dns_skip_search ( struct dns_name *name ) {
+       int offset;
+
+       /* Find next name */
+       offset = dns_skip ( name );
+       if ( offset < 0 )
+               return offset;
+
+       /* Skip over any subsequent empty names (e.g. due to padding
+        * bytes used in the NDP DNSSL option).
+        */
+       while ( ( offset < ( ( int ) name->len ) ) &&
+               ( *( ( uint8_t * ) ( name->data + offset ) ) == 0 ) ) {
+               offset++;
+       }
+
+       return offset;
+}
+
+/**
+ * Transcribe DNS name (for debugging)
+ *
+ * @v name             DNS name
+ * @ret string         Transcribed DNS name
+ */
+static const char * dns_name ( struct dns_name *name ) {
+       static char buf[256];
+       int len;
+
+       len = dns_decode ( name, buf, sizeof ( buf ) );
+       return ( ( len < 0 ) ? "<INVALID>" : buf );
+}
+
+/**
+ * Name a DNS query type (for debugging)
+ *
+ * @v type             Query type (in network byte order)
+ * @ret name           Type name
+ */
+static const char * dns_type ( uint16_t type ) {
+       switch ( type ) {
+       case htons ( DNS_TYPE_A ):      return "A";
+       case htons ( DNS_TYPE_AAAA ):   return "AAAA";
+       case htons ( DNS_TYPE_CNAME ):  return "CNAME";
+       default:                        return "<UNKNOWN>";
+       }
+}
+
+/** A DNS request */
+struct dns_request {
+       /** Reference counter */
+       struct refcnt refcnt;
+       /** Name resolution interface */
+       struct interface resolv;
+       /** Data transfer interface */
+       struct interface socket;
+       /** Retry timer */
+       struct retry_timer timer;
+
+       /** Socket address to fill in with resolved address */
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in sin;
+               struct sockaddr_in6 sin6;
+       } address;
+       /** Initial query type */
+       uint16_t qtype;
+       /** Buffer for current query */
+       struct {
+               /** Query header */
+               struct dns_header query;
+               /** Name buffer */
+               char name[DNS_MAX_NAME_LEN];
+               /** Space for question */
+               struct dns_question padding;
+       } __attribute__ (( packed )) buf;
+       /** Current query name */
+       struct dns_name name;
+       /** Question within current query */
+       struct dns_question *question;
+       /** Length of current query */
+       size_t len;
+       /** Offset of search suffix within current query */
+       size_t offset;
+       /** Search list */
+       struct dns_name search;
+       /** Recursion counter */
+       unsigned int recursion;
+};
+
+/**
+ * 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 );
+
+       /* Shut down interfaces */
+       intf_shutdown ( &dns->socket, rc );
+       intf_shutdown ( &dns->resolv, rc );
+}
+
+/**
+ * Mark DNS request as resolved and complete
+ *
+ * @v dns              DNS request
+ * @v rc               Return status code
+ */
+static void dns_resolved ( struct dns_request *dns ) {
+
+       DBGC ( dns, "DNS %p found address %s\n",
+              dns, sock_ntoa ( &dns->address.sa ) );
+
+       /* Return resolved address */
+       resolv_done ( &dns->resolv, &dns->address.sa );
+
+       /* Mark operation as complete */
+       dns_done ( dns, 0 );
+}
+
+/**
+ * Construct DNS question
+ *
+ * @v dns              DNS request
+ * @ret rc             Return status code
+ */
+static int dns_question ( struct dns_request *dns ) {
+       static struct dns_name search_root = {
+               .data = "",
+               .len = 1,
+       };
+       struct dns_name *search = &dns->search;
+       int len;
+       size_t offset;
+
+       /* Use root suffix if search list is empty */
+       if ( search->offset == search->len )
+               search = &search_root;
+
+       /* Overwrite current suffix */
+       dns->name.offset = dns->offset;
+       len = dns_copy ( search, &dns->name );
+       if ( len < 0 )
+               return len;
+
+       /* Sanity check */
+       offset = ( dns->name.offset + len );
+       if ( offset > dns->name.len ) {
+               DBGC ( dns, "DNS %p name is too long\n", dns );
+               return -EINVAL;
+       }
+
+       /* Construct question */
+       dns->question = ( ( ( void * ) &dns->buf ) + offset );
+       dns->question->qtype = dns->qtype;
+       dns->question->qclass = htons ( DNS_CLASS_IN );
+
+       /* Store length */
+       dns->len = ( offset + sizeof ( *(dns->question) ) );
+
+       /* Restore name */
+       dns->name.offset = offsetof ( typeof ( dns->buf ), name );
+
+       DBGC2 ( dns, "DNS %p question is %s type %s\n", dns,
+               dns_name ( &dns->name ), dns_type ( dns->question->qtype ) );
+
+       return 0;
+}
+
+/**
+ * Send DNS query
+ *
+ * @v dns              DNS request
+ * @ret rc             Return status code
+ */
+static int dns_send_packet ( struct dns_request *dns ) {
+       struct dns_header *query = &dns->buf.query;
+
+       /* Start retransmission timer */
+       start_timer ( &dns->timer );
+
+       /* Generate query identifier */
+       query->id = random();
+
+       /* Send query */
+       DBGC ( dns, "DNS %p sending query ID %#04x for %s type %s\n", dns,
+              ntohs ( query->id ), dns_name ( &dns->name ),
+              dns_type ( dns->question->qtype ) );
+
+       /* Send the data */
+       return xfer_deliver_raw ( &dns->socket, query, dns->len );
+}
+
+/**
+ * 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 dns              DNS request
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int dns_xfer_deliver ( struct dns_request *dns,
+                             struct io_buffer *iobuf,
+                             struct xfer_metadata *meta __unused ) {
+       struct dns_header *response = iobuf->data;
+       struct dns_header *query = &dns->buf.query;
+       unsigned int qtype = dns->question->qtype;
+       struct dns_name buf;
+       union dns_rr *rr;
+       int offset;
+       size_t answer_offset;
+       size_t next_offset;
+       size_t rdlength;
+       size_t name_len;
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( *response ) ) {
+               DBGC ( dns, "DNS %p received underlength packet length %zd\n",
+                      dns, iob_len ( iobuf ) );
+               rc = -EINVAL;
+               goto done;
+       }
+
+       /* Check response ID matches query ID */
+       if ( response->id != query->id ) {
+               DBGC ( dns, "DNS %p received unexpected response ID %#04x "
+                      "(wanted %d)\n", dns, ntohs ( response->id ),
+                      ntohs ( query->id ) );
+               rc = -EINVAL;
+               goto done;
+       }
+       DBGC ( dns, "DNS %p received response ID %#04x\n",
+              dns, ntohs ( response->id ) );
+
+       /* Check that we have exactly one question */
+       if ( response->qdcount != htons ( 1 ) ) {
+               DBGC ( dns, "DNS %p received response with %d questions\n",
+                      dns, ntohs ( response->qdcount ) );
+               rc = -EINVAL;
+               goto done;
+       }
+
+       /* Skip question section */
+       buf.data = iobuf->data;
+       buf.offset = sizeof ( *response );
+       buf.len = iob_len ( iobuf );
+       offset = dns_skip ( &buf );
+       if ( offset < 0 ) {
+               rc = offset;
+               DBGC ( dns, "DNS %p received response with malformed "
+                      "question: %s\n", dns, strerror ( rc ) );
+               goto done;
+       }
+       answer_offset = ( offset + sizeof ( struct dns_question ) );
+
+       /* 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.
+        */
+       for ( buf.offset = answer_offset ; buf.offset != buf.len ;
+             buf.offset = next_offset ) {
+
+               /* Check for valid name */
+               offset = dns_skip ( &buf );
+               if ( offset < 0 ) {
+                       rc = offset;
+                       DBGC ( dns, "DNS %p received response with malformed "
+                              "answer: %s\n", dns, strerror ( rc ) );
+                       goto done;
+               }
+
+               /* Check for sufficient space for resource record */
+               rr = ( buf.data + offset );
+               if ( ( offset + sizeof ( rr->common ) ) > buf.len ) {
+                       DBGC ( dns, "DNS %p received response with underlength "
+                              "RR\n", dns );
+                       rc = -EINVAL;
+                       goto done;
+               }
+               rdlength = ntohs ( rr->common.rdlength );
+               next_offset = ( offset + sizeof ( rr->common ) + rdlength );
+               if ( next_offset > buf.len ) {
+                       DBGC ( dns, "DNS %p received response with underlength "
+                              "RR\n", dns );
+                       rc = -EINVAL;
+                       goto done;
+               }
+
+               /* Skip non-matching names */
+               if ( dns_compare ( &buf, &dns->name ) != 0 ) {
+                       DBGC2 ( dns, "DNS %p ignoring response for %s type "
+                               "%s\n", dns, dns_name ( &buf ),
+                               dns_type ( rr->common.type ) );
+                       continue;
+               }
+
+               /* Handle answer */
+               switch ( rr->common.type ) {
+
+               case htons ( DNS_TYPE_AAAA ):
+
+                       /* Found the target AAAA record */
+                       if ( rdlength < sizeof ( dns->address.sin6.sin6_addr )){
+                               DBGC ( dns, "DNS %p received response with "
+                                      "underlength AAAA\n", dns );
+                               rc = -EINVAL;
+                               goto done;
+                       }
+                       dns->address.sin6.sin6_family = AF_INET6;
+                       memcpy ( &dns->address.sin6.sin6_addr,
+                                &rr->aaaa.in6_addr,
+                                sizeof ( dns->address.sin6.sin6_addr ) );
+                       dns_resolved ( dns );
+                       rc = 0;
+                       goto done;
+
+               case htons ( DNS_TYPE_A ):
+
+                       /* Found the target A record */
+                       if ( rdlength < sizeof ( dns->address.sin.sin_addr ) ) {
+                               DBGC ( dns, "DNS %p received response with "
+                                      "underlength A\n", dns );
+                               rc = -EINVAL;
+                               goto done;
+                       }
+                       dns->address.sin.sin_family = AF_INET;
+                       dns->address.sin.sin_addr = rr->a.in_addr;
+                       dns_resolved ( dns );
+                       rc = 0;
+                       goto done;
+
+               case htons ( DNS_TYPE_CNAME ):
+
+                       /* Terminate the operation if we recurse too far */
+                       if ( ++dns->recursion > DNS_MAX_CNAME_RECURSION ) {
+                               DBGC ( dns, "DNS %p recursion exceeded\n",
+                                      dns );
+                               rc = -ELOOP;
+                               dns_done ( dns, rc );
+                               goto done;
+                       }
+
+                       /* Found a CNAME record; update query and recurse */
+                       buf.offset = ( offset + sizeof ( rr->cname ) );
+                       DBGC ( dns, "DNS %p found CNAME %s\n",
+                              dns, dns_name ( &buf ) );
+                       dns->search.offset = dns->search.len;
+                       name_len = dns_copy ( &buf, &dns->name );
+                       dns->offset = ( offsetof ( typeof ( dns->buf ), name ) +
+                                       name_len - 1 /* Strip root label */ );
+                       if ( ( rc = dns_question ( dns ) ) != 0 ) {
+                               dns_done ( dns, rc );
+                               goto done;
+                       }
+                       next_offset = answer_offset;
+                       break;
+
+               default:
+                       DBGC ( dns, "DNS %p got unknown record type %d\n",
+                              dns, ntohs ( rr->common.type ) );
+                       break;
+               }
+       }
+
+       /* 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 );
+
+       /* Determine what to do next based on the type of query we
+        * issued and the response we received
+        */
+       switch ( qtype ) {
+
+       case htons ( DNS_TYPE_AAAA ):
+               /* We asked for an AAAA record and got nothing; try
+                * the A.
+                */
+               DBGC ( dns, "DNS %p found no AAAA record; trying A\n", dns );
+               dns->question->qtype = htons ( DNS_TYPE_A );
+               dns_send_packet ( dns );
+               rc = 0;
+               goto done;
+
+       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->question->qtype = htons ( DNS_TYPE_CNAME );
+               dns_send_packet ( dns );
+               rc = 0;
+               goto done;
+
+       case htons ( DNS_TYPE_CNAME ):
+               /* We asked for a CNAME record.  If we got a response
+                * (i.e. if the next AAAA/A query is already set up),
+                * then issue it.
+                */
+               if ( qtype == dns->qtype ) {
+                       dns_send_packet ( dns );
+                       rc = 0;
+                       goto done;
+               }
+
+               /* If we have already reached the end of the search list,
+                * then terminate lookup.
+                */
+               if ( dns->search.offset == dns->search.len ) {
+                       DBGC ( dns, "DNS %p found no CNAME record\n", dns );
+                       rc = -ENXIO_NO_RECORD;
+                       dns_done ( dns, rc );
+                       goto done;
+               }
+
+               /* Move to next entry in search list.  This can never fail,
+                * since we have already used this entry.
+                */
+               DBGC ( dns, "DNS %p found no CNAME record; trying next "
+                      "suffix\n", dns );
+               dns->search.offset = dns_skip_search ( &dns->search );
+               if ( ( rc = dns_question ( dns ) ) != 0 ) {
+                       dns_done ( dns, rc );
+                       goto done;
+               }
+               dns_send_packet ( dns );
+               goto done;
+
+       default:
+               assert ( 0 );
+               rc = -EINVAL;
+               dns_done ( dns, rc );
+               goto done;
+       }
+
+ done:
+       /* Free I/O buffer */
+       free_iob ( iobuf );
+       return rc;
+}
+
+/**
+ * Receive new data
+ *
+ * @v dns              DNS request
+ * @v rc               Reason for close
+ */
+static void dns_xfer_close ( struct dns_request *dns, int rc ) {
+
+       if ( ! rc )
+               rc = -ECONNABORTED;
+
+       dns_done ( dns, rc );
+}
+
+/** DNS socket interface operations */
+static struct interface_operation dns_socket_operations[] = {
+       INTF_OP ( xfer_deliver, struct dns_request *, dns_xfer_deliver ),
+       INTF_OP ( intf_close, struct dns_request *, dns_xfer_close ),
+};
+
+/** DNS socket interface descriptor */
+static struct interface_descriptor dns_socket_desc =
+       INTF_DESC ( struct dns_request, socket, dns_socket_operations );
+
+/** DNS resolver interface operations */
+static struct interface_operation dns_resolv_op[] = {
+       INTF_OP ( intf_close, struct dns_request *, dns_done ),
+};
+
+/** DNS resolver interface descriptor */
+static struct interface_descriptor dns_resolv_desc =
+       INTF_DESC ( struct dns_request, resolv, dns_resolv_op );
+
+/**
+ * Resolve name using DNS
+ *
+ * @v resolv           Name resolution interface
+ * @v name             Name to resolve
+ * @v sa               Socket address to fill in
+ * @ret rc             Return status code
+ */
+static int dns_resolv ( struct interface *resolv,
+                       const char *name, struct sockaddr *sa ) {
+       struct dns_request *dns;
+       struct dns_header *query;
+       size_t search_len;
+       int name_len;
+       int rc;
+
+       /* Fail immediately if no DNS servers */
+       if ( ! nameserver.sa.sa_family ) {
+               DBG ( "DNS not attempting to resolve \"%s\": "
+                     "no DNS servers\n", name );
+               rc = -ENXIO_NO_NAMESERVER;
+               goto err_no_nameserver;
+       }
+
+       /* Determine whether or not to use search list */
+       search_len = ( strchr ( name, '.' ) ? 0 : dns_search.len );
+
+       /* Allocate DNS structure */
+       dns = zalloc ( sizeof ( *dns ) + search_len );
+       if ( ! dns ) {
+               rc = -ENOMEM;
+               goto err_alloc_dns;
+       }
+       ref_init ( &dns->refcnt, NULL );
+       intf_init ( &dns->resolv, &dns_resolv_desc, &dns->refcnt );
+       intf_init ( &dns->socket, &dns_socket_desc, &dns->refcnt );
+       timer_init ( &dns->timer, dns_timer_expired, &dns->refcnt );
+       memcpy ( &dns->address.sa, sa, sizeof ( dns->address.sa ) );
+       dns->search.data = ( ( ( void * ) dns ) + sizeof ( *dns ) );
+       dns->search.len = search_len;
+       memcpy ( dns->search.data, dns_search.data, search_len );
+
+       /* Determine initial query type */
+       switch ( nameserver.sa.sa_family ) {
+       case AF_INET:
+               dns->qtype = htons ( DNS_TYPE_A );
+               break;
+       case AF_INET6:
+               dns->qtype = htons ( DNS_TYPE_AAAA );
+               break;
+       default:
+               rc = -ENOTSUP;
+               goto err_type;
+       }
+
+       /* Construct query */
+       query = &dns->buf.query;
+       query->flags = htons ( DNS_FLAG_RD );
+       query->qdcount = htons ( 1 );
+       dns->name.data = &dns->buf;
+       dns->name.offset = offsetof ( typeof ( dns->buf ), name );
+       dns->name.len = offsetof ( typeof ( dns->buf ), padding );
+       name_len = dns_encode ( name, &dns->name );
+       if ( name_len < 0 ) {
+               rc = name_len;
+               goto err_encode;
+       }
+       dns->offset = ( offsetof ( typeof ( dns->buf ), name ) +
+                       name_len - 1 /* Strip root label */ );
+       if ( ( rc = dns_question ( dns ) ) != 0 )
+               goto err_question;
+
+       /* Open UDP connection */
+       if ( ( rc = xfer_open_socket ( &dns->socket, SOCK_DGRAM,
+                                      &nameserver.sa, NULL ) ) != 0 ) {
+               DBGC ( dns, "DNS %p could not open socket: %s\n",
+                      dns, strerror ( rc ) );
+               goto err_open_socket;
+       }
+
+       /* Start timer to trigger first packet */
+       start_timer_nodelay ( &dns->timer );
+
+       /* Attach parent interface, mortalise self, and return */
+       intf_plug_plug ( &dns->resolv, resolv );
+       ref_put ( &dns->refcnt );
+       return 0;       
+
+ err_open_socket:
+ err_question:
+ err_encode:
+ err_type:
+       ref_put ( &dns->refcnt );
+ err_alloc_dns:
+ err_no_nameserver:
+       return rc;
+}
+
+/** DNS name resolver */
+struct resolver dns_resolver __resolver ( RESOLV_NORMAL ) = {
+       .name = "DNS",
+       .resolv = dns_resolv,
+};
+
+/******************************************************************************
+ *
+ * Settings
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Format DNS search list setting
+ *
+ * @v type             Setting type
+ * @v raw              Raw setting value
+ * @v raw_len          Length of raw setting value
+ * @v buf              Buffer to contain formatted value
+ * @v len              Length of buffer
+ * @ret len            Length of formatted value, or negative error
+ */
+static int format_dnssl_setting ( const struct setting_type *type __unused,
+                                 const void *raw, size_t raw_len,
+                                 char *buf, size_t len ) {
+       struct dns_name name = {
+               .data = ( ( void * ) raw ),
+               .len = raw_len,
+       };
+       size_t remaining = len;
+       size_t total = 0;
+       int name_len;
+
+       while ( name.offset < raw_len ) {
+
+               /* Decode name */
+               remaining = ( ( total < len ) ? ( len - total ) : 0 );
+               name_len = dns_decode ( &name, ( buf + total ), remaining );
+               if ( name_len < 0 )
+                       return name_len;
+               total += name_len;
+
+               /* Move to next name */
+               name.offset = dns_skip_search ( &name );
+
+               /* Add separator if applicable */
+               if ( name.offset != raw_len ) {
+                       if ( total < len )
+                               buf[total] = ' ';
+                       total++;
+               }
+       }
+
+       return total;
+}
+
+/** A DNS search list setting type */
+const struct setting_type setting_type_dnssl __setting_type = {
+       .name = "dnssl",
+       .format = format_dnssl_setting,
+};
+
+/** IPv4 DNS server setting */
+const struct setting dns_setting __setting ( SETTING_IP_EXTRA, dns ) = {
+       .name = "dns",
+       .description = "DNS server",
+       .tag = DHCP_DNS_SERVERS,
+       .type = &setting_type_ipv4,
+};
+
+/** IPv6 DNS server setting */
+const struct setting dns6_setting __setting ( SETTING_IP_EXTRA, dns6 ) = {
+       .name = "dns6",
+       .description = "DNS server",
+       .tag = DHCPV6_DNS_SERVERS,
+       .type = &setting_type_ipv6,
+       .scope = &ipv6_scope,
+};
+
+/** DNS search list */
+const struct setting dnssl_setting __setting ( SETTING_IP_EXTRA, dnssl ) = {
+       .name = "dnssl",
+       .description = "DNS search list",
+       .tag = DHCP_DOMAIN_SEARCH,
+       .type = &setting_type_dnssl,
+};
+
+/**
+ * Apply DNS search list
+ *
+ */
+static void apply_dns_search ( void ) {
+       char *localdomain;
+       int len;
+
+       /* Free existing search list */
+       free ( dns_search.data );
+       memset ( &dns_search, 0, sizeof ( dns_search ) );
+
+       /* Fetch DNS search list */
+       len = fetch_setting_copy ( NULL, &dnssl_setting, NULL, NULL,
+                                  &dns_search.data );
+       if ( len >= 0 ) {
+               dns_search.len = len;
+               return;
+       }
+
+       /* If no DNS search list exists, try to fetch the local domain */
+       fetch_string_setting_copy ( NULL, &domain_setting, &localdomain );
+       if ( localdomain ) {
+               len = dns_encode ( localdomain, &dns_search );
+               if ( len >= 0 ) {
+                       dns_search.data = malloc ( len );
+                       if ( dns_search.data ) {
+                               dns_search.len = len;
+                               dns_encode ( localdomain, &dns_search );
+                       }
+               }
+               free ( localdomain );
+               return;
+       }
+}
+
+/**
+ * Apply DNS settings
+ *
+ * @ret rc             Return status code
+ */
+static int apply_dns_settings ( void ) {
+
+       /* Fetch DNS server address */
+       nameserver.sa.sa_family = 0;
+       if ( fetch_ipv6_setting ( NULL, &dns6_setting,
+                                 &nameserver.sin6.sin6_addr ) >= 0 ) {
+               nameserver.sin6.sin6_family = AF_INET6;
+       } else if ( fetch_ipv4_setting ( NULL, &dns_setting,
+                                        &nameserver.sin.sin_addr ) >= 0 ) {
+               nameserver.sin.sin_family = AF_INET;
+       }
+       if ( nameserver.sa.sa_family ) {
+               DBG ( "DNS using nameserver %s\n",
+                     sock_ntoa ( &nameserver.sa ) );
+       }
+
+       /* Fetch DNS search list */
+       apply_dns_search();
+       if ( DBG_LOG && ( dns_search.len != 0 ) ) {
+               struct dns_name name;
+               int offset;
+
+               DBG ( "DNS search list:" );
+               memcpy ( &name, &dns_search, sizeof ( name ) );
+               while ( name.offset != name.len ) {
+                       DBG ( " %s", dns_name ( &name ) );
+                       offset = dns_skip_search ( &name );
+                       if ( offset < 0 )
+                               break;
+                       name.offset = offset;
+               }
+               DBG ( "\n" );
+       }
+
+       return 0;
+}
+
+/** DNS settings applicator */
+struct settings_applicator dns_applicator __settings_applicator = {
+       .apply = apply_dns_settings,
+};