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