2 * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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
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.
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL
);
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>
40 #include <ipxe/crc32.h>
41 #include <ipxe/errortab.h>
42 #include <ipxe/ipv6.h>
43 #include <ipxe/dhcpv6.h>
47 * Dynamic Host Configuration Protocol for IPv6
51 /* Disambiguate the various error causes */
52 #define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL )
53 #define EINFO_EPROTO_UNSPECFAIL \
54 __einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" )
55 #define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL )
56 #define EINFO_EPROTO_NOADDRSAVAIL \
57 __einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" )
58 #define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING )
59 #define EINFO_EPROTO_NOBINDING \
60 __einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" )
61 #define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK )
62 #define EINFO_EPROTO_NOTONLINK \
63 __einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" )
64 #define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST )
65 #define EINFO_EPROTO_USEMULTICAST \
66 __einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" )
67 #define EPROTO_STATUS( status ) \
68 EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL, \
69 EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING, \
70 EPROTO_NOTONLINK, EPROTO_USEMULTICAST )
72 /** Human-readable error messages */
73 struct errortab dhcpv6_errors
[] __errortab
= {
74 __einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL
),
77 /****************************************************************************
83 /** A DHCPv6 option list */
84 struct dhcpv6_option_list
{
87 /** Length of data buffer */
94 * @v options DHCPv6 option list
96 * @ret option DHCPv6 option, or NULL if not found
98 static const union dhcpv6_any_option
*
99 dhcpv6_option ( struct dhcpv6_option_list
*options
, unsigned int code
) {
100 const union dhcpv6_any_option
*option
= options
->data
;
101 size_t remaining
= options
->len
;
104 /* Scan through list of options */
105 while ( remaining
>= sizeof ( option
->header
) ) {
107 /* Calculate and validate option length */
108 remaining
-= sizeof ( option
->header
);
109 data_len
= ntohs ( option
->header
.len
);
110 if ( data_len
> remaining
) {
111 /* Malformed option list */
115 /* Return if we have found the specified option */
116 if ( option
->header
.code
== htons ( code
) )
119 /* Otherwise, move to the next option */
120 option
= ( ( ( void * ) option
->header
.data
) + data_len
);
121 remaining
-= data_len
;
128 * Check DHCPv6 client or server identifier
130 * @v options DHCPv6 option list
131 * @v code Option code
132 * @v expected Expected value
133 * @v len Length of expected value
134 * @ret rc Return status code
136 static int dhcpv6_check_duid ( struct dhcpv6_option_list
*options
,
137 unsigned int code
, const void *expected
,
139 const union dhcpv6_any_option
*option
;
140 const struct dhcpv6_duid_option
*duid
;
143 option
= dhcpv6_option ( options
, code
);
146 duid
= &option
->duid
;
148 /* Check option length */
149 if ( ntohs ( duid
->header
.len
) != len
)
152 /* Compare option value */
153 if ( memcmp ( duid
->duid
, expected
, len
) != 0 )
160 * Get DHCPv6 status code
162 * @v options DHCPv6 option list
163 * @ret rc Return status code
165 static int dhcpv6_status_code ( struct dhcpv6_option_list
*options
) {
166 const union dhcpv6_any_option
*option
;
167 const struct dhcpv6_status_code_option
*status_code
;
170 /* Find status code option, if present */
171 option
= dhcpv6_option ( options
, DHCPV6_STATUS_CODE
);
173 /* Omitted status code should be treated as "success" */
176 status_code
= &option
->status_code
;
179 if ( ntohs ( status_code
->header
.len
) <
180 ( sizeof ( *status_code
) - sizeof ( status_code
->header
) ) ) {
184 /* Calculate iPXE error code from DHCPv6 status code */
185 status
= ntohs ( status_code
->status
);
186 return ( status ?
-EPROTO_STATUS ( status
) : 0 );
190 * Get DHCPv6 identity association address
192 * @v options DHCPv6 option list
193 * @v iaid Identity association ID
194 * @v address IPv6 address to fill in
195 * @ret rc Return status code
197 static int dhcpv6_iaaddr ( struct dhcpv6_option_list
*options
, uint32_t iaid
,
198 struct in6_addr
*address
) {
199 const union dhcpv6_any_option
*option
;
200 const struct dhcpv6_ia_na_option
*ia_na
;
201 const struct dhcpv6_iaaddr_option
*iaaddr
;
202 struct dhcpv6_option_list suboptions
;
206 /* Find identity association option, if present */
207 option
= dhcpv6_option ( options
, DHCPV6_IA_NA
);
210 ia_na
= &option
->ia_na
;
213 len
= ntohs ( ia_na
->header
.len
);
214 if ( len
< ( sizeof ( *ia_na
) - sizeof ( ia_na
->header
) ) )
217 /* Check identity association ID */
218 if ( ia_na
->iaid
!= htonl ( iaid
) )
221 /* Construct IA_NA sub-options list */
222 suboptions
.data
= ia_na
->options
;
223 suboptions
.len
= ( len
+ sizeof ( ia_na
->header
) -
224 offsetof ( typeof ( *ia_na
), options
) );
226 /* Check IA_NA status code */
227 if ( ( rc
= dhcpv6_status_code ( &suboptions
) ) != 0 )
230 /* Find identity association address, if present */
231 option
= dhcpv6_option ( &suboptions
, DHCPV6_IAADDR
);
234 iaaddr
= &option
->iaaddr
;
237 len
= ntohs ( iaaddr
->header
.len
);
238 if ( len
< ( sizeof ( *iaaddr
) - sizeof ( iaaddr
->header
) ) )
241 /* Construct IAADDR sub-options list */
242 suboptions
.data
= iaaddr
->options
;
243 suboptions
.len
= ( len
+ sizeof ( iaaddr
->header
) -
244 offsetof ( typeof ( *iaaddr
), options
) );
246 /* Check IAADDR status code */
247 if ( ( rc
= dhcpv6_status_code ( &suboptions
) ) != 0 )
250 /* Extract IPv6 address */
251 memcpy ( address
, &iaaddr
->address
, sizeof ( *address
) );
256 /****************************************************************************
258 * DHCPv6 settings blocks
262 /** A DHCPv6 settings block */
263 struct dhcpv6_settings
{
264 /** Reference count */
265 struct refcnt refcnt
;
266 /** Settings block */
267 struct settings settings
;
269 struct dhcpv6_option_list options
;
273 * Check applicability of DHCPv6 setting
275 * @v settings Settings block
277 * @ret applies Setting applies within this settings block
279 static int dhcpv6_applies ( struct settings
*settings __unused
,
280 const struct setting
*setting
) {
282 return ( setting
->scope
== &ipv6_scope
);
286 * Fetch value of DHCPv6 setting
288 * @v settings Settings block
289 * @v setting Setting to fetch
290 * @v data Buffer to fill with setting data
291 * @v len Length of buffer
292 * @ret len Length of setting data, or negative error
294 static int dhcpv6_fetch ( struct settings
*settings
,
295 struct setting
*setting
,
296 void *data
, size_t len
) {
297 struct dhcpv6_settings
*dhcpv6set
=
298 container_of ( settings
, struct dhcpv6_settings
, settings
);
299 const union dhcpv6_any_option
*option
;
303 option
= dhcpv6_option ( &dhcpv6set
->options
, setting
->tag
);
307 /* Copy option to data buffer */
308 option_len
= ntohs ( option
->header
.len
);
309 if ( len
> option_len
)
311 memcpy ( data
, option
->header
.data
, len
);
315 /** DHCPv6 settings operations */
316 static struct settings_operations dhcpv6_settings_operations
= {
317 .applies
= dhcpv6_applies
,
318 .fetch
= dhcpv6_fetch
,
322 * Register DHCPv6 options as network device settings
324 * @v options DHCPv6 option list
325 * @v parent Parent settings block
326 * @ret rc Return status code
328 static int dhcpv6_register ( struct dhcpv6_option_list
*options
,
329 struct settings
*parent
) {
330 struct dhcpv6_settings
*dhcpv6set
;
335 /* Allocate and initialise structure */
336 dhcpv6set
= zalloc ( sizeof ( *dhcpv6set
) + options
->len
);
341 ref_init ( &dhcpv6set
->refcnt
, NULL
);
342 settings_init ( &dhcpv6set
->settings
, &dhcpv6_settings_operations
,
343 &dhcpv6set
->refcnt
, &ipv6_scope
);
344 data
= ( ( ( void * ) dhcpv6set
) + sizeof ( *dhcpv6set
) );
346 memcpy ( data
, options
->data
, len
);
347 dhcpv6set
->options
.data
= data
;
348 dhcpv6set
->options
.len
= len
;
350 /* Register settings */
351 if ( ( rc
= register_settings ( &dhcpv6set
->settings
, parent
,
352 DHCPV6_SETTINGS_NAME
) ) != 0 )
356 ref_put ( &dhcpv6set
->refcnt
);
361 /****************************************************************************
367 /** Options to be requested */
368 static uint16_t dhcpv6_requested_options
[] = {
369 htons ( DHCPV6_DNS_SERVERS
), htons ( DHCPV6_DOMAIN_LIST
),
370 htons ( DHCPV6_BOOTFILE_URL
), htons ( DHCPV6_BOOTFILE_PARAM
),
374 * Name a DHCPv6 packet type
376 * @v type DHCPv6 packet type
377 * @ret name DHCPv6 packet type name
379 static __attribute__ (( unused
)) const char *
380 dhcpv6_type_name ( unsigned int type
) {
381 static char buf
[ 12 /* "UNKNOWN-xxx" + NUL */ ];
384 case DHCPV6_SOLICIT
: return "SOLICIT";
385 case DHCPV6_ADVERTISE
: return "ADVERTISE";
386 case DHCPV6_REQUEST
: return "REQUEST";
387 case DHCPV6_REPLY
: return "REPLY";
388 case DHCPV6_INFORMATION_REQUEST
: return "INFORMATION-REQUEST";
390 snprintf ( buf
, sizeof ( buf
), "UNKNOWN-%d", type
);
395 /** A DHCPv6 session state */
396 struct dhcpv6_session_state
{
397 /** Current transmitted packet type */
399 /** Current expected received packet type */
403 /** Next state (or NULL to terminate) */
404 struct dhcpv6_session_state
*next
;
407 /** DHCPv6 session state flags */
408 enum dhcpv6_session_state_flags
{
409 /** Include identity association within request */
410 DHCPV6_TX_IA_NA
= 0x01,
411 /** Include leased IPv6 address within request */
412 DHCPV6_TX_IAADDR
= 0x02,
413 /** Record received server ID */
414 DHCPV6_RX_RECORD_SERVER_ID
= 0x04,
415 /** Record received IPv6 address */
416 DHCPV6_RX_RECORD_IAADDR
= 0x08,
417 /** Apply received IPv6 address */
418 DHCPV6_RX_APPLY_IAADDR
= 0x10,
421 /** DHCPv6 request state */
422 static struct dhcpv6_session_state dhcpv6_request
= {
423 .tx_type
= DHCPV6_REQUEST
,
424 .rx_type
= DHCPV6_REPLY
,
425 .flags
= ( DHCPV6_TX_IA_NA
| DHCPV6_TX_IAADDR
|
426 DHCPV6_RX_RECORD_IAADDR
| DHCPV6_RX_APPLY_IAADDR
),
430 /** DHCPv6 solicitation state */
431 static struct dhcpv6_session_state dhcpv6_solicit
= {
432 .tx_type
= DHCPV6_SOLICIT
,
433 .rx_type
= DHCPV6_ADVERTISE
,
434 .flags
= ( DHCPV6_TX_IA_NA
| DHCPV6_RX_RECORD_SERVER_ID
|
435 DHCPV6_RX_RECORD_IAADDR
),
436 .next
= &dhcpv6_request
,
439 /** DHCPv6 information request state */
440 static struct dhcpv6_session_state dhcpv6_information_request
= {
441 .tx_type
= DHCPV6_INFORMATION_REQUEST
,
442 .rx_type
= DHCPV6_REPLY
,
447 /** A DHCPv6 session */
448 struct dhcpv6_session
{
449 /** Reference counter */
450 struct refcnt refcnt
;
451 /** Job control interface */
452 struct interface job
;
453 /** Data transfer interface */
454 struct interface xfer
;
456 /** Network device being configured */
457 struct net_device
*netdev
;
458 /** Transaction ID */
460 /** Identity association ID */
462 /** Start time (in ticks) */
465 struct dhcpv6_duid_uuid client_duid
;
466 /** Server DUID, if known */
468 /** Server DUID length */
469 size_t server_duid_len
;
470 /** Leased IPv6 address */
471 struct in6_addr lease
;
473 /** Retransmission timer */
474 struct retry_timer timer
;
476 /** Current session state */
477 struct dhcpv6_session_state
*state
;
478 /** Current timeout status code */
483 * Free DHCPv6 session
485 * @v refcnt Reference count
487 static void dhcpv6_free ( struct refcnt
*refcnt
) {
488 struct dhcpv6_session
*dhcpv6
=
489 container_of ( refcnt
, struct dhcpv6_session
, refcnt
);
491 netdev_put ( dhcpv6
->netdev
);
492 free ( dhcpv6
->server_duid
);
497 * Terminate DHCPv6 session
499 * @v dhcpv6 DHCPv6 session
500 * @v rc Reason for close
502 static void dhcpv6_finished ( struct dhcpv6_session
*dhcpv6
, int rc
) {
505 stop_timer ( &dhcpv6
->timer
);
507 /* Shut down interfaces */
508 intf_shutdown ( &dhcpv6
->xfer
, rc
);
509 intf_shutdown ( &dhcpv6
->job
, rc
);
513 * Transition to new DHCPv6 session state
515 * @v dhcpv6 DHCPv6 session
516 * @v state New session state
518 static void dhcpv6_set_state ( struct dhcpv6_session
*dhcpv6
,
519 struct dhcpv6_session_state
*state
) {
521 DBGC ( dhcpv6
, "DHCPv6 %s entering %s state\n", dhcpv6
->netdev
->name
,
522 dhcpv6_type_name ( state
->tx_type
) );
525 dhcpv6
->state
= state
;
527 /* Default to -ETIMEDOUT if no more specific error is recorded */
528 dhcpv6
->rc
= -ETIMEDOUT
;
530 /* Start timer to trigger transmission */
531 start_timer_nodelay ( &dhcpv6
->timer
);
535 * Get DHCPv6 user class
537 * @v data Data buffer
538 * @v len Length of data buffer
539 * @ret len Length of user class
541 static size_t dhcpv6_user_class ( void *data
, size_t len
) {
542 static const char default_user_class
[4] = { 'i', 'P', 'X', 'E' };
545 /* Fetch user-class setting, if defined */
546 actual_len
= fetch_raw_setting ( NULL
, &user_class_setting
, data
, len
);
547 if ( actual_len
>= 0 )
550 /* Otherwise, use the default user class ("iPXE") */
551 if ( len
> sizeof ( default_user_class
) )
552 len
= sizeof ( default_user_class
);
553 memcpy ( data
, default_user_class
, len
);
554 return sizeof ( default_user_class
);
558 * Transmit current request
560 * @v dhcpv6 DHCPv6 session
561 * @ret rc Return status code
563 static int dhcpv6_tx ( struct dhcpv6_session
*dhcpv6
) {
564 struct dhcpv6_duid_option
*client_id
;
565 struct dhcpv6_duid_option
*server_id
;
566 struct dhcpv6_ia_na_option
*ia_na
;
567 struct dhcpv6_iaaddr_option
*iaaddr
;
568 struct dhcpv6_option_request_option
*option_request
;
569 struct dhcpv6_user_class_option
*user_class
;
570 struct dhcpv6_elapsed_time_option
*elapsed
;
571 struct dhcpv6_header
*dhcphdr
;
572 struct io_buffer
*iobuf
;
573 size_t client_id_len
;
574 size_t server_id_len
;
576 size_t option_request_len
;
577 size_t user_class_string_len
;
578 size_t user_class_len
;
583 /* Calculate lengths */
584 client_id_len
= ( sizeof ( *client_id
) +
585 sizeof ( dhcpv6
->client_duid
) );
586 server_id_len
= ( dhcpv6
->server_duid ?
( sizeof ( *server_id
) +
587 dhcpv6
->server_duid_len
) :0);
588 if ( dhcpv6
->state
->flags
& DHCPV6_TX_IA_NA
) {
589 ia_na_len
= sizeof ( *ia_na
);
590 if ( dhcpv6
->state
->flags
& DHCPV6_TX_IAADDR
)
591 ia_na_len
+= sizeof ( *iaaddr
);
595 option_request_len
= ( sizeof ( *option_request
) +
596 sizeof ( dhcpv6_requested_options
) );
597 user_class_string_len
= dhcpv6_user_class ( NULL
, 0 );
598 user_class_len
= ( sizeof ( *user_class
) +
599 sizeof ( user_class
->user_class
[0] ) +
600 user_class_string_len
);
601 elapsed_len
= sizeof ( *elapsed
);
602 total_len
= ( sizeof ( *dhcphdr
) + client_id_len
+ server_id_len
+
603 ia_na_len
+ option_request_len
+ user_class_len
+
606 /* Allocate packet */
607 iobuf
= xfer_alloc_iob ( &dhcpv6
->xfer
, total_len
);
611 /* Construct header */
612 dhcphdr
= iob_put ( iobuf
, sizeof ( *dhcphdr
) );
613 dhcphdr
->type
= dhcpv6
->state
->tx_type
;
614 memcpy ( dhcphdr
->xid
, dhcpv6
->xid
, sizeof ( dhcphdr
->xid
) );
616 /* Construct client identifier */
617 client_id
= iob_put ( iobuf
, client_id_len
);
618 client_id
->header
.code
= htons ( DHCPV6_CLIENT_ID
);
619 client_id
->header
.len
= htons ( client_id_len
-
620 sizeof ( client_id
->header
) );
621 memcpy ( client_id
->duid
, &dhcpv6
->client_duid
,
622 sizeof ( dhcpv6
->client_duid
) );
624 /* Construct server identifier, if applicable */
625 if ( server_id_len
) {
626 server_id
= iob_put ( iobuf
, server_id_len
);
627 server_id
->header
.code
= htons ( DHCPV6_SERVER_ID
);
628 server_id
->header
.len
= htons ( server_id_len
-
629 sizeof ( server_id
->header
) );
630 memcpy ( server_id
->duid
, dhcpv6
->server_duid
,
631 dhcpv6
->server_duid_len
);
634 /* Construct identity association, if applicable */
636 ia_na
= iob_put ( iobuf
, ia_na_len
);
637 ia_na
->header
.code
= htons ( DHCPV6_IA_NA
);
638 ia_na
->header
.len
= htons ( ia_na_len
-
639 sizeof ( ia_na
->header
) );
640 ia_na
->iaid
= htonl ( dhcpv6
->iaid
);
641 ia_na
->renew
= htonl ( 0 );
642 ia_na
->rebind
= htonl ( 0 );
643 if ( dhcpv6
->state
->flags
& DHCPV6_TX_IAADDR
) {
644 iaaddr
= ( ( void * ) ia_na
->options
);
645 iaaddr
->header
.code
= htons ( DHCPV6_IAADDR
);
646 iaaddr
->header
.len
= htons ( sizeof ( *iaaddr
) -
647 sizeof ( iaaddr
->header
));
648 memcpy ( &iaaddr
->address
, &dhcpv6
->lease
,
649 sizeof ( iaaddr
->address
) );
650 iaaddr
->preferred
= htonl ( 0 );
651 iaaddr
->valid
= htonl ( 0 );
655 /* Construct option request */
656 option_request
= iob_put ( iobuf
, option_request_len
);
657 option_request
->header
.code
= htons ( DHCPV6_OPTION_REQUEST
);
658 option_request
->header
.len
= htons ( option_request_len
-
659 sizeof ( option_request
->header
));
660 memcpy ( option_request
->requested
, dhcpv6_requested_options
,
661 sizeof ( dhcpv6_requested_options
) );
663 /* Construct user class */
664 user_class
= iob_put ( iobuf
, user_class_len
);
665 user_class
->header
.code
= htons ( DHCPV6_USER_CLASS
);
666 user_class
->header
.len
= htons ( user_class_len
-
667 sizeof ( user_class
->header
) );
668 user_class
->user_class
[0].len
= htons ( user_class_string_len
);
669 dhcpv6_user_class ( user_class
->user_class
[0].string
,
670 user_class_string_len
);
672 /* Construct elapsed time */
673 elapsed
= iob_put ( iobuf
, elapsed_len
);
674 elapsed
->header
.code
= htons ( DHCPV6_ELAPSED_TIME
);
675 elapsed
->header
.len
= htons ( elapsed_len
-
676 sizeof ( elapsed
->header
) );
677 elapsed
->elapsed
= htons ( ( ( currticks() - dhcpv6
->start
) * 100 ) /
681 assert ( iob_len ( iobuf
) == total_len
);
683 /* Transmit packet */
684 if ( ( rc
= xfer_deliver_iob ( &dhcpv6
->xfer
, iobuf
) ) != 0 ) {
685 DBGC ( dhcpv6
, "DHCPv6 %s could not transmit: %s\n",
686 dhcpv6
->netdev
->name
, strerror ( rc
) );
694 * Handle timer expiry
696 * @v timer Retransmission timer
697 * @v fail Failure indicator
699 static void dhcpv6_timer_expired ( struct retry_timer
*timer
, int fail
) {
700 struct dhcpv6_session
*dhcpv6
=
701 container_of ( timer
, struct dhcpv6_session
, timer
);
703 /* If we have failed, terminate DHCPv6 */
705 dhcpv6_finished ( dhcpv6
, dhcpv6
->rc
);
710 start_timer ( &dhcpv6
->timer
);
712 /* (Re)transmit current request */
713 dhcpv6_tx ( dhcpv6
);
719 * @v dhcpv6 DHCPv6 session
720 * @v iobuf I/O buffer
721 * @v meta Data transfer metadata
722 * @ret rc Return status code
724 static int dhcpv6_rx ( struct dhcpv6_session
*dhcpv6
,
725 struct io_buffer
*iobuf
,
726 struct xfer_metadata
*meta
) {
727 struct settings
*parent
= netdev_settings ( dhcpv6
->netdev
);
728 struct sockaddr_in6
*src
= ( ( struct sockaddr_in6
* ) meta
->src
);
729 struct dhcpv6_header
*dhcphdr
= iobuf
->data
;
730 struct dhcpv6_option_list options
;
731 const union dhcpv6_any_option
*option
;
735 if ( iob_len ( iobuf
) < sizeof ( *dhcphdr
) ) {
736 DBGC ( dhcpv6
, "DHCPv6 %s received packet too short (%zd "
737 "bytes, min %zd bytes)\n", dhcpv6
->netdev
->name
,
738 iob_len ( iobuf
), sizeof ( *dhcphdr
) );
742 assert ( src
!= NULL
);
743 assert ( src
->sin6_family
== AF_INET6
);
744 DBGC ( dhcpv6
, "DHCPv6 %s received %s from %s\n",
745 dhcpv6
->netdev
->name
, dhcpv6_type_name ( dhcphdr
->type
),
746 inet6_ntoa ( &src
->sin6_addr
) );
748 /* Construct option list */
749 options
.data
= dhcphdr
->options
;
750 options
.len
= ( iob_len ( iobuf
) -
751 offsetof ( typeof ( *dhcphdr
), options
) );
753 /* Verify client identifier */
754 if ( ( rc
= dhcpv6_check_duid ( &options
, DHCPV6_CLIENT_ID
,
755 &dhcpv6
->client_duid
,
756 sizeof ( dhcpv6
->client_duid
) ) ) !=0){
757 DBGC ( dhcpv6
, "DHCPv6 %s received %s without correct client "
758 "ID: %s\n", dhcpv6
->netdev
->name
,
759 dhcpv6_type_name ( dhcphdr
->type
), strerror ( rc
) );
763 /* Verify server identifier, if applicable */
764 if ( dhcpv6
->server_duid
&&
765 ( ( rc
= dhcpv6_check_duid ( &options
, DHCPV6_SERVER_ID
,
767 dhcpv6
->server_duid_len
) ) != 0 ) ) {
768 DBGC ( dhcpv6
, "DHCPv6 %s received %s without correct server "
769 "ID: %s\n", dhcpv6
->netdev
->name
,
770 dhcpv6_type_name ( dhcphdr
->type
), strerror ( rc
) );
774 /* Check message type */
775 if ( dhcphdr
->type
!= dhcpv6
->state
->rx_type
) {
776 DBGC ( dhcpv6
, "DHCPv6 %s received %s while expecting %s\n",
777 dhcpv6
->netdev
->name
, dhcpv6_type_name ( dhcphdr
->type
),
778 dhcpv6_type_name ( dhcpv6
->state
->rx_type
) );
783 /* Fetch status code, if present */
784 if ( ( rc
= dhcpv6_status_code ( &options
) ) != 0 ) {
785 DBGC ( dhcpv6
, "DHCPv6 %s received %s with error status: %s\n",
786 dhcpv6
->netdev
->name
, dhcpv6_type_name ( dhcphdr
->type
),
788 /* This is plausibly the error we want to return */
793 /* Record identity association address, if applicable */
794 if ( dhcpv6
->state
->flags
& DHCPV6_RX_RECORD_IAADDR
) {
795 if ( ( rc
= dhcpv6_iaaddr ( &options
, dhcpv6
->iaid
,
796 &dhcpv6
->lease
) ) != 0 ) {
797 DBGC ( dhcpv6
, "DHCPv6 %s received %s with unusable "
798 "IAADDR: %s\n", dhcpv6
->netdev
->name
,
799 dhcpv6_type_name ( dhcphdr
->type
),
801 /* This is plausibly the error we want to return */
805 DBGC ( dhcpv6
, "DHCPv6 %s received %s is for %s\n",
806 dhcpv6
->netdev
->name
, dhcpv6_type_name ( dhcphdr
->type
),
807 inet6_ntoa ( &dhcpv6
->lease
) );
810 /* Record server ID, if applicable */
811 if ( dhcpv6
->state
->flags
& DHCPV6_RX_RECORD_SERVER_ID
) {
812 assert ( dhcpv6
->server_duid
== NULL
);
813 option
= dhcpv6_option ( &options
, DHCPV6_SERVER_ID
);
815 DBGC ( dhcpv6
, "DHCPv6 %s received %s missing server "
816 "ID\n", dhcpv6
->netdev
->name
,
817 dhcpv6_type_name ( dhcphdr
->type
) );
821 dhcpv6
->server_duid_len
= ntohs ( option
->duid
.header
.len
);
822 dhcpv6
->server_duid
= malloc ( dhcpv6
->server_duid_len
);
823 if ( ! dhcpv6
->server_duid
) {
827 memcpy ( dhcpv6
->server_duid
, option
->duid
.duid
,
828 dhcpv6
->server_duid_len
);
831 /* Apply identity association address, if applicable */
832 if ( dhcpv6
->state
->flags
& DHCPV6_RX_APPLY_IAADDR
) {
833 if ( ( rc
= ipv6_set_address ( dhcpv6
->netdev
,
834 &dhcpv6
->lease
) ) != 0 ) {
835 DBGC ( dhcpv6
, "DHCPv6 %s could not apply %s: %s\n",
836 dhcpv6
->netdev
->name
,
837 inet6_ntoa ( &dhcpv6
->lease
), strerror ( rc
) );
838 /* This is plausibly the error we want to return */
844 /* Transition to next state or complete DHCPv6, as applicable */
845 if ( dhcpv6
->state
->next
) {
847 /* Transition to next state */
848 dhcpv6_set_state ( dhcpv6
, dhcpv6
->state
->next
);
853 /* Register settings */
854 if ( ( rc
= dhcpv6_register ( &options
, parent
) ) != 0 ) {
855 DBGC ( dhcpv6
, "DHCPv6 %s could not register "
856 "settings: %s\n", dhcpv6
->netdev
->name
,
861 /* Mark as complete */
862 dhcpv6_finished ( dhcpv6
, 0 );
863 DBGC ( dhcpv6
, "DHCPv6 %s complete\n", dhcpv6
->netdev
->name
);
871 /** DHCPv6 job control interface operations */
872 static struct interface_operation dhcpv6_job_op
[] = {
873 INTF_OP ( intf_close
, struct dhcpv6_session
*, dhcpv6_finished
),
876 /** DHCPv6 job control interface descriptor */
877 static struct interface_descriptor dhcpv6_job_desc
=
878 INTF_DESC ( struct dhcpv6_session
, job
, dhcpv6_job_op
);
880 /** DHCPv6 data transfer interface operations */
881 static struct interface_operation dhcpv6_xfer_op
[] = {
882 INTF_OP ( xfer_deliver
, struct dhcpv6_session
*, dhcpv6_rx
),
885 /** DHCPv6 data transfer interface descriptor */
886 static struct interface_descriptor dhcpv6_xfer_desc
=
887 INTF_DESC ( struct dhcpv6_session
, xfer
, dhcpv6_xfer_op
);
892 * @v job Job control interface
893 * @v netdev Network device
894 * @v stateful Perform stateful address autoconfiguration
895 * @ret rc Return status code
897 int start_dhcpv6 ( struct interface
*job
, struct net_device
*netdev
,
899 struct ll_protocol
*ll_protocol
= netdev
->ll_protocol
;
900 struct dhcpv6_session
*dhcpv6
;
903 struct sockaddr_in6 sin6
;
907 struct sockaddr_in6 sin6
;
915 /* Allocate and initialise structure */
916 dhcpv6
= zalloc ( sizeof ( *dhcpv6
) );
919 ref_init ( &dhcpv6
->refcnt
, dhcpv6_free
);
920 intf_init ( &dhcpv6
->job
, &dhcpv6_job_desc
, &dhcpv6
->refcnt
);
921 intf_init ( &dhcpv6
->xfer
, &dhcpv6_xfer_desc
, &dhcpv6
->refcnt
);
922 dhcpv6
->netdev
= netdev_get ( netdev
);
924 memcpy ( dhcpv6
->xid
, &xid
, sizeof ( dhcpv6
->xid
) );
925 dhcpv6
->start
= currticks();
926 timer_init ( &dhcpv6
->timer
, dhcpv6_timer_expired
, &dhcpv6
->refcnt
);
928 /* Construct client and server addresses */
929 memset ( &addresses
, 0, sizeof ( addresses
) );
930 addresses
.client
.sin6
.sin6_family
= AF_INET6
;
931 addresses
.client
.sin6
.sin6_port
= htons ( DHCPV6_CLIENT_PORT
);
932 addresses
.server
.sin6
.sin6_family
= AF_INET6
;
933 ipv6_all_dhcp_relay_and_servers ( &addresses
.server
.sin6
.sin6_addr
);
934 addresses
.server
.sin6
.sin6_scope_id
= netdev
->index
;
935 addresses
.server
.sin6
.sin6_port
= htons ( DHCPV6_SERVER_PORT
);
937 /* Construct client DUID from system UUID */
938 dhcpv6
->client_duid
.type
= htons ( DHCPV6_DUID_UUID
);
939 if ( ( len
= fetch_uuid_setting ( NULL
, &uuid_setting
,
940 &dhcpv6
->client_duid
.uuid
) ) < 0 ) {
942 DBGC ( dhcpv6
, "DHCPv6 %s could not create DUID-UUID: %s\n",
943 dhcpv6
->netdev
->name
, strerror ( rc
) );
944 goto err_client_duid
;
947 /* Construct IAID from link-layer address */
948 dhcpv6
->iaid
= crc32_le ( 0, netdev
->ll_addr
, ll_protocol
->ll_addr_len
);
949 DBGC ( dhcpv6
, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6
->netdev
->name
,
950 dhcpv6
->xid
[0], dhcpv6
->xid
[1], dhcpv6
->xid
[2] );
952 /* Enter initial state */
953 dhcpv6_set_state ( dhcpv6
, ( stateful ?
&dhcpv6_solicit
:
954 &dhcpv6_information_request
) );
957 if ( ( rc
= xfer_open_socket ( &dhcpv6
->xfer
, SOCK_DGRAM
,
958 &addresses
.server
.sa
,
959 &addresses
.client
.sa
) ) != 0 ) {
960 DBGC ( dhcpv6
, "DHCPv6 %s could not open socket: %s\n",
961 dhcpv6
->netdev
->name
, strerror ( rc
) );
962 goto err_open_socket
;
965 /* Attach parent interface, mortalise self, and return */
966 intf_plug_plug ( &dhcpv6
->job
, job
);
967 ref_put ( &dhcpv6
->refcnt
);
971 dhcpv6_finished ( dhcpv6
, rc
);
973 ref_put ( &dhcpv6
->refcnt
);
977 /** Boot filename setting */
978 const struct setting filename6_setting
__setting ( SETTING_BOOT
, filename
) = {
980 .description
= "Boot filename",
981 .tag
= DHCPV6_BOOTFILE_URL
,
982 .type
= &setting_type_string
,
983 .scope
= &ipv6_scope
,
986 /** DNS search list setting */
987 const struct setting dnssl6_setting
__setting ( SETTING_IP_EXTRA
, dnssl
) = {
989 .description
= "DNS search list",
990 .tag
= DHCPV6_DOMAIN_LIST
,
991 .type
= &setting_type_dnssl
,
992 .scope
= &ipv6_scope
,