[ipv6] Change return code handling from router solicits
[people/pcmattman/gpxe.git] / src / net / udp / dhcp6.c
1 /*
2  * Copyright (C) 2011 Matthew Iselin <matthew@theiselins.net>.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 FILE_LICENCE ( GPL2_OR_LATER );
20
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>
45
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 ) )
51
52 /* Get an option given it's header in an iobuf. */
53 #define dhcp6_get_option( iobuf ) \
54         ( ( iobuf )->data )
55
56 /** Prototype for the DHCP6 tx function */
57 struct dhcp6_session;
58 static int dhcp6_tx ( struct dhcp6_session *dhcp_session );
59
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 ) }
65 };
66
67 struct dhcp6_session_state;
68
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;
77
78         /** Network device being configured */
79         struct net_device *netdev;
80         /** Local socket address */
81         struct sockaddr_in6 local;
82         
83         /** Current state of the transaction. */
84         struct dhcp6_session_state *state;
85
86         /** Retransmission timer */
87         struct retry_timer timer;
88         /** Start time of the current state (in ticks) */
89         unsigned long start;
90         
91         /** Our client ID, for response verification. */
92         void *client_duid;
93         /** Length of the client ID. */
94         size_t client_duid_len;
95         
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;
102         
103         /** Settings to apply as a result of a DHCPv6 session. */
104         struct settings *settings;
105         
106         /** Information about the router to use for address assignment. */
107         struct rsolicit_info router;
108 };
109
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.
113
114 /** DHCP6 state, for the state machine. */
115 struct dhcp6_session_state {
116         /** Name for debugging. */
117         const char *name;
118         /**
119          * Construct transmitted packet
120          *
121          * @v dhcp              DHCP6 session
122          * @v iobuf             I/O buffer for the DHCP6 options & data
123          * @v peer              Destination address
124          */
125         int ( * tx ) ( struct dhcp6_session *dhcp,
126                        struct io_buffer *iobuf,
127                        struct sockaddr_in6 *peer );
128         /** Handle received packet
129          *
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
135          */
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
141          *
142          * @v dhcp              DHCP6 session
143          */
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;
149 };
150
151 /****************************************************************************
152  *
153  * Utility Functions
154  *
155  */
156
157 /**
158  * Calculate DHCP6 transaction ID for a network device
159  *
160  * @v netdev            Network device
161  * @ret xid             DHCP6 XID
162  *
163  * Extract the least significant bits of the hardware address for use
164  * as the transaction ID.
165  */
166 static uint32_t dhcp6_xid ( struct net_device *netdev ) {
167         uint32_t xid;
168
169         memcpy ( &xid, ( netdev->ll_addr + netdev->ll_protocol->ll_addr_len
170                          - sizeof ( xid ) ), sizeof ( xid ) );
171         return xid;
172 }
173
174 /**
175  * Free DHCP6 session
176  *
177  * @v refcnt            Reference counter
178  */
179 static void dhcp6_free ( struct refcnt *refcnt ) {
180         struct dhcp6_session *dhcp =
181                 container_of ( refcnt, struct dhcp6_session, refcnt );
182
183         netdev_put ( dhcp->netdev );
184         free ( dhcp );
185 }
186
187 /**
188  * Mark DHCP6 session as complete
189  *
190  * @v dhcp              DHCP6 session
191  * @v rc                Return status code
192  */
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 );
197         
198         /* Block futher incoming messages */
199         job_nullify ( &dhcp->job );
200         xfer_nullify ( &dhcp->xfer );
201
202         /* Stop retry timer */
203         stop_timer ( &dhcp->timer );
204
205         /* Free resources and close interfaces */
206         xfer_close ( &dhcp->xfer, rc );
207         job_done ( &dhcp->job, rc );
208 }
209
210 /**
211  * Handle DHCP6 retry timer expiry
212  *
213  * @v timer             DHCP retry timer
214  * @v fail              Failure indicator
215  */
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 );
219
220         /* If we have failed, terminate DHCP */
221         if ( fail ) {
222                 dhcp6_finished ( dhcp, -ETIMEDOUT );
223                 return;
224         }
225
226         /* Handle timer expiry based on current state */
227         dhcp->state->expired ( dhcp );
228 }
229
230 /**
231  * Transition to new DHCP6 session state
232  *
233  * @v dhcp              DHCP6 session
234  * @v state             New session state
235  */
236 static void dhcp6_set_state ( struct dhcp6_session *dhcp,
237                              struct dhcp6_session_state *state ) {
238
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 );
247 }
248
249 /**
250  * Receive new data
251  *
252  * @v xfer              Data transfer interface
253  * @v iobuf             I/O buffer
254  * @v meta              Transfer metadata
255  * @ret rc              Return status code
256  */
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;
267
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;
274         }
275         peer = ( struct sockaddr_in6 * ) meta->src;
276         
277         DBG ( "type: %d, xid: %x\n", msgtype, xid );
278         
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 );
282                 
283                 /* Remove the DHCP6 header from the packet. */
284                 iob_pull ( iobuf, sizeof ( struct dhcp6_msg ) );
285                 
286                 dhcp->state->rx ( dhcp, iobuf, peer, msgtype );
287         }
288
289 err_no_src:
290         free_iob ( iobuf );
291         return rc;
292 }
293
294 /**
295  * Searches for a given option in a DHCP6 packet.
296  *
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.
301  */
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;
306         
307         while ( 1 ) {
308                 if ( ntohs ( opt->code ) == optcode ) {
309                         rc = 1;
310                         break;
311                 }
312                 
313                 offset += sizeof ( *opt ) + ntohs ( opt->len );
314                 if ( offset > iob_len ( iobuf ) )
315                         break;
316                 
317                 opt = iobuf->data + offset;
318                 
319         }
320         
321         return rc;
322 }
323
324 /**
325  * Handles a specific option from a DHCP6 packet.
326  *
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
334  */
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;
343         
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;
349         }
350         
351         /* What option is this? */
352         switch ( ntohs ( opt->code ) ) {
353                 case DHCP6_OPT_IA_NA:
354                 case DHCP6_OPT_IA_TA:
355                         {
356                         DBG ( "dhcp6: IA_NA/IA_TA option\n" );
357                         
358                         DBG ( "dhcp6: assigned address is %s\n", inet6_ntoa ( addr->addr ) );
359                         
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;
365                                         }
366                                         
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 ) );
380                                         
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" );
389                                 }
390                         } else {
391                                 dhcp->offer = addr->addr;
392                         }
393                         }
394                         break;
395                 case DHCP6_OPT_DNS_SERVERS:
396                         {
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 );
400                         
401                         DBG ( "dhcp6: DNS servers option - %d addresses\n", nAddrs );
402                         
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 ) );
409                         }
410                         }
411                         break;
412                 case DHCP6_OPT_DNS_DOMAINS:
413                         DBG ( "dhcp6: DNS search domains option\n" );
414                         
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" );
427                                 }
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 );
433                         }
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" );
445                                 }
446                         } else {
447                                 DBG ( "dhcp6: no client DUID yet, assuming unsolicited DHCP6 packet\n" );
448                                 rc = -EINVAL;
449                         }
450                         break;
451                 default:
452                         DBG ( "dhcp6: unhandled option %d\n", ntohs ( opt->code ) );
453                         break;
454         };
455         
456 err:
457         return rc;
458 }
459
460 /**
461  * Takes options from a DHCP6 packet and configures gPXE and the network
462  * face accordingly.
463  *
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.
470  */
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;
477         
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 ) );
482                 
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;
488                 }
489                 
490                 /* Grab the next option. */
491                 opt = iob_pull ( iobuf, optlen );
492         }
493         
494         return rc;
495 }
496
497 /****************************************************************************
498  *
499  * DHCP6 Solicitation State
500  *
501  */
502
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;
510         
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 ) );
514         
515         /* Request rapid commits wherever possible. */
516         rcommit->code = htons ( DHCP6_OPT_RCOMMIT );
517         rcommit->len = 0;
518         
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 );
526         
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;
534         
535         return 0;
536 }
537
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" );
545                 
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 ) );
551                 }
552         } else if ( msgtype == DHCP6_ADVERTISE ) {
553                 DBG ( "dhcp6: received an advertise during solicit, standard transaction taking place\n" );
554                 
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 );
561                 }
562         } else {
563                 DBG ( "dhcp6: got an unknown message during solicit, retrying!\n" );
564         }
565 }
566
567 /** DHCP6 solicit state timer expiry handler. */
568 void dhcp6_solicit_expired ( struct dhcp6_session *dhcp ) {
569         dhcp6_tx ( dhcp );
570 }
571
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,
580 };
581
582 /****************************************************************************
583  *
584  * DHCP6 Request State
585  *
586  */
587
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;
596         
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. */
601         
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 );
609         
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;
617         
618         /* Add the server ID. */
619         serverid->code = htons ( DHCP6_OPT_SERVERID );
620         serverid->len = htons ( dhcp->server_duid_len );
621         
622         tmp = iob_put ( iobuf, dhcp->server_duid_len );
623         memcpy ( tmp, dhcp->server_duid, dhcp->server_duid_len );
624         
625         return 0;
626 }
627
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" );
635                 
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" );
640         }
641 }
642
643 /** DHCP6 request state timer expiry handler. */
644 void dhcp6_request_expired ( struct dhcp6_session *dhcp ) {
645         dhcp6_tx ( dhcp );
646 }
647
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,
656 };
657
658 /****************************************************************************
659  *
660  * DHCP6 Information Request State
661  *
662  */
663
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;
670 }
671
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" );
679                 
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" );
684         }
685 }
686
687 /** DHCP6 information request state timer expiry handler. */
688 void dhcp6_info_request_expired ( struct dhcp6_session *dhcp ) {
689         dhcp6_tx ( dhcp );
690 }
691
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,
700 };
701
702 /****************************************************************************
703  *
704  * Job control interface
705  *
706  */
707
708 /**
709  * Handle kill() event received via job control interface
710  *
711  * @v job               DHCP6 job control interface
712  */
713 static void dhcp6_job_kill ( struct job_interface *job ) {
714         struct dhcp6_session *dhcp =
715                 container_of ( job, struct dhcp6_session, job );
716
717         /* Terminate DHCP session */
718         dhcp6_finished ( dhcp, -ECANCELED );
719 }
720
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,
726 };
727
728 /****************************************************************************
729  *
730  * Public interface
731  *
732  */
733
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,
742 };
743
744 /**
745  * Start a DHCP6 transaction.
746  *
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
755  *
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.
760  */
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;
765         
766         dhcp = zalloc ( sizeof ( *dhcp ) );
767         if ( ! dhcp )
768                 return -ENOMEM;
769         
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" );
781                 }
782         
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;
788                 }
789         }
790         
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 );
800
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 );
805
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;
813         }
814
815         /* Attach parent interface, mortalise self, and return */
816         job_plug_plug ( &dhcp->job, job );
817         ref_put ( &dhcp->refcnt );
818         return 0;
819
820 err:
821         dhcp6_free ( &dhcp->refcnt );
822         return 0;
823 }
824
825 /****************************************************************************
826  *
827  * TX work.
828  *
829  */
830
831 /**
832  * Transmit a DHCP6 packet.
833  */
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,
839         };
840         
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;
849
850         /* Start retry timer.  Do this first so that failures to
851          * transmit will be retried.
852          */
853         start_timer ( &dhcp_session->timer );
854         
855         struct io_buffer *iobuf = xfer_alloc_iob ( &dhcp_session->xfer, DHCP_MIN_LEN );
856         if ( ! iobuf )
857                 return -ENOMEM;
858         
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 );
868         
869         memcpy ( duid_ll_addr, dhcp_session->netdev->ll_addr, ll_protocol->ll_addr_len );
870         
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 );
875         
876         opt_clientid->code = htons ( DHCP6_OPT_CLIENTID );
877         opt_clientid->len = htons ( ll_protocol->ll_addr_len + sizeof ( *duid ) );
878         
879         /* DUID LL */
880         duid->code = htons ( DHCP6_DUID_LL );
881         duid->hwtype = ll_protocol->ll_proto;
882         
883         /* Set up the option request section. */
884         oro_hdr->code = htons ( DHCP6_OPT_ORO );
885         oro_hdr->len = htons ( sizeof ( uint16_t ) * 2 );
886         
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 );
890         
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 );
896         }
897         
898         /* Pass up to the current transaction state to fill options and such. */
899         dhcp_session->state->tx ( dhcp_session, iobuf, &dhcp6_peer );
900         
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;
906         }
907
908 done:
909         free_iob ( iobuf );
910         return rc;
911 }
912