mirror of https://github.com/ipxe/ipxe.git
[ipv6] Add IPv6 network device configurator
Include IPv6 within the generic network device configurator mechanism. The IPv6 configurator will send a router solicitation and wait for a router advertisement to be received. (As per RFC4861 section 6.3.7, we do this even if advertisements have been received prior to sending the router solicitation.) Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/17/head
parent
c99f9ee9df
commit
6871a8113f
|
@ -166,6 +166,4 @@ static inline int ndp_tx ( struct io_buffer *iobuf, struct net_device *netdev,
|
||||||
&ndp_discovery, net_source, ll_source );
|
&ndp_discovery, net_source, ll_source );
|
||||||
}
|
}
|
||||||
|
|
||||||
extern int ndp_tx_router_solicitation ( struct net_device *netdev );
|
|
||||||
|
|
||||||
#endif /* _IPXE_NDP_H */
|
#endif /* _IPXE_NDP_H */
|
||||||
|
|
243
src/net/ndp.c
243
src/net/ndp.c
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
FILE_LICENCE ( GPL2_OR_LATER );
|
FILE_LICENCE ( GPL2_OR_LATER );
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <byteswap.h>
|
#include <byteswap.h>
|
||||||
|
@ -36,6 +37,9 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
||||||
|
unsigned int flags );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transmit NDP packet with link-layer address option
|
* Transmit NDP packet with link-layer address option
|
||||||
*
|
*
|
||||||
|
@ -147,7 +151,7 @@ struct neighbour_discovery ndp_discovery = {
|
||||||
* @v netdev Network device
|
* @v netdev Network device
|
||||||
* @ret rc Return status code
|
* @ret rc Return status code
|
||||||
*/
|
*/
|
||||||
int ndp_tx_router_solicitation ( struct net_device *netdev ) {
|
static int ndp_tx_router_solicitation ( struct net_device *netdev ) {
|
||||||
struct ndp_router_solicitation_header rsol;
|
struct ndp_router_solicitation_header rsol;
|
||||||
struct sockaddr_in6 sin6_dest;
|
struct sockaddr_in6 sin6_dest;
|
||||||
int rc;
|
int rc;
|
||||||
|
@ -443,35 +447,33 @@ static int ndp_rx_option ( struct net_device *netdev,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process received NDP packet
|
* Process received NDP packet options
|
||||||
*
|
*
|
||||||
* @v iobuf I/O buffer
|
|
||||||
* @v netdev Network device
|
* @v netdev Network device
|
||||||
* @v sin6_src Source socket address
|
* @v sin6_src Source socket address
|
||||||
|
* @v ndp NDP header
|
||||||
* @v offset Offset to NDP options
|
* @v offset Offset to NDP options
|
||||||
|
* @v len Length of NDP packet
|
||||||
* @ret rc Return status code
|
* @ret rc Return status code
|
||||||
*/
|
*/
|
||||||
static int ndp_rx ( struct io_buffer *iobuf,
|
static int ndp_rx_options ( struct net_device *netdev,
|
||||||
struct net_device *netdev,
|
struct sockaddr_in6 *sin6_src,
|
||||||
struct sockaddr_in6 *sin6_src,
|
union ndp_header *ndp, size_t offset, size_t len ) {
|
||||||
size_t offset ) {
|
|
||||||
union ndp_header *ndp = iobuf->data;
|
|
||||||
union ndp_option *option;
|
union ndp_option *option;
|
||||||
size_t remaining;
|
size_t remaining;
|
||||||
size_t option_len;
|
size_t option_len;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( iob_len ( iobuf ) < offset ) {
|
if ( len < offset ) {
|
||||||
DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd "
|
DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd "
|
||||||
"bytes)\n", iob_len ( iobuf ), offset );
|
"bytes)\n", len, offset );
|
||||||
rc = -EINVAL;
|
return -EINVAL;
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search for option */
|
/* Search for option */
|
||||||
option = ( ( ( void * ) ndp ) + offset );
|
option = ( ( ( void * ) ndp ) + offset );
|
||||||
remaining = ( iob_len ( iobuf ) - offset );
|
remaining = ( len - offset );
|
||||||
while ( remaining ) {
|
while ( remaining ) {
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
|
@ -481,27 +483,21 @@ static int ndp_rx ( struct io_buffer *iobuf,
|
||||||
NDP_OPTION_BLKSZ ) ) ) {
|
NDP_OPTION_BLKSZ ) ) ) {
|
||||||
DBGC ( netdev, "NDP bad option length:\n" );
|
DBGC ( netdev, "NDP bad option length:\n" );
|
||||||
DBGC_HDA ( netdev, 0, option, remaining );
|
DBGC_HDA ( netdev, 0, option, remaining );
|
||||||
rc = -EINVAL;
|
return -EINVAL;
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
option_len = ( option->header.blocks * NDP_OPTION_BLKSZ );
|
option_len = ( option->header.blocks * NDP_OPTION_BLKSZ );
|
||||||
|
|
||||||
/* Handle option */
|
/* Handle option */
|
||||||
if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, option,
|
if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, option,
|
||||||
option_len ) ) != 0 )
|
option_len ) ) != 0 )
|
||||||
goto done;
|
return rc;
|
||||||
|
|
||||||
/* Move to next option */
|
/* Move to next option */
|
||||||
option = ( ( ( void * ) option ) + option_len );
|
option = ( ( ( void * ) option ) + option_len );
|
||||||
remaining -= option_len;
|
remaining -= option_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Success */
|
return 0;
|
||||||
rc = 0;
|
|
||||||
|
|
||||||
done:
|
|
||||||
free_iob ( iobuf );
|
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -519,9 +515,18 @@ static int ndp_rx_neighbour ( struct io_buffer *iobuf,
|
||||||
struct sockaddr_in6 *sin6_dest __unused ) {
|
struct sockaddr_in6 *sin6_dest __unused ) {
|
||||||
union ndp_header *ndp = iobuf->data;
|
union ndp_header *ndp = iobuf->data;
|
||||||
struct ndp_neighbour_header *neigh = &ndp->neigh;
|
struct ndp_neighbour_header *neigh = &ndp->neigh;
|
||||||
|
size_t len = iob_len ( iobuf );
|
||||||
|
int rc;
|
||||||
|
|
||||||
return ndp_rx ( iobuf, netdev, sin6_src,
|
/* Process options */
|
||||||
offsetof ( typeof ( *neigh ), option ) );
|
if ( ( rc = ndp_rx_options ( netdev, sin6_src, ndp,
|
||||||
|
offsetof ( typeof ( *neigh ), option ),
|
||||||
|
len ) ) != 0 )
|
||||||
|
goto err_options;
|
||||||
|
|
||||||
|
err_options:
|
||||||
|
free_iob ( iobuf );
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -540,9 +545,24 @@ ndp_rx_router_advertisement ( struct io_buffer *iobuf,
|
||||||
struct sockaddr_in6 *sin6_dest __unused ) {
|
struct sockaddr_in6 *sin6_dest __unused ) {
|
||||||
union ndp_header *ndp = iobuf->data;
|
union ndp_header *ndp = iobuf->data;
|
||||||
struct ndp_router_advertisement_header *radv = &ndp->radv;
|
struct ndp_router_advertisement_header *radv = &ndp->radv;
|
||||||
|
size_t len = iob_len ( iobuf );
|
||||||
|
int rc;
|
||||||
|
|
||||||
return ndp_rx ( iobuf, netdev, sin6_src,
|
/* Process options */
|
||||||
offsetof ( typeof ( *radv ), option ) );
|
if ( ( rc = ndp_rx_options ( netdev, sin6_src, ndp,
|
||||||
|
offsetof ( typeof ( *radv ), option ),
|
||||||
|
len ) ) != 0 )
|
||||||
|
goto err_options;
|
||||||
|
|
||||||
|
/* Pass to IPv6 autoconfiguration */
|
||||||
|
if ( ( rc = ipv6conf_rx_router_advertisement ( netdev,
|
||||||
|
radv->flags ) ) != 0 )
|
||||||
|
goto err_ipv6conf;
|
||||||
|
|
||||||
|
err_ipv6conf:
|
||||||
|
err_options:
|
||||||
|
free_iob ( iobuf );
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** NDP ICMPv6 handlers */
|
/** NDP ICMPv6 handlers */
|
||||||
|
@ -560,3 +580,174 @@ struct icmpv6_handler ndp_handlers[] __icmpv6_handler = {
|
||||||
.rx = ndp_rx_router_advertisement,
|
.rx = ndp_rx_router_advertisement,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* IPv6 autoconfiguration
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** An IPv6 configurator */
|
||||||
|
struct ipv6conf {
|
||||||
|
/** Reference count */
|
||||||
|
struct refcnt refcnt;
|
||||||
|
/** List of configurators */
|
||||||
|
struct list_head list;
|
||||||
|
|
||||||
|
/** Job control interface */
|
||||||
|
struct interface job;
|
||||||
|
|
||||||
|
/** Network device being configured */
|
||||||
|
struct net_device *netdev;
|
||||||
|
|
||||||
|
/** Retransmission timer */
|
||||||
|
struct retry_timer timer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** List of IPv6 configurators */
|
||||||
|
static LIST_HEAD ( ipv6confs );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free IPv6 configurator
|
||||||
|
*
|
||||||
|
* @v refcnt Reference count
|
||||||
|
*/
|
||||||
|
static void ipv6conf_free ( struct refcnt *refcnt ) {
|
||||||
|
struct ipv6conf *ipv6conf =
|
||||||
|
container_of ( refcnt, struct ipv6conf, refcnt );
|
||||||
|
|
||||||
|
netdev_put ( ipv6conf->netdev );
|
||||||
|
free ( ipv6conf );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify IPv6 configurator by network device
|
||||||
|
*
|
||||||
|
* @v netdev Network device
|
||||||
|
* @ret ipv6 IPv6 configurator, or NULL
|
||||||
|
*/
|
||||||
|
static struct ipv6conf * ipv6conf_demux ( struct net_device *netdev ) {
|
||||||
|
struct ipv6conf *ipv6conf;
|
||||||
|
|
||||||
|
list_for_each_entry ( ipv6conf, &ipv6confs, list ) {
|
||||||
|
if ( ipv6conf->netdev == netdev )
|
||||||
|
return ipv6conf;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish IPv6 autoconfiguration
|
||||||
|
*
|
||||||
|
* @v ipv6 IPv6 configurator
|
||||||
|
* @v rc Reason for finishing
|
||||||
|
*/
|
||||||
|
static void ipv6conf_done ( struct ipv6conf *ipv6conf, int rc ) {
|
||||||
|
|
||||||
|
/* Shut down interfaces */
|
||||||
|
intf_shutdown ( &ipv6conf->job, rc );
|
||||||
|
|
||||||
|
/* Stop timer */
|
||||||
|
stop_timer ( &ipv6conf->timer );
|
||||||
|
|
||||||
|
/* Remove from list and drop list's reference */
|
||||||
|
list_del ( &ipv6conf->list );
|
||||||
|
ref_put ( &ipv6conf->refcnt );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle IPv6 configurator timer expiry
|
||||||
|
*
|
||||||
|
* @v timer Retry timer
|
||||||
|
* @v fail Failure indicator
|
||||||
|
*/
|
||||||
|
static void ipv6conf_expired ( struct retry_timer *timer, int fail ) {
|
||||||
|
struct ipv6conf *ipv6conf =
|
||||||
|
container_of ( timer, struct ipv6conf, timer );
|
||||||
|
|
||||||
|
/* If we have failed, terminate autoconfiguration */
|
||||||
|
if ( fail ) {
|
||||||
|
ipv6conf_done ( ipv6conf, -ETIMEDOUT );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, transmit router solicitation and restart timer */
|
||||||
|
start_timer ( &ipv6conf->timer );
|
||||||
|
ndp_tx_router_solicitation ( ipv6conf->netdev );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle router advertisement during IPv6 autoconfiguration
|
||||||
|
*
|
||||||
|
* @v netdev Network device
|
||||||
|
* @v flags Router flags
|
||||||
|
* @ret rc Return status code
|
||||||
|
*/
|
||||||
|
static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
||||||
|
unsigned int flags ) {
|
||||||
|
struct ipv6conf *ipv6conf;
|
||||||
|
|
||||||
|
/* Identify IPv6 configurator, if any */
|
||||||
|
ipv6conf = ipv6conf_demux ( netdev );
|
||||||
|
if ( ! ipv6conf ) {
|
||||||
|
/* Not an error; router advertisements are processed
|
||||||
|
* as a background activity even when no explicit
|
||||||
|
* autoconfiguration is taking place.
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fail if stateful address autoconfiguration is required */
|
||||||
|
if ( flags & NDP_ROUTER_MANAGED ) {
|
||||||
|
ipv6conf_done ( ipv6conf, -ENOTSUP );
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark autoconfiguration as complete */
|
||||||
|
ipv6conf_done ( ipv6conf, 0 );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IPv6 configurator job interface operations */
|
||||||
|
static struct interface_operation ipv6conf_job_op[] = {
|
||||||
|
INTF_OP ( intf_close, struct ipv6conf *, ipv6conf_done ),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** IPv6 configurator job interface descriptor */
|
||||||
|
static struct interface_descriptor ipv6conf_job_desc =
|
||||||
|
INTF_DESC ( struct ipv6conf, job, ipv6conf_job_op );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start IPv6 autoconfiguration
|
||||||
|
*
|
||||||
|
* @v job Job control interface
|
||||||
|
* @v netdev Network device
|
||||||
|
* @ret rc Return status code
|
||||||
|
*/
|
||||||
|
int start_ipv6conf ( struct interface *job, struct net_device *netdev ) {
|
||||||
|
struct ipv6conf *ipv6conf;
|
||||||
|
|
||||||
|
/* Allocate and initialise structure */
|
||||||
|
ipv6conf = zalloc ( sizeof ( *ipv6conf ) );
|
||||||
|
if ( ! ipv6conf )
|
||||||
|
return -ENOMEM;
|
||||||
|
ref_init ( &ipv6conf->refcnt, ipv6conf_free );
|
||||||
|
intf_init ( &ipv6conf->job, &ipv6conf_job_desc, &ipv6conf->refcnt );
|
||||||
|
timer_init ( &ipv6conf->timer, ipv6conf_expired, &ipv6conf->refcnt );
|
||||||
|
ipv6conf->netdev = netdev_get ( netdev );
|
||||||
|
|
||||||
|
/* Start timer to initiate router solicitation */
|
||||||
|
start_timer_nodelay ( &ipv6conf->timer );
|
||||||
|
|
||||||
|
/* Attach parent interface, transfer reference to list, and return */
|
||||||
|
intf_plug_plug ( &ipv6conf->job, job );
|
||||||
|
list_add ( &ipv6conf->list, &ipv6confs );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IPv6 network device configurator */
|
||||||
|
struct net_device_configurator ipv6_configurator __net_device_configurator = {
|
||||||
|
.name = "ipv6",
|
||||||
|
.start = start_ipv6conf,
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue