From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/ipxe/src/net/udp/dns.c | 1152 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1152 insertions(+) create mode 100644 qemu/roms/ipxe/src/net/udp/dns.c (limited to '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 index 000000000..fffe6e697 --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/dns.c @@ -0,0 +1,1152 @@ +/* + * Copyright (C) 2006 Michael Brown . + * + * Portions copyright (C) 2004 Anselm M. Hoffmeister + * . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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 ) ? "" : 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 ""; + } +} + +/** 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, +}; -- cgit 1.2.3-korg