From 6b1eee04527969f21ab65245b67cf9efa4b4aea8 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 15 Nov 2013 15:12:25 +0000 Subject: [PATCH] [ipv6] Separate the concepts of prefix and address creation Allow for IPv6 routing table entries to be created for an on-link prefix where a local address has not yet been assigned to the network device. Signed-off-by: Michael Brown --- src/include/ipxe/ipv6.h | 20 +++- src/net/ipv6.c | 214 +++++++++++++++++++++++----------------- src/net/ndp.c | 97 +++++++++++------- src/usr/route_ipv6.c | 4 +- 4 files changed, 206 insertions(+), 129 deletions(-) diff --git a/src/include/ipxe/ipv6.h b/src/include/ipxe/ipv6.h index 3cb878262..c4a9f15e3 100644 --- a/src/include/ipxe/ipv6.h +++ b/src/include/ipxe/ipv6.h @@ -160,16 +160,24 @@ struct ipv6_miniroute { /** Network device */ struct net_device *netdev; - /** IPv6 address */ + /** IPv6 address (or prefix if no address is defined) */ struct in6_addr address; /** Prefix length */ unsigned int prefix_len; /** IPv6 prefix mask (derived from prefix length) */ struct in6_addr prefix_mask; - /** Router address is present */ - int has_router; /** Router address */ struct in6_addr router; + /** Flags */ + unsigned int flags; +}; + +/** IPv6 address/routing table entry flags */ +enum ipv6_miniroute_flags { + /** Routing table entry address is valid */ + IPV6_HAS_ADDRESS = 0x0001, + /** Routing table entry router address is valid */ + IPV6_HAS_ROUTER = 0x0002, }; /** @@ -235,7 +243,9 @@ 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 ); +extern int ipv6_set_prefix ( struct net_device *netdev, struct in6_addr *prefix, + unsigned int prefix_len, struct in6_addr *router ); +extern int ipv6_set_address ( struct net_device *netdev, + struct in6_addr *address ); #endif /* _IPXE_IPV6_H */ diff --git a/src/net/ipv6.c b/src/net/ipv6.c index b9619a1aa..b4f33f0d0 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -67,6 +67,24 @@ static uint32_t ipv6col ( struct in6_addr *in ) { return crc32_le ( 0, in, sizeof ( *in ) ); } +/** + * Dump IPv6 routing table entry + * + * @v miniroute Routing table entry + */ +static inline __attribute__ (( always_inline )) void +ipv6_dump_miniroute ( struct ipv6_miniroute *miniroute ) { + struct net_device *netdev = miniroute->netdev; + + DBGC ( netdev, "IPv6 %s has %s %s/%d", netdev->name, + ( ( miniroute->flags & IPV6_HAS_ADDRESS ) ? + "address" : "prefix" ), + inet6_ntoa ( &miniroute->address ), miniroute->prefix_len ); + if ( miniroute->flags & IPV6_HAS_ROUTER ) + DBGC ( netdev, " router %s", inet6_ntoa ( &miniroute->router )); + DBGC ( netdev, "\n" ); +} + /** * Check if network device has a specific IPv6 address * @@ -79,6 +97,7 @@ int ipv6_has_addr ( struct net_device *netdev, struct in6_addr *addr ) { list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) { if ( ( miniroute->netdev == netdev ) && + ( miniroute->flags & IPV6_HAS_ADDRESS ) && ( memcmp ( &miniroute->address, addr, sizeof ( miniroute->address ) ) == 0 ) ) { /* Found matching address */ @@ -109,31 +128,45 @@ static int ipv6_is_on_link ( struct ipv6_miniroute *miniroute, } /** - * Add IPv6 minirouting table entry + * Find IPv6 routing table entry for a given address * * @v netdev Network device * @v address IPv6 address + * @ret miniroute Routing table entry, or NULL if not found + */ +static struct ipv6_miniroute * ipv6_miniroute ( struct net_device *netdev, + struct in6_addr *address ) { + struct ipv6_miniroute *miniroute; + + list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) { + if ( ( miniroute->netdev == netdev ) && + ipv6_is_on_link ( miniroute, address ) ) { + return miniroute; + } + } + return NULL; +} + +/** + * Add IPv6 routing table entry + * + * @v netdev Network device + * @v address IPv6 address (or prefix) * @v prefix_len Prefix length - * @v router Router address (or NULL) + * @v flags Flags * @ret miniroute Routing table entry, or NULL on failure */ -static struct ipv6_miniroute * __malloc -add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr *address, - unsigned int prefix_len, struct in6_addr *router ) { +static struct ipv6_miniroute * ipv6_add_miniroute ( struct net_device *netdev, + struct in6_addr *address, + unsigned int prefix_len, + unsigned int flags ) { struct ipv6_miniroute *miniroute; uint8_t *prefix_mask; - DBGC ( netdev, "IPv6 add %s/%d ", inet6_ntoa ( address ), prefix_len ); - if ( router ) - DBGC ( netdev, "router %s ", inet6_ntoa ( router ) ); - DBGC ( netdev, "via %s\n", netdev->name ); - - /* Allocate and populate miniroute structure */ + /* Create routing table entry */ miniroute = zalloc ( sizeof ( *miniroute ) ); if ( ! miniroute ) return NULL; - - /* Record routing information */ miniroute->netdev = netdev_get ( netdev ); memcpy ( &miniroute->address, address, sizeof ( miniroute->address ) ); miniroute->prefix_len = prefix_len; @@ -144,41 +177,83 @@ add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr *address, } if ( prefix_len ) *prefix_mask <<= ( 8 - prefix_len ); - if ( router ) { - miniroute->has_router = 1; - memcpy ( &miniroute->router, router, - sizeof ( miniroute->router ) ); - } - - /* Add to end of list if we have a gateway, otherwise to start - * of list. - */ - if ( router ) { - list_add_tail ( &miniroute->list, &ipv6_miniroutes ); - } else { - list_add ( &miniroute->list, &ipv6_miniroutes ); - } + miniroute->flags = flags; + list_add ( &miniroute->list, &ipv6_miniroutes ); + ipv6_dump_miniroute ( miniroute ); return miniroute; } /** - * Delete IPv6 minirouting table entry + * Define IPv6 on-link prefix * - * @v miniroute Routing table entry + * @v netdev Network device + * @v prefix IPv6 address prefix + * @v prefix_len Prefix length + * @v router Router address (or NULL) + * @ret rc Return status code */ -static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) { - struct net_device *netdev = miniroute->netdev; +int ipv6_set_prefix ( struct net_device *netdev, struct in6_addr *prefix, + unsigned int prefix_len, struct in6_addr *router ) { + struct ipv6_miniroute *miniroute; + int changed; - DBGC ( netdev, "IPv6 del %s/%d ", inet6_ntoa ( &miniroute->address ), - miniroute->prefix_len ); - if ( miniroute->has_router ) - DBGC ( netdev, "router %s ", inet6_ntoa ( &miniroute->router )); - DBGC ( netdev, "via %s\n", netdev->name ); + /* Find or create routing table entry */ + miniroute = ipv6_miniroute ( netdev, prefix ); + if ( ! miniroute ) + miniroute = ipv6_add_miniroute ( netdev, prefix, prefix_len, 0); + if ( ! miniroute ) + return -ENOMEM; - netdev_put ( miniroute->netdev ); + /* Record router and add to start or end of list as appropriate */ list_del ( &miniroute->list ); - free ( miniroute ); + if ( router ) { + changed = ( ( ! ( miniroute->flags & IPV6_HAS_ROUTER ) ) || + ( memcmp ( &miniroute->router, router, + sizeof ( miniroute->router ) ) != 0 ) ); + miniroute->flags |= IPV6_HAS_ROUTER; + memcpy ( &miniroute->router, router, + sizeof ( miniroute->router ) ); + list_add_tail ( &miniroute->list, &ipv6_miniroutes ); + } else { + changed = ( miniroute->flags & IPV6_HAS_ROUTER ); + miniroute->flags &= ~IPV6_HAS_ROUTER; + list_add ( &miniroute->list, &ipv6_miniroutes ); + } + if ( changed ) + ipv6_dump_miniroute ( miniroute ); + + return 0; +} + +/** + * Add IPv6 on-link address + * + * @v netdev Network device + * @v address IPv6 address + * @ret rc Return status code + * + * An on-link prefix for the address must already exist. + */ +int ipv6_set_address ( struct net_device *netdev, struct in6_addr *address ) { + struct ipv6_miniroute *miniroute; + int changed; + + /* Find routing table entry */ + miniroute = ipv6_miniroute ( netdev, address ); + if ( ! miniroute ) + return -EADDRNOTAVAIL; + + /* Record address */ + changed = ( ( ! ( miniroute->flags & IPV6_HAS_ADDRESS ) ) || + ( memcmp ( &miniroute->address, address, + sizeof ( miniroute->address ) ) != 0 ) ); + memcpy ( &miniroute->address, address, sizeof ( miniroute->address ) ); + miniroute->flags |= IPV6_HAS_ADDRESS; + if ( changed ) + ipv6_dump_miniroute ( miniroute ); + + return 0; } /** @@ -200,6 +275,10 @@ static struct ipv6_miniroute * ipv6_route ( unsigned int scope_id, if ( ! netdev_is_open ( miniroute->netdev ) ) continue; + /* Skip routing table entries with no usable source address */ + if ( ! ( miniroute->flags & IPV6_HAS_ADDRESS ) ) + continue; + if ( IN6_IS_ADDR_LINKLOCAL ( *dest ) || IN6_IS_ADDR_MULTICAST ( *dest ) ) { @@ -221,7 +300,7 @@ static struct ipv6_miniroute * ipv6_route ( unsigned int scope_id, * address, and we have a default gateway, * then use this route. */ - if ( miniroute->has_router ) { + if ( miniroute->flags & IPV6_HAS_ROUTER ) { *dest = &miniroute->router; return miniroute; } @@ -919,53 +998,6 @@ struct setting_type setting_type_ipv6 __setting_type = { .format = format_ipv6_setting, }; -/** - * 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_on_link ( 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 * @@ -989,7 +1021,8 @@ static int ipv6_probe ( struct net_device *netdev ) { } /* Create link-local address for this network device */ - miniroute = add_ipv6_miniroute ( netdev, &address, prefix_len, NULL ); + miniroute = ipv6_add_miniroute ( netdev, &address, prefix_len, + IPV6_HAS_ADDRESS ); if ( ! miniroute ) return -ENOMEM; @@ -1007,8 +1040,11 @@ static void ipv6_remove ( struct net_device *netdev ) { /* Delete all miniroutes for this network device */ list_for_each_entry_safe ( miniroute, tmp, &ipv6_miniroutes, list ) { - if ( miniroute->netdev == netdev ) - del_ipv6_miniroute ( miniroute ); + if ( miniroute->netdev == netdev ) { + netdev_put ( miniroute->netdev ); + list_del ( &miniroute->list ); + free ( miniroute ); + } } } diff --git a/src/net/ndp.c b/src/net/ndp.c index f56bbe928..862e31ef4 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -88,8 +88,8 @@ static int ndp_tx_ll_addr ( struct net_device *netdev, /* Transmit packet */ if ( ( rc = tcpip_tx ( iobuf, &icmpv6_protocol, st_src, st_dest, netdev, &ndp->icmp.chksum ) ) != 0 ) { - DBGC ( netdev, "NDP could not transmit packet: %s\n", - strerror ( rc ) ); + DBGC ( netdev, "NDP %s could not transmit packet: %s\n", + netdev->name, strerror ( rc ) ); return rc; } @@ -205,8 +205,9 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev, /* Sanity check */ if ( offsetof ( typeof ( *ll_addr_opt ), ll_addr[ll_protocol->ll_addr_len] ) > len ) { - DBGC ( netdev, "NDP neighbour solicitation link-layer address " - "option too short at %zd bytes\n", len ); + DBGC ( netdev, "NDP %s neighbour solicitation link-layer " + "address option too short at %zd bytes\n", + netdev->name, len ); return -EINVAL; } @@ -214,8 +215,8 @@ ndp_rx_neighbour_solicitation_ll_source ( struct net_device *netdev, if ( ( rc = neighbour_define ( netdev, &ipv6_protocol, &sin6_src->sin6_addr, ll_addr_opt->ll_addr ) ) != 0 ) { - DBGC ( netdev, "NDP could not define %s => %s: %s\n", - inet6_ntoa ( &sin6_src->sin6_addr ), + DBGC ( netdev, "NDP %s could not define %s => %s: %s\n", + netdev->name, inet6_ntoa ( &sin6_src->sin6_addr ), ll_protocol->ntoa ( ll_addr_opt->ll_addr ), strerror ( rc ) ); return rc; @@ -260,16 +261,17 @@ ndp_rx_neighbour_advertisement_ll_target ( struct net_device *netdev, /* Sanity check */ if ( offsetof ( typeof ( *ll_addr_opt ), ll_addr[ll_protocol->ll_addr_len] ) > len ) { - DBGC ( netdev, "NDP neighbour advertisement link-layer address " - "option too short at %zd bytes\n", len ); + DBGC ( netdev, "NDP %s neighbour advertisement link-layer " + "address option too short at %zd bytes\n", + netdev->name, len ); return -EINVAL; } /* Update neighbour cache entry, if any */ if ( ( rc = neighbour_update ( netdev, &ipv6_protocol, &neigh->target, ll_addr_opt->ll_addr ) ) != 0 ) { - DBGC ( netdev, "NDP could not update %s => %s: %s\n", - inet6_ntoa ( &neigh->target ), + DBGC ( netdev, "NDP %s could not update %s => %s: %s\n", + netdev->name, inet6_ntoa ( &neigh->target ), ll_protocol->ntoa ( ll_addr_opt->ll_addr ), strerror ( rc ) ); return rc; @@ -300,8 +302,8 @@ ndp_rx_router_advertisement_ll_source ( struct net_device *netdev, /* Sanity check */ if ( offsetof ( typeof ( *ll_addr_opt ), ll_addr[ll_protocol->ll_addr_len] ) > len ) { - DBGC ( netdev, "NDP router advertisement link-layer address " - "option too short at %zd bytes\n", len ); + DBGC ( netdev, "NDP %s router advertisement link-layer address " + "option too short at %zd bytes\n", netdev->name, len ); return -EINVAL; } @@ -309,8 +311,8 @@ ndp_rx_router_advertisement_ll_source ( struct net_device *netdev, if ( ( rc = neighbour_define ( netdev, &ipv6_protocol, &sin6_src->sin6_addr, ll_addr_opt->ll_addr ) ) != 0 ) { - DBGC ( netdev, "NDP could not define %s => %s: %s\n", - inet6_ntoa ( &sin6_src->sin6_addr ), + DBGC ( netdev, "NDP %s could not define %s => %s: %s\n", + netdev->name, inet6_ntoa ( &sin6_src->sin6_addr ), ll_protocol->ntoa ( ll_addr_opt->ll_addr ), strerror ( rc ) ); return rc; @@ -337,16 +339,18 @@ ndp_rx_router_advertisement_prefix ( struct net_device *netdev, 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; + struct in6_addr address; + int prefix_len; int rc; /* Sanity check */ if ( sizeof ( *prefix_opt ) > len ) { - DBGC ( netdev, "NDP router advertisement prefix option too " - "short at %zd bytes\n", len ); + DBGC ( netdev, "NDP %s router advertisement prefix option too " + "short at %zd bytes\n", netdev->name, len ); return -EINVAL; } - DBGC ( netdev, "NDP found %sdefault router %s ", - ( radv->lifetime ? "" : "non-" ), + DBGC ( netdev, "NDP %s found %sdefault router %s ", + netdev->name, ( 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" ), @@ -354,17 +358,41 @@ ndp_rx_router_advertisement_prefix ( struct net_device *netdev, inet6_ntoa ( &prefix_opt->prefix ), prefix_opt->prefix_len ); + /* Ignore off-link prefixes */ + if ( ! ( prefix_opt->flags & NDP_PREFIX_ON_LINK ) ) + return 0; + + /* Define prefix */ + if ( ( rc = ipv6_set_prefix ( netdev, &prefix_opt->prefix, + prefix_opt->prefix_len, + ( radv->lifetime ? + router : NULL ) ) ) != 0 ) { + DBGC ( netdev, "NDP %s could not define prefix %s/%d: %s\n", + netdev->name, inet6_ntoa ( &prefix_opt->prefix ), + prefix_opt->prefix_len, strerror ( rc ) ); + return rc; + } + /* 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 ) ); + if ( prefix_opt->flags & NDP_PREFIX_AUTONOMOUS ) { + memcpy ( &address, &prefix_opt->prefix, sizeof ( address ) ); + prefix_len = ipv6_eui64 ( &address, netdev ); + if ( prefix_len < 0 ) { + rc = prefix_len; + DBGC ( netdev, "NDP %s could not construct SLAAC " + "address: %s\n", netdev->name, strerror ( rc ) ); + return rc; + } + if ( prefix_len != prefix_opt->prefix_len ) { + DBGC ( netdev, "NDP %s incorrect SLAAC prefix length " + "%d (expected %d)\n", netdev->name, + prefix_opt->prefix_len, prefix_len ); + return -EINVAL; + } + if ( ( rc = ipv6_set_address ( netdev, &address ) ) != 0 ) { + DBGC ( netdev, "NDP %s could not set address %s: %s\n", + netdev->name, inet6_ntoa ( &address ), + strerror ( rc ) ); return rc; } } @@ -467,8 +495,8 @@ static int ndp_rx_options ( struct net_device *netdev, /* Sanity check */ if ( len < offset ) { - DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd " - "bytes)\n", len, offset ); + DBGC ( netdev, "NDP %s packet too short at %zd bytes (min %zd " + "bytes)\n", netdev->name, len, offset ); return -EINVAL; } @@ -482,7 +510,8 @@ static int ndp_rx_options ( struct net_device *netdev, ( option->header.blocks == 0 ) || ( remaining < ( option->header.blocks * NDP_OPTION_BLKSZ ) ) ) { - DBGC ( netdev, "NDP bad option length:\n" ); + DBGC ( netdev, "NDP %s bad option length:\n", + netdev->name ); DBGC_HDA ( netdev, 0, option, remaining ); return -EINVAL; } @@ -715,9 +744,9 @@ static int ipv6conf_rx_router_advertisement ( struct net_device *netdev, 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 ) ); + DBGC ( netdev, "NDP %s could not start state%s DHCPv6: " + "%s\n", netdev->name, + ( stateful ? "ful" : "less" ), strerror ( rc ) ); ipv6conf_done ( ipv6conf, rc ); return rc; } diff --git a/src/usr/route_ipv6.c b/src/usr/route_ipv6.c index 8a6fbde37..6045f85bb 100644 --- a/src/usr/route_ipv6.c +++ b/src/usr/route_ipv6.c @@ -44,8 +44,10 @@ static void route_ipv6_print ( struct net_device *netdev ) { printf ( "%s: %s/%d", netdev->name, inet6_ntoa ( &miniroute->address ), miniroute->prefix_len ); - if ( miniroute->has_router ) + if ( miniroute->flags & IPV6_HAS_ROUTER ) printf ( " gw %s", inet6_ntoa ( &miniroute->router ) ); + if ( ! ( miniroute->flags & IPV6_HAS_ADDRESS ) ) + printf ( " (no address)" ); if ( ! netdev_is_open ( miniroute->netdev ) ) printf ( " (inaccessible)" ); printf ( "\n" );