mirror of https://github.com/ipxe/ipxe.git
[dhcpv6] Add basic support for stateful and stateless DHCPv6
Add support for the stateful and stateless variants of the DHCPv6 protocol. The resulting settings block is registered as "net<x>.dhcpv6", and DHCPv6 options can be obtained using e.g. "${net0.dhcpv6/23:ipv6}" to obtain the IPv6 DNS server address. IPv6 addresses obtained via stateful DHCPv6 are not yet applied to the network device. Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/17/head
parent
c1570d3dfb
commit
2fa34085e2
|
@ -0,0 +1,215 @@
|
|||
#ifndef _IPXE_DHCPV6_H
|
||||
#define _IPXE_DHCPV6_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Dynamic Host Configuration Protocol for IPv6
|
||||
*
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ipxe/in.h>
|
||||
|
||||
/** DHCPv6 server port */
|
||||
#define DHCPV6_SERVER_PORT 547
|
||||
|
||||
/** DHCPv6 client port */
|
||||
#define DHCPV6_CLIENT_PORT 546
|
||||
|
||||
/**
|
||||
* A DHCPv6 option
|
||||
*
|
||||
*/
|
||||
struct dhcpv6_option {
|
||||
/** Code */
|
||||
uint16_t code;
|
||||
/** Length of the data field */
|
||||
uint16_t len;
|
||||
/** Data */
|
||||
uint8_t data[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCP unique identifier based on link-layer address (DUID-LL) */
|
||||
struct dhcpv6_duid_ll {
|
||||
/** Type */
|
||||
uint16_t type;
|
||||
/** Hardware type */
|
||||
uint16_t htype;
|
||||
/** Link-layer address */
|
||||
uint8_t ll_addr[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCP unique identifier based on link-layer address (DUID-LL) */
|
||||
#define DHCPV6_DUID_LL 3
|
||||
|
||||
/** DHCPv6 client or server identifier option */
|
||||
struct dhcpv6_duid_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** DHCP unique identifier (DUID) */
|
||||
uint8_t duid[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 client identifier option */
|
||||
#define DHCPV6_CLIENT_ID 1
|
||||
|
||||
/** DHCPv6 server identifier option */
|
||||
#define DHCPV6_SERVER_ID 2
|
||||
|
||||
/** DHCPv6 identity association for non-temporary address (IA_NA) option */
|
||||
struct dhcpv6_ia_na_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** Identity association identifier (IAID) */
|
||||
uint32_t iaid;
|
||||
/** Renew time (in seconds) */
|
||||
uint32_t renew;
|
||||
/** Rebind time (in seconds) */
|
||||
uint32_t rebind;
|
||||
/** IA_NA options */
|
||||
struct dhcpv6_option options[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 identity association for non-temporary address (IA_NA) option */
|
||||
#define DHCPV6_IA_NA 3
|
||||
|
||||
/** DHCPv6 identity association address (IAADDR) option */
|
||||
struct dhcpv6_iaaddr_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** IPv6 address */
|
||||
struct in6_addr address;
|
||||
/** Preferred lifetime (in seconds) */
|
||||
uint32_t preferred;
|
||||
/** Valid lifetime (in seconds) */
|
||||
uint32_t valid;
|
||||
/** IAADDR options */
|
||||
struct dhcpv6_option options[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 identity association address (IAADDR) option */
|
||||
#define DHCPV6_IAADDR 5
|
||||
|
||||
/** DHCPv6 option request option */
|
||||
struct dhcpv6_option_request_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** Requested options */
|
||||
uint16_t requested[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 option request option */
|
||||
#define DHCPV6_OPTION_REQUEST 6
|
||||
|
||||
/** DHCPv6 elapsed time option */
|
||||
struct dhcpv6_elapsed_time_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** Elapsed time, in centiseconds */
|
||||
uint16_t elapsed;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 elapsed time option */
|
||||
#define DHCPV6_ELAPSED_TIME 8
|
||||
|
||||
/** DHCPv6 status code option */
|
||||
struct dhcpv6_status_code_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** Status code */
|
||||
uint16_t status;
|
||||
/** Status message */
|
||||
char message[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 status code option */
|
||||
#define DHCPV6_STATUS_CODE 13
|
||||
|
||||
/** DHCPv6 user class */
|
||||
struct dhcpv6_user_class {
|
||||
/** Length */
|
||||
uint16_t len;
|
||||
/** User class string */
|
||||
char string[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 user class option */
|
||||
struct dhcpv6_user_class_option {
|
||||
/** Option header */
|
||||
struct dhcpv6_option header;
|
||||
/** User class */
|
||||
struct dhcpv6_user_class user_class[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 user class option */
|
||||
#define DHCPV6_USER_CLASS 15
|
||||
|
||||
/** DHCPv6 DNS recursive name server option */
|
||||
#define DHCPV6_DNS_SERVER 23
|
||||
|
||||
/** DHCPv6 domain search list option */
|
||||
#define DHCPV6_DOMAIN_SEARCH 24
|
||||
|
||||
/**
|
||||
* Any DHCPv6 option
|
||||
*
|
||||
*/
|
||||
union dhcpv6_any_option {
|
||||
struct dhcpv6_option header;
|
||||
struct dhcpv6_duid_option duid;
|
||||
struct dhcpv6_ia_na_option ia_na;
|
||||
struct dhcpv6_iaaddr_option iaaddr;
|
||||
struct dhcpv6_option_request_option option_request;
|
||||
struct dhcpv6_elapsed_time_option elapsed_time;
|
||||
struct dhcpv6_status_code_option status_code;
|
||||
struct dhcpv6_user_class_option user_class;
|
||||
};
|
||||
|
||||
/**
|
||||
* A DHCPv6 header
|
||||
*
|
||||
*/
|
||||
struct dhcpv6_header {
|
||||
/** Message type */
|
||||
uint8_t type;
|
||||
/** Transaction ID */
|
||||
uint8_t xid[3];
|
||||
/** Options */
|
||||
struct dhcpv6_option options[0];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** DHCPv6 solicitation */
|
||||
#define DHCPV6_SOLICIT 1
|
||||
|
||||
/** DHCPv6 advertisement */
|
||||
#define DHCPV6_ADVERTISE 2
|
||||
|
||||
/** DHCPv6 request */
|
||||
#define DHCPV6_REQUEST 3
|
||||
|
||||
/** DHCPv6 reply */
|
||||
#define DHCPV6_REPLY 7
|
||||
|
||||
/** DHCPv6 information request */
|
||||
#define DHCPV6_INFORMATION_REQUEST 11
|
||||
|
||||
/** DHCPv6 settings block name */
|
||||
#define DHCPV6_SETTINGS_NAME "dhcpv6"
|
||||
|
||||
/**
|
||||
* Construct all-DHCP-relay-agents-and-servers multicast address
|
||||
*
|
||||
* @v addr Zeroed address to construct
|
||||
*/
|
||||
static inline void ipv6_all_dhcp_relay_and_servers ( struct in6_addr *addr ) {
|
||||
addr->s6_addr16[0] = htons ( 0xff02 );
|
||||
addr->s6_addr[13] = 1;
|
||||
addr->s6_addr[15] = 2;
|
||||
}
|
||||
|
||||
extern int start_dhcpv6 ( struct interface *job, struct net_device *netdev,
|
||||
int stateful );
|
||||
|
||||
#endif /* _IPXE_DHCPV6_H */
|
|
@ -219,6 +219,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||
#define ERRFILE_socket ( ERRFILE_NET | 0x00380000 )
|
||||
#define ERRFILE_icmp ( ERRFILE_NET | 0x00390000 )
|
||||
#define ERRFILE_ping ( ERRFILE_NET | 0x003a0000 )
|
||||
#define ERRFILE_dhcpv6 ( ERRFILE_NET | 0x003b0000 )
|
||||
|
||||
#define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 )
|
||||
#define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 )
|
||||
|
|
|
@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||
#include <ipxe/ipv6.h>
|
||||
#include <ipxe/icmpv6.h>
|
||||
#include <ipxe/neighbour.h>
|
||||
#include <ipxe/dhcpv6.h>
|
||||
#include <ipxe/ndp.h>
|
||||
|
||||
/** @file
|
||||
|
@ -596,6 +597,8 @@ struct ipv6conf {
|
|||
|
||||
/** Job control interface */
|
||||
struct interface job;
|
||||
/** DHCPv6 interface */
|
||||
struct interface dhcp;
|
||||
|
||||
/** Network device being configured */
|
||||
struct net_device *netdev;
|
||||
|
@ -646,6 +649,7 @@ static void ipv6conf_done ( struct ipv6conf *ipv6conf, int rc ) {
|
|||
|
||||
/* Shut down interfaces */
|
||||
intf_shutdown ( &ipv6conf->job, rc );
|
||||
intf_shutdown ( &ipv6conf->dhcp, rc );
|
||||
|
||||
/* Stop timer */
|
||||
stop_timer ( &ipv6conf->timer );
|
||||
|
@ -686,6 +690,8 @@ static void ipv6conf_expired ( struct retry_timer *timer, int fail ) {
|
|||
static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
||||
unsigned int flags ) {
|
||||
struct ipv6conf *ipv6conf;
|
||||
int stateful;
|
||||
int rc;
|
||||
|
||||
/* Identify IPv6 configurator, if any */
|
||||
ipv6conf = ipv6conf_demux ( netdev );
|
||||
|
@ -697,13 +703,28 @@ static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Fail if stateful address autoconfiguration is required */
|
||||
if ( flags & NDP_ROUTER_MANAGED ) {
|
||||
ipv6conf_done ( ipv6conf, -ENOTSUP );
|
||||
return -ENOTSUP;
|
||||
/* If this is not the first solicited router advertisement, ignore it */
|
||||
if ( ! timer_running ( &ipv6conf->timer ) )
|
||||
return 0;
|
||||
|
||||
/* Stop router solicitation timer */
|
||||
stop_timer ( &ipv6conf->timer );
|
||||
|
||||
/* Start DHCPv6 if required */
|
||||
if ( flags & ( NDP_ROUTER_MANAGED | NDP_ROUTER_OTHER ) ) {
|
||||
stateful = ( flags & NDP_ROUTER_MANAGED );
|
||||
if ( ( rc = start_dhcpv6 ( &ipv6conf->dhcp, netdev,
|
||||
stateful ) ) != 0 ) {
|
||||
DBGC ( netdev, "NDP could not start state%s DHCPv6: "
|
||||
"%s\n", ( stateful ? "ful" : "less" ),
|
||||
strerror ( rc ) );
|
||||
ipv6conf_done ( ipv6conf, rc );
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark autoconfiguration as complete */
|
||||
/* Otherwise, terminate autoconfiguration */
|
||||
ipv6conf_done ( ipv6conf, 0 );
|
||||
|
||||
return 0;
|
||||
|
@ -718,6 +739,15 @@ static struct interface_operation ipv6conf_job_op[] = {
|
|||
static struct interface_descriptor ipv6conf_job_desc =
|
||||
INTF_DESC ( struct ipv6conf, job, ipv6conf_job_op );
|
||||
|
||||
/** IPv6 configurator DHCPv6 interface operations */
|
||||
static struct interface_operation ipv6conf_dhcp_op[] = {
|
||||
INTF_OP ( intf_close, struct ipv6conf *, ipv6conf_done ),
|
||||
};
|
||||
|
||||
/** IPv6 configurator DHCPv6 interface descriptor */
|
||||
static struct interface_descriptor ipv6conf_dhcp_desc =
|
||||
INTF_DESC ( struct ipv6conf, dhcp, ipv6conf_dhcp_op );
|
||||
|
||||
/**
|
||||
* Start IPv6 autoconfiguration
|
||||
*
|
||||
|
@ -734,6 +764,7 @@ int start_ipv6conf ( struct interface *job, struct net_device *netdev ) {
|
|||
return -ENOMEM;
|
||||
ref_init ( &ipv6conf->refcnt, ipv6conf_free );
|
||||
intf_init ( &ipv6conf->job, &ipv6conf_job_desc, &ipv6conf->refcnt );
|
||||
intf_init ( &ipv6conf->dhcp, &ipv6conf_dhcp_desc, &ipv6conf->refcnt );
|
||||
timer_init ( &ipv6conf->timer, ipv6conf_expired, &ipv6conf->refcnt );
|
||||
ipv6conf->netdev = netdev_get ( netdev );
|
||||
|
||||
|
|
|
@ -0,0 +1,955 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#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/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
|
||||
*
|
||||
*/
|
||||
|
||||
/** DHCPv6 settings scope */
|
||||
static struct settings_scope dhcpv6_settings_scope;
|
||||
|
||||
/** 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,
|
||||
struct setting *setting ) {
|
||||
|
||||
return ( setting->scope == &dhcpv6_settings_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, &dhcpv6_settings_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_SERVER ), htons ( DHCPV6_DOMAIN_SEARCH ),
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_SERVER_ID = 0x04,
|
||||
/** Record received IPv6 address */
|
||||
DHCPV6_RX_IAADDR = 0x08,
|
||||
};
|
||||
|
||||
/** 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_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_SERVER_ID | DHCPV6_RX_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 */
|
||||
void *client_duid;
|
||||
/** Client DUID length */
|
||||
size_t client_duid_len;
|
||||
/** 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_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 ) + dhcpv6->client_duid_len );
|
||||
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,
|
||||
dhcpv6->client_duid_len );
|
||||
|
||||
/* 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,
|
||||
dhcpv6->client_duid_len ) ) != 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_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_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 );
|
||||
}
|
||||
|
||||
/* 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;
|
||||
struct dhcpv6_duid_ll *client_duid;
|
||||
size_t client_duid_len;
|
||||
uint32_t xid;
|
||||
int rc;
|
||||
|
||||
/* Allocate and initialise structure */
|
||||
client_duid_len = ( sizeof ( *client_duid ) + ll_protocol->ll_addr_len);
|
||||
dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) + client_duid_len );
|
||||
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();
|
||||
dhcpv6->client_duid = ( ( ( void * ) dhcpv6 ) + sizeof ( *dhcpv6 ) );
|
||||
dhcpv6->client_duid_len = client_duid_len;
|
||||
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_scope_id = netdev->index;
|
||||
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 and IAID from link-layer address */
|
||||
client_duid = dhcpv6->client_duid;
|
||||
client_duid->type = htons ( DHCPV6_DUID_LL );
|
||||
client_duid->htype = ll_protocol->ll_proto;
|
||||
memcpy ( client_duid->ll_addr, netdev->ll_addr,
|
||||
ll_protocol->ll_addr_len );
|
||||
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 );
|
||||
ref_put ( &dhcpv6->refcnt );
|
||||
return rc;
|
||||
}
|
Loading…
Reference in New Issue