diff --git a/src/include/ipxe/ipv6.h b/src/include/ipxe/ipv6.h index f404ba64..6a9aa606 100644 --- a/src/include/ipxe/ipv6.h +++ b/src/include/ipxe/ipv6.h @@ -173,7 +173,26 @@ struct ipv6_miniroute { }; /** - * Construct link-local address (via EUI-64) + * Construct local IPv6 address via EUI-64 + * + * @v addr Prefix to be completed + * @v netdev Network device + * @ret prefix_len Prefix length, or negative error + */ +static inline int ipv6_eui64 ( struct in6_addr *addr, + struct net_device *netdev ) { + struct ll_protocol *ll_protocol = netdev->ll_protocol; + const void *ll_addr = netdev->ll_addr; + int rc; + + if ( ( rc = ll_protocol->eui64 ( ll_addr, &addr->s6_addr[8] ) ) != 0 ) + return rc; + addr->s6_addr[8] ^= 0x02; + return 64; +} + +/** + * Construct link-local address via EUI-64 * * @v addr Address to construct * @v netdev Network device @@ -181,16 +200,10 @@ struct ipv6_miniroute { */ static inline int ipv6_link_local ( struct in6_addr *addr, struct net_device *netdev ) { - struct ll_protocol *ll_protocol = netdev->ll_protocol; - const void *ll_addr = netdev->ll_addr; - int rc; memset ( addr, 0, sizeof ( *addr ) ); addr->s6_addr16[0] = htons ( 0xfe80 ); - if ( ( rc = ll_protocol->eui64 ( ll_addr, &addr->s6_addr[8] ) ) != 0 ) - return rc; - addr->s6_addr[8] ^= 0x02; - return 64; + return ipv6_eui64 ( addr, netdev ); } /** @@ -214,5 +227,7 @@ extern struct list_head ipv6_miniroutes; extern struct net_protocol ipv6_protocol __net_protocol; extern int ipv6_has_addr ( struct net_device *netdev, struct in6_addr *addr ); +extern int ipv6_slaac ( struct net_device *netdev, struct in6_addr *prefix, + unsigned int prefix_len, struct in6_addr *router ); #endif /* _IPXE_IPV6_H */ diff --git a/src/include/ipxe/ndp.h b/src/include/ipxe/ndp.h index 92f6da59..4edd96a4 100644 --- a/src/include/ipxe/ndp.h +++ b/src/include/ipxe/ndp.h @@ -15,19 +15,68 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include -/** An NDP option */ -struct ndp_option { +/** An NDP option header */ +struct ndp_option_header { /** Type */ uint8_t type; /** Length (in blocks of 8 bytes) */ uint8_t blocks; - /** Value */ - uint8_t value[0]; } __attribute__ (( packed )); /** NDP option block size */ #define NDP_OPTION_BLKSZ 8 +/** NDP source link-layer address option */ +#define NDP_OPT_LL_SOURCE 1 + +/** NDP target link-layer address option */ +#define NDP_OPT_LL_TARGET 2 + +/** NDP source or target link-layer address option */ +struct ndp_ll_addr_option { + /** NDP option header */ + struct ndp_option_header header; + /** Link-layer address */ + uint8_t ll_addr[0]; +} __attribute__ (( packed )); + +/** NDP prefix information option */ +#define NDP_OPT_PREFIX 3 + +/** NDP prefix information */ +struct ndp_prefix_information_option { + /** NDP option header */ + struct ndp_option_header header; + /** Prefix length */ + uint8_t prefix_len; + /** Flags */ + uint8_t flags; + /** Valid lifetime */ + uint32_t valid; + /** Preferred lifetime */ + uint32_t preferred; + /** Reserved */ + uint32_t reserved; + /** Prefix */ + struct in6_addr prefix; +} __attribute__ (( packed )); + +/** NDP on-link flag */ +#define NDP_PREFIX_ON_LINK 0x80 + +/** NDP autonomous address configuration flag */ +#define NDP_PREFIX_AUTONOMOUS 0x40 + +/** An NDP option */ +union ndp_option { + /** Option header */ + struct ndp_option_header header; + /** Source or target link-layer address option */ + struct ndp_ll_addr_option ll_addr; + /** Prefix information option */ + struct ndp_prefix_information_option prefix; +} __attribute__ (( packed )); + /** An NDP neighbour solicitation or advertisement header */ struct ndp_neighbour_header { /** ICMPv6 header */ @@ -39,7 +88,7 @@ struct ndp_neighbour_header { /** Target address */ struct in6_addr target; /** Options */ - struct ndp_option option[0]; + union ndp_option option[0]; } __attribute__ (( packed )); /** NDP router flag */ @@ -66,7 +115,7 @@ struct ndp_router_advertisement_header { /** Retransmission timer */ uint32_t retransmit; /** Options */ - struct ndp_option option[0]; + union ndp_option option[0]; } __attribute__ (( packed )); /** NDP managed address configuration */ @@ -85,12 +134,6 @@ union ndp_header { struct ndp_router_advertisement_header radv; } __attribute__ (( packed )); -/** NDP source link-layer address option */ -#define NDP_OPT_LL_SOURCE 1 - -/** NDP target link-layer address option */ -#define NDP_OPT_LL_TARGET 2 - extern struct neighbour_discovery ndp_discovery; /** diff --git a/src/net/ipv6.c b/src/net/ipv6.c index c5f8d2d9..4cd67209 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -862,6 +862,53 @@ struct sockaddr_converter ipv6_sockaddr_converter __sockaddr_converter = { .aton = ipv6_sock_aton, }; +/** + * Perform IPv6 stateless address autoconfiguration (SLAAC) + * + * @v netdev Network device + * @v prefix Prefix + * @v prefix_len Prefix length + * @v router Router address (or NULL) + * @ret rc Return status code + */ +int ipv6_slaac ( struct net_device *netdev, struct in6_addr *prefix, + unsigned int prefix_len, struct in6_addr *router ) { + struct ipv6_miniroute *miniroute; + struct ipv6_miniroute *tmp; + struct in6_addr address; + int check_prefix_len; + int rc; + + /* Construct local address */ + memcpy ( &address, prefix, sizeof ( address ) ); + check_prefix_len = ipv6_eui64 ( &address, netdev ); + if ( check_prefix_len < 0 ) { + rc = check_prefix_len; + DBGC ( netdev, "IPv6 %s could not construct SLAAC address: " + "%s\n", netdev->name, strerror ( rc ) ); + return rc; + } + if ( check_prefix_len != ( int ) prefix_len ) { + DBGC ( netdev, "IPv6 %s incorrect SLAAC prefix length %d " + "(expected %d)\n", netdev->name, prefix_len, + check_prefix_len ); + return -EINVAL; + } + + /* Delete any existing SLAAC miniroutes for this prefix */ + list_for_each_entry_safe ( miniroute, tmp, &ipv6_miniroutes, list ) { + if ( ipv6_is_local ( miniroute, &address ) ) + del_ipv6_miniroute ( miniroute ); + } + + /* Add miniroute */ + miniroute = add_ipv6_miniroute ( netdev, &address, prefix_len, router ); + if ( ! miniroute ) + return -ENOMEM; + + return 0; +} + /** * Create IPv6 network device * diff --git a/src/net/ndp.c b/src/net/ndp.c index b05756aa..cc57478a 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -62,12 +62,13 @@ static int ndp_tx_neighbour ( struct net_device *netdev, struct ll_protocol *ll_protocol = netdev->ll_protocol; struct io_buffer *iobuf; struct ndp_neighbour_header *neigh; + struct ndp_ll_addr_option *ll_addr_opt; size_t option_len; size_t len; int rc; /* Allocate and populate buffer */ - option_len = ( ( sizeof ( neigh->option[0] ) + + option_len = ( ( sizeof ( *ll_addr_opt ) + ll_protocol->ll_addr_len + NDP_OPTION_BLKSZ - 1 ) & ~( NDP_OPTION_BLKSZ - 1 ) ); len = ( sizeof ( *neigh ) + option_len ); @@ -80,9 +81,10 @@ static int ndp_tx_neighbour ( struct net_device *netdev, neigh->icmp.type = icmp_type; neigh->flags = flags; memcpy ( &neigh->target, target, sizeof ( neigh->target ) ); - neigh->option[0].type = option_type; - neigh->option[0].blocks = ( option_len / NDP_OPTION_BLKSZ ); - memcpy ( neigh->option[0].value, netdev->ll_addr, + ll_addr_opt = &neigh->option[0].ll_addr; + ll_addr_opt->header.type = option_type; + ll_addr_opt->header.blocks = ( option_len / NDP_OPTION_BLKSZ ); + memcpy ( ll_addr_opt->ll_addr, netdev->ll_addr, ll_protocol->ll_addr_len ); neigh->icmp.chksum = tcpip_chksum ( neigh, len ); @@ -143,17 +145,18 @@ struct neighbour_discovery ndp_discovery = { * @v netdev Network device * @v sin6_src Source socket address * @v ndp NDP packet - * @v ll_addr Source link-layer address - * @v ll_addr_len Source link-layer address length + * @v option NDP option + * @v len NDP option length * @ret rc Return status code */ static int ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev, struct sockaddr_in6 *sin6_src, union ndp_header *ndp, - const void *ll_addr, - size_t ll_addr_len ) { + union ndp_option *option, + size_t len ) { struct ndp_neighbour_header *neigh = &ndp->neigh; + struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr; struct ll_protocol *ll_protocol = netdev->ll_protocol; int rc; @@ -164,20 +167,21 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev, return 0; /* Sanity check */ - if ( ll_addr_len < ll_protocol->ll_addr_len ) { + if ( offsetof ( typeof ( *ll_addr_opt ), + ll_addr[ll_protocol->ll_addr_len] ) > len ) { DBGC ( netdev, "NDP neighbour solicitation link-layer address " - "too short at %zd bytes (min %d bytes)\n", - ll_addr_len, ll_protocol->ll_addr_len ); + "option too short at %zd bytes\n", len ); return -EINVAL; } /* Create or update neighbour cache entry */ if ( ( rc = neighbour_define ( netdev, &ipv6_protocol, &sin6_src->sin6_addr, - ll_addr ) ) != 0 ) { + ll_addr_opt->ll_addr ) ) != 0 ) { DBGC ( netdev, "NDP could not define %s => %s: %s\n", inet6_ntoa ( &sin6_src->sin6_addr ), - ll_protocol->ntoa ( ll_addr ), strerror ( rc ) ); + ll_protocol->ntoa ( ll_addr_opt->ll_addr ), + strerror ( rc ) ); return rc; } @@ -199,8 +203,8 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev, * @v netdev Network device * @v sin6_src Source socket address * @v ndp NDP packet - * @v ll_addr Target link-layer address - * @v ll_addr_len Target link-layer address length + * @v option NDP option + * @v len NDP option length * @ret rc Return status code */ static int @@ -208,26 +212,28 @@ ndp_rx_neighbour_advertisement_ll_target ( struct net_device *netdev, struct sockaddr_in6 *sin6_src __unused, union ndp_header *ndp, - const void *ll_addr, - size_t ll_addr_len ) { + union ndp_option *option, + size_t len ) { struct ndp_neighbour_header *neigh = &ndp->neigh; + struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr; struct ll_protocol *ll_protocol = netdev->ll_protocol; int rc; /* Sanity check */ - if ( ll_addr_len < ll_protocol->ll_addr_len ) { + if ( offsetof ( typeof ( *ll_addr_opt ), + ll_addr[ll_protocol->ll_addr_len] ) > len ) { DBGC ( netdev, "NDP neighbour advertisement link-layer address " - "too short at %zd bytes (min %d bytes)\n", - ll_addr_len, ll_protocol->ll_addr_len ); + "option too short at %zd bytes\n", len ); return -EINVAL; } /* Update neighbour cache entry, if any */ if ( ( rc = neighbour_update ( netdev, &ipv6_protocol, &neigh->target, - ll_addr ) ) != 0 ) { + ll_addr_opt->ll_addr ) ) != 0 ) { DBGC ( netdev, "NDP could not update %s => %s: %s\n", inet6_ntoa ( &neigh->target ), - ll_protocol->ntoa ( ll_addr ), strerror ( rc ) ); + ll_protocol->ntoa ( ll_addr_opt->ll_addr ), + strerror ( rc ) ); return rc; } @@ -240,40 +246,94 @@ ndp_rx_neighbour_advertisement_ll_target ( struct net_device *netdev, * @v netdev Network device * @v sin6_src Source socket address * @v ndp NDP packet - * @v ll_addr Target link-layer address - * @v ll_addr_len Target link-layer address length + * @v option NDP option + * @v len NDP option length * @ret rc Return status code */ static int ndp_rx_router_advertisement_ll_source ( struct net_device *netdev, struct sockaddr_in6 *sin6_src, union ndp_header *ndp __unused, - const void *ll_addr, - size_t ll_addr_len ) { + union ndp_option *option, size_t len ) { + struct ndp_ll_addr_option *ll_addr_opt = &option->ll_addr; struct ll_protocol *ll_protocol = netdev->ll_protocol; int rc; /* Sanity check */ - if ( ll_addr_len < ll_protocol->ll_addr_len ) { + if ( offsetof ( typeof ( *ll_addr_opt ), + ll_addr[ll_protocol->ll_addr_len] ) > len ) { DBGC ( netdev, "NDP router advertisement link-layer address " - "too short at %zd bytes (min %d bytes)\n", - ll_addr_len, ll_protocol->ll_addr_len ); + "option too short at %zd bytes\n", len ); return -EINVAL; } /* Define neighbour cache entry */ if ( ( rc = neighbour_define ( netdev, &ipv6_protocol, &sin6_src->sin6_addr, - ll_addr ) ) != 0 ) { + ll_addr_opt->ll_addr ) ) != 0 ) { DBGC ( netdev, "NDP could not define %s => %s: %s\n", inet6_ntoa ( &sin6_src->sin6_addr ), - ll_protocol->ntoa ( ll_addr ), strerror ( rc ) ); + ll_protocol->ntoa ( ll_addr_opt->ll_addr ), + strerror ( rc ) ); return rc; } return 0; } +/** + * Process NDP router advertisement prefix information option + * + * @v netdev Network device + * @v sin6_src Source socket address + * @v ndp NDP packet + * @v option NDP option + * @v len NDP option length + * @ret rc Return status code + */ +static int +ndp_rx_router_advertisement_prefix ( struct net_device *netdev, + struct sockaddr_in6 *sin6_src, + union ndp_header *ndp, + union ndp_option *option, size_t len ) { + struct ndp_router_advertisement_header *radv = &ndp->radv; + struct ndp_prefix_information_option *prefix_opt = &option->prefix; + struct in6_addr *router = &sin6_src->sin6_addr; + int rc; + + /* Sanity check */ + if ( sizeof ( *prefix_opt ) > len ) { + DBGC ( netdev, "NDP router advertisement prefix option too " + "short at %zd bytes\n", len ); + return -EINVAL; + } + DBGC ( netdev, "NDP found %sdefault router %s ", + ( radv->lifetime ? "" : "non-" ), + inet6_ntoa ( &sin6_src->sin6_addr ) ); + DBGC ( netdev, "for %s-link %sautonomous prefix %s/%d\n", + ( ( prefix_opt->flags & NDP_PREFIX_ON_LINK ) ? "on" : "off" ), + ( ( prefix_opt->flags & NDP_PREFIX_AUTONOMOUS ) ? "" : "non-" ), + inet6_ntoa ( &prefix_opt->prefix ), + prefix_opt->prefix_len ); + + /* Perform stateless address autoconfiguration, if applicable */ + if ( ( prefix_opt->flags & + ( NDP_PREFIX_ON_LINK | NDP_PREFIX_AUTONOMOUS ) ) == + ( NDP_PREFIX_ON_LINK | NDP_PREFIX_AUTONOMOUS ) ) { + if ( ( rc = ipv6_slaac ( netdev, &prefix_opt->prefix, + prefix_opt->prefix_len, + ( radv->lifetime ? + router : NULL ) ) ) != 0 ) { + DBGC ( netdev, "NDP could not autoconfigure prefix %s/" + "%d: %s\n", inet6_ntoa ( &prefix_opt->prefix ), + prefix_opt->prefix_len, strerror ( rc ) ); + return rc; + } + } + + return 0; +} + /** An NDP option handler */ struct ndp_option_handler { /** ICMPv6 type */ @@ -286,12 +346,12 @@ struct ndp_option_handler { * @v netdev Network device * @v sin6_src Source socket address * @v ndp NDP packet - * @v value Option value - * @v len Option length + * @v option NDP option * @ret rc Return status code */ int ( * rx ) ( struct net_device *netdev, struct sockaddr_in6 *sin6_src, - union ndp_header *ndp, const void *value, size_t len ); + union ndp_header *ndp, union ndp_option *option, + size_t len ); }; /** NDP option handlers */ @@ -311,6 +371,11 @@ static struct ndp_option_handler ndp_option_handlers[] = { .option_type = NDP_OPT_LL_SOURCE, .rx = ndp_rx_router_advertisement_ll_source, }, + { + .icmp_type = ICMPV6_ROUTER_ADVERTISEMENT, + .option_type = NDP_OPT_PREFIX, + .rx = ndp_rx_router_advertisement_prefix, + }, }; /** @@ -319,15 +384,13 @@ static struct ndp_option_handler ndp_option_handlers[] = { * @v netdev Network device * @v sin6_src Source socket address * @v ndp NDP packet - * @v type Option type - * @v value Option value + * @v option NDP option * @v len Option length * @ret rc Return status code */ static int ndp_rx_option ( struct net_device *netdev, - struct sockaddr_in6 *sin6_src, - union ndp_header *ndp, unsigned int type, - const void *value, size_t len ) { + struct sockaddr_in6 *sin6_src, union ndp_header *ndp, + union ndp_option *option, size_t len ) { struct ndp_option_handler *handler; unsigned int i; @@ -336,9 +399,9 @@ static int ndp_rx_option ( struct net_device *netdev, sizeof ( ndp_option_handlers[0] ) ) ; i++ ) { handler = &ndp_option_handlers[i]; if ( ( handler->icmp_type == ndp->icmp.type ) && - ( handler->option_type == type ) ) { + ( handler->option_type == option->header.type ) ) { return handler->rx ( netdev, sin6_src, ndp, - value, len ); + option, len ); } } @@ -360,10 +423,9 @@ static int ndp_rx ( struct io_buffer *iobuf, struct sockaddr_in6 *sin6_src, size_t offset ) { union ndp_header *ndp = iobuf->data; - struct ndp_option *option; + union ndp_option *option; size_t remaining; size_t option_len; - size_t option_value_len; int rc; /* Sanity check */ @@ -380,23 +442,21 @@ static int ndp_rx ( struct io_buffer *iobuf, while ( remaining ) { /* Sanity check */ - if ( ( remaining < sizeof ( *option ) ) || - ( option->blocks == 0 ) || - ( remaining < ( option->blocks * NDP_OPTION_BLKSZ ) ) ) { + if ( ( remaining < sizeof ( option->header ) ) || + ( option->header.blocks == 0 ) || + ( remaining < ( option->header.blocks * + NDP_OPTION_BLKSZ ) ) ) { DBGC ( netdev, "NDP bad option length:\n" ); DBGC_HDA ( netdev, 0, option, remaining ); rc = -EINVAL; goto done; } - option_len = ( option->blocks * NDP_OPTION_BLKSZ ); - option_value_len = ( option_len - sizeof ( *option ) ); + option_len = ( option->header.blocks * NDP_OPTION_BLKSZ ); /* Handle option */ - if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, - option->type, option->value, - option_value_len ) ) != 0 ) { + if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, option, + option_len ) ) != 0 ) goto done; - } /* Move to next option */ option = ( ( ( void * ) option ) + option_len );