source: bootcd/isolinux/syslinux-6.03/gpxe/src/net/udp/dns.c @ 26ffad7

Last change on this file since 26ffad7 was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

  • Property mode set to 100644
File size: 14.7 KB
Line 
1/*
2 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * Portions copyright (C) 2004 Anselm M. Hoffmeister
5 * <stockholm@users.sourceforge.net>.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22FILE_LICENCE ( GPL2_OR_LATER );
23
24#include <stdint.h>
25#include <stdlib.h>
26#include <string.h>
27#include <stdio.h>
28#include <errno.h>
29#include <byteswap.h>
30#include <gpxe/refcnt.h>
31#include <gpxe/xfer.h>
32#include <gpxe/open.h>
33#include <gpxe/resolv.h>
34#include <gpxe/retry.h>
35#include <gpxe/tcpip.h>
36#include <gpxe/settings.h>
37#include <gpxe/features.h>
38#include <gpxe/dns.h>
39
40/** @file
41 *
42 * DNS protocol
43 *
44 */
45
46FEATURE ( FEATURE_PROTOCOL, "DNS", DHCP_EB_FEATURE_DNS, 1 );
47
48/** The DNS server */
49static struct sockaddr_tcpip nameserver = {
50        .st_port = htons ( DNS_PORT ),
51};
52
53/** The local domain */
54static char *localdomain;
55
56/** A DNS request */
57struct dns_request {
58        /** Reference counter */
59        struct refcnt refcnt;
60        /** Name resolution interface */
61        struct resolv_interface resolv;
62        /** Data transfer interface */
63        struct xfer_interface socket;
64        /** Retry timer */
65        struct retry_timer timer;
66
67        /** Socket address to fill in with resolved address */
68        struct sockaddr sa;
69        /** Current query packet */
70        struct dns_query query;
71        /** Location of query info structure within current packet
72         *
73         * The query info structure is located immediately after the
74         * compressed name.
75         */
76        struct dns_query_info *qinfo;
77        /** Recursion counter */
78        unsigned int recursion;
79};
80
81/**
82 * Mark DNS request as complete
83 *
84 * @v dns               DNS request
85 * @v rc                Return status code
86 */
87static void dns_done ( struct dns_request *dns, int rc ) {
88
89        /* Stop the retry timer */
90        stop_timer ( &dns->timer );
91
92        /* Close data transfer interface */
93        xfer_nullify ( &dns->socket );
94        xfer_close ( &dns->socket, rc );
95
96        /* Mark name resolution as complete */
97        resolv_done ( &dns->resolv, &dns->sa, rc );
98}
99
100/**
101 * Compare DNS reply name against the query name from the original request
102 *
103 * @v dns               DNS request
104 * @v reply             DNS reply
105 * @v rname             Reply name
106 * @ret zero            Names match
107 * @ret non-zero        Names do not match
108 */
109static int dns_name_cmp ( struct dns_request *dns,
110                          const struct dns_header *reply,
111                          const char *rname ) {
112        const char *qname = dns->query.payload;
113        int i;
114
115        while ( 1 ) {
116                /* Obtain next section of rname */
117                while ( ( *rname ) & 0xc0 ) {
118                        rname = ( ( ( char * ) reply ) +
119                                  ( ntohs( *((uint16_t *)rname) ) & ~0xc000 ));
120                }
121                /* Check that lengths match */
122                if ( *rname != *qname )
123                        return -1;
124                /* If length is zero, we have reached the end */
125                if ( ! *qname )
126                        return 0;
127                /* Check that data matches */
128                for ( i = *qname + 1; i > 0 ; i-- ) {
129                        if ( *(rname++) != *(qname++) )
130                                return -1;
131                }
132        }
133}
134
135/**
136 * Skip over a (possibly compressed) DNS name
137 *
138 * @v name              DNS name
139 * @ret name            Next DNS name
140 */
141static const char * dns_skip_name ( const char *name ) {
142        while ( 1 ) {
143                if ( ! *name ) {
144                        /* End of name */
145                        return ( name + 1);
146                }
147                if ( *name & 0xc0 ) {
148                        /* Start of a compressed name */
149                        return ( name + 2 );
150                }
151                /* Uncompressed name portion */
152                name += *name + 1;
153        }
154}
155
156/**
157 * Find an RR in a reply packet corresponding to our query
158 *
159 * @v dns               DNS request
160 * @v reply             DNS reply
161 * @ret rr              DNS RR, or NULL if not found
162 */
163static union dns_rr_info * dns_find_rr ( struct dns_request *dns,
164                                         const struct dns_header *reply ) {
165        int i, cmp;
166        const char *p = ( ( char * ) reply ) + sizeof ( struct dns_header );
167        union dns_rr_info *rr_info;
168
169        /* Skip over the questions section */
170        for ( i = ntohs ( reply->qdcount ) ; i > 0 ; i-- ) {
171                p = dns_skip_name ( p ) + sizeof ( struct dns_query_info );
172        }
173
174        /* Process the answers section */
175        for ( i = ntohs ( reply->ancount ) ; i > 0 ; i-- ) {
176                cmp = dns_name_cmp ( dns, reply, p );
177                p = dns_skip_name ( p );
178                rr_info = ( ( union dns_rr_info * ) p );
179                if ( cmp == 0 )
180                        return rr_info;
181                p += ( sizeof ( rr_info->common ) +
182                       ntohs ( rr_info->common.rdlength ) );
183        }
184
185        return NULL;
186}
187
188/**
189 * Append DHCP domain name if available and name is not fully qualified
190 *
191 * @v string            Name as a NUL-terminated string
192 * @ret fqdn            Fully-qualified domain name, malloc'd copy
193 *
194 * The caller must free fqdn which is allocated even if the name is already
195 * fully qualified.
196 */
197static char * dns_qualify_name ( const char *string ) {
198        char *fqdn;
199
200        /* Leave unchanged if already fully-qualified or no local domain */
201        if ( ( ! localdomain ) || ( strchr ( string, '.' ) != 0 ) )
202                return strdup ( string );
203
204        /* Append local domain to name */
205        asprintf ( &fqdn, "%s.%s", string, localdomain );
206        return fqdn;
207}
208
209/**
210 * Convert a standard NUL-terminated string to a DNS name
211 *
212 * @v string            Name as a NUL-terminated string
213 * @v buf               Buffer in which to place DNS name
214 * @ret next            Byte following constructed DNS name
215 *
216 * DNS names consist of "<length>element" pairs.
217 */
218static char * dns_make_name ( const char *string, char *buf ) {
219        char *length_byte = buf++;
220        char c;
221
222        while ( ( c = *(string++) ) ) {
223                if ( c == '.' ) {
224                        *length_byte = buf - length_byte - 1;
225                        length_byte = buf;
226                }
227                *(buf++) = c;
228        }
229        *length_byte = buf - length_byte - 1;
230        *(buf++) = '\0';
231        return buf;
232}
233
234/**
235 * Convert an uncompressed DNS name to a NUL-terminated string
236 *
237 * @v name              DNS name
238 * @ret string          NUL-terminated string
239 *
240 * Produce a printable version of a DNS name.  Used only for debugging.
241 */
242static inline char * dns_unmake_name ( char *name ) {
243        char *p;
244        unsigned int len;
245
246        p = name;
247        while ( ( len = *p ) ) {
248                *(p++) = '.';
249                p += len;
250        }
251
252        return name + 1;
253}
254
255/**
256 * Decompress a DNS name
257 *
258 * @v reply             DNS replay
259 * @v name              DNS name
260 * @v buf               Buffer into which to decompress DNS name
261 * @ret next            Byte following decompressed DNS name
262 */
263static char * dns_decompress_name ( const struct dns_header *reply,
264                                    const char *name, char *buf ) {
265        int i, len;
266
267        do {
268                /* Obtain next section of name */
269                while ( ( *name ) & 0xc0 ) {
270                        name = ( ( char * ) reply +
271                                 ( ntohs ( *((uint16_t *)name) ) & ~0xc000 ) );
272                }
273                /* Copy data */
274                len = *name;
275                for ( i = len + 1 ; i > 0 ; i-- ) {
276                        *(buf++) = *(name++);
277                }
278        } while ( len );
279        return buf;
280}
281
282/**
283 * Send next packet in DNS request
284 *
285 * @v dns               DNS request
286 */
287static int dns_send_packet ( struct dns_request *dns ) {
288        static unsigned int qid = 0;
289        size_t qlen;
290
291        /* Increment query ID */
292        dns->query.dns.id = htons ( ++qid );
293
294        DBGC ( dns, "DNS %p sending query ID %d\n", dns, qid );
295
296        /* Start retransmission timer */
297        start_timer ( &dns->timer );
298
299        /* Send the data */
300        qlen = ( ( ( void * ) dns->qinfo ) - ( ( void * ) &dns->query )
301                 + sizeof ( dns->qinfo ) );
302        return xfer_deliver_raw ( &dns->socket, &dns->query, qlen );
303}
304
305/**
306 * Handle DNS retransmission timer expiry
307 *
308 * @v timer             Retry timer
309 * @v fail              Failure indicator
310 */
311static void dns_timer_expired ( struct retry_timer *timer, int fail ) {
312        struct dns_request *dns =
313                container_of ( timer, struct dns_request, timer );
314
315        if ( fail ) {
316                dns_done ( dns, -ETIMEDOUT );
317        } else {
318                dns_send_packet ( dns );
319        }
320}
321
322/**
323 * Receive new data
324 *
325 * @v socket            UDP socket
326 * @v data              DNS reply
327 * @v len               Length of DNS reply
328 * @ret rc              Return status code
329 */
330static int dns_xfer_deliver_raw ( struct xfer_interface *socket,
331                                  const void *data, size_t len ) {
332        struct dns_request *dns =
333                container_of ( socket, struct dns_request, socket );
334        const struct dns_header *reply = data;
335        union dns_rr_info *rr_info;
336        struct sockaddr_in *sin;
337        unsigned int qtype = dns->qinfo->qtype;
338
339        /* Sanity check */
340        if ( len < sizeof ( *reply ) ) {
341                DBGC ( dns, "DNS %p received underlength packet length %zd\n",
342                       dns, len );
343                return -EINVAL;
344        }
345
346        /* Check reply ID matches query ID */
347        if ( reply->id != dns->query.dns.id ) {
348                DBGC ( dns, "DNS %p received unexpected reply ID %d "
349                       "(wanted %d)\n", dns, ntohs ( reply->id ),
350                       ntohs ( dns->query.dns.id ) );
351                return -EINVAL;
352        }
353
354        DBGC ( dns, "DNS %p received reply ID %d\n", dns, ntohs ( reply->id ));
355
356        /* Stop the retry timer.  After this point, each code path
357         * must either restart the timer by calling dns_send_packet(),
358         * or mark the DNS operation as complete by calling
359         * dns_done()
360         */
361        stop_timer ( &dns->timer );
362
363        /* Search through response for useful answers.  Do this
364         * multiple times, to take advantage of useful nameservers
365         * which send us e.g. the CNAME *and* the A record for the
366         * pointed-to name.
367         */
368        while ( ( rr_info = dns_find_rr ( dns, reply ) ) ) {
369                switch ( rr_info->common.type ) {
370
371                case htons ( DNS_TYPE_A ):
372
373                        /* Found the target A record */
374                        DBGC ( dns, "DNS %p found address %s\n",
375                               dns, inet_ntoa ( rr_info->a.in_addr ) );
376                        sin = ( struct sockaddr_in * ) &dns->sa;
377                        sin->sin_family = AF_INET;
378                        sin->sin_addr = rr_info->a.in_addr;
379
380                        /* Mark operation as complete */
381                        dns_done ( dns, 0 );
382                        return 0;
383
384                case htons ( DNS_TYPE_CNAME ):
385
386                        /* Found a CNAME record; update query and recurse */
387                        DBGC ( dns, "DNS %p found CNAME\n", dns );
388                        dns->qinfo = ( void * ) dns_decompress_name ( reply,
389                                                         rr_info->cname.cname,
390                                                         dns->query.payload );
391                        dns->qinfo->qtype = htons ( DNS_TYPE_A );
392                        dns->qinfo->qclass = htons ( DNS_CLASS_IN );
393                       
394                        /* Terminate the operation if we recurse too far */
395                        if ( ++dns->recursion > DNS_MAX_CNAME_RECURSION ) {
396                                DBGC ( dns, "DNS %p recursion exceeded\n",
397                                       dns );
398                                dns_done ( dns, -ELOOP );
399                                return 0;
400                        }
401                        break;
402
403                default:
404                        DBGC ( dns, "DNS %p got unknown record type %d\n",
405                               dns, ntohs ( rr_info->common.type ) );
406                        break;
407                }
408        }
409       
410        /* Determine what to do next based on the type of query we
411         * issued and the reponse we received
412         */
413        switch ( qtype ) {
414
415        case htons ( DNS_TYPE_A ):
416                /* We asked for an A record and got nothing;
417                 * try the CNAME.
418                 */
419                DBGC ( dns, "DNS %p found no A record; trying CNAME\n", dns );
420                dns->qinfo->qtype = htons ( DNS_TYPE_CNAME );
421                dns_send_packet ( dns );
422                return 0;
423
424        case htons ( DNS_TYPE_CNAME ):
425                /* We asked for a CNAME record.  If we got a response
426                 * (i.e. if the next A query is already set up), then
427                 * issue it, otherwise abort.
428                 */
429                if ( dns->qinfo->qtype == htons ( DNS_TYPE_A ) ) {
430                        dns_send_packet ( dns );
431                        return 0;
432                } else {
433                        DBGC ( dns, "DNS %p found no CNAME record\n", dns );
434                        dns_done ( dns, -ENXIO );
435                        return 0;
436                }
437
438        default:
439                assert ( 0 );
440                dns_done ( dns, -EINVAL );
441                return 0;
442        }
443}
444
445/**
446 * Receive new data
447 *
448 * @v socket            UDP socket
449 * @v rc                Reason for close
450 */
451static void dns_xfer_close ( struct xfer_interface *socket, int rc ) {
452        struct dns_request *dns =
453                container_of ( socket, struct dns_request, socket );
454
455        if ( ! rc )
456                rc = -ECONNABORTED;
457
458        dns_done ( dns, rc );
459}
460
461/** DNS socket operations */
462static struct xfer_interface_operations dns_socket_operations = {
463        .close          = dns_xfer_close,
464        .vredirect      = xfer_vreopen,
465        .window         = unlimited_xfer_window,
466        .alloc_iob      = default_xfer_alloc_iob,
467        .deliver_iob    = xfer_deliver_as_raw,
468        .deliver_raw    = dns_xfer_deliver_raw,
469};
470
471/**
472 * Resolve name using DNS
473 *
474 * @v resolv            Name resolution interface
475 * @v name              Name to resolve
476 * @v sa                Socket address to fill in
477 * @ret rc              Return status code
478 */
479static int dns_resolv ( struct resolv_interface *resolv,
480                        const char *name, struct sockaddr *sa ) {
481        struct dns_request *dns;
482        char *fqdn;
483        int rc;
484
485        /* Fail immediately if no DNS servers */
486        if ( ! nameserver.st_family ) {
487                DBG ( "DNS not attempting to resolve \"%s\": "
488                      "no DNS servers\n", name );
489                rc = -ENXIO;
490                goto err_no_nameserver;
491        }
492
493        /* Ensure fully-qualified domain name if DHCP option was given */
494        fqdn = dns_qualify_name ( name );
495        if ( ! fqdn ) {
496                rc = -ENOMEM;
497                goto err_qualify_name;
498        }
499
500        /* Allocate DNS structure */
501        dns = zalloc ( sizeof ( *dns ) );
502        if ( ! dns ) {
503                rc = -ENOMEM;
504                goto err_alloc_dns;
505        }
506        resolv_init ( &dns->resolv, &null_resolv_ops, &dns->refcnt );
507        xfer_init ( &dns->socket, &dns_socket_operations, &dns->refcnt );
508        dns->timer.expired = dns_timer_expired;
509        memcpy ( &dns->sa, sa, sizeof ( dns->sa ) );
510
511        /* Create query */
512        dns->query.dns.flags = htons ( DNS_FLAG_QUERY | DNS_FLAG_OPCODE_QUERY |
513                                       DNS_FLAG_RD );
514        dns->query.dns.qdcount = htons ( 1 );
515        dns->qinfo = ( void * ) dns_make_name ( fqdn, dns->query.payload );
516        dns->qinfo->qtype = htons ( DNS_TYPE_A );
517        dns->qinfo->qclass = htons ( DNS_CLASS_IN );
518
519        /* Open UDP connection */
520        if ( ( rc = xfer_open_socket ( &dns->socket, SOCK_DGRAM,
521                                       ( struct sockaddr * ) &nameserver,
522                                       NULL ) ) != 0 ) {
523                DBGC ( dns, "DNS %p could not open socket: %s\n",
524                       dns, strerror ( rc ) );
525                goto err_open_socket;
526        }
527
528        /* Send first DNS packet */
529        dns_send_packet ( dns );
530
531        /* Attach parent interface, mortalise self, and return */
532        resolv_plug_plug ( &dns->resolv, resolv );
533        ref_put ( &dns->refcnt );
534        free ( fqdn );
535        return 0;       
536
537 err_open_socket:
538 err_alloc_dns:
539        ref_put ( &dns->refcnt );
540 err_qualify_name:
541        free ( fqdn );
542 err_no_nameserver:
543        return rc;
544}
545
546/** DNS name resolver */
547struct resolver dns_resolver __resolver ( RESOLV_NORMAL ) = {
548        .name = "DNS",
549        .resolv = dns_resolv,
550};
551
552/******************************************************************************
553 *
554 * Settings
555 *
556 ******************************************************************************
557 */
558
559/** DNS server setting */
560struct setting dns_setting __setting = {
561        .name = "dns",
562        .description = "DNS server",
563        .tag = DHCP_DNS_SERVERS,
564        .type = &setting_type_ipv4,
565};
566
567/** Domain name setting */
568struct setting domain_setting __setting = {
569        .name = "domain",
570        .description = "Local domain",
571        .tag = DHCP_DOMAIN_NAME,
572        .type = &setting_type_string,
573};
574
575/**
576 * Apply DNS settings
577 *
578 * @ret rc              Return status code
579 */
580static int apply_dns_settings ( void ) {
581        struct sockaddr_in *sin_nameserver =
582                ( struct sockaddr_in * ) &nameserver;
583        int len;
584
585        if ( ( len = fetch_ipv4_setting ( NULL, &dns_setting,
586                                          &sin_nameserver->sin_addr ) ) >= 0 ){
587                sin_nameserver->sin_family = AF_INET;
588                DBG ( "DNS using nameserver %s\n",
589                      inet_ntoa ( sin_nameserver->sin_addr ) );
590        }
591
592        /* Get local domain DHCP option */
593        if ( ( len = fetch_string_setting_copy ( NULL, &domain_setting,
594                                                 &localdomain ) ) >= 0 )
595                DBG ( "DNS local domain %s\n", localdomain );
596
597        return 0;
598}
599
600/** DNS settings applicator */
601struct settings_applicator dns_applicator __settings_applicator = {
602        .apply = apply_dns_settings,
603};
Note: See TracBrowser for help on using the repository browser.