2 * Copyright (C) 2011 Matthew Iselin <matthew@theiselins.net>.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
19 FILE_LICENCE ( GPL2_OR_LATER
);
30 #include <gpxe/list.h>
32 #include <gpxe/socket.h>
33 #include <gpxe/iobuf.h>
34 #include <gpxe/xfer.h>
35 #include <gpxe/open.h>
37 #include <gpxe/monojob.h>
38 #include <gpxe/netdevice.h>
39 #include <gpxe/features.h>
40 #include <gpxe/retry.h>
41 #include <gpxe/timer.h>
42 #include <gpxe/settings.h>
44 #include <gpxe/dhcp6.h>
46 /* Get an option encapsulated inside another option. */
47 #define dhcp6_get_encapsulated_option( iobuf, parent_type ) \
49 sizeof ( struct parent_type ) - \
50 sizeof ( struct dhcp6_opt_hdr ) )
52 /* Get an option given it's header in an iobuf. */
53 #define dhcp6_get_option( iobuf ) \
56 /** Prototype for the DHCP6 tx function */
58 static int dhcp6_tx ( struct dhcp6_session
*dhcp_session
);
60 /** Address for all DHCP servers and relay agents - FF02::1:2 */
61 static struct sockaddr_in6 dhcp6_peer
= {
62 .sin_family
= AF_INET6
,
63 .sin_port
= htons ( DHCP6S_PORT
),
64 .sin6_addr
.in6_u
.u6_addr32
= { htons ( 0xFF02 ), 0, 0, htonl ( 0x10002 ) }
67 struct dhcp6_session_state
;
69 /** DHCP6 active session */
70 struct dhcp6_session
{
71 /** Reference counter */
73 /** Job control interface */
74 struct job_interface job
;
75 /** Data transfer interface */
76 struct xfer_interface xfer
;
78 /** Network device being configured */
79 struct net_device
*netdev
;
80 /** Local socket address */
81 struct sockaddr_in6 local
;
83 /** Current state of the transaction. */
84 struct dhcp6_session_state
*state
;
86 /** Retransmission timer */
87 struct retry_timer timer
;
88 /** Start time of the current state (in ticks) */
91 /** Our client ID, for response verification. */
93 /** Length of the client ID. */
94 size_t client_duid_len
;
96 /** Server DUID - for direct copy. */
98 /** Length of the DUID. */
99 size_t server_duid_len
;
100 /** IPv6 address we are looking at keeping. */
101 struct in6_addr offer
;
103 /** Settings to apply as a result of a DHCPv6 session. */
104 struct settings
*settings
;
106 /** Information about the router to use for address assignment. */
107 struct rsolicit_info router
;
110 static struct dhcp6_session_state dhcp6_solicit
;
111 static struct dhcp6_session_state dhcp6_request
;
112 static struct dhcp6_session_state dhcp6_inforeq
; // For Information-Request.
114 /** DHCP6 state, for the state machine. */
115 struct dhcp6_session_state
{
116 /** Name for debugging. */
119 * Construct transmitted packet
121 * @v dhcp DHCP6 session
122 * @v iobuf I/O buffer for the DHCP6 options & data
123 * @v peer Destination address
125 int ( * tx
) ( struct dhcp6_session
*dhcp
,
126 struct io_buffer
*iobuf
,
127 struct sockaddr_in6
*peer
);
128 /** Handle received packet
130 * @v dhcp DHCP6 session
131 * @v iobuf I/O buffer for the DHCP6 packet
132 * @v peer DHCP server address
133 * @v msgtype DHCP message type
134 * @v server_id DHCP server ID
136 void ( * rx
) ( struct dhcp6_session
*dhcp
,
137 struct io_buffer
*iobuf
,
138 struct sockaddr_in6
*peer
,
140 /** Handle timer expiry
142 * @v dhcp DHCP6 session
144 void ( * expired
) ( struct dhcp6_session
*dhcp
);
145 /** Transmitted message type */
147 /** Apply minimum timeout */
148 uint8_t apply_min_timeout
;
151 /****************************************************************************
158 * Calculate DHCP6 transaction ID for a network device
160 * @v netdev Network device
163 * Extract the least significant bits of the hardware address for use
164 * as the transaction ID.
166 static uint32_t dhcp6_xid ( struct net_device
*netdev
) {
169 memcpy ( &xid
, ( netdev
->ll_addr
+ netdev
->ll_protocol
->ll_addr_len
170 - sizeof ( xid
) ), sizeof ( xid
) );
177 * @v refcnt Reference counter
179 static void dhcp6_free ( struct refcnt
*refcnt
) {
180 struct dhcp6_session
*dhcp
=
181 container_of ( refcnt
, struct dhcp6_session
, refcnt
);
183 netdev_put ( dhcp
->netdev
);
188 * Mark DHCP6 session as complete
190 * @v dhcp DHCP6 session
191 * @v rc Return status code
193 static void dhcp6_finished ( struct dhcp6_session
*dhcp
, int rc
) {
195 if ( dhcp
->server_duid
!= NULL
)
196 free ( dhcp
->server_duid
);
198 /* Block futher incoming messages */
199 job_nullify ( &dhcp
->job
);
200 xfer_nullify ( &dhcp
->xfer
);
202 /* Stop retry timer */
203 stop_timer ( &dhcp
->timer
);
205 /* Free resources and close interfaces */
206 xfer_close ( &dhcp
->xfer
, rc
);
207 job_done ( &dhcp
->job
, rc
);
211 * Handle DHCP6 retry timer expiry
213 * @v timer DHCP retry timer
214 * @v fail Failure indicator
216 static void dhcp_timer_expired ( struct retry_timer
*timer
, int fail
) {
217 struct dhcp6_session
*dhcp
=
218 container_of ( timer
, struct dhcp6_session
, timer
);
220 /* If we have failed, terminate DHCP */
222 dhcp6_finished ( dhcp
, -ETIMEDOUT
);
226 /* Handle timer expiry based on current state */
227 dhcp
->state
->expired ( dhcp
);
231 * Transition to new DHCP6 session state
233 * @v dhcp DHCP6 session
234 * @v state New session state
236 static void dhcp6_set_state ( struct dhcp6_session
*dhcp
,
237 struct dhcp6_session_state
*state
) {
239 DBGC ( dhcp
, "DHCP6 %p entering %s state\n", dhcp
, state
->name
);
241 dhcp
->start
= currticks();
242 stop_timer ( &dhcp
->timer
);
243 dhcp
->timer
.min_timeout
=
244 ( state
->apply_min_timeout
? DHCP_MIN_TIMEOUT
: 0 );
245 dhcp
->timer
.max_timeout
= DHCP_MAX_TIMEOUT
;
246 start_timer_nodelay ( &dhcp
->timer
);
252 * @v xfer Data transfer interface
253 * @v iobuf I/O buffer
254 * @v meta Transfer metadata
255 * @ret rc Return status code
257 static int dhcp6_deliver_iob ( struct xfer_interface
*xfer
,
258 struct io_buffer
*iobuf
,
259 struct xfer_metadata
*meta
) {
260 struct dhcp6_session
*dhcp
=
261 container_of ( xfer
, struct dhcp6_session
, xfer
);
262 struct dhcp6_msg
*dhcp_hdr
= iobuf
->data
;
263 struct sockaddr_in6
*peer
;
264 uint8_t msgtype
= ntohl ( dhcp_hdr
->type_id
) >> 24;
265 uint32_t xid
= ntohl ( dhcp_hdr
->type_id
) & 0xFFFFFF;
270 DBGC ( dhcp
, "DHCP %p received packet without source port\n",
275 peer
= ( struct sockaddr_in6
* ) meta
->src
;
277 DBG ( "type: %d, xid: %x\n", msgtype
, xid
);
279 /* Check the transaction ID. */
280 if ( xid
== ( dhcp6_xid ( dhcp
->netdev
) & 0xFFFFFF ) ) {
281 DBG ( "ipv6: dhcp6 iob arrived in state %s\n", dhcp
->state
->name
);
283 /* Remove the DHCP6 header from the packet. */
284 iob_pull ( iobuf
, sizeof ( struct dhcp6_msg
) );
286 dhcp
->state
->rx ( dhcp
, iobuf
, peer
, msgtype
);
295 * Searches for a given option in a DHCP6 packet.
297 * @v iobuf iobuf to search through (must start with an option
299 * @v optcode Option code of the option to search for.
300 * @ret found 1 if found, 0 otherwise.
302 int dhcp6_find_opt ( struct io_buffer
*iobuf
, int optcode
) {
303 struct dhcp6_opt_hdr
*opt
= iobuf
->data
;
308 if ( ntohs ( opt
->code
) == optcode
) {
313 offset
+= sizeof ( *opt
) + ntohs ( opt
->len
);
314 if ( offset
> iob_len ( iobuf
) )
317 opt
= iobuf
->data
+ offset
;
325 * Handles a specific option from a DHCP6 packet.
327 * @v dhcp DHCP6 session.
328 * @v opt Option to parse.
329 * @v iobuf I/O buffer for extra data.
330 * @v completed 1 if we should add addresses and nameservers as a result
331 * of this option, zero if we still have to request an
333 * @ret rc Return status, for error handling if options are invalid
335 int dhcp6_handle_option ( struct dhcp6_session
*dhcp
,
336 struct dhcp6_opt_hdr
*opt
,
337 struct io_buffer
*iobuf
,
339 size_t datalen
= ntohs ( opt
->len
);
340 struct settings
*parent
= netdev_settings ( dhcp
->netdev
);
341 struct dhcp6_opt_iaaddr
*addr
= dhcp6_get_encapsulated_option( iobuf
, dhcp6_opt_ia_na
);
344 /* Verify the option length. */
345 if ( datalen
> iob_len ( iobuf
) ) {
346 DBG ( "dhcp6: option length is larger than the packet size, invalid!\n" );
351 /* What option is this? */
352 switch ( ntohs ( opt
->code
) ) {
353 case DHCP6_OPT_IA_NA
:
354 case DHCP6_OPT_IA_TA
:
356 DBG ( "dhcp6: IA_NA/IA_TA option\n" );
358 DBG ( "dhcp6: assigned address is %s\n", inet6_ntoa ( addr
->addr
) );
361 if ( dhcp
->router
.no_address
) {
362 /* Handle "no router" */
363 if ( dhcp
->router
.prefix_length
== 128 ) {
364 dhcp
->router
.prefix
= addr
->addr
;
367 /* Store the completed IPv6 address. */
368 store_setting ( parent
,
371 sizeof ( struct in6_addr
) );
372 store_setting ( parent
,
375 sizeof ( struct in6_addr
) );
376 store_setting ( parent
,
378 &dhcp
->router
.prefix_length
,
379 sizeof ( dhcp
->router
.prefix_length
) );
381 /* Add a fully-routable version now. */
382 add_ipv6_address ( dhcp
->netdev
,
384 dhcp
->router
.prefix_length
,
386 dhcp
->router
.router
);
388 DBG ( "dhcp6: not adding an address as SLAAC has done that\n" );
391 dhcp
->offer
= addr
->addr
;
395 case DHCP6_OPT_DNS_SERVERS
:
397 /* This ends up being a list of IPv6 addresses. */
398 struct in6_addr
*addrs __unused
= iobuf
->data
;
399 size_t nAddrs
= datalen
/ sizeof ( struct in6_addr
);
401 DBG ( "dhcp6: DNS servers option - %d addresses\n", nAddrs
);
403 /* Verify that there are addresses. */
404 if ( ( datalen
/ sizeof ( struct in6_addr
) ) > 0 ) {
405 store_setting ( NULL
,
408 sizeof ( struct in6_addr
) );
412 case DHCP6_OPT_DNS_DOMAINS
:
413 DBG ( "dhcp6: DNS search domains option\n" );
415 /* TODO: set DNS search domain, needs parsing though. */
417 case DHCP6_OPT_SERVERID
:
418 /* Verify the DUID if we already store one. */
419 if ( dhcp
->server_duid
!= NULL
) {
420 if ( memcmp ( dhcp
->server_duid
,
422 dhcp
->server_duid_len
) ) {
423 DBG ( "dhcp6: server DUID is invalid\n" );
426 DBG ( "dhcp6: server DUID is valid\n" );
429 /* Grab in the server DUID for this session. */
430 dhcp
->server_duid
= malloc ( datalen
);
431 dhcp
->server_duid_len
= datalen
;
432 memcpy ( dhcp
->server_duid
, iobuf
->data
, datalen
);
435 case DHCP6_OPT_CLIENTID
:
436 /* Verify that this client ID option matches our own ID. */
437 if ( dhcp
->client_duid
!= NULL
) {
438 if ( memcmp ( dhcp
->client_duid
,
440 dhcp
->client_duid_len
) ) {
441 DBG ( "dhcp6: client DUID is invalid\n" );
444 DBG ( "dhcp6: client DUID is valid\n" );
447 DBG ( "dhcp6: no client DUID yet, assuming unsolicited DHCP6 packet\n" );
452 DBG ( "dhcp6: unhandled option %d\n", ntohs ( opt
->code
) );
461 * Takes options from a DHCP6 packet and configures gPXE and the network
464 * @v dhcp DHCP6 session.
465 * @v iobuf I/O buffer containing options.
466 * @ret rc Status code for return.
467 * @v completed 1 if we should add addresses and nameservers as a result
468 * of these options, zero if we still have to request an
471 int dhcp6_parse_config ( struct dhcp6_session
*dhcp
,
472 struct io_buffer
*iobuf
,
474 struct dhcp6_opt_hdr
*opt
= iobuf
->data
;
478 while ( iob_len ( iobuf
) ) {
479 /* Remove the option header to make getting data easier. */
480 optlen
= ntohs ( opt
->len
);
481 iob_pull ( iobuf
, sizeof ( *opt
) );
483 /* Handle this option. */
484 rc
= dhcp6_handle_option ( dhcp
, opt
, iobuf
, completed
);
486 DBG ( "dhcp6: hit an invalid option when parsing options, aborting parse\n" );
490 /* Grab the next option. */
491 opt
= iob_pull ( iobuf
, optlen
);
497 /****************************************************************************
499 * DHCP6 Solicitation State
503 /** DHCP6 solicit state TX handler. */
504 int dhcp6_solicit_tx ( struct dhcp6_session
*dhcp __unused
,
505 struct io_buffer
*iobuf
,
506 struct sockaddr_in6
*peer __unused
) {
507 struct dhcp6_opt_ia_na
*ia_na
;
508 struct dhcp6_opt_iaaddr
*ia_addr
;
509 struct dhcp6_opt_hdr
*rcommit
;
511 ia_na
= iob_put ( iobuf
, sizeof ( *ia_na
) );
512 ia_addr
= iob_put ( iobuf
, sizeof ( *ia_addr
) );
513 rcommit
= iob_put ( iobuf
, sizeof ( *rcommit
) );
515 /* Request rapid commits wherever possible. */
516 rcommit
->code
= htons ( DHCP6_OPT_RCOMMIT
);
519 /* Set up the IA-NA option. */
520 ia_na
->code
= htons ( DHCP6_OPT_IA_NA
);
521 ia_na
->len
= htons ( sizeof ( *ia_na
) + sizeof ( *ia_addr
) -
522 sizeof ( struct dhcp6_opt_hdr
) );
523 ia_na
->iaid
= htonl ( 0xdeadbeef );
524 ia_na
->t1
= htonl ( 3600 ); // 60 minutes before expected renew.
525 ia_na
->t2
= htonl ( 3600 );
527 /* Set up the IA_ADDR option. */
528 ia_addr
->code
= htons ( DHCP6_OPT_IAADDR
);
529 ia_addr
->len
= htons ( sizeof ( *ia_addr
) -
530 sizeof ( struct dhcp6_opt_hdr
) );
531 ia_addr
->pref_lifetime
= htonl ( 3600 );
532 ia_addr
->valid_lifetime
= htonl ( 3600 );
533 ia_addr
->addr
= dhcp
->local
.sin6_addr
;
538 /** DHCP6 solicit state RX handler. */
539 void dhcp6_solicit_rx ( struct dhcp6_session
*dhcp
,
540 struct io_buffer
*iobuf
,
541 struct sockaddr_in6
*peer __unused
,
543 if ( msgtype
== DHCP6_REPLY
) {
544 DBG ( "dhcp6: received a reply during solicit, expecting a rapid commit\n" );
546 if ( ! dhcp6_find_opt ( iobuf
, DHCP6_OPT_RCOMMIT
) ) {
547 DBG ( "dhcp6: received a reply that was not a rapid commit!\n" );
550 dhcp6_finished ( dhcp
, dhcp6_parse_config ( dhcp
, iobuf
, 1 ) );
552 } else if ( msgtype
== DHCP6_ADVERTISE
) {
553 DBG ( "dhcp6: received an advertise during solicit, standard transaction taking place\n" );
555 /* Grab the server ID and such. */
556 if ( dhcp6_parse_config ( dhcp
, iobuf
, 0 ) != 0 ) {
557 DBG ( "dhcp6: not a valid advertisement! retrying!\n" );
559 /* Move to the REQUEST state. */
560 dhcp6_set_state ( dhcp
, &dhcp6_request
);
563 DBG ( "dhcp6: got an unknown message during solicit, retrying!\n" );
567 /** DHCP6 solicit state timer expiry handler. */
568 void dhcp6_solicit_expired ( struct dhcp6_session
*dhcp
) {
572 /** DHCP6 solicit state operations */
573 static struct dhcp6_session_state dhcp6_solicit
= {
575 .tx
= dhcp6_solicit_tx
,
576 .rx
= dhcp6_solicit_rx
,
577 .expired
= dhcp6_solicit_expired
,
578 .tx_msgtype
= DHCP6_SOLICIT
,
579 .apply_min_timeout
= 1,
582 /****************************************************************************
584 * DHCP6 Request State
588 /** DHCP6 request state TX handler. */
589 int dhcp6_request_tx ( struct dhcp6_session
*dhcp
,
590 struct io_buffer
*iobuf
,
591 struct sockaddr_in6
*peer __unused
) {
592 struct dhcp6_opt_ia_na
*ia_na
;
593 struct dhcp6_opt_iaaddr
*ia_addr
;
594 struct dhcp6_opt_hdr
*serverid
;
597 ia_na
= iob_put ( iobuf
, sizeof ( *ia_na
) );
598 ia_addr
= iob_put ( iobuf
, sizeof ( *ia_addr
) );
599 serverid
= iob_put ( iobuf
, sizeof ( *serverid
) );
600 /* Do not add any data after serverid, it is manipulated later. */
602 /* Set up the IA-NA option. */
603 ia_na
->code
= htons ( DHCP6_OPT_IA_NA
);
604 ia_na
->len
= htons ( sizeof ( *ia_na
) + sizeof ( *ia_addr
) -
605 sizeof ( struct dhcp6_opt_hdr
) );
606 ia_na
->iaid
= htonl ( 0xdeadbeef );
607 ia_na
->t1
= htonl ( 3600 ); // 60 minutes before expected renew.
608 ia_na
->t2
= htonl ( 3600 );
610 /* Set up the IA_ADDR option. */
611 ia_addr
->code
= htons ( DHCP6_OPT_IAADDR
);
612 ia_addr
->len
= htons ( sizeof ( *ia_addr
) -
613 sizeof ( struct dhcp6_opt_hdr
) );
614 ia_addr
->pref_lifetime
= htonl ( 3600 );
615 ia_addr
->valid_lifetime
= htonl ( 3600 );
616 ia_addr
->addr
= dhcp
->offer
;
618 /* Add the server ID. */
619 serverid
->code
= htons ( DHCP6_OPT_SERVERID
);
620 serverid
->len
= htons ( dhcp
->server_duid_len
);
622 tmp
= iob_put ( iobuf
, dhcp
->server_duid_len
);
623 memcpy ( tmp
, dhcp
->server_duid
, dhcp
->server_duid_len
);
628 /** DHCP6 request state RX handler. */
629 void dhcp6_request_rx ( struct dhcp6_session
*dhcp
,
630 struct io_buffer
*iobuf
,
631 struct sockaddr_in6
*peer __unused
,
633 if ( msgtype
== DHCP6_REPLY
) {
634 DBG ( "dhcp6: received a confirm during request, all done!\n" );
637 dhcp6_finished ( dhcp
, dhcp6_parse_config ( dhcp
, iobuf
, 1 ) );
639 DBG ( "dhcp6: got an unknown message during request, retrying!\n" );
643 /** DHCP6 request state timer expiry handler. */
644 void dhcp6_request_expired ( struct dhcp6_session
*dhcp
) {
648 /** DHCP6 request state operations */
649 static struct dhcp6_session_state dhcp6_request
= {
651 .tx
= dhcp6_request_tx
,
652 .rx
= dhcp6_request_rx
,
653 .expired
= dhcp6_request_expired
,
654 .tx_msgtype
= DHCP6_REQUEST
,
655 .apply_min_timeout
= 1,
658 /****************************************************************************
660 * DHCP6 Information Request State
664 /** DHCP6 information request state TX handler. */
665 int dhcp6_info_request_tx ( struct dhcp6_session
*dhcp __unused
,
666 struct io_buffer
*iobuf __unused
,
667 struct sockaddr_in6
*peer __unused
) {
668 /* Everything else is already provided by dhcp6_tx. */
672 /** DHCP6 information request state RX handler. */
673 void dhcp6_info_request_rx ( struct dhcp6_session
*dhcp
,
674 struct io_buffer
*iobuf
,
675 struct sockaddr_in6
*peer __unused
,
677 if ( msgtype
== DHCP6_REPLY
) {
678 DBG ( "dhcp6: received a response during info request, all done!\n" );
681 dhcp6_finished ( dhcp
, dhcp6_parse_config ( dhcp
, iobuf
, 1 ) );
683 DBG ( "dhcp6: got an unknown message during info request, retrying!\n" );
687 /** DHCP6 information request state timer expiry handler. */
688 void dhcp6_info_request_expired ( struct dhcp6_session
*dhcp
) {
692 /** DHCP6 information request state operations */
693 static struct dhcp6_session_state dhcp6_inforeq
= {
694 .name
= "info_request",
695 .tx
= dhcp6_info_request_tx
,
696 .rx
= dhcp6_info_request_rx
,
697 .expired
= dhcp6_info_request_expired
,
698 .tx_msgtype
= DHCP6_INFOREQ
,
699 .apply_min_timeout
= 1,
702 /****************************************************************************
704 * Job control interface
709 * Handle kill() event received via job control interface
711 * @v job DHCP6 job control interface
713 static void dhcp6_job_kill ( struct job_interface
*job
) {
714 struct dhcp6_session
*dhcp
=
715 container_of ( job
, struct dhcp6_session
, job
);
717 /* Terminate DHCP session */
718 dhcp6_finished ( dhcp
, -ECANCELED
);
721 /** DHCP job control interface operations */
722 static struct job_interface_operations dhcp6_job_operations
= {
723 .done
= ignore_job_done
,
724 .kill
= dhcp6_job_kill
,
725 .progress
= ignore_job_progress
,
728 /****************************************************************************
734 /** DHCP6 data transfer interface operations */
735 static struct xfer_interface_operations dhcp6_xfer_operations
= {
736 .close
= ignore_xfer_close
,
737 .vredirect
= xfer_vreopen
,
738 .window
= unlimited_xfer_window
,
739 .alloc_iob
= default_xfer_alloc_iob
,
740 .deliver_iob
= dhcp6_deliver_iob
,
741 .deliver_raw
= xfer_deliver_as_iob
,
745 * Start a DHCP6 transaction.
747 * @v job Job control interface
748 * @v netdev Network device
749 * @v onlyinfo Only get information from the DHCPv6 server, not an
751 * @v router Router information, or NULL if none is available or
752 * needed. If provided, DHCP6 won't perform a router
753 * solicit automatically.
754 * @ret rc Return status code, or positive if cached
756 * On a return of 0, a background job has been started to perform the
757 * DHCP6 transaction. Any nonzero return means the job has not been
758 * started; a positive return value indicates the success condition of
759 * having fetched the appropriate data from cached information.
761 int start_dhcp6 ( struct job_interface
*job
, struct net_device
*netdev
,
762 int onlyinfo
, struct rsolicit_info
*router
) {
763 struct dhcp6_session
*dhcp
;
766 dhcp
= zalloc ( sizeof ( *dhcp
) );
770 if ( router
!= NULL
) {
771 dhcp
->router
= *router
;
773 /* Get information about routers on this network first. */
774 memset ( &dhcp
->router
, 0, sizeof ( dhcp
->router
) );
775 rc
= ndp_send_rsolicit ( netdev
, &monojob
, &dhcp
->router
);
777 /* Couldn't TX a solicit for some reason... */
778 DBG ( "dhcp6: couldn't TX a router solicit?\n" );
780 rc
= monojob_wait ( "dhcp6 is finding routers" );
783 /* If no router advertisement, set some sane defaults. */
785 DBG ( "dhcp6: can't find a router on the network, continuing\n" );
786 dhcp
->router
.prefix_length
= 128;
787 dhcp
->router
.no_address
= 1;
791 ref_init ( &dhcp
->refcnt
, dhcp6_free
);
792 job_init ( &dhcp
->job
, &dhcp6_job_operations
, &dhcp
->refcnt
);
793 xfer_init ( &dhcp
->xfer
, &dhcp6_xfer_operations
, &dhcp
->refcnt
);
794 timer_init ( &dhcp
->timer
, dhcp_timer_expired
);
795 dhcp
->netdev
= netdev_get ( netdev
);
796 dhcp
->local
.sin_family
= AF_INET6
;
797 dhcp
->local
.sin_port
= htons ( DHCP6C_PORT
);
798 fetch_ipv6_setting ( netdev_settings ( netdev
), &ip6_setting
,
799 &dhcp
->local
.sin6_addr
);
801 /* Instantiate child objects and attach to our interfaces */
802 rc
= xfer_open_socket ( &dhcp
->xfer
, SOCK_DGRAM
,
803 ( struct sockaddr
* ) &dhcp6_peer
,
804 ( struct sockaddr
* ) &dhcp
->local
);
808 dhcp6_set_state ( dhcp
, &dhcp6_inforeq
);
810 dhcp6_set_state ( dhcp
, &dhcp6_solicit
);
815 /* Attach parent interface, mortalise self, and return */
816 job_plug_plug ( &dhcp
->job
, job
);
817 ref_put ( &dhcp
->refcnt
);
821 dhcp6_free ( &dhcp
->refcnt
);
825 /****************************************************************************
832 * Transmit a DHCP6 packet.
834 static int dhcp6_tx ( struct dhcp6_session
*dhcp_session
) {
835 struct xfer_metadata meta
= {
836 .netdev
= dhcp_session
->netdev
,
837 .src
= ( struct sockaddr
* ) &dhcp_session
->local
,
838 .dest
= ( struct sockaddr
* ) &dhcp6_peer
,
841 struct ll_protocol
*ll_protocol
= dhcp_session
->netdev
->ll_protocol
;
842 struct dhcp6_msg
*dhcp
;
843 struct dhcp6_opt_hdr
*opt_clientid
;
844 struct dhcp6_duid_ll
*duid
;
845 struct dhcp6_opt_hdr
*oro_hdr
; /* Option requests are the same for all */
846 uint16_t *opts_to_req
; /* three DHCPv6 session types. */
847 uint8_t *duid_ll_addr
= NULL
;
850 /* Start retry timer. Do this first so that failures to
851 * transmit will be retried.
853 start_timer ( &dhcp_session
->timer
);
855 struct io_buffer
*iobuf
= xfer_alloc_iob ( &dhcp_session
->xfer
, DHCP_MIN_LEN
);
859 /* Set up the DHCP6 header and a DUID option. This will be common across
860 * all request types, and is fortunately quite simple. */
861 iob_reserve ( iobuf
, MAX_HDR_LEN
);
862 dhcp
= iob_put ( iobuf
, sizeof ( *dhcp
) );
863 opt_clientid
= iob_put ( iobuf
, sizeof ( *opt_clientid
) );
864 duid
= iob_put ( iobuf
, sizeof ( *duid
) );
865 duid_ll_addr
= iob_put ( iobuf
, ll_protocol
->ll_addr_len
);
866 oro_hdr
= iob_put ( iobuf
, sizeof ( *oro_hdr
) );
867 opts_to_req
= iob_put ( iobuf
, sizeof ( uint16_t ) * 2 );
869 memcpy ( duid_ll_addr
, dhcp_session
->netdev
->ll_addr
, ll_protocol
->ll_addr_len
);
871 /* Transaction ID - bottom 8 bits are the message type, the rest is
872 * the transaction ID itself. */
873 dhcp
->type_id
= htonl ( dhcp_session
->state
->tx_msgtype
<< 24 );
874 dhcp
->type_id
|= htonl ( dhcp6_xid ( dhcp_session
->netdev
) & 0xFFFFFF );
876 opt_clientid
->code
= htons ( DHCP6_OPT_CLIENTID
);
877 opt_clientid
->len
= htons ( ll_protocol
->ll_addr_len
+ sizeof ( *duid
) );
880 duid
->code
= htons ( DHCP6_DUID_LL
);
881 duid
->hwtype
= ll_protocol
->ll_proto
;
883 /* Set up the option request section. */
884 oro_hdr
->code
= htons ( DHCP6_OPT_ORO
);
885 oro_hdr
->len
= htons ( sizeof ( uint16_t ) * 2 );
887 /* Set the options we want to request. */
888 opts_to_req
[0] = htons ( DHCP6_OPT_DNS_SERVERS
);
889 opts_to_req
[1] = htons ( DHCP6_OPT_DNS_DOMAINS
);
891 /* Fill the DUID in the DHCP session state if it isn't already set. */
892 if ( dhcp_session
->client_duid
== NULL
) {
893 dhcp_session
->client_duid_len
= ll_protocol
->ll_addr_len
+ sizeof ( *duid
);
894 dhcp_session
->client_duid
= zalloc ( dhcp_session
->client_duid_len
);
895 memcpy ( dhcp_session
->client_duid
, duid
, dhcp_session
->client_duid_len
);
898 /* Pass up to the current transaction state to fill options and such. */
899 dhcp_session
->state
->tx ( dhcp_session
, iobuf
, &dhcp6_peer
);
901 rc
= xfer_deliver_iob_meta ( &dhcp_session
->xfer
, iob_disown ( iobuf
), &meta
);
903 DBGC ( dhcp
, "DHCP %p could not transmit UDP packet: %s\n",
904 dhcp
, strerror ( rc
) );