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};
604