/* * 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. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #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 ); } /** * Report job progress * * @v dns DNS request * @v progress Progress report to fill in * @ret ongoing_rc Ongoing job status code (if known) */ static int dns_progress ( struct dns_request *dns, struct job_progress *progress ) { /* Show current question as progress message */ dns_decode ( &dns->name, progress->message, sizeof ( progress->message ) ); return 0; } /** 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 ( job_progress, struct dns_request *, dns_progress ), 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_IP4_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_IP6_EXTRA, dns6 ) = { .name = "dns6", .description = "DNS server", .tag = DHCPV6_DNS_SERVERS, .type = &setting_type_ipv6, .scope = &dhcpv6_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, };