[ipv6] Change return code handling from router solicits
[gpxe/hramrach.git] / src / net / udp / dhcp6.c
blob94da1e9ca14c1ec1bb1ab6f529c899209c1273ff
1 /*
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 );
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <byteswap.h>
28 #include <gpxe/in.h>
29 #include <gpxe/ip6.h>
30 #include <gpxe/list.h>
31 #include <gpxe/udp.h>
32 #include <gpxe/socket.h>
33 #include <gpxe/iobuf.h>
34 #include <gpxe/xfer.h>
35 #include <gpxe/open.h>
36 #include <gpxe/job.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>
43 #include <gpxe/ndp.h>
44 #include <gpxe/dhcp6.h>
46 /* Get an option encapsulated inside another option. */
47 #define dhcp6_get_encapsulated_option( iobuf, parent_type ) \
48 ( ( iobuf )->data + \
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 ) \
54 ( ( iobuf )->data )
56 /** Prototype for the DHCP6 tx function */
57 struct dhcp6_session;
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 */
72 struct refcnt refcnt;
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) */
89 unsigned long start;
91 /** Our client ID, for response verification. */
92 void *client_duid;
93 /** Length of the client ID. */
94 size_t client_duid_len;
96 /** Server DUID - for direct copy. */
97 void *server_duid;
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. */
117 const char *name;
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,
139 uint8_t msgtype );
140 /** Handle timer expiry
142 * @v dhcp DHCP6 session
144 void ( * expired ) ( struct dhcp6_session *dhcp );
145 /** Transmitted message type */
146 uint8_t tx_msgtype;
147 /** Apply minimum timeout */
148 uint8_t apply_min_timeout;
151 /****************************************************************************
153 * Utility Functions
158 * Calculate DHCP6 transaction ID for a network device
160 * @v netdev Network device
161 * @ret xid DHCP6 XID
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 ) {
167 uint32_t xid;
169 memcpy ( &xid, ( netdev->ll_addr + netdev->ll_protocol->ll_addr_len
170 - sizeof ( xid ) ), sizeof ( xid ) );
171 return xid;
175 * Free DHCP6 session
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 );
184 free ( dhcp );
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 ) {
194 /* Clean up. */
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 */
221 if ( fail ) {
222 dhcp6_finished ( dhcp, -ETIMEDOUT );
223 return;
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 );
240 dhcp->state = state;
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 );
250 * Receive new data
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;
266 int rc = 0;
268 /* Sanity checks */
269 if ( ! meta->src ) {
270 DBGC ( dhcp, "DHCP %p received packet without source port\n",
271 dhcp );
272 rc = -EINVAL;
273 goto err_no_src;
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 );
289 err_no_src:
290 free_iob ( iobuf );
291 return rc;
295 * Searches for a given option in a DHCP6 packet.
297 * @v iobuf iobuf to search through (must start with an option
298 * header).
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;
304 int rc = 0;
305 size_t offset = 0;
307 while ( 1 ) {
308 if ( ntohs ( opt->code ) == optcode ) {
309 rc = 1;
310 break;
313 offset += sizeof ( *opt ) + ntohs ( opt->len );
314 if ( offset > iob_len ( iobuf ) )
315 break;
317 opt = iobuf->data + offset;
321 return rc;
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
332 * address.
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,
338 int completed ) {
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 );
342 int rc = 0;
344 /* Verify the option length. */
345 if ( datalen > iob_len ( iobuf ) ) {
346 DBG ( "dhcp6: option length is larger than the packet size, invalid!\n" );
347 rc = -EINVAL;
348 goto err;
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 ) );
360 if ( completed ) {
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,
369 &ip6_setting,
370 &addr->addr,
371 sizeof ( struct in6_addr ) );
372 store_setting ( parent,
373 &gateway6_setting,
374 &dhcp->router,
375 sizeof ( struct in6_addr ) );
376 store_setting ( parent,
377 &prefix_setting,
378 &dhcp->router.prefix_length,
379 sizeof ( dhcp->router.prefix_length ) );
381 /* Add a fully-routable version now. */
382 add_ipv6_address ( dhcp->netdev,
383 dhcp->router.prefix,
384 dhcp->router.prefix_length,
385 addr->addr,
386 dhcp->router.router );
387 } else {
388 DBG ( "dhcp6: not adding an address as SLAAC has done that\n" );
390 } else {
391 dhcp->offer = addr->addr;
394 break;
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,
406 &dns6_setting,
407 iobuf->data,
408 sizeof ( struct in6_addr ) );
411 break;
412 case DHCP6_OPT_DNS_DOMAINS:
413 DBG ( "dhcp6: DNS search domains option\n" );
415 /* TODO: set DNS search domain, needs parsing though. */
416 break;
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,
421 iobuf->data,
422 dhcp->server_duid_len ) ) {
423 DBG ( "dhcp6: server DUID is invalid\n" );
424 rc = -EINVAL;
425 } else {
426 DBG ( "dhcp6: server DUID is valid\n" );
428 } else {
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 );
434 break;
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,
439 iobuf->data,
440 dhcp->client_duid_len ) ) {
441 DBG ( "dhcp6: client DUID is invalid\n" );
442 rc = -EINVAL;
443 } else {
444 DBG ( "dhcp6: client DUID is valid\n" );
446 } else {
447 DBG ( "dhcp6: no client DUID yet, assuming unsolicited DHCP6 packet\n" );
448 rc = -EINVAL;
450 break;
451 default:
452 DBG ( "dhcp6: unhandled option %d\n", ntohs ( opt->code ) );
453 break;
456 err:
457 return rc;
461 * Takes options from a DHCP6 packet and configures gPXE and the network
462 * face accordingly.
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
469 * address.
471 int dhcp6_parse_config ( struct dhcp6_session *dhcp,
472 struct io_buffer *iobuf,
473 int completed ) {
474 struct dhcp6_opt_hdr *opt = iobuf->data;
475 int rc = 0;
476 size_t optlen = 0;
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 );
485 if ( rc != 0 ) {
486 DBG ( "dhcp6: hit an invalid option when parsing options, aborting parse\n" );
487 return rc;
490 /* Grab the next option. */
491 opt = iob_pull ( iobuf, optlen );
494 return rc;
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 );
517 rcommit->len = 0;
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;
535 return 0;
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,
542 uint8_t msgtype ) {
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" );
548 } else {
549 /* Completed. */
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" );
558 } else {
559 /* Move to the REQUEST state. */
560 dhcp6_set_state ( dhcp, &dhcp6_request );
562 } else {
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 ) {
569 dhcp6_tx ( dhcp );
572 /** DHCP6 solicit state operations */
573 static struct dhcp6_session_state dhcp6_solicit = {
574 .name = "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;
595 void *tmp;
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 );
625 return 0;
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,
632 uint8_t msgtype ) {
633 if ( msgtype == DHCP6_REPLY ) {
634 DBG ( "dhcp6: received a confirm during request, all done!\n" );
636 /* Completed. */
637 dhcp6_finished ( dhcp, dhcp6_parse_config ( dhcp, iobuf, 1 ) );
638 } else {
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 ) {
645 dhcp6_tx ( dhcp );
648 /** DHCP6 request state operations */
649 static struct dhcp6_session_state dhcp6_request = {
650 .name = "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. */
669 return 0;
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,
676 uint8_t msgtype ) {
677 if ( msgtype == DHCP6_REPLY ) {
678 DBG ( "dhcp6: received a response during info request, all done!\n" );
680 /* Completed. */
681 dhcp6_finished ( dhcp, dhcp6_parse_config ( dhcp, iobuf, 1 ) );
682 } else {
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 ) {
689 dhcp6_tx ( 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 /****************************************************************************
730 * Public interface
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
750 * actual address.
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;
764 int rc;
766 dhcp = zalloc ( sizeof ( *dhcp ) );
767 if ( ! dhcp )
768 return -ENOMEM;
770 if ( router != NULL ) {
771 dhcp->router = *router;
772 } else {
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 );
776 if ( rc != 0 ) {
777 /* Couldn't TX a solicit for some reason... */
778 DBG ( "dhcp6: couldn't TX a router solicit?\n" );
779 } else {
780 rc = monojob_wait ( "dhcp6 is finding routers" );
783 /* If no router advertisement, set some sane defaults. */
784 if ( rc != 0 ) {
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 );
806 if ( rc == 0 ) {
807 if ( onlyinfo )
808 dhcp6_set_state ( dhcp, &dhcp6_inforeq );
809 else
810 dhcp6_set_state ( dhcp, &dhcp6_solicit );
811 } else {
812 goto err;
815 /* Attach parent interface, mortalise self, and return */
816 job_plug_plug ( &dhcp->job, job );
817 ref_put ( &dhcp->refcnt );
818 return 0;
820 err:
821 dhcp6_free ( &dhcp->refcnt );
822 return 0;
825 /****************************************************************************
827 * TX work.
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;
848 int rc = 0;
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 );
856 if ( ! iobuf )
857 return -ENOMEM;
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 ) );
879 /* DUID LL */
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 );
902 if ( rc != 0 ) {
903 DBGC ( dhcp, "DHCP %p could not transmit UDP packet: %s\n",
904 dhcp, strerror ( rc ) );
905 goto done;
908 done:
909 free_iob ( iobuf );
910 return rc;