diff options
Diffstat (limited to 'qemu/roms/ipxe/src/net/udp/dhcpv6.c')
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/dhcpv6.c | 993 |
1 files changed, 0 insertions, 993 deletions
diff --git a/qemu/roms/ipxe/src/net/udp/dhcpv6.c b/qemu/roms/ipxe/src/net/udp/dhcpv6.c deleted file mode 100644 index a63543775..000000000 --- a/qemu/roms/ipxe/src/net/udp/dhcpv6.c +++ /dev/null @@ -1,993 +0,0 @@ -/* - * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * - * You can also choose to distribute this program under the terms of - * the Unmodified Binary Distribution Licence (as given in the file - * COPYING.UBDL), provided that you have satisfied its requirements. - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <errno.h> -#include <byteswap.h> -#include <ipxe/interface.h> -#include <ipxe/xfer.h> -#include <ipxe/iobuf.h> -#include <ipxe/open.h> -#include <ipxe/netdevice.h> -#include <ipxe/settings.h> -#include <ipxe/retry.h> -#include <ipxe/timer.h> -#include <ipxe/in.h> -#include <ipxe/crc32.h> -#include <ipxe/errortab.h> -#include <ipxe/ipv6.h> -#include <ipxe/dhcpv6.h> - -/** @file - * - * Dynamic Host Configuration Protocol for IPv6 - * - */ - -/* Disambiguate the various error causes */ -#define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL ) -#define EINFO_EPROTO_UNSPECFAIL \ - __einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" ) -#define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL ) -#define EINFO_EPROTO_NOADDRSAVAIL \ - __einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" ) -#define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING ) -#define EINFO_EPROTO_NOBINDING \ - __einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" ) -#define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK ) -#define EINFO_EPROTO_NOTONLINK \ - __einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" ) -#define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST ) -#define EINFO_EPROTO_USEMULTICAST \ - __einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" ) -#define EPROTO_STATUS( status ) \ - EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL, \ - EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING, \ - EPROTO_NOTONLINK, EPROTO_USEMULTICAST ) - -/** Human-readable error messages */ -struct errortab dhcpv6_errors[] __errortab = { - __einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL ), -}; - -/**************************************************************************** - * - * DHCPv6 option lists - * - */ - -/** A DHCPv6 option list */ -struct dhcpv6_option_list { - /** Data buffer */ - const void *data; - /** Length of data buffer */ - size_t len; -}; - -/** - * Find DHCPv6 option - * - * @v options DHCPv6 option list - * @v code Option code - * @ret option DHCPv6 option, or NULL if not found - */ -static const union dhcpv6_any_option * -dhcpv6_option ( struct dhcpv6_option_list *options, unsigned int code ) { - const union dhcpv6_any_option *option = options->data; - size_t remaining = options->len; - size_t data_len; - - /* Scan through list of options */ - while ( remaining >= sizeof ( option->header ) ) { - - /* Calculate and validate option length */ - remaining -= sizeof ( option->header ); - data_len = ntohs ( option->header.len ); - if ( data_len > remaining ) { - /* Malformed option list */ - return NULL; - } - - /* Return if we have found the specified option */ - if ( option->header.code == htons ( code ) ) - return option; - - /* Otherwise, move to the next option */ - option = ( ( ( void * ) option->header.data ) + data_len ); - remaining -= data_len; - } - - return NULL; -} - -/** - * Check DHCPv6 client or server identifier - * - * @v options DHCPv6 option list - * @v code Option code - * @v expected Expected value - * @v len Length of expected value - * @ret rc Return status code - */ -static int dhcpv6_check_duid ( struct dhcpv6_option_list *options, - unsigned int code, const void *expected, - size_t len ) { - const union dhcpv6_any_option *option; - const struct dhcpv6_duid_option *duid; - - /* Find option */ - option = dhcpv6_option ( options, code ); - if ( ! option ) - return -ENOENT; - duid = &option->duid; - - /* Check option length */ - if ( ntohs ( duid->header.len ) != len ) - return -EINVAL; - - /* Compare option value */ - if ( memcmp ( duid->duid, expected, len ) != 0 ) - return -EINVAL; - - return 0; -} - -/** - * Get DHCPv6 status code - * - * @v options DHCPv6 option list - * @ret rc Return status code - */ -static int dhcpv6_status_code ( struct dhcpv6_option_list *options ) { - const union dhcpv6_any_option *option; - const struct dhcpv6_status_code_option *status_code; - unsigned int status; - - /* Find status code option, if present */ - option = dhcpv6_option ( options, DHCPV6_STATUS_CODE ); - if ( ! option ) { - /* Omitted status code should be treated as "success" */ - return 0; - } - status_code = &option->status_code; - - /* Sanity check */ - if ( ntohs ( status_code->header.len ) < - ( sizeof ( *status_code ) - sizeof ( status_code->header ) ) ) { - return -EINVAL; - } - - /* Calculate iPXE error code from DHCPv6 status code */ - status = ntohs ( status_code->status ); - return ( status ? -EPROTO_STATUS ( status ) : 0 ); -} - -/** - * Get DHCPv6 identity association address - * - * @v options DHCPv6 option list - * @v iaid Identity association ID - * @v address IPv6 address to fill in - * @ret rc Return status code - */ -static int dhcpv6_iaaddr ( struct dhcpv6_option_list *options, uint32_t iaid, - struct in6_addr *address ) { - const union dhcpv6_any_option *option; - const struct dhcpv6_ia_na_option *ia_na; - const struct dhcpv6_iaaddr_option *iaaddr; - struct dhcpv6_option_list suboptions; - size_t len; - int rc; - - /* Find identity association option, if present */ - option = dhcpv6_option ( options, DHCPV6_IA_NA ); - if ( ! option ) - return -ENOENT; - ia_na = &option->ia_na; - - /* Sanity check */ - len = ntohs ( ia_na->header.len ); - if ( len < ( sizeof ( *ia_na ) - sizeof ( ia_na->header ) ) ) - return -EINVAL; - - /* Check identity association ID */ - if ( ia_na->iaid != htonl ( iaid ) ) - return -EINVAL; - - /* Construct IA_NA sub-options list */ - suboptions.data = ia_na->options; - suboptions.len = ( len + sizeof ( ia_na->header ) - - offsetof ( typeof ( *ia_na ), options ) ); - - /* Check IA_NA status code */ - if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 ) - return rc; - - /* Find identity association address, if present */ - option = dhcpv6_option ( &suboptions, DHCPV6_IAADDR ); - if ( ! option ) - return -ENOENT; - iaaddr = &option->iaaddr; - - /* Sanity check */ - len = ntohs ( iaaddr->header.len ); - if ( len < ( sizeof ( *iaaddr ) - sizeof ( iaaddr->header ) ) ) - return -EINVAL; - - /* Construct IAADDR sub-options list */ - suboptions.data = iaaddr->options; - suboptions.len = ( len + sizeof ( iaaddr->header ) - - offsetof ( typeof ( *iaaddr ), options ) ); - - /* Check IAADDR status code */ - if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 ) - return rc; - - /* Extract IPv6 address */ - memcpy ( address, &iaaddr->address, sizeof ( *address ) ); - - return 0; -} - -/**************************************************************************** - * - * DHCPv6 settings blocks - * - */ - -/** A DHCPv6 settings block */ -struct dhcpv6_settings { - /** Reference count */ - struct refcnt refcnt; - /** Settings block */ - struct settings settings; - /** Option list */ - struct dhcpv6_option_list options; -}; - -/** - * Check applicability of DHCPv6 setting - * - * @v settings Settings block - * @v setting Setting - * @ret applies Setting applies within this settings block - */ -static int dhcpv6_applies ( struct settings *settings __unused, - const struct setting *setting ) { - - return ( setting->scope == &ipv6_scope ); -} - -/** - * Fetch value of DHCPv6 setting - * - * @v settings Settings block - * @v setting Setting to fetch - * @v data Buffer to fill with setting data - * @v len Length of buffer - * @ret len Length of setting data, or negative error - */ -static int dhcpv6_fetch ( struct settings *settings, - struct setting *setting, - void *data, size_t len ) { - struct dhcpv6_settings *dhcpv6set = - container_of ( settings, struct dhcpv6_settings, settings ); - const union dhcpv6_any_option *option; - size_t option_len; - - /* Find option */ - option = dhcpv6_option ( &dhcpv6set->options, setting->tag ); - if ( ! option ) - return -ENOENT; - - /* Copy option to data buffer */ - option_len = ntohs ( option->header.len ); - if ( len > option_len ) - len = option_len; - memcpy ( data, option->header.data, len ); - return option_len; -} - -/** DHCPv6 settings operations */ -static struct settings_operations dhcpv6_settings_operations = { - .applies = dhcpv6_applies, - .fetch = dhcpv6_fetch, -}; - -/** - * Register DHCPv6 options as network device settings - * - * @v options DHCPv6 option list - * @v parent Parent settings block - * @ret rc Return status code - */ -static int dhcpv6_register ( struct dhcpv6_option_list *options, - struct settings *parent ) { - struct dhcpv6_settings *dhcpv6set; - void *data; - size_t len; - int rc; - - /* Allocate and initialise structure */ - dhcpv6set = zalloc ( sizeof ( *dhcpv6set ) + options->len ); - if ( ! dhcpv6set ) { - rc = -ENOMEM; - goto err_alloc; - } - ref_init ( &dhcpv6set->refcnt, NULL ); - settings_init ( &dhcpv6set->settings, &dhcpv6_settings_operations, - &dhcpv6set->refcnt, &ipv6_scope ); - data = ( ( ( void * ) dhcpv6set ) + sizeof ( *dhcpv6set ) ); - len = options->len; - memcpy ( data, options->data, len ); - dhcpv6set->options.data = data; - dhcpv6set->options.len = len; - - /* Register settings */ - if ( ( rc = register_settings ( &dhcpv6set->settings, parent, - DHCPV6_SETTINGS_NAME ) ) != 0 ) - goto err_register; - - err_register: - ref_put ( &dhcpv6set->refcnt ); - err_alloc: - return rc; -} - -/**************************************************************************** - * - * DHCPv6 protocol - * - */ - -/** Options to be requested */ -static uint16_t dhcpv6_requested_options[] = { - htons ( DHCPV6_DNS_SERVERS ), htons ( DHCPV6_DOMAIN_LIST ), - htons ( DHCPV6_BOOTFILE_URL ), htons ( DHCPV6_BOOTFILE_PARAM ), -}; - -/** - * Name a DHCPv6 packet type - * - * @v type DHCPv6 packet type - * @ret name DHCPv6 packet type name - */ -static __attribute__ (( unused )) const char * -dhcpv6_type_name ( unsigned int type ) { - static char buf[ 12 /* "UNKNOWN-xxx" + NUL */ ]; - - switch ( type ) { - case DHCPV6_SOLICIT: return "SOLICIT"; - case DHCPV6_ADVERTISE: return "ADVERTISE"; - case DHCPV6_REQUEST: return "REQUEST"; - case DHCPV6_REPLY: return "REPLY"; - case DHCPV6_INFORMATION_REQUEST: return "INFORMATION-REQUEST"; - default: - snprintf ( buf, sizeof ( buf ), "UNKNOWN-%d", type ); - return buf; - } -} - -/** A DHCPv6 session state */ -struct dhcpv6_session_state { - /** Current transmitted packet type */ - uint8_t tx_type; - /** Current expected received packet type */ - uint8_t rx_type; - /** Flags */ - uint8_t flags; - /** Next state (or NULL to terminate) */ - struct dhcpv6_session_state *next; -}; - -/** DHCPv6 session state flags */ -enum dhcpv6_session_state_flags { - /** Include identity association within request */ - DHCPV6_TX_IA_NA = 0x01, - /** Include leased IPv6 address within request */ - DHCPV6_TX_IAADDR = 0x02, - /** Record received server ID */ - DHCPV6_RX_RECORD_SERVER_ID = 0x04, - /** Record received IPv6 address */ - DHCPV6_RX_RECORD_IAADDR = 0x08, - /** Apply received IPv6 address */ - DHCPV6_RX_APPLY_IAADDR = 0x10, -}; - -/** DHCPv6 request state */ -static struct dhcpv6_session_state dhcpv6_request = { - .tx_type = DHCPV6_REQUEST, - .rx_type = DHCPV6_REPLY, - .flags = ( DHCPV6_TX_IA_NA | DHCPV6_TX_IAADDR | - DHCPV6_RX_RECORD_IAADDR | DHCPV6_RX_APPLY_IAADDR ), - .next = NULL, -}; - -/** DHCPv6 solicitation state */ -static struct dhcpv6_session_state dhcpv6_solicit = { - .tx_type = DHCPV6_SOLICIT, - .rx_type = DHCPV6_ADVERTISE, - .flags = ( DHCPV6_TX_IA_NA | DHCPV6_RX_RECORD_SERVER_ID | - DHCPV6_RX_RECORD_IAADDR ), - .next = &dhcpv6_request, -}; - -/** DHCPv6 information request state */ -static struct dhcpv6_session_state dhcpv6_information_request = { - .tx_type = DHCPV6_INFORMATION_REQUEST, - .rx_type = DHCPV6_REPLY, - .flags = 0, - .next = NULL, -}; - -/** A DHCPv6 session */ -struct dhcpv6_session { - /** Reference counter */ - struct refcnt refcnt; - /** Job control interface */ - struct interface job; - /** Data transfer interface */ - struct interface xfer; - - /** Network device being configured */ - struct net_device *netdev; - /** Transaction ID */ - uint8_t xid[3]; - /** Identity association ID */ - uint32_t iaid; - /** Start time (in ticks) */ - unsigned long start; - /** Client DUID */ - struct dhcpv6_duid_uuid client_duid; - /** Server DUID, if known */ - void *server_duid; - /** Server DUID length */ - size_t server_duid_len; - /** Leased IPv6 address */ - struct in6_addr lease; - - /** Retransmission timer */ - struct retry_timer timer; - - /** Current session state */ - struct dhcpv6_session_state *state; - /** Current timeout status code */ - int rc; -}; - -/** - * Free DHCPv6 session - * - * @v refcnt Reference count - */ -static void dhcpv6_free ( struct refcnt *refcnt ) { - struct dhcpv6_session *dhcpv6 = - container_of ( refcnt, struct dhcpv6_session, refcnt ); - - netdev_put ( dhcpv6->netdev ); - free ( dhcpv6->server_duid ); - free ( dhcpv6 ); -} - -/** - * Terminate DHCPv6 session - * - * @v dhcpv6 DHCPv6 session - * @v rc Reason for close - */ -static void dhcpv6_finished ( struct dhcpv6_session *dhcpv6, int rc ) { - - /* Stop timer */ - stop_timer ( &dhcpv6->timer ); - - /* Shut down interfaces */ - intf_shutdown ( &dhcpv6->xfer, rc ); - intf_shutdown ( &dhcpv6->job, rc ); -} - -/** - * Transition to new DHCPv6 session state - * - * @v dhcpv6 DHCPv6 session - * @v state New session state - */ -static void dhcpv6_set_state ( struct dhcpv6_session *dhcpv6, - struct dhcpv6_session_state *state ) { - - DBGC ( dhcpv6, "DHCPv6 %s entering %s state\n", dhcpv6->netdev->name, - dhcpv6_type_name ( state->tx_type ) ); - - /* Record state */ - dhcpv6->state = state; - - /* Default to -ETIMEDOUT if no more specific error is recorded */ - dhcpv6->rc = -ETIMEDOUT; - - /* Start timer to trigger transmission */ - start_timer_nodelay ( &dhcpv6->timer ); -} - -/** - * Get DHCPv6 user class - * - * @v data Data buffer - * @v len Length of data buffer - * @ret len Length of user class - */ -static size_t dhcpv6_user_class ( void *data, size_t len ) { - static const char default_user_class[4] = { 'i', 'P', 'X', 'E' }; - int actual_len; - - /* Fetch user-class setting, if defined */ - actual_len = fetch_raw_setting ( NULL, &user_class_setting, data, len ); - if ( actual_len >= 0 ) - return actual_len; - - /* Otherwise, use the default user class ("iPXE") */ - if ( len > sizeof ( default_user_class ) ) - len = sizeof ( default_user_class ); - memcpy ( data, default_user_class, len ); - return sizeof ( default_user_class ); -} - -/** - * Transmit current request - * - * @v dhcpv6 DHCPv6 session - * @ret rc Return status code - */ -static int dhcpv6_tx ( struct dhcpv6_session *dhcpv6 ) { - struct dhcpv6_duid_option *client_id; - struct dhcpv6_duid_option *server_id; - struct dhcpv6_ia_na_option *ia_na; - struct dhcpv6_iaaddr_option *iaaddr; - struct dhcpv6_option_request_option *option_request; - struct dhcpv6_user_class_option *user_class; - struct dhcpv6_elapsed_time_option *elapsed; - struct dhcpv6_header *dhcphdr; - struct io_buffer *iobuf; - size_t client_id_len; - size_t server_id_len; - size_t ia_na_len; - size_t option_request_len; - size_t user_class_string_len; - size_t user_class_len; - size_t elapsed_len; - size_t total_len; - int rc; - - /* Calculate lengths */ - client_id_len = ( sizeof ( *client_id ) + - sizeof ( dhcpv6->client_duid ) ); - server_id_len = ( dhcpv6->server_duid ? ( sizeof ( *server_id ) + - dhcpv6->server_duid_len ) :0); - if ( dhcpv6->state->flags & DHCPV6_TX_IA_NA ) { - ia_na_len = sizeof ( *ia_na ); - if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) - ia_na_len += sizeof ( *iaaddr ); - } else { - ia_na_len = 0; - } - option_request_len = ( sizeof ( *option_request ) + - sizeof ( dhcpv6_requested_options ) ); - user_class_string_len = dhcpv6_user_class ( NULL, 0 ); - user_class_len = ( sizeof ( *user_class ) + - sizeof ( user_class->user_class[0] ) + - user_class_string_len ); - elapsed_len = sizeof ( *elapsed ); - total_len = ( sizeof ( *dhcphdr ) + client_id_len + server_id_len + - ia_na_len + option_request_len + user_class_len + - elapsed_len ); - - /* Allocate packet */ - iobuf = xfer_alloc_iob ( &dhcpv6->xfer, total_len ); - if ( ! iobuf ) - return -ENOMEM; - - /* Construct header */ - dhcphdr = iob_put ( iobuf, sizeof ( *dhcphdr ) ); - dhcphdr->type = dhcpv6->state->tx_type; - memcpy ( dhcphdr->xid, dhcpv6->xid, sizeof ( dhcphdr->xid ) ); - - /* Construct client identifier */ - client_id = iob_put ( iobuf, client_id_len ); - client_id->header.code = htons ( DHCPV6_CLIENT_ID ); - client_id->header.len = htons ( client_id_len - - sizeof ( client_id->header ) ); - memcpy ( client_id->duid, &dhcpv6->client_duid, - sizeof ( dhcpv6->client_duid ) ); - - /* Construct server identifier, if applicable */ - if ( server_id_len ) { - server_id = iob_put ( iobuf, server_id_len ); - server_id->header.code = htons ( DHCPV6_SERVER_ID ); - server_id->header.len = htons ( server_id_len - - sizeof ( server_id->header ) ); - memcpy ( server_id->duid, dhcpv6->server_duid, - dhcpv6->server_duid_len ); - } - - /* Construct identity association, if applicable */ - if ( ia_na_len ) { - ia_na = iob_put ( iobuf, ia_na_len ); - ia_na->header.code = htons ( DHCPV6_IA_NA ); - ia_na->header.len = htons ( ia_na_len - - sizeof ( ia_na->header ) ); - ia_na->iaid = htonl ( dhcpv6->iaid ); - ia_na->renew = htonl ( 0 ); - ia_na->rebind = htonl ( 0 ); - if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) { - iaaddr = ( ( void * ) ia_na->options ); - iaaddr->header.code = htons ( DHCPV6_IAADDR ); - iaaddr->header.len = htons ( sizeof ( *iaaddr ) - - sizeof ( iaaddr->header )); - memcpy ( &iaaddr->address, &dhcpv6->lease, - sizeof ( iaaddr->address ) ); - iaaddr->preferred = htonl ( 0 ); - iaaddr->valid = htonl ( 0 ); - } - } - - /* Construct option request */ - option_request = iob_put ( iobuf, option_request_len ); - option_request->header.code = htons ( DHCPV6_OPTION_REQUEST ); - option_request->header.len = htons ( option_request_len - - sizeof ( option_request->header )); - memcpy ( option_request->requested, dhcpv6_requested_options, - sizeof ( dhcpv6_requested_options ) ); - - /* Construct user class */ - user_class = iob_put ( iobuf, user_class_len ); - user_class->header.code = htons ( DHCPV6_USER_CLASS ); - user_class->header.len = htons ( user_class_len - - sizeof ( user_class->header ) ); - user_class->user_class[0].len = htons ( user_class_string_len ); - dhcpv6_user_class ( user_class->user_class[0].string, - user_class_string_len ); - - /* Construct elapsed time */ - elapsed = iob_put ( iobuf, elapsed_len ); - elapsed->header.code = htons ( DHCPV6_ELAPSED_TIME ); - elapsed->header.len = htons ( elapsed_len - - sizeof ( elapsed->header ) ); - elapsed->elapsed = htons ( ( ( currticks() - dhcpv6->start ) * 100 ) / - TICKS_PER_SEC ); - - /* Sanity check */ - assert ( iob_len ( iobuf ) == total_len ); - - /* Transmit packet */ - if ( ( rc = xfer_deliver_iob ( &dhcpv6->xfer, iobuf ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s could not transmit: %s\n", - dhcpv6->netdev->name, strerror ( rc ) ); - return rc; - } - - return 0; -} - -/** - * Handle timer expiry - * - * @v timer Retransmission timer - * @v fail Failure indicator - */ -static void dhcpv6_timer_expired ( struct retry_timer *timer, int fail ) { - struct dhcpv6_session *dhcpv6 = - container_of ( timer, struct dhcpv6_session, timer ); - - /* If we have failed, terminate DHCPv6 */ - if ( fail ) { - dhcpv6_finished ( dhcpv6, dhcpv6->rc ); - return; - } - - /* Restart timer */ - start_timer ( &dhcpv6->timer ); - - /* (Re)transmit current request */ - dhcpv6_tx ( dhcpv6 ); -} - -/** - * Receive new data - * - * @v dhcpv6 DHCPv6 session - * @v iobuf I/O buffer - * @v meta Data transfer metadata - * @ret rc Return status code - */ -static int dhcpv6_rx ( struct dhcpv6_session *dhcpv6, - struct io_buffer *iobuf, - struct xfer_metadata *meta ) { - struct settings *parent = netdev_settings ( dhcpv6->netdev ); - struct sockaddr_in6 *src = ( ( struct sockaddr_in6 * ) meta->src ); - struct dhcpv6_header *dhcphdr = iobuf->data; - struct dhcpv6_option_list options; - const union dhcpv6_any_option *option; - int rc; - - /* Sanity checks */ - if ( iob_len ( iobuf ) < sizeof ( *dhcphdr ) ) { - DBGC ( dhcpv6, "DHCPv6 %s received packet too short (%zd " - "bytes, min %zd bytes)\n", dhcpv6->netdev->name, - iob_len ( iobuf ), sizeof ( *dhcphdr ) ); - rc = -EINVAL; - goto done; - } - assert ( src != NULL ); - assert ( src->sin6_family == AF_INET6 ); - DBGC ( dhcpv6, "DHCPv6 %s received %s from %s\n", - dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), - inet6_ntoa ( &src->sin6_addr ) ); - - /* Construct option list */ - options.data = dhcphdr->options; - options.len = ( iob_len ( iobuf ) - - offsetof ( typeof ( *dhcphdr ), options ) ); - - /* Verify client identifier */ - if ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_CLIENT_ID, - &dhcpv6->client_duid, - sizeof ( dhcpv6->client_duid ) ) ) !=0){ - DBGC ( dhcpv6, "DHCPv6 %s received %s without correct client " - "ID: %s\n", dhcpv6->netdev->name, - dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) ); - goto done; - } - - /* Verify server identifier, if applicable */ - if ( dhcpv6->server_duid && - ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_SERVER_ID, - dhcpv6->server_duid, - dhcpv6->server_duid_len ) ) != 0 ) ) { - DBGC ( dhcpv6, "DHCPv6 %s received %s without correct server " - "ID: %s\n", dhcpv6->netdev->name, - dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) ); - goto done; - } - - /* Check message type */ - if ( dhcphdr->type != dhcpv6->state->rx_type ) { - DBGC ( dhcpv6, "DHCPv6 %s received %s while expecting %s\n", - dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), - dhcpv6_type_name ( dhcpv6->state->rx_type ) ); - rc = -ENOTTY; - goto done; - } - - /* Fetch status code, if present */ - if ( ( rc = dhcpv6_status_code ( &options ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s received %s with error status: %s\n", - dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), - strerror ( rc ) ); - /* This is plausibly the error we want to return */ - dhcpv6->rc = rc; - goto done; - } - - /* Record identity association address, if applicable */ - if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_IAADDR ) { - if ( ( rc = dhcpv6_iaaddr ( &options, dhcpv6->iaid, - &dhcpv6->lease ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s received %s with unusable " - "IAADDR: %s\n", dhcpv6->netdev->name, - dhcpv6_type_name ( dhcphdr->type ), - strerror ( rc ) ); - /* This is plausibly the error we want to return */ - dhcpv6->rc = rc; - goto done; - } - DBGC ( dhcpv6, "DHCPv6 %s received %s is for %s\n", - dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), - inet6_ntoa ( &dhcpv6->lease ) ); - } - - /* Record server ID, if applicable */ - if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_SERVER_ID ) { - assert ( dhcpv6->server_duid == NULL ); - option = dhcpv6_option ( &options, DHCPV6_SERVER_ID ); - if ( ! option ) { - DBGC ( dhcpv6, "DHCPv6 %s received %s missing server " - "ID\n", dhcpv6->netdev->name, - dhcpv6_type_name ( dhcphdr->type ) ); - rc = -EINVAL; - goto done; - } - dhcpv6->server_duid_len = ntohs ( option->duid.header.len ); - dhcpv6->server_duid = malloc ( dhcpv6->server_duid_len ); - if ( ! dhcpv6->server_duid ) { - rc = -ENOMEM; - goto done; - } - memcpy ( dhcpv6->server_duid, option->duid.duid, - dhcpv6->server_duid_len ); - } - - /* Apply identity association address, if applicable */ - if ( dhcpv6->state->flags & DHCPV6_RX_APPLY_IAADDR ) { - if ( ( rc = ipv6_set_address ( dhcpv6->netdev, - &dhcpv6->lease ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s could not apply %s: %s\n", - dhcpv6->netdev->name, - inet6_ntoa ( &dhcpv6->lease ), strerror ( rc ) ); - /* This is plausibly the error we want to return */ - dhcpv6->rc = rc; - goto done; - } - } - - /* Transition to next state or complete DHCPv6, as applicable */ - if ( dhcpv6->state->next ) { - - /* Transition to next state */ - dhcpv6_set_state ( dhcpv6, dhcpv6->state->next ); - rc = 0; - - } else { - - /* Register settings */ - if ( ( rc = dhcpv6_register ( &options, parent ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s could not register " - "settings: %s\n", dhcpv6->netdev->name, - strerror ( rc ) ); - goto done; - } - - /* Mark as complete */ - dhcpv6_finished ( dhcpv6, 0 ); - DBGC ( dhcpv6, "DHCPv6 %s complete\n", dhcpv6->netdev->name ); - } - - done: - free_iob ( iobuf ); - return rc; -} - -/** DHCPv6 job control interface operations */ -static struct interface_operation dhcpv6_job_op[] = { - INTF_OP ( intf_close, struct dhcpv6_session *, dhcpv6_finished ), -}; - -/** DHCPv6 job control interface descriptor */ -static struct interface_descriptor dhcpv6_job_desc = - INTF_DESC ( struct dhcpv6_session, job, dhcpv6_job_op ); - -/** DHCPv6 data transfer interface operations */ -static struct interface_operation dhcpv6_xfer_op[] = { - INTF_OP ( xfer_deliver, struct dhcpv6_session *, dhcpv6_rx ), -}; - -/** DHCPv6 data transfer interface descriptor */ -static struct interface_descriptor dhcpv6_xfer_desc = - INTF_DESC ( struct dhcpv6_session, xfer, dhcpv6_xfer_op ); - -/** - * Start DHCPv6 - * - * @v job Job control interface - * @v netdev Network device - * @v stateful Perform stateful address autoconfiguration - * @ret rc Return status code - */ -int start_dhcpv6 ( struct interface *job, struct net_device *netdev, - int stateful ) { - struct ll_protocol *ll_protocol = netdev->ll_protocol; - struct dhcpv6_session *dhcpv6; - struct { - union { - struct sockaddr_in6 sin6; - struct sockaddr sa; - } client; - union { - struct sockaddr_in6 sin6; - struct sockaddr sa; - } server; - } addresses; - uint32_t xid; - int len; - int rc; - - /* Allocate and initialise structure */ - dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) ); - if ( ! dhcpv6 ) - return -ENOMEM; - ref_init ( &dhcpv6->refcnt, dhcpv6_free ); - intf_init ( &dhcpv6->job, &dhcpv6_job_desc, &dhcpv6->refcnt ); - intf_init ( &dhcpv6->xfer, &dhcpv6_xfer_desc, &dhcpv6->refcnt ); - dhcpv6->netdev = netdev_get ( netdev ); - xid = random(); - memcpy ( dhcpv6->xid, &xid, sizeof ( dhcpv6->xid ) ); - dhcpv6->start = currticks(); - timer_init ( &dhcpv6->timer, dhcpv6_timer_expired, &dhcpv6->refcnt ); - - /* Construct client and server addresses */ - memset ( &addresses, 0, sizeof ( addresses ) ); - addresses.client.sin6.sin6_family = AF_INET6; - addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT ); - addresses.server.sin6.sin6_family = AF_INET6; - ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr ); - addresses.server.sin6.sin6_scope_id = netdev->index; - addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT ); - - /* Construct client DUID from system UUID */ - dhcpv6->client_duid.type = htons ( DHCPV6_DUID_UUID ); - if ( ( len = fetch_uuid_setting ( NULL, &uuid_setting, - &dhcpv6->client_duid.uuid ) ) < 0 ) { - rc = len; - DBGC ( dhcpv6, "DHCPv6 %s could not create DUID-UUID: %s\n", - dhcpv6->netdev->name, strerror ( rc ) ); - goto err_client_duid; - } - - /* Construct IAID from link-layer address */ - dhcpv6->iaid = crc32_le ( 0, netdev->ll_addr, ll_protocol->ll_addr_len); - DBGC ( dhcpv6, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6->netdev->name, - dhcpv6->xid[0], dhcpv6->xid[1], dhcpv6->xid[2] ); - - /* Enter initial state */ - dhcpv6_set_state ( dhcpv6, ( stateful ? &dhcpv6_solicit : - &dhcpv6_information_request ) ); - - /* Open socket */ - if ( ( rc = xfer_open_socket ( &dhcpv6->xfer, SOCK_DGRAM, - &addresses.server.sa, - &addresses.client.sa ) ) != 0 ) { - DBGC ( dhcpv6, "DHCPv6 %s could not open socket: %s\n", - dhcpv6->netdev->name, strerror ( rc ) ); - goto err_open_socket; - } - - /* Attach parent interface, mortalise self, and return */ - intf_plug_plug ( &dhcpv6->job, job ); - ref_put ( &dhcpv6->refcnt ); - return 0; - - err_open_socket: - dhcpv6_finished ( dhcpv6, rc ); - err_client_duid: - ref_put ( &dhcpv6->refcnt ); - return rc; -} - -/** Boot filename setting */ -const struct setting filename6_setting __setting ( SETTING_BOOT, filename ) = { - .name = "filename", - .description = "Boot filename", - .tag = DHCPV6_BOOTFILE_URL, - .type = &setting_type_string, - .scope = &ipv6_scope, -}; - -/** DNS search list setting */ -const struct setting dnssl6_setting __setting ( SETTING_IP_EXTRA, dnssl ) = { - .name = "dnssl", - .description = "DNS search list", - .tag = DHCPV6_DOMAIN_LIST, - .type = &setting_type_dnssl, - .scope = &ipv6_scope, -}; |