1 /*
2  * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <byteswap.h>
31 #include <ipxe/interface.h>
32 #include <ipxe/xfer.h>
33 #include <ipxe/iobuf.h>
34 #include <ipxe/open.h>
35 #include <ipxe/netdevice.h>
36 #include <ipxe/settings.h>
37 #include <ipxe/retry.h>
38 #include <ipxe/timer.h>
39 #include <ipxe/in.h>
40 #include <ipxe/crc32.h>
41 #include <ipxe/errortab.h>
42 #include <ipxe/ipv6.h>
43 #include <ipxe/dhcp_arch.h>
44 #include <ipxe/dhcpv6.h>
45 
46 /** @file
47  *
48  * Dynamic Host Configuration Protocol for IPv6
49  *
50  */
51 
52 /* Disambiguate the various error causes */
53 #define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL )
54 #define EINFO_EPROTO_UNSPECFAIL \
55 	__einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" )
56 #define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL )
57 #define EINFO_EPROTO_NOADDRSAVAIL \
58 	__einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" )
59 #define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING )
60 #define EINFO_EPROTO_NOBINDING \
61 	__einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" )
62 #define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK )
63 #define EINFO_EPROTO_NOTONLINK \
64 	__einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" )
65 #define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST )
66 #define EINFO_EPROTO_USEMULTICAST \
67 	__einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" )
68 #define EPROTO_STATUS( status )						\
69 	EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL,	\
70 		EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING,			\
71 		EPROTO_NOTONLINK, EPROTO_USEMULTICAST )
72 
73 /** Human-readable error messages */
74 struct errortab dhcpv6_errors[] __errortab = {
75 	__einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL ),
76 };
77 
78 /****************************************************************************
79  *
80  * DHCPv6 option lists
81  *
82  */
83 
84 /** A DHCPv6 option list */
85 struct dhcpv6_option_list {
86 	/** Data buffer */
87 	const void *data;
88 	/** Length of data buffer */
89 	size_t len;
90 };
91 
92 /**
93  * Find DHCPv6 option
94  *
95  * @v options		DHCPv6 option list
96  * @v code		Option code
97  * @ret option		DHCPv6 option, or NULL if not found
98  */
99 static const union dhcpv6_any_option *
dhcpv6_option(struct dhcpv6_option_list * options,unsigned int code)100 dhcpv6_option ( struct dhcpv6_option_list *options, unsigned int code ) {
101 	const union dhcpv6_any_option *option = options->data;
102 	size_t remaining = options->len;
103 	size_t data_len;
104 
105 	/* Scan through list of options */
106 	while ( remaining >= sizeof ( option->header ) ) {
107 
108 		/* Calculate and validate option length */
109 		remaining -= sizeof ( option->header );
110 		data_len = ntohs ( option->header.len );
111 		if ( data_len > remaining ) {
112 			/* Malformed option list */
113 			return NULL;
114 		}
115 
116 		/* Return if we have found the specified option */
117 		if ( option->header.code == htons ( code ) )
118 			return option;
119 
120 		/* Otherwise, move to the next option */
121 		option = ( ( ( void * ) option->header.data ) + data_len );
122 		remaining -= data_len;
123 	}
124 
125 	return NULL;
126 }
127 
128 /**
129  * Check DHCPv6 client or server identifier
130  *
131  * @v options		DHCPv6 option list
132  * @v code		Option code
133  * @v expected		Expected value
134  * @v len		Length of expected value
135  * @ret rc		Return status code
136  */
dhcpv6_check_duid(struct dhcpv6_option_list * options,unsigned int code,const void * expected,size_t len)137 static int dhcpv6_check_duid ( struct dhcpv6_option_list *options,
138 			       unsigned int code, const void *expected,
139 			       size_t len ) {
140 	const union dhcpv6_any_option *option;
141 	const struct dhcpv6_duid_option *duid;
142 
143 	/* Find option */
144 	option = dhcpv6_option ( options, code );
145 	if ( ! option )
146 		return -ENOENT;
147 	duid = &option->duid;
148 
149 	/* Check option length */
150 	if ( ntohs ( duid->header.len ) != len )
151 		return -EINVAL;
152 
153 	/* Compare option value */
154 	if ( memcmp ( duid->duid, expected, len ) != 0 )
155 		return -EINVAL;
156 
157 	return 0;
158 }
159 
160 /**
161  * Get DHCPv6 status code
162  *
163  * @v options		DHCPv6 option list
164  * @ret rc		Return status code
165  */
dhcpv6_status_code(struct dhcpv6_option_list * options)166 static int dhcpv6_status_code ( struct dhcpv6_option_list *options ) {
167 	const union dhcpv6_any_option *option;
168 	const struct dhcpv6_status_code_option *status_code;
169 	unsigned int status;
170 
171 	/* Find status code option, if present */
172 	option = dhcpv6_option ( options, DHCPV6_STATUS_CODE );
173 	if ( ! option ) {
174 		/* Omitted status code should be treated as "success" */
175 		return 0;
176 	}
177 	status_code = &option->status_code;
178 
179 	/* Sanity check */
180 	if ( ntohs ( status_code->header.len ) <
181 	     ( sizeof ( *status_code ) - sizeof ( status_code->header ) ) ) {
182 		return -EINVAL;
183 	}
184 
185 	/* Calculate iPXE error code from DHCPv6 status code */
186 	status = ntohs ( status_code->status );
187 	return ( status ? -EPROTO_STATUS ( status ) : 0 );
188 }
189 
190 /**
191  * Get DHCPv6 identity association address
192  *
193  * @v options		DHCPv6 option list
194  * @v iaid		Identity association ID
195  * @v address		IPv6 address to fill in
196  * @ret rc		Return status code
197  */
dhcpv6_iaaddr(struct dhcpv6_option_list * options,uint32_t iaid,struct in6_addr * address)198 static int dhcpv6_iaaddr ( struct dhcpv6_option_list *options, uint32_t iaid,
199 			   struct in6_addr *address ) {
200 	const union dhcpv6_any_option *option;
201 	const struct dhcpv6_ia_na_option *ia_na;
202 	const struct dhcpv6_iaaddr_option *iaaddr;
203 	struct dhcpv6_option_list suboptions;
204 	size_t len;
205 	int rc;
206 
207 	/* Find identity association option, if present */
208 	option = dhcpv6_option ( options, DHCPV6_IA_NA );
209 	if ( ! option )
210 		return -ENOENT;
211 	ia_na = &option->ia_na;
212 
213 	/* Sanity check */
214 	len = ntohs ( ia_na->header.len );
215 	if ( len < ( sizeof ( *ia_na ) - sizeof ( ia_na->header ) ) )
216 		return -EINVAL;
217 
218 	/* Check identity association ID */
219 	if ( ia_na->iaid != htonl ( iaid ) )
220 		return -EINVAL;
221 
222 	/* Construct IA_NA sub-options list */
223 	suboptions.data = ia_na->options;
224 	suboptions.len = ( len + sizeof ( ia_na->header ) -
225 			   offsetof ( typeof ( *ia_na ), options ) );
226 
227 	/* Check IA_NA status code */
228 	if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
229 		return rc;
230 
231 	/* Find identity association address, if present */
232 	option = dhcpv6_option ( &suboptions, DHCPV6_IAADDR );
233 	if ( ! option )
234 		return -ENOENT;
235 	iaaddr = &option->iaaddr;
236 
237 	/* Sanity check */
238 	len = ntohs ( iaaddr->header.len );
239 	if ( len < ( sizeof ( *iaaddr ) - sizeof ( iaaddr->header ) ) )
240 		return -EINVAL;
241 
242 	/* Construct IAADDR sub-options list */
243 	suboptions.data = iaaddr->options;
244 	suboptions.len = ( len + sizeof ( iaaddr->header ) -
245 			   offsetof ( typeof ( *iaaddr ), options ) );
246 
247 	/* Check IAADDR status code */
248 	if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
249 		return rc;
250 
251 	/* Extract IPv6 address */
252 	memcpy ( address, &iaaddr->address, sizeof ( *address ) );
253 
254 	return 0;
255 }
256 
257 /****************************************************************************
258  *
259  * DHCPv6 settings blocks
260  *
261  */
262 
263 /** A DHCPv6 settings block */
264 struct dhcpv6_settings {
265 	/** Reference count */
266 	struct refcnt refcnt;
267 	/** Settings block */
268 	struct settings settings;
269 	/** Leased address */
270 	struct in6_addr lease;
271 	/** Option list */
272 	struct dhcpv6_option_list options;
273 };
274 
275 /**
276  * Check applicability of DHCPv6 setting
277  *
278  * @v settings		Settings block
279  * @v setting		Setting
280  * @ret applies		Setting applies within this settings block
281  */
dhcpv6_applies(struct settings * settings __unused,const struct setting * setting)282 static int dhcpv6_applies ( struct settings *settings __unused,
283 			    const struct setting *setting ) {
284 
285 	return ( ( setting->scope == &dhcpv6_scope ) ||
286 		 ( setting_cmp ( setting, &ip6_setting ) == 0 ) );
287 }
288 
289 /**
290  * Fetch value of DHCPv6 leased address
291  *
292  * @v dhcpset		DHCPv6 settings
293  * @v data		Buffer to fill with setting data
294  * @v len		Length of buffer
295  * @ret len		Length of setting data, or negative error
296  */
dhcpv6_fetch_lease(struct dhcpv6_settings * dhcpv6set,void * data,size_t len)297 static int dhcpv6_fetch_lease ( struct dhcpv6_settings *dhcpv6set,
298 				void *data, size_t len ) {
299 	struct in6_addr *lease = &dhcpv6set->lease;
300 
301 	/* Do nothing unless a leased address exists */
302 	if ( IN6_IS_ADDR_UNSPECIFIED ( lease ) )
303 		return -ENOENT;
304 
305 	/* Copy leased address */
306 	if ( len > sizeof ( *lease ) )
307 		len = sizeof ( *lease );
308 	memcpy ( data, lease, len );
309 
310 	return sizeof ( *lease );
311 }
312 
313 /**
314  * Fetch value of DHCPv6 setting
315  *
316  * @v settings		Settings block
317  * @v setting		Setting to fetch
318  * @v data		Buffer to fill with setting data
319  * @v len		Length of buffer
320  * @ret len		Length of setting data, or negative error
321  */
dhcpv6_fetch(struct settings * settings,struct setting * setting,void * data,size_t len)322 static int dhcpv6_fetch ( struct settings *settings,
323 			  struct setting *setting,
324 			  void *data, size_t len ) {
325 	struct dhcpv6_settings *dhcpv6set =
326 		container_of ( settings, struct dhcpv6_settings, settings );
327 	const union dhcpv6_any_option *option;
328 	size_t option_len;
329 
330 	/* Handle leased address */
331 	if ( setting_cmp ( setting, &ip6_setting ) == 0 )
332 		return dhcpv6_fetch_lease ( dhcpv6set, data, len );
333 
334 	/* Find option */
335 	option = dhcpv6_option ( &dhcpv6set->options, setting->tag );
336 	if ( ! option )
337 		return -ENOENT;
338 
339 	/* Copy option to data buffer */
340 	option_len = ntohs ( option->header.len );
341 	if ( len > option_len )
342 		len = option_len;
343 	memcpy ( data, option->header.data, len );
344 	return option_len;
345 }
346 
347 /** DHCPv6 settings operations */
348 static struct settings_operations dhcpv6_settings_operations = {
349 	.applies = dhcpv6_applies,
350 	.fetch = dhcpv6_fetch,
351 };
352 
353 /**
354  * Register DHCPv6 options as network device settings
355  *
356  * @v lease		DHCPv6 leased address
357  * @v options		DHCPv6 option list
358  * @v parent		Parent settings block
359  * @ret rc		Return status code
360  */
dhcpv6_register(struct in6_addr * lease,struct dhcpv6_option_list * options,struct settings * parent)361 static int dhcpv6_register ( struct in6_addr *lease,
362 			     struct dhcpv6_option_list *options,
363 			     struct settings *parent ) {
364 	struct dhcpv6_settings *dhcpv6set;
365 	void *data;
366 	size_t len;
367 	int rc;
368 
369 	/* Allocate and initialise structure */
370 	dhcpv6set = zalloc ( sizeof ( *dhcpv6set ) + options->len );
371 	if ( ! dhcpv6set ) {
372 		rc = -ENOMEM;
373 		goto err_alloc;
374 	}
375 	ref_init ( &dhcpv6set->refcnt, NULL );
376 	settings_init ( &dhcpv6set->settings, &dhcpv6_settings_operations,
377 			&dhcpv6set->refcnt, &dhcpv6_scope );
378 	dhcpv6set->settings.order = IPV6_ORDER_DHCPV6;
379 	data = ( ( ( void * ) dhcpv6set ) + sizeof ( *dhcpv6set ) );
380 	len = options->len;
381 	memcpy ( data, options->data, len );
382 	dhcpv6set->options.data = data;
383 	dhcpv6set->options.len = len;
384 	memcpy ( &dhcpv6set->lease, lease, sizeof ( dhcpv6set->lease ) );
385 
386 	/* Register settings */
387 	if ( ( rc = register_settings ( &dhcpv6set->settings, parent,
388 					DHCPV6_SETTINGS_NAME ) ) != 0 )
389 		goto err_register;
390 
391  err_register:
392 	ref_put ( &dhcpv6set->refcnt );
393  err_alloc:
394 	return rc;
395 }
396 
397 /****************************************************************************
398  *
399  * DHCPv6 protocol
400  *
401  */
402 
403 /** Raw option data for options common to all DHCPv6 requests */
404 static uint8_t dhcpv6_request_options_data[] = {
405 	DHCPV6_CODE ( DHCPV6_OPTION_REQUEST ),
406 	DHCPV6_OPTION ( DHCPV6_CODE ( DHCPV6_DNS_SERVERS ),
407 			DHCPV6_CODE ( DHCPV6_DOMAIN_LIST ),
408 			DHCPV6_CODE ( DHCPV6_BOOTFILE_URL ),
409 			DHCPV6_CODE ( DHCPV6_BOOTFILE_PARAM ) ),
410 	DHCPV6_CODE ( DHCPV6_VENDOR_CLASS ),
411 	DHCPV6_OPTION ( DHCPV6_DWORD_VALUE ( DHCPV6_VENDOR_CLASS_PXE ),
412 			DHCPV6_STRING (
413 			  DHCP_VENDOR_PXECLIENT ( DHCP_ARCH_CLIENT_ARCHITECTURE,
414 						  DHCP_ARCH_CLIENT_NDI ) ) ),
415 	DHCPV6_CODE ( DHCPV6_CLIENT_ARCHITECTURE ),
416 	DHCPV6_WORD ( DHCP_ARCH_CLIENT_ARCHITECTURE ),
417 	DHCPV6_CODE ( DHCPV6_CLIENT_NDI ),
418 	DHCPV6_OPTION ( DHCP_ARCH_CLIENT_NDI )
419 };
420 
421 /**
422  * Name a DHCPv6 packet type
423  *
424  * @v type		DHCPv6 packet type
425  * @ret name		DHCPv6 packet type name
426  */
427 static __attribute__ (( unused )) const char *
dhcpv6_type_name(unsigned int type)428 dhcpv6_type_name ( unsigned int type ) {
429 	static char buf[ 12 /* "UNKNOWN-xxx" + NUL */ ];
430 
431 	switch ( type ) {
432 	case DHCPV6_SOLICIT:			return "SOLICIT";
433 	case DHCPV6_ADVERTISE:			return "ADVERTISE";
434 	case DHCPV6_REQUEST:			return "REQUEST";
435 	case DHCPV6_REPLY:			return "REPLY";
436 	case DHCPV6_INFORMATION_REQUEST:	return "INFORMATION-REQUEST";
437 	default:
438 		snprintf ( buf, sizeof ( buf ), "UNKNOWN-%d", type );
439 		return buf;
440 	}
441 }
442 
443 /** A DHCPv6 session state */
444 struct dhcpv6_session_state {
445 	/** Current transmitted packet type */
446 	uint8_t tx_type;
447 	/** Current expected received packet type */
448 	uint8_t rx_type;
449 	/** Flags */
450 	uint8_t flags;
451 	/** Next state (or NULL to terminate) */
452 	struct dhcpv6_session_state *next;
453 };
454 
455 /** DHCPv6 session state flags */
456 enum dhcpv6_session_state_flags {
457 	/** Include identity association within request */
458 	DHCPV6_TX_IA_NA = 0x01,
459 	/** Include leased IPv6 address within request */
460 	DHCPV6_TX_IAADDR = 0x02,
461 	/** Record received server ID */
462 	DHCPV6_RX_RECORD_SERVER_ID = 0x04,
463 	/** Record received IPv6 address */
464 	DHCPV6_RX_RECORD_IAADDR = 0x08,
465 };
466 
467 /** DHCPv6 request state */
468 static struct dhcpv6_session_state dhcpv6_request = {
469 	.tx_type = DHCPV6_REQUEST,
470 	.rx_type = DHCPV6_REPLY,
471 	.flags = ( DHCPV6_TX_IA_NA | DHCPV6_TX_IAADDR |
472 		   DHCPV6_RX_RECORD_IAADDR ),
473 	.next = NULL,
474 };
475 
476 /** DHCPv6 solicitation state */
477 static struct dhcpv6_session_state dhcpv6_solicit = {
478 	.tx_type = DHCPV6_SOLICIT,
479 	.rx_type = DHCPV6_ADVERTISE,
480 	.flags = ( DHCPV6_TX_IA_NA | DHCPV6_RX_RECORD_SERVER_ID |
481 		   DHCPV6_RX_RECORD_IAADDR ),
482 	.next = &dhcpv6_request,
483 };
484 
485 /** DHCPv6 information request state */
486 static struct dhcpv6_session_state dhcpv6_information_request = {
487 	.tx_type = DHCPV6_INFORMATION_REQUEST,
488 	.rx_type = DHCPV6_REPLY,
489 	.flags = 0,
490 	.next = NULL,
491 };
492 
493 /** A DHCPv6 session */
494 struct dhcpv6_session {
495 	/** Reference counter */
496 	struct refcnt refcnt;
497 	/** Job control interface */
498 	struct interface job;
499 	/** Data transfer interface */
500 	struct interface xfer;
501 
502 	/** Network device being configured */
503 	struct net_device *netdev;
504 	/** Transaction ID */
505 	uint8_t xid[3];
506 	/** Identity association ID */
507 	uint32_t iaid;
508 	/** Start time (in ticks) */
509 	unsigned long start;
510 	/** Client DUID */
511 	struct dhcpv6_duid_uuid client_duid;
512 	/** Server DUID, if known */
513 	void *server_duid;
514 	/** Server DUID length */
515 	size_t server_duid_len;
516 	/** Leased IPv6 address */
517 	struct in6_addr lease;
518 
519 	/** Retransmission timer */
520 	struct retry_timer timer;
521 
522 	/** Current session state */
523 	struct dhcpv6_session_state *state;
524 	/** Current timeout status code */
525 	int rc;
526 };
527 
528 /**
529  * Free DHCPv6 session
530  *
531  * @v refcnt		Reference count
532  */
dhcpv6_free(struct refcnt * refcnt)533 static void dhcpv6_free ( struct refcnt *refcnt ) {
534 	struct dhcpv6_session *dhcpv6 =
535 		container_of ( refcnt, struct dhcpv6_session, refcnt );
536 
537 	netdev_put ( dhcpv6->netdev );
538 	free ( dhcpv6->server_duid );
539 	free ( dhcpv6 );
540 }
541 
542 /**
543  * Terminate DHCPv6 session
544  *
545  * @v dhcpv6		DHCPv6 session
546  * @v rc		Reason for close
547  */
dhcpv6_finished(struct dhcpv6_session * dhcpv6,int rc)548 static void dhcpv6_finished ( struct dhcpv6_session *dhcpv6, int rc ) {
549 
550 	/* Stop timer */
551 	stop_timer ( &dhcpv6->timer );
552 
553 	/* Shut down interfaces */
554 	intf_shutdown ( &dhcpv6->xfer, rc );
555 	intf_shutdown ( &dhcpv6->job, rc );
556 }
557 
558 /**
559  * Transition to new DHCPv6 session state
560  *
561  * @v dhcpv6		DHCPv6 session
562  * @v state		New session state
563  */
dhcpv6_set_state(struct dhcpv6_session * dhcpv6,struct dhcpv6_session_state * state)564 static void dhcpv6_set_state ( struct dhcpv6_session *dhcpv6,
565 			       struct dhcpv6_session_state *state ) {
566 
567 	DBGC ( dhcpv6, "DHCPv6 %s entering %s state\n", dhcpv6->netdev->name,
568 	       dhcpv6_type_name ( state->tx_type ) );
569 
570 	/* Record state */
571 	dhcpv6->state = state;
572 
573 	/* Default to -ETIMEDOUT if no more specific error is recorded */
574 	dhcpv6->rc = -ETIMEDOUT;
575 
576 	/* Start timer to trigger transmission */
577 	start_timer_nodelay ( &dhcpv6->timer );
578 }
579 
580 /**
581  * Get DHCPv6 user class
582  *
583  * @v data		Data buffer
584  * @v len		Length of data buffer
585  * @ret len		Length of user class
586  */
dhcpv6_user_class(void * data,size_t len)587 static size_t dhcpv6_user_class ( void *data, size_t len ) {
588 	static const char default_user_class[4] = { 'i', 'P', 'X', 'E' };
589 	int actual_len;
590 
591 	/* Fetch user-class setting, if defined */
592 	actual_len = fetch_raw_setting ( NULL, &user_class_setting, data, len );
593 	if ( actual_len >= 0 )
594 		return actual_len;
595 
596 	/* Otherwise, use the default user class ("iPXE") */
597 	if ( len > sizeof ( default_user_class ) )
598 		len = sizeof ( default_user_class );
599 	memcpy ( data, default_user_class, len );
600 	return sizeof ( default_user_class );
601 }
602 
603 /**
604  * Transmit current request
605  *
606  * @v dhcpv6		DHCPv6 session
607  * @ret rc		Return status code
608  */
dhcpv6_tx(struct dhcpv6_session * dhcpv6)609 static int dhcpv6_tx ( struct dhcpv6_session *dhcpv6 ) {
610 	struct dhcpv6_duid_option *client_id;
611 	struct dhcpv6_duid_option *server_id;
612 	struct dhcpv6_ia_na_option *ia_na;
613 	struct dhcpv6_iaaddr_option *iaaddr;
614 	struct dhcpv6_user_class_option *user_class;
615 	struct dhcpv6_elapsed_time_option *elapsed;
616 	struct dhcpv6_header *dhcphdr;
617 	struct io_buffer *iobuf;
618 	void *options;
619 	size_t client_id_len;
620 	size_t server_id_len;
621 	size_t ia_na_len;
622 	size_t user_class_string_len;
623 	size_t user_class_len;
624 	size_t elapsed_len;
625 	size_t total_len;
626 	int rc;
627 
628 	/* Calculate lengths */
629 	client_id_len = ( sizeof ( *client_id ) +
630 			  sizeof ( dhcpv6->client_duid ) );
631 	server_id_len = ( dhcpv6->server_duid ? ( sizeof ( *server_id ) +
632 						  dhcpv6->server_duid_len ) :0);
633 	if ( dhcpv6->state->flags & DHCPV6_TX_IA_NA ) {
634 		ia_na_len = sizeof ( *ia_na );
635 		if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR )
636 			ia_na_len += sizeof ( *iaaddr );
637 	} else {
638 		ia_na_len = 0;
639 	}
640 	user_class_string_len = dhcpv6_user_class ( NULL, 0 );
641 	user_class_len = ( sizeof ( *user_class ) +
642 			   sizeof ( user_class->user_class[0] ) +
643 			   user_class_string_len );
644 	elapsed_len = sizeof ( *elapsed );
645 	total_len = ( sizeof ( *dhcphdr ) + client_id_len + server_id_len +
646 		      ia_na_len + sizeof ( dhcpv6_request_options_data ) +
647 		      user_class_len + elapsed_len );
648 
649 	/* Allocate packet */
650 	iobuf = xfer_alloc_iob ( &dhcpv6->xfer, total_len );
651 	if ( ! iobuf )
652 		return -ENOMEM;
653 
654 	/* Construct header */
655 	dhcphdr = iob_put ( iobuf, sizeof ( *dhcphdr ) );
656 	dhcphdr->type = dhcpv6->state->tx_type;
657 	memcpy ( dhcphdr->xid, dhcpv6->xid, sizeof ( dhcphdr->xid ) );
658 
659 	/* Construct client identifier */
660 	client_id = iob_put ( iobuf, client_id_len );
661 	client_id->header.code = htons ( DHCPV6_CLIENT_ID );
662 	client_id->header.len = htons ( client_id_len -
663 					sizeof ( client_id->header ) );
664 	memcpy ( client_id->duid, &dhcpv6->client_duid,
665 		 sizeof ( dhcpv6->client_duid ) );
666 
667 	/* Construct server identifier, if applicable */
668 	if ( server_id_len ) {
669 		server_id = iob_put ( iobuf, server_id_len );
670 		server_id->header.code = htons ( DHCPV6_SERVER_ID );
671 		server_id->header.len = htons ( server_id_len -
672 						sizeof ( server_id->header ) );
673 		memcpy ( server_id->duid, dhcpv6->server_duid,
674 			 dhcpv6->server_duid_len );
675 	}
676 
677 	/* Construct identity association, if applicable */
678 	if ( ia_na_len ) {
679 		ia_na = iob_put ( iobuf, ia_na_len );
680 		ia_na->header.code = htons ( DHCPV6_IA_NA );
681 		ia_na->header.len = htons ( ia_na_len -
682 					    sizeof ( ia_na->header ) );
683 		ia_na->iaid = htonl ( dhcpv6->iaid );
684 		ia_na->renew = htonl ( 0 );
685 		ia_na->rebind = htonl ( 0 );
686 		if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) {
687 			iaaddr = ( ( void * ) ia_na->options );
688 			iaaddr->header.code = htons ( DHCPV6_IAADDR );
689 			iaaddr->header.len = htons ( sizeof ( *iaaddr ) -
690 						     sizeof ( iaaddr->header ));
691 			memcpy ( &iaaddr->address, &dhcpv6->lease,
692 				 sizeof ( iaaddr->address ) );
693 			iaaddr->preferred = htonl ( 0 );
694 			iaaddr->valid = htonl ( 0 );
695 		}
696 	}
697 
698 	/* Construct fixed request options */
699 	options = iob_put ( iobuf, sizeof ( dhcpv6_request_options_data ) );
700 	memcpy ( options, dhcpv6_request_options_data,
701 		 sizeof ( dhcpv6_request_options_data ) );
702 
703 	/* Construct user class */
704 	user_class = iob_put ( iobuf, user_class_len );
705 	user_class->header.code = htons ( DHCPV6_USER_CLASS );
706 	user_class->header.len = htons ( user_class_len -
707 					 sizeof ( user_class->header ) );
708 	user_class->user_class[0].len = htons ( user_class_string_len );
709 	dhcpv6_user_class ( user_class->user_class[0].string,
710 			    user_class_string_len );
711 
712 	/* Construct elapsed time */
713 	elapsed = iob_put ( iobuf, elapsed_len );
714 	elapsed->header.code = htons ( DHCPV6_ELAPSED_TIME );
715 	elapsed->header.len = htons ( elapsed_len -
716 				      sizeof ( elapsed->header ) );
717 	elapsed->elapsed = htons ( ( ( currticks() - dhcpv6->start ) * 100 ) /
718 				   TICKS_PER_SEC );
719 
720 	/* Sanity check */
721 	assert ( iob_len ( iobuf ) == total_len );
722 
723 	/* Transmit packet */
724 	if ( ( rc = xfer_deliver_iob ( &dhcpv6->xfer, iobuf ) ) != 0 ) {
725 		DBGC ( dhcpv6, "DHCPv6 %s could not transmit: %s\n",
726 		       dhcpv6->netdev->name, strerror ( rc ) );
727 		return rc;
728 	}
729 
730 	return 0;
731 }
732 
733 /**
734  * Handle timer expiry
735  *
736  * @v timer		Retransmission timer
737  * @v fail		Failure indicator
738  */
dhcpv6_timer_expired(struct retry_timer * timer,int fail)739 static void dhcpv6_timer_expired ( struct retry_timer *timer, int fail ) {
740 	struct dhcpv6_session *dhcpv6 =
741 		container_of ( timer, struct dhcpv6_session, timer );
742 
743 	/* If we have failed, terminate DHCPv6 */
744 	if ( fail ) {
745 		dhcpv6_finished ( dhcpv6, dhcpv6->rc );
746 		return;
747 	}
748 
749 	/* Restart timer */
750 	start_timer ( &dhcpv6->timer );
751 
752 	/* (Re)transmit current request */
753 	dhcpv6_tx ( dhcpv6 );
754 }
755 
756 /**
757  * Receive new data
758  *
759  * @v dhcpv6		DHCPv6 session
760  * @v iobuf		I/O buffer
761  * @v meta		Data transfer metadata
762  * @ret rc		Return status code
763  */
dhcpv6_rx(struct dhcpv6_session * dhcpv6,struct io_buffer * iobuf,struct xfer_metadata * meta)764 static int dhcpv6_rx ( struct dhcpv6_session *dhcpv6,
765 		       struct io_buffer *iobuf,
766 		       struct xfer_metadata *meta ) {
767 	struct settings *parent = netdev_settings ( dhcpv6->netdev );
768 	struct sockaddr_in6 *src = ( ( struct sockaddr_in6 * ) meta->src );
769 	struct dhcpv6_header *dhcphdr = iobuf->data;
770 	struct dhcpv6_option_list options;
771 	const union dhcpv6_any_option *option;
772 	int rc;
773 
774 	/* Sanity checks */
775 	if ( iob_len ( iobuf ) < sizeof ( *dhcphdr ) ) {
776 		DBGC ( dhcpv6, "DHCPv6 %s received packet too short (%zd "
777 		       "bytes, min %zd bytes)\n", dhcpv6->netdev->name,
778 		       iob_len ( iobuf ), sizeof ( *dhcphdr ) );
779 		rc = -EINVAL;
780 		goto done;
781 	}
782 	assert ( src != NULL );
783 	assert ( src->sin6_family == AF_INET6 );
784 	DBGC ( dhcpv6, "DHCPv6 %s received %s from %s\n",
785 	       dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
786 	       inet6_ntoa ( &src->sin6_addr ) );
787 
788 	/* Construct option list */
789 	options.data = dhcphdr->options;
790 	options.len = ( iob_len ( iobuf ) -
791 			offsetof ( typeof ( *dhcphdr ), options ) );
792 
793 	/* Verify client identifier */
794 	if ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_CLIENT_ID,
795 					&dhcpv6->client_duid,
796 					sizeof ( dhcpv6->client_duid ) ) ) !=0){
797 		DBGC ( dhcpv6, "DHCPv6 %s received %s without correct client "
798 		       "ID: %s\n", dhcpv6->netdev->name,
799 		       dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
800 		goto done;
801 	}
802 
803 	/* Verify server identifier, if applicable */
804 	if ( dhcpv6->server_duid &&
805 	     ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_SERVER_ID,
806 					  dhcpv6->server_duid,
807 					  dhcpv6->server_duid_len ) ) != 0 ) ) {
808 		DBGC ( dhcpv6, "DHCPv6 %s received %s without correct server "
809 		       "ID: %s\n", dhcpv6->netdev->name,
810 		       dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
811 		goto done;
812 	}
813 
814 	/* Check message type */
815 	if ( dhcphdr->type != dhcpv6->state->rx_type ) {
816 		DBGC ( dhcpv6, "DHCPv6 %s received %s while expecting %s\n",
817 		       dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
818 		       dhcpv6_type_name ( dhcpv6->state->rx_type ) );
819 		rc = -ENOTTY;
820 		goto done;
821 	}
822 
823 	/* Fetch status code, if present */
824 	if ( ( rc = dhcpv6_status_code ( &options ) ) != 0 ) {
825 		DBGC ( dhcpv6, "DHCPv6 %s received %s with error status: %s\n",
826 		       dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
827 		       strerror ( rc ) );
828 		/* This is plausibly the error we want to return */
829 		dhcpv6->rc = rc;
830 		goto done;
831 	}
832 
833 	/* Record identity association address, if applicable */
834 	if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_IAADDR ) {
835 		if ( ( rc = dhcpv6_iaaddr ( &options, dhcpv6->iaid,
836 					    &dhcpv6->lease ) ) != 0 ) {
837 			DBGC ( dhcpv6, "DHCPv6 %s received %s with unusable "
838 			       "IAADDR: %s\n", dhcpv6->netdev->name,
839 			       dhcpv6_type_name ( dhcphdr->type ),
840 			       strerror ( rc ) );
841 			/* This is plausibly the error we want to return */
842 			dhcpv6->rc = rc;
843 			goto done;
844 		}
845 		DBGC ( dhcpv6, "DHCPv6 %s received %s is for %s\n",
846 		       dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
847 		       inet6_ntoa ( &dhcpv6->lease ) );
848 	}
849 
850 	/* Record server ID, if applicable */
851 	if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_SERVER_ID ) {
852 		assert ( dhcpv6->server_duid == NULL );
853 		option = dhcpv6_option ( &options, DHCPV6_SERVER_ID );
854 		if ( ! option ) {
855 			DBGC ( dhcpv6, "DHCPv6 %s received %s missing server "
856 			       "ID\n", dhcpv6->netdev->name,
857 			       dhcpv6_type_name ( dhcphdr->type ) );
858 			rc = -EINVAL;
859 			goto done;
860 		}
861 		dhcpv6->server_duid_len = ntohs ( option->duid.header.len );
862 		dhcpv6->server_duid = malloc ( dhcpv6->server_duid_len );
863 		if ( ! dhcpv6->server_duid ) {
864 			rc = -ENOMEM;
865 			goto done;
866 		}
867 		memcpy ( dhcpv6->server_duid, option->duid.duid,
868 			 dhcpv6->server_duid_len );
869 	}
870 
871 	/* Transition to next state, if applicable */
872 	if ( dhcpv6->state->next ) {
873 		dhcpv6_set_state ( dhcpv6, dhcpv6->state->next );
874 		rc = 0;
875 		goto done;
876 	}
877 
878 	/* Register settings */
879 	if ( ( rc = dhcpv6_register ( &dhcpv6->lease, &options,
880 				      parent ) ) != 0 ) {
881 		DBGC ( dhcpv6, "DHCPv6 %s could not register settings: %s\n",
882 		       dhcpv6->netdev->name, strerror ( rc ) );
883 		goto done;
884 	}
885 
886 	/* Mark as complete */
887 	dhcpv6_finished ( dhcpv6, 0 );
888 	DBGC ( dhcpv6, "DHCPv6 %s complete\n", dhcpv6->netdev->name );
889 
890  done:
891 	free_iob ( iobuf );
892 	return rc;
893 }
894 
895 /** DHCPv6 job control interface operations */
896 static struct interface_operation dhcpv6_job_op[] = {
897 	INTF_OP ( intf_close, struct dhcpv6_session *, dhcpv6_finished ),
898 };
899 
900 /** DHCPv6 job control interface descriptor */
901 static struct interface_descriptor dhcpv6_job_desc =
902 	INTF_DESC ( struct dhcpv6_session, job, dhcpv6_job_op );
903 
904 /** DHCPv6 data transfer interface operations */
905 static struct interface_operation dhcpv6_xfer_op[] = {
906 	INTF_OP ( xfer_deliver, struct dhcpv6_session *, dhcpv6_rx ),
907 };
908 
909 /** DHCPv6 data transfer interface descriptor */
910 static struct interface_descriptor dhcpv6_xfer_desc =
911 	INTF_DESC ( struct dhcpv6_session, xfer, dhcpv6_xfer_op );
912 
913 /**
914  * Start DHCPv6
915  *
916  * @v job		Job control interface
917  * @v netdev		Network device
918  * @v stateful		Perform stateful address autoconfiguration
919  * @ret rc		Return status code
920  */
start_dhcpv6(struct interface * job,struct net_device * netdev,int stateful)921 int start_dhcpv6 ( struct interface *job, struct net_device *netdev,
922 		   int stateful ) {
923 	struct ll_protocol *ll_protocol = netdev->ll_protocol;
924 	struct dhcpv6_session *dhcpv6;
925 	struct {
926 		union {
927 			struct sockaddr_in6 sin6;
928 			struct sockaddr sa;
929 		} client;
930 		union {
931 			struct sockaddr_in6 sin6;
932 			struct sockaddr sa;
933 		} server;
934 	} addresses;
935 	uint32_t xid;
936 	int len;
937 	int rc;
938 
939 	/* Allocate and initialise structure */
940 	dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) );
941 	if ( ! dhcpv6 )
942 		return -ENOMEM;
943 	ref_init ( &dhcpv6->refcnt, dhcpv6_free );
944 	intf_init ( &dhcpv6->job, &dhcpv6_job_desc, &dhcpv6->refcnt );
945 	intf_init ( &dhcpv6->xfer, &dhcpv6_xfer_desc, &dhcpv6->refcnt );
946 	dhcpv6->netdev = netdev_get ( netdev );
947 	xid = random();
948 	memcpy ( dhcpv6->xid, &xid, sizeof ( dhcpv6->xid ) );
949 	dhcpv6->start = currticks();
950 	timer_init ( &dhcpv6->timer, dhcpv6_timer_expired, &dhcpv6->refcnt );
951 
952 	/* Construct client and server addresses */
953 	memset ( &addresses, 0, sizeof ( addresses ) );
954 	addresses.client.sin6.sin6_family = AF_INET6;
955 	addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT );
956 	addresses.server.sin6.sin6_family = AF_INET6;
957 	ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr );
958 	addresses.server.sin6.sin6_scope_id = netdev->index;
959 	addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT );
960 
961 	/* Construct client DUID from system UUID */
962 	dhcpv6->client_duid.type = htons ( DHCPV6_DUID_UUID );
963 	if ( ( len = fetch_uuid_setting ( NULL, &uuid_setting,
964 					  &dhcpv6->client_duid.uuid ) ) < 0 ) {
965 		rc = len;
966 		DBGC ( dhcpv6, "DHCPv6 %s could not create DUID-UUID: %s\n",
967 		       dhcpv6->netdev->name, strerror ( rc ) );
968 		goto err_client_duid;
969 	}
970 
971 	/* Construct IAID from link-layer address */
972 	dhcpv6->iaid = crc32_le ( 0, netdev->ll_addr, ll_protocol->ll_addr_len);
973 	DBGC ( dhcpv6, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6->netdev->name,
974 	       dhcpv6->xid[0], dhcpv6->xid[1], dhcpv6->xid[2] );
975 
976 	/* Enter initial state */
977 	dhcpv6_set_state ( dhcpv6, ( stateful ? &dhcpv6_solicit :
978 				     &dhcpv6_information_request ) );
979 
980 	/* Open socket */
981 	if ( ( rc = xfer_open_socket ( &dhcpv6->xfer, SOCK_DGRAM,
982 				       &addresses.server.sa,
983 				       &addresses.client.sa ) ) != 0 ) {
984 		DBGC ( dhcpv6, "DHCPv6 %s could not open socket: %s\n",
985 		       dhcpv6->netdev->name, strerror ( rc ) );
986 		goto err_open_socket;
987 	}
988 
989 	/* Attach parent interface, mortalise self, and return */
990 	intf_plug_plug ( &dhcpv6->job, job );
991 	ref_put ( &dhcpv6->refcnt );
992 	return 0;
993 
994  err_open_socket:
995 	dhcpv6_finished ( dhcpv6, rc );
996  err_client_duid:
997 	ref_put ( &dhcpv6->refcnt );
998 	return rc;
999 }
1000 
1001 /** Boot filename setting */
1002 const struct setting filename6_setting __setting ( SETTING_BOOT, filename ) = {
1003 	.name = "filename",
1004 	.description = "Boot filename",
1005 	.tag = DHCPV6_BOOTFILE_URL,
1006 	.type = &setting_type_string,
1007 	.scope = &dhcpv6_scope,
1008 };
1009 
1010 /** DNS search list setting */
1011 const struct setting dnssl6_setting __setting ( SETTING_IP_EXTRA, dnssl ) = {
1012 	.name = "dnssl",
1013 	.description = "DNS search list",
1014 	.tag = DHCPV6_DOMAIN_LIST,
1015 	.type = &setting_type_dnssl,
1016 	.scope = &dhcpv6_scope,
1017 };
1018