iPXE
dhcpv6.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdlib.h>
00027 #include <stdio.h>
00028 #include <string.h>
00029 #include <errno.h>
00030 #include <byteswap.h>
00031 #include <ipxe/interface.h>
00032 #include <ipxe/xfer.h>
00033 #include <ipxe/iobuf.h>
00034 #include <ipxe/open.h>
00035 #include <ipxe/netdevice.h>
00036 #include <ipxe/settings.h>
00037 #include <ipxe/retry.h>
00038 #include <ipxe/timer.h>
00039 #include <ipxe/in.h>
00040 #include <ipxe/crc32.h>
00041 #include <ipxe/errortab.h>
00042 #include <ipxe/ipv6.h>
00043 #include <ipxe/dhcp_arch.h>
00044 #include <ipxe/dhcpv6.h>
00045 
00046 /** @file
00047  *
00048  * Dynamic Host Configuration Protocol for IPv6
00049  *
00050  */
00051 
00052 /* Disambiguate the various error causes */
00053 #define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL )
00054 #define EINFO_EPROTO_UNSPECFAIL \
00055         __einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" )
00056 #define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL )
00057 #define EINFO_EPROTO_NOADDRSAVAIL \
00058         __einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" )
00059 #define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING )
00060 #define EINFO_EPROTO_NOBINDING \
00061         __einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" )
00062 #define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK )
00063 #define EINFO_EPROTO_NOTONLINK \
00064         __einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" )
00065 #define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST )
00066 #define EINFO_EPROTO_USEMULTICAST \
00067         __einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" )
00068 #define EPROTO_STATUS( status )                                         \
00069         EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL,   \
00070                 EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING,                  \
00071                 EPROTO_NOTONLINK, EPROTO_USEMULTICAST )
00072 
00073 /** Human-readable error messages */
00074 struct errortab dhcpv6_errors[] __errortab = {
00075         __einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL ),
00076 };
00077 
00078 /****************************************************************************
00079  *
00080  * DHCPv6 option lists
00081  *
00082  */
00083 
00084 /** A DHCPv6 option list */
00085 struct dhcpv6_option_list {
00086         /** Data buffer */
00087         const void *data;
00088         /** Length of data buffer */
00089         size_t len;
00090 };
00091 
00092 /**
00093  * Find DHCPv6 option
00094  *
00095  * @v options           DHCPv6 option list
00096  * @v code              Option code
00097  * @ret option          DHCPv6 option, or NULL if not found
00098  */
00099 static const union dhcpv6_any_option *
00100 dhcpv6_option ( struct dhcpv6_option_list *options, unsigned int code ) {
00101         const union dhcpv6_any_option *option = options->data;
00102         size_t remaining = options->len;
00103         size_t data_len;
00104 
00105         /* Scan through list of options */
00106         while ( remaining >= sizeof ( option->header ) ) {
00107 
00108                 /* Calculate and validate option length */
00109                 remaining -= sizeof ( option->header );
00110                 data_len = ntohs ( option->header.len );
00111                 if ( data_len > remaining ) {
00112                         /* Malformed option list */
00113                         return NULL;
00114                 }
00115 
00116                 /* Return if we have found the specified option */
00117                 if ( option->header.code == htons ( code ) )
00118                         return option;
00119 
00120                 /* Otherwise, move to the next option */
00121                 option = ( ( ( void * ) option->header.data ) + data_len );
00122                 remaining -= data_len;
00123         }
00124 
00125         return NULL;
00126 }
00127 
00128 /**
00129  * Check DHCPv6 client or server identifier
00130  *
00131  * @v options           DHCPv6 option list
00132  * @v code              Option code
00133  * @v expected          Expected value
00134  * @v len               Length of expected value
00135  * @ret rc              Return status code
00136  */
00137 static int dhcpv6_check_duid ( struct dhcpv6_option_list *options,
00138                                unsigned int code, const void *expected,
00139                                size_t len ) {
00140         const union dhcpv6_any_option *option;
00141         const struct dhcpv6_duid_option *duid;
00142 
00143         /* Find option */
00144         option = dhcpv6_option ( options, code );
00145         if ( ! option )
00146                 return -ENOENT;
00147         duid = &option->duid;
00148 
00149         /* Check option length */
00150         if ( ntohs ( duid->header.len ) != len )
00151                 return -EINVAL;
00152 
00153         /* Compare option value */
00154         if ( memcmp ( duid->duid, expected, len ) != 0 )
00155                 return -EINVAL;
00156 
00157         return 0;
00158 }
00159 
00160 /**
00161  * Get DHCPv6 status code
00162  *
00163  * @v options           DHCPv6 option list
00164  * @ret rc              Return status code
00165  */
00166 static int dhcpv6_status_code ( struct dhcpv6_option_list *options ) {
00167         const union dhcpv6_any_option *option;
00168         const struct dhcpv6_status_code_option *status_code;
00169         unsigned int status;
00170 
00171         /* Find status code option, if present */
00172         option = dhcpv6_option ( options, DHCPV6_STATUS_CODE );
00173         if ( ! option ) {
00174                 /* Omitted status code should be treated as "success" */
00175                 return 0;
00176         }
00177         status_code = &option->status_code;
00178 
00179         /* Sanity check */
00180         if ( ntohs ( status_code->header.len ) <
00181              ( sizeof ( *status_code ) - sizeof ( status_code->header ) ) ) {
00182                 return -EINVAL;
00183         }
00184 
00185         /* Calculate iPXE error code from DHCPv6 status code */
00186         status = ntohs ( status_code->status );
00187         return ( status ? -EPROTO_STATUS ( status ) : 0 );
00188 }
00189 
00190 /**
00191  * Get DHCPv6 identity association address
00192  *
00193  * @v options           DHCPv6 option list
00194  * @v iaid              Identity association ID
00195  * @v address           IPv6 address to fill in
00196  * @ret rc              Return status code
00197  */
00198 static int dhcpv6_iaaddr ( struct dhcpv6_option_list *options, uint32_t iaid,
00199                            struct in6_addr *address ) {
00200         const union dhcpv6_any_option *option;
00201         const struct dhcpv6_ia_na_option *ia_na;
00202         const struct dhcpv6_iaaddr_option *iaaddr;
00203         struct dhcpv6_option_list suboptions;
00204         size_t len;
00205         int rc;
00206 
00207         /* Find identity association option, if present */
00208         option = dhcpv6_option ( options, DHCPV6_IA_NA );
00209         if ( ! option )
00210                 return -ENOENT;
00211         ia_na = &option->ia_na;
00212 
00213         /* Sanity check */
00214         len = ntohs ( ia_na->header.len );
00215         if ( len < ( sizeof ( *ia_na ) - sizeof ( ia_na->header ) ) )
00216                 return -EINVAL;
00217 
00218         /* Check identity association ID */
00219         if ( ia_na->iaid != htonl ( iaid ) )
00220                 return -EINVAL;
00221 
00222         /* Construct IA_NA sub-options list */
00223         suboptions.data = ia_na->options;
00224         suboptions.len = ( len + sizeof ( ia_na->header ) -
00225                            offsetof ( typeof ( *ia_na ), options ) );
00226 
00227         /* Check IA_NA status code */
00228         if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
00229                 return rc;
00230 
00231         /* Find identity association address, if present */
00232         option = dhcpv6_option ( &suboptions, DHCPV6_IAADDR );
00233         if ( ! option )
00234                 return -ENOENT;
00235         iaaddr = &option->iaaddr;
00236 
00237         /* Sanity check */
00238         len = ntohs ( iaaddr->header.len );
00239         if ( len < ( sizeof ( *iaaddr ) - sizeof ( iaaddr->header ) ) )
00240                 return -EINVAL;
00241 
00242         /* Construct IAADDR sub-options list */
00243         suboptions.data = iaaddr->options;
00244         suboptions.len = ( len + sizeof ( iaaddr->header ) -
00245                            offsetof ( typeof ( *iaaddr ), options ) );
00246 
00247         /* Check IAADDR status code */
00248         if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
00249                 return rc;
00250 
00251         /* Extract IPv6 address */
00252         memcpy ( address, &iaaddr->address, sizeof ( *address ) );
00253 
00254         return 0;
00255 }
00256 
00257 /****************************************************************************
00258  *
00259  * DHCPv6 settings blocks
00260  *
00261  */
00262 
00263 /** A DHCPv6 settings block */
00264 struct dhcpv6_settings {
00265         /** Reference count */
00266         struct refcnt refcnt;
00267         /** Settings block */
00268         struct settings settings;
00269         /** Leased address */
00270         struct in6_addr lease;
00271         /** Option list */
00272         struct dhcpv6_option_list options;
00273 };
00274 
00275 /**
00276  * Check applicability of DHCPv6 setting
00277  *
00278  * @v settings          Settings block
00279  * @v setting           Setting
00280  * @ret applies         Setting applies within this settings block
00281  */
00282 static int dhcpv6_applies ( struct settings *settings __unused,
00283                             const struct setting *setting ) {
00284 
00285         return ( ( setting->scope == &dhcpv6_scope ) ||
00286                  ( setting_cmp ( setting, &ip6_setting ) == 0 ) );
00287 }
00288 
00289 /**
00290  * Fetch value of DHCPv6 leased address
00291  *
00292  * @v dhcpset           DHCPv6 settings
00293  * @v data              Buffer to fill with setting data
00294  * @v len               Length of buffer
00295  * @ret len             Length of setting data, or negative error
00296  */
00297 static int dhcpv6_fetch_lease ( struct dhcpv6_settings *dhcpv6set,
00298                                 void *data, size_t len ) {
00299         struct in6_addr *lease = &dhcpv6set->lease;
00300 
00301         /* Do nothing unless a leased address exists */
00302         if ( IN6_IS_ADDR_UNSPECIFIED ( lease ) )
00303                 return -ENOENT;
00304 
00305         /* Copy leased address */
00306         if ( len > sizeof ( *lease ) )
00307                 len = sizeof ( *lease );
00308         memcpy ( data, lease, len );
00309 
00310         return sizeof ( *lease );
00311 }
00312 
00313 /**
00314  * Fetch value of DHCPv6 setting
00315  *
00316  * @v settings          Settings block
00317  * @v setting           Setting to fetch
00318  * @v data              Buffer to fill with setting data
00319  * @v len               Length of buffer
00320  * @ret len             Length of setting data, or negative error
00321  */
00322 static int dhcpv6_fetch ( struct settings *settings,
00323                           struct setting *setting,
00324                           void *data, size_t len ) {
00325         struct dhcpv6_settings *dhcpv6set =
00326                 container_of ( settings, struct dhcpv6_settings, settings );
00327         const union dhcpv6_any_option *option;
00328         size_t option_len;
00329 
00330         /* Handle leased address */
00331         if ( setting_cmp ( setting, &ip6_setting ) == 0 )
00332                 return dhcpv6_fetch_lease ( dhcpv6set, data, len );
00333 
00334         /* Find option */
00335         option = dhcpv6_option ( &dhcpv6set->options, setting->tag );
00336         if ( ! option )
00337                 return -ENOENT;
00338 
00339         /* Copy option to data buffer */
00340         option_len = ntohs ( option->header.len );
00341         if ( len > option_len )
00342                 len = option_len;
00343         memcpy ( data, option->header.data, len );
00344         return option_len;
00345 }
00346 
00347 /** DHCPv6 settings operations */
00348 static struct settings_operations dhcpv6_settings_operations = {
00349         .applies = dhcpv6_applies,
00350         .fetch = dhcpv6_fetch,
00351 };
00352 
00353 /**
00354  * Register DHCPv6 options as network device settings
00355  *
00356  * @v lease             DHCPv6 leased address
00357  * @v options           DHCPv6 option list
00358  * @v parent            Parent settings block
00359  * @ret rc              Return status code
00360  */
00361 static int dhcpv6_register ( struct in6_addr *lease,
00362                              struct dhcpv6_option_list *options,
00363                              struct settings *parent ) {
00364         struct dhcpv6_settings *dhcpv6set;
00365         void *data;
00366         size_t len;
00367         int rc;
00368 
00369         /* Allocate and initialise structure */
00370         dhcpv6set = zalloc ( sizeof ( *dhcpv6set ) + options->len );
00371         if ( ! dhcpv6set ) {
00372                 rc = -ENOMEM;
00373                 goto err_alloc;
00374         }
00375         ref_init ( &dhcpv6set->refcnt, NULL );
00376         settings_init ( &dhcpv6set->settings, &dhcpv6_settings_operations,
00377                         &dhcpv6set->refcnt, &dhcpv6_scope );
00378         dhcpv6set->settings.order = IPV6_ORDER_DHCPV6;
00379         data = ( ( ( void * ) dhcpv6set ) + sizeof ( *dhcpv6set ) );
00380         len = options->len;
00381         memcpy ( data, options->data, len );
00382         dhcpv6set->options.data = data;
00383         dhcpv6set->options.len = len;
00384         memcpy ( &dhcpv6set->lease, lease, sizeof ( dhcpv6set->lease ) );
00385 
00386         /* Register settings */
00387         if ( ( rc = register_settings ( &dhcpv6set->settings, parent,
00388                                         DHCPV6_SETTINGS_NAME ) ) != 0 )
00389                 goto err_register;
00390 
00391  err_register:
00392         ref_put ( &dhcpv6set->refcnt );
00393  err_alloc:
00394         return rc;
00395 }
00396 
00397 /****************************************************************************
00398  *
00399  * DHCPv6 protocol
00400  *
00401  */
00402 
00403 /** Raw option data for options common to all DHCPv6 requests */
00404 static uint8_t dhcpv6_request_options_data[] = {
00405         DHCPV6_CODE ( DHCPV6_OPTION_REQUEST ),
00406         DHCPV6_OPTION ( DHCPV6_CODE ( DHCPV6_DNS_SERVERS ),
00407                         DHCPV6_CODE ( DHCPV6_DOMAIN_LIST ),
00408                         DHCPV6_CODE ( DHCPV6_BOOTFILE_URL ),
00409                         DHCPV6_CODE ( DHCPV6_BOOTFILE_PARAM ) ),
00410         DHCPV6_CODE ( DHCPV6_VENDOR_CLASS ),
00411         DHCPV6_OPTION ( DHCPV6_DWORD_VALUE ( DHCPV6_VENDOR_CLASS_PXE ),
00412                         DHCPV6_STRING (
00413                           DHCP_VENDOR_PXECLIENT ( DHCP_ARCH_CLIENT_ARCHITECTURE,
00414                                                   DHCP_ARCH_CLIENT_NDI ) ) ),
00415         DHCPV6_CODE ( DHCPV6_CLIENT_ARCHITECTURE ),
00416         DHCPV6_WORD ( DHCP_ARCH_CLIENT_ARCHITECTURE ),
00417         DHCPV6_CODE ( DHCPV6_CLIENT_NDI ),
00418         DHCPV6_OPTION ( DHCP_ARCH_CLIENT_NDI )
00419 };
00420 
00421 /**
00422  * Name a DHCPv6 packet type
00423  *
00424  * @v type              DHCPv6 packet type
00425  * @ret name            DHCPv6 packet type name
00426  */
00427 static __attribute__ (( unused )) const char *
00428 dhcpv6_type_name ( unsigned int type ) {
00429         static char buf[ 12 /* "UNKNOWN-xxx" + NUL */ ];
00430 
00431         switch ( type ) {
00432         case DHCPV6_SOLICIT:                    return "SOLICIT";
00433         case DHCPV6_ADVERTISE:                  return "ADVERTISE";
00434         case DHCPV6_REQUEST:                    return "REQUEST";
00435         case DHCPV6_REPLY:                      return "REPLY";
00436         case DHCPV6_INFORMATION_REQUEST:        return "INFORMATION-REQUEST";
00437         default:
00438                 snprintf ( buf, sizeof ( buf ), "UNKNOWN-%d", type );
00439                 return buf;
00440         }
00441 }
00442 
00443 /** A DHCPv6 session state */
00444 struct dhcpv6_session_state {
00445         /** Current transmitted packet type */
00446         uint8_t tx_type;
00447         /** Current expected received packet type */
00448         uint8_t rx_type;
00449         /** Flags */
00450         uint8_t flags;
00451         /** Next state (or NULL to terminate) */
00452         struct dhcpv6_session_state *next;
00453 };
00454 
00455 /** DHCPv6 session state flags */
00456 enum dhcpv6_session_state_flags {
00457         /** Include identity association within request */
00458         DHCPV6_TX_IA_NA = 0x01,
00459         /** Include leased IPv6 address within request */
00460         DHCPV6_TX_IAADDR = 0x02,
00461         /** Record received server ID */
00462         DHCPV6_RX_RECORD_SERVER_ID = 0x04,
00463         /** Record received IPv6 address */
00464         DHCPV6_RX_RECORD_IAADDR = 0x08,
00465 };
00466 
00467 /** DHCPv6 request state */
00468 static struct dhcpv6_session_state dhcpv6_request = {
00469         .tx_type = DHCPV6_REQUEST,
00470         .rx_type = DHCPV6_REPLY,
00471         .flags = ( DHCPV6_TX_IA_NA | DHCPV6_TX_IAADDR |
00472                    DHCPV6_RX_RECORD_IAADDR ),
00473         .next = NULL,
00474 };
00475 
00476 /** DHCPv6 solicitation state */
00477 static struct dhcpv6_session_state dhcpv6_solicit = {
00478         .tx_type = DHCPV6_SOLICIT,
00479         .rx_type = DHCPV6_ADVERTISE,
00480         .flags = ( DHCPV6_TX_IA_NA | DHCPV6_RX_RECORD_SERVER_ID |
00481                    DHCPV6_RX_RECORD_IAADDR ),
00482         .next = &dhcpv6_request,
00483 };
00484 
00485 /** DHCPv6 information request state */
00486 static struct dhcpv6_session_state dhcpv6_information_request = {
00487         .tx_type = DHCPV6_INFORMATION_REQUEST,
00488         .rx_type = DHCPV6_REPLY,
00489         .flags = 0,
00490         .next = NULL,
00491 };
00492 
00493 /** A DHCPv6 session */
00494 struct dhcpv6_session {
00495         /** Reference counter */
00496         struct refcnt refcnt;
00497         /** Job control interface */
00498         struct interface job;
00499         /** Data transfer interface */
00500         struct interface xfer;
00501 
00502         /** Network device being configured */
00503         struct net_device *netdev;
00504         /** Transaction ID */
00505         uint8_t xid[3];
00506         /** Identity association ID */
00507         uint32_t iaid;
00508         /** Start time (in ticks) */
00509         unsigned long start;
00510         /** Client DUID */
00511         struct dhcpv6_duid_uuid client_duid;
00512         /** Server DUID, if known */
00513         void *server_duid;
00514         /** Server DUID length */
00515         size_t server_duid_len;
00516         /** Leased IPv6 address */
00517         struct in6_addr lease;
00518 
00519         /** Retransmission timer */
00520         struct retry_timer timer;
00521 
00522         /** Current session state */
00523         struct dhcpv6_session_state *state;
00524         /** Current timeout status code */
00525         int rc;
00526 };
00527 
00528 /**
00529  * Free DHCPv6 session
00530  *
00531  * @v refcnt            Reference count
00532  */
00533 static void dhcpv6_free ( struct refcnt *refcnt ) {
00534         struct dhcpv6_session *dhcpv6 =
00535                 container_of ( refcnt, struct dhcpv6_session, refcnt );
00536 
00537         netdev_put ( dhcpv6->netdev );
00538         free ( dhcpv6->server_duid );
00539         free ( dhcpv6 );
00540 }
00541 
00542 /**
00543  * Terminate DHCPv6 session
00544  *
00545  * @v dhcpv6            DHCPv6 session
00546  * @v rc                Reason for close
00547  */
00548 static void dhcpv6_finished ( struct dhcpv6_session *dhcpv6, int rc ) {
00549 
00550         /* Stop timer */
00551         stop_timer ( &dhcpv6->timer );
00552 
00553         /* Shut down interfaces */
00554         intf_shutdown ( &dhcpv6->xfer, rc );
00555         intf_shutdown ( &dhcpv6->job, rc );
00556 }
00557 
00558 /**
00559  * Transition to new DHCPv6 session state
00560  *
00561  * @v dhcpv6            DHCPv6 session
00562  * @v state             New session state
00563  */
00564 static void dhcpv6_set_state ( struct dhcpv6_session *dhcpv6,
00565                                struct dhcpv6_session_state *state ) {
00566 
00567         DBGC ( dhcpv6, "DHCPv6 %s entering %s state\n", dhcpv6->netdev->name,
00568                dhcpv6_type_name ( state->tx_type ) );
00569 
00570         /* Record state */
00571         dhcpv6->state = state;
00572 
00573         /* Default to -ETIMEDOUT if no more specific error is recorded */
00574         dhcpv6->rc = -ETIMEDOUT;
00575 
00576         /* Start timer to trigger transmission */
00577         start_timer_nodelay ( &dhcpv6->timer );
00578 }
00579 
00580 /**
00581  * Get DHCPv6 user class
00582  *
00583  * @v data              Data buffer
00584  * @v len               Length of data buffer
00585  * @ret len             Length of user class
00586  */
00587 static size_t dhcpv6_user_class ( void *data, size_t len ) {
00588         static const char default_user_class[4] = { 'i', 'P', 'X', 'E' };
00589         int actual_len;
00590 
00591         /* Fetch user-class setting, if defined */
00592         actual_len = fetch_raw_setting ( NULL, &user_class_setting, data, len );
00593         if ( actual_len >= 0 )
00594                 return actual_len;
00595 
00596         /* Otherwise, use the default user class ("iPXE") */
00597         if ( len > sizeof ( default_user_class ) )
00598                 len = sizeof ( default_user_class );
00599         memcpy ( data, default_user_class, len );
00600         return sizeof ( default_user_class );
00601 }
00602 
00603 /**
00604  * Transmit current request
00605  *
00606  * @v dhcpv6            DHCPv6 session
00607  * @ret rc              Return status code
00608  */
00609 static int dhcpv6_tx ( struct dhcpv6_session *dhcpv6 ) {
00610         struct dhcpv6_duid_option *client_id;
00611         struct dhcpv6_duid_option *server_id;
00612         struct dhcpv6_ia_na_option *ia_na;
00613         struct dhcpv6_iaaddr_option *iaaddr;
00614         struct dhcpv6_user_class_option *user_class;
00615         struct dhcpv6_elapsed_time_option *elapsed;
00616         struct dhcpv6_header *dhcphdr;
00617         struct io_buffer *iobuf;
00618         void *options;
00619         size_t client_id_len;
00620         size_t server_id_len;
00621         size_t ia_na_len;
00622         size_t user_class_string_len;
00623         size_t user_class_len;
00624         size_t elapsed_len;
00625         size_t total_len;
00626         int rc;
00627 
00628         /* Calculate lengths */
00629         client_id_len = ( sizeof ( *client_id ) +
00630                           sizeof ( dhcpv6->client_duid ) );
00631         server_id_len = ( dhcpv6->server_duid ? ( sizeof ( *server_id ) +
00632                                                   dhcpv6->server_duid_len ) :0);
00633         if ( dhcpv6->state->flags & DHCPV6_TX_IA_NA ) {
00634                 ia_na_len = sizeof ( *ia_na );
00635                 if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR )
00636                         ia_na_len += sizeof ( *iaaddr );
00637         } else {
00638                 ia_na_len = 0;
00639         }
00640         user_class_string_len = dhcpv6_user_class ( NULL, 0 );
00641         user_class_len = ( sizeof ( *user_class ) +
00642                            sizeof ( user_class->user_class[0] ) +
00643                            user_class_string_len );
00644         elapsed_len = sizeof ( *elapsed );
00645         total_len = ( sizeof ( *dhcphdr ) + client_id_len + server_id_len +
00646                       ia_na_len + sizeof ( dhcpv6_request_options_data ) +
00647                       user_class_len + elapsed_len );
00648 
00649         /* Allocate packet */
00650         iobuf = xfer_alloc_iob ( &dhcpv6->xfer, total_len );
00651         if ( ! iobuf )
00652                 return -ENOMEM;
00653 
00654         /* Construct header */
00655         dhcphdr = iob_put ( iobuf, sizeof ( *dhcphdr ) );
00656         dhcphdr->type = dhcpv6->state->tx_type;
00657         memcpy ( dhcphdr->xid, dhcpv6->xid, sizeof ( dhcphdr->xid ) );
00658 
00659         /* Construct client identifier */
00660         client_id = iob_put ( iobuf, client_id_len );
00661         client_id->header.code = htons ( DHCPV6_CLIENT_ID );
00662         client_id->header.len = htons ( client_id_len -
00663                                         sizeof ( client_id->header ) );
00664         memcpy ( client_id->duid, &dhcpv6->client_duid,
00665                  sizeof ( dhcpv6->client_duid ) );
00666 
00667         /* Construct server identifier, if applicable */
00668         if ( server_id_len ) {
00669                 server_id = iob_put ( iobuf, server_id_len );
00670                 server_id->header.code = htons ( DHCPV6_SERVER_ID );
00671                 server_id->header.len = htons ( server_id_len -
00672                                                 sizeof ( server_id->header ) );
00673                 memcpy ( server_id->duid, dhcpv6->server_duid,
00674                          dhcpv6->server_duid_len );
00675         }
00676 
00677         /* Construct identity association, if applicable */
00678         if ( ia_na_len ) {
00679                 ia_na = iob_put ( iobuf, ia_na_len );
00680                 ia_na->header.code = htons ( DHCPV6_IA_NA );
00681                 ia_na->header.len = htons ( ia_na_len -
00682                                             sizeof ( ia_na->header ) );
00683                 ia_na->iaid = htonl ( dhcpv6->iaid );
00684                 ia_na->renew = htonl ( 0 );
00685                 ia_na->rebind = htonl ( 0 );
00686                 if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) {
00687                         iaaddr = ( ( void * ) ia_na->options );
00688                         iaaddr->header.code = htons ( DHCPV6_IAADDR );
00689                         iaaddr->header.len = htons ( sizeof ( *iaaddr ) -
00690                                                      sizeof ( iaaddr->header ));
00691                         memcpy ( &iaaddr->address, &dhcpv6->lease,
00692                                  sizeof ( iaaddr->address ) );
00693                         iaaddr->preferred = htonl ( 0 );
00694                         iaaddr->valid = htonl ( 0 );
00695                 }
00696         }
00697 
00698         /* Construct fixed request options */
00699         options = iob_put ( iobuf, sizeof ( dhcpv6_request_options_data ) );
00700         memcpy ( options, dhcpv6_request_options_data,
00701                  sizeof ( dhcpv6_request_options_data ) );
00702 
00703         /* Construct user class */
00704         user_class = iob_put ( iobuf, user_class_len );
00705         user_class->header.code = htons ( DHCPV6_USER_CLASS );
00706         user_class->header.len = htons ( user_class_len -
00707                                          sizeof ( user_class->header ) );
00708         user_class->user_class[0].len = htons ( user_class_string_len );
00709         dhcpv6_user_class ( user_class->user_class[0].string,
00710                             user_class_string_len );
00711 
00712         /* Construct elapsed time */
00713         elapsed = iob_put ( iobuf, elapsed_len );
00714         elapsed->header.code = htons ( DHCPV6_ELAPSED_TIME );
00715         elapsed->header.len = htons ( elapsed_len -
00716                                       sizeof ( elapsed->header ) );
00717         elapsed->elapsed = htons ( ( ( currticks() - dhcpv6->start ) * 100 ) /
00718                                    TICKS_PER_SEC );
00719 
00720         /* Sanity check */
00721         assert ( iob_len ( iobuf ) == total_len );
00722 
00723         /* Transmit packet */
00724         if ( ( rc = xfer_deliver_iob ( &dhcpv6->xfer, iobuf ) ) != 0 ) {
00725                 DBGC ( dhcpv6, "DHCPv6 %s could not transmit: %s\n",
00726                        dhcpv6->netdev->name, strerror ( rc ) );
00727                 return rc;
00728         }
00729 
00730         return 0;
00731 }
00732 
00733 /**
00734  * Handle timer expiry
00735  *
00736  * @v timer             Retransmission timer
00737  * @v fail              Failure indicator
00738  */
00739 static void dhcpv6_timer_expired ( struct retry_timer *timer, int fail ) {
00740         struct dhcpv6_session *dhcpv6 =
00741                 container_of ( timer, struct dhcpv6_session, timer );
00742 
00743         /* If we have failed, terminate DHCPv6 */
00744         if ( fail ) {
00745                 dhcpv6_finished ( dhcpv6, dhcpv6->rc );
00746                 return;
00747         }
00748 
00749         /* Restart timer */
00750         start_timer ( &dhcpv6->timer );
00751 
00752         /* (Re)transmit current request */
00753         dhcpv6_tx ( dhcpv6 );
00754 }
00755 
00756 /**
00757  * Receive new data
00758  *
00759  * @v dhcpv6            DHCPv6 session
00760  * @v iobuf             I/O buffer
00761  * @v meta              Data transfer metadata
00762  * @ret rc              Return status code
00763  */
00764 static int dhcpv6_rx ( struct dhcpv6_session *dhcpv6,
00765                        struct io_buffer *iobuf,
00766                        struct xfer_metadata *meta ) {
00767         struct settings *parent = netdev_settings ( dhcpv6->netdev );
00768         struct sockaddr_in6 *src = ( ( struct sockaddr_in6 * ) meta->src );
00769         struct dhcpv6_header *dhcphdr = iobuf->data;
00770         struct dhcpv6_option_list options;
00771         const union dhcpv6_any_option *option;
00772         int rc;
00773 
00774         /* Sanity checks */
00775         if ( iob_len ( iobuf ) < sizeof ( *dhcphdr ) ) {
00776                 DBGC ( dhcpv6, "DHCPv6 %s received packet too short (%zd "
00777                        "bytes, min %zd bytes)\n", dhcpv6->netdev->name,
00778                        iob_len ( iobuf ), sizeof ( *dhcphdr ) );
00779                 rc = -EINVAL;
00780                 goto done;
00781         }
00782         assert ( src != NULL );
00783         assert ( src->sin6_family == AF_INET6 );
00784         DBGC ( dhcpv6, "DHCPv6 %s received %s from %s\n",
00785                dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
00786                inet6_ntoa ( &src->sin6_addr ) );
00787 
00788         /* Construct option list */
00789         options.data = dhcphdr->options;
00790         options.len = ( iob_len ( iobuf ) -
00791                         offsetof ( typeof ( *dhcphdr ), options ) );
00792 
00793         /* Verify client identifier */
00794         if ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_CLIENT_ID,
00795                                         &dhcpv6->client_duid,
00796                                         sizeof ( dhcpv6->client_duid ) ) ) !=0){
00797                 DBGC ( dhcpv6, "DHCPv6 %s received %s without correct client "
00798                        "ID: %s\n", dhcpv6->netdev->name,
00799                        dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
00800                 goto done;
00801         }
00802 
00803         /* Verify server identifier, if applicable */
00804         if ( dhcpv6->server_duid &&
00805              ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_SERVER_ID,
00806                                           dhcpv6->server_duid,
00807                                           dhcpv6->server_duid_len ) ) != 0 ) ) {
00808                 DBGC ( dhcpv6, "DHCPv6 %s received %s without correct server "
00809                        "ID: %s\n", dhcpv6->netdev->name,
00810                        dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
00811                 goto done;
00812         }
00813 
00814         /* Check message type */
00815         if ( dhcphdr->type != dhcpv6->state->rx_type ) {
00816                 DBGC ( dhcpv6, "DHCPv6 %s received %s while expecting %s\n",
00817                        dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
00818                        dhcpv6_type_name ( dhcpv6->state->rx_type ) );
00819                 rc = -ENOTTY;
00820                 goto done;
00821         }
00822 
00823         /* Fetch status code, if present */
00824         if ( ( rc = dhcpv6_status_code ( &options ) ) != 0 ) {
00825                 DBGC ( dhcpv6, "DHCPv6 %s received %s with error status: %s\n",
00826                        dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
00827                        strerror ( rc ) );
00828                 /* This is plausibly the error we want to return */
00829                 dhcpv6->rc = rc;
00830                 goto done;
00831         }
00832 
00833         /* Record identity association address, if applicable */
00834         if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_IAADDR ) {
00835                 if ( ( rc = dhcpv6_iaaddr ( &options, dhcpv6->iaid,
00836                                             &dhcpv6->lease ) ) != 0 ) {
00837                         DBGC ( dhcpv6, "DHCPv6 %s received %s with unusable "
00838                                "IAADDR: %s\n", dhcpv6->netdev->name,
00839                                dhcpv6_type_name ( dhcphdr->type ),
00840                                strerror ( rc ) );
00841                         /* This is plausibly the error we want to return */
00842                         dhcpv6->rc = rc;
00843                         goto done;
00844                 }
00845                 DBGC ( dhcpv6, "DHCPv6 %s received %s is for %s\n",
00846                        dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
00847                        inet6_ntoa ( &dhcpv6->lease ) );
00848         }
00849 
00850         /* Record server ID, if applicable */
00851         if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_SERVER_ID ) {
00852                 assert ( dhcpv6->server_duid == NULL );
00853                 option = dhcpv6_option ( &options, DHCPV6_SERVER_ID );
00854                 if ( ! option ) {
00855                         DBGC ( dhcpv6, "DHCPv6 %s received %s missing server "
00856                                "ID\n", dhcpv6->netdev->name,
00857                                dhcpv6_type_name ( dhcphdr->type ) );
00858                         rc = -EINVAL;
00859                         goto done;
00860                 }
00861                 dhcpv6->server_duid_len = ntohs ( option->duid.header.len );
00862                 dhcpv6->server_duid = malloc ( dhcpv6->server_duid_len );
00863                 if ( ! dhcpv6->server_duid ) {
00864                         rc = -ENOMEM;
00865                         goto done;
00866                 }
00867                 memcpy ( dhcpv6->server_duid, option->duid.duid,
00868                          dhcpv6->server_duid_len );
00869         }
00870 
00871         /* Transition to next state, if applicable */
00872         if ( dhcpv6->state->next ) {
00873                 dhcpv6_set_state ( dhcpv6, dhcpv6->state->next );
00874                 rc = 0;
00875                 goto done;
00876         }
00877 
00878         /* Register settings */
00879         if ( ( rc = dhcpv6_register ( &dhcpv6->lease, &options,
00880                                       parent ) ) != 0 ) {
00881                 DBGC ( dhcpv6, "DHCPv6 %s could not register settings: %s\n",
00882                        dhcpv6->netdev->name, strerror ( rc ) );
00883                 goto done;
00884         }
00885 
00886         /* Mark as complete */
00887         dhcpv6_finished ( dhcpv6, 0 );
00888         DBGC ( dhcpv6, "DHCPv6 %s complete\n", dhcpv6->netdev->name );
00889 
00890  done:
00891         free_iob ( iobuf );
00892         return rc;
00893 }
00894 
00895 /** DHCPv6 job control interface operations */
00896 static struct interface_operation dhcpv6_job_op[] = {
00897         INTF_OP ( intf_close, struct dhcpv6_session *, dhcpv6_finished ),
00898 };
00899 
00900 /** DHCPv6 job control interface descriptor */
00901 static struct interface_descriptor dhcpv6_job_desc =
00902         INTF_DESC ( struct dhcpv6_session, job, dhcpv6_job_op );
00903 
00904 /** DHCPv6 data transfer interface operations */
00905 static struct interface_operation dhcpv6_xfer_op[] = {
00906         INTF_OP ( xfer_deliver, struct dhcpv6_session *, dhcpv6_rx ),
00907 };
00908 
00909 /** DHCPv6 data transfer interface descriptor */
00910 static struct interface_descriptor dhcpv6_xfer_desc =
00911         INTF_DESC ( struct dhcpv6_session, xfer, dhcpv6_xfer_op );
00912 
00913 /**
00914  * Start DHCPv6
00915  *
00916  * @v job               Job control interface
00917  * @v netdev            Network device
00918  * @v stateful          Perform stateful address autoconfiguration
00919  * @ret rc              Return status code
00920  */
00921 int start_dhcpv6 ( struct interface *job, struct net_device *netdev,
00922                    int stateful ) {
00923         struct ll_protocol *ll_protocol = netdev->ll_protocol;
00924         struct dhcpv6_session *dhcpv6;
00925         struct {
00926                 union {
00927                         struct sockaddr_in6 sin6;
00928                         struct sockaddr sa;
00929                 } client;
00930                 union {
00931                         struct sockaddr_in6 sin6;
00932                         struct sockaddr sa;
00933                 } server;
00934         } addresses;
00935         uint32_t xid;
00936         int len;
00937         int rc;
00938 
00939         /* Allocate and initialise structure */
00940         dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) );
00941         if ( ! dhcpv6 )
00942                 return -ENOMEM;
00943         ref_init ( &dhcpv6->refcnt, dhcpv6_free );
00944         intf_init ( &dhcpv6->job, &dhcpv6_job_desc, &dhcpv6->refcnt );
00945         intf_init ( &dhcpv6->xfer, &dhcpv6_xfer_desc, &dhcpv6->refcnt );
00946         dhcpv6->netdev = netdev_get ( netdev );
00947         xid = random();
00948         memcpy ( dhcpv6->xid, &xid, sizeof ( dhcpv6->xid ) );
00949         dhcpv6->start = currticks();
00950         timer_init ( &dhcpv6->timer, dhcpv6_timer_expired, &dhcpv6->refcnt );
00951 
00952         /* Construct client and server addresses */
00953         memset ( &addresses, 0, sizeof ( addresses ) );
00954         addresses.client.sin6.sin6_family = AF_INET6;
00955         addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT );
00956         addresses.server.sin6.sin6_family = AF_INET6;
00957         ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr );
00958         addresses.server.sin6.sin6_scope_id = netdev->index;
00959         addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT );
00960 
00961         /* Construct client DUID from system UUID */
00962         dhcpv6->client_duid.type = htons ( DHCPV6_DUID_UUID );
00963         if ( ( len = fetch_uuid_setting ( NULL, &uuid_setting,
00964                                           &dhcpv6->client_duid.uuid ) ) < 0 ) {
00965                 rc = len;
00966                 DBGC ( dhcpv6, "DHCPv6 %s could not create DUID-UUID: %s\n",
00967                        dhcpv6->netdev->name, strerror ( rc ) );
00968                 goto err_client_duid;
00969         }
00970 
00971         /* Construct IAID from link-layer address */
00972         dhcpv6->iaid = crc32_le ( 0, netdev->ll_addr, ll_protocol->ll_addr_len);
00973         DBGC ( dhcpv6, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6->netdev->name,
00974                dhcpv6->xid[0], dhcpv6->xid[1], dhcpv6->xid[2] );
00975 
00976         /* Enter initial state */
00977         dhcpv6_set_state ( dhcpv6, ( stateful ? &dhcpv6_solicit :
00978                                      &dhcpv6_information_request ) );
00979 
00980         /* Open socket */
00981         if ( ( rc = xfer_open_socket ( &dhcpv6->xfer, SOCK_DGRAM,
00982                                        &addresses.server.sa,
00983                                        &addresses.client.sa ) ) != 0 ) {
00984                 DBGC ( dhcpv6, "DHCPv6 %s could not open socket: %s\n",
00985                        dhcpv6->netdev->name, strerror ( rc ) );
00986                 goto err_open_socket;
00987         }
00988 
00989         /* Attach parent interface, mortalise self, and return */
00990         intf_plug_plug ( &dhcpv6->job, job );
00991         ref_put ( &dhcpv6->refcnt );
00992         return 0;
00993 
00994  err_open_socket:
00995         dhcpv6_finished ( dhcpv6, rc );
00996  err_client_duid:
00997         ref_put ( &dhcpv6->refcnt );
00998         return rc;
00999 }
01000 
01001 /** Boot filename setting */
01002 const struct setting filename6_setting __setting ( SETTING_BOOT, filename ) = {
01003         .name = "filename",
01004         .description = "Boot filename",
01005         .tag = DHCPV6_BOOTFILE_URL,
01006         .type = &setting_type_string,
01007         .scope = &dhcpv6_scope,
01008 };
01009 
01010 /** DNS search list setting */
01011 const struct setting dnssl6_setting __setting ( SETTING_IP_EXTRA, dnssl ) = {
01012         .name = "dnssl",
01013         .description = "DNS search list",
01014         .tag = DHCPV6_DOMAIN_LIST,
01015         .type = &setting_type_dnssl,
01016         .scope = &dhcpv6_scope,
01017 };