#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#include <errno.h>
#include <assert.h>
#include <byteswap.h>
return crc32_le ( 0, in, sizeof ( *in ) );
}
+/**
+ * Determine IPv6 address scope
+ *
+ * @v addr IPv6 address
+ * @ret scope Address scope
+ */
+static unsigned int ipv6_scope ( const struct in6_addr *addr ) {
+
+ /* Multicast addresses directly include a scope field */
+ if ( IN6_IS_ADDR_MULTICAST ( addr ) )
+ return ipv6_multicast_scope ( addr );
+
+ /* Link-local addresses have link-local scope */
+ if ( IN6_IS_ADDR_LINKLOCAL ( addr ) )
+ return IPV6_SCOPE_LINK_LOCAL;
+
+ /* Site-local addresses have site-local scope */
+ if ( IN6_IS_ADDR_SITELOCAL ( addr ) )
+ return IPV6_SCOPE_SITE_LOCAL;
+
+ /* Unique local addresses do not directly map to a defined
+ * scope. They effectively have a scope which is wider than
+ * link-local but narrower than global. Since the only
+ * multicast packets that we transmit are link-local, we can
+ * simply choose an arbitrary scope between link-local and
+ * global.
+ */
+ if ( IN6_IS_ADDR_ULA ( addr ) )
+ return IPV6_SCOPE_ORGANISATION_LOCAL;
+
+ /* All other addresses are assumed to be global */
+ return IPV6_SCOPE_GLOBAL;
+}
+
/**
* Dump IPv6 routing table entry
*
}
/**
- * Check if IPv6 address is within a routing table entry's local network
+ * Count matching bits of an IPv6 routing table entry prefix
*
* @v miniroute Routing table entry
* @v address IPv6 address
- * @ret is_on_link Address is within this entry's local network
+ * @ret match_len Number of matching prefix bits
*/
-static int ipv6_is_on_link ( struct ipv6_miniroute *miniroute,
- struct in6_addr *address ) {
+static unsigned int ipv6_match_len ( struct ipv6_miniroute *miniroute,
+ struct in6_addr *address ) {
+ unsigned int match_len = 0;
unsigned int i;
+ uint32_t diff;
for ( i = 0 ; i < ( sizeof ( address->s6_addr32 ) /
sizeof ( address->s6_addr32[0] ) ) ; i++ ) {
- if ( (( address->s6_addr32[i] ^ miniroute->address.s6_addr32[i])
- & miniroute->prefix_mask.s6_addr32[i] ) != 0 )
- return 0;
+
+ diff = ntohl ( ~( ( ~( address->s6_addr32[i] ^
+ miniroute->address.s6_addr32[i] ) )
+ & miniroute->prefix_mask.s6_addr32[i] ) );
+ match_len += 32;
+ if ( diff ) {
+ match_len -= flsl ( diff );
+ break;
+ }
}
- return 1;
+
+ return match_len;
}
/**
static struct ipv6_miniroute * ipv6_miniroute ( struct net_device *netdev,
struct in6_addr *address ) {
struct ipv6_miniroute *miniroute;
+ unsigned int match_len;
list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) {
- if ( ( miniroute->netdev == netdev ) &&
- ipv6_is_on_link ( miniroute, address ) ) {
- return miniroute;
- }
+ if ( miniroute->netdev != netdev )
+ continue;
+ match_len = ipv6_match_len ( miniroute, address );
+ if ( match_len < miniroute->prefix_len )
+ continue;
+ return miniroute;
}
return NULL;
}
* @v router Router address (if any)
* @ret rc Return status code
*/
-static int ipv6_add_miniroute ( struct net_device *netdev,
- struct in6_addr *address,
- unsigned int prefix_len,
- struct in6_addr *router ) {
+int ipv6_add_miniroute ( struct net_device *netdev, struct in6_addr *address,
+ unsigned int prefix_len, struct in6_addr *router ) {
struct ipv6_miniroute *miniroute;
uint8_t *prefix_mask;
unsigned int remaining;
/* Find or create routing table entry */
miniroute = ipv6_miniroute ( netdev, address );
- if ( ! miniroute ) {
+ if ( miniroute ) {
+
+ /* Remove from existing position in routing table */
+ list_del ( &miniroute->list );
+
+ } else {
/* Create new routing table entry */
miniroute = zalloc ( sizeof ( *miniroute ) );
}
if ( remaining )
*prefix_mask <<= ( 8 - remaining );
-
- /* Add to list of routes */
- list_add ( &miniroute->list, &ipv6_miniroutes );
}
+ /* Add to start of routing table */
+ list_add ( &miniroute->list, &ipv6_miniroutes );
+
/* Set or update address, if applicable */
for ( i = 0 ; i < ( sizeof ( address->s6_addr32 ) /
sizeof ( address->s6_addr32[0] ) ) ; i++ ) {
if ( miniroute->prefix_len == IPV6_MAX_PREFIX_LEN )
miniroute->flags |= IPV6_HAS_ADDRESS;
+ /* Update scope */
+ miniroute->scope = ipv6_scope ( &miniroute->address );
+
/* Set or update router, if applicable */
if ( router ) {
memcpy ( &miniroute->router, router,
sizeof ( miniroute->router ) );
miniroute->flags |= IPV6_HAS_ROUTER;
- list_del ( &miniroute->list );
- list_add_tail ( &miniroute->list, &ipv6_miniroutes );
}
ipv6_dump_miniroute ( miniroute );
*
* @v miniroute Routing table entry
*/
-static void ipv6_del_miniroute ( struct ipv6_miniroute *miniroute ) {
+void ipv6_del_miniroute ( struct ipv6_miniroute *miniroute ) {
netdev_put ( miniroute->netdev );
list_del ( &miniroute->list );
* @ret dest Next hop destination address
* @ret miniroute Routing table entry to use, or NULL if no route
*/
-static struct ipv6_miniroute * ipv6_route ( unsigned int scope_id,
- struct in6_addr **dest ) {
+struct ipv6_miniroute * ipv6_route ( unsigned int scope_id,
+ struct in6_addr **dest ) {
struct ipv6_miniroute *miniroute;
+ struct ipv6_miniroute *chosen = NULL;
+ unsigned int best = 0;
+ unsigned int match_len;
+ unsigned int score;
+ unsigned int scope;
+
+ /* Calculate destination address scope */
+ scope = ipv6_scope ( *dest );
/* Find first usable route in routing table */
list_for_each_entry ( miniroute, &ipv6_miniroutes, list ) {
if ( ! netdev_is_open ( miniroute->netdev ) )
continue;
- /* Skip routing table entries with no usable source address */
+ /* Skip entries with no usable source address */
if ( ! ( miniroute->flags & IPV6_HAS_ADDRESS ) )
continue;
- if ( IN6_IS_ADDR_NONGLOBAL ( *dest ) ) {
+ /* Skip entries with a non-matching scope ID, if
+ * destination specifies a scope ID.
+ */
+ if ( scope_id && ( miniroute->netdev->index != scope_id ) )
+ continue;
- /* If destination is non-global, and the scope ID
- * matches this network device, then use this route.
- */
- if ( miniroute->netdev->index == scope_id )
- return miniroute;
+ /* Skip entries that are out of scope */
+ if ( miniroute->scope < scope )
+ continue;
- } else {
+ /* Calculate match length */
+ match_len = ipv6_match_len ( miniroute, *dest );
- /* If destination is an on-link global
- * address, then use this route.
- */
- if ( ipv6_is_on_link ( miniroute, *dest ) )
- return miniroute;
-
- /* If destination is an off-link global
- * address, and we have a default gateway,
- * then use this route.
- */
- if ( miniroute->flags & IPV6_HAS_ROUTER ) {
- *dest = &miniroute->router;
- return miniroute;
- }
+ /* If destination is on-link, then use this route */
+ if ( match_len >= miniroute->prefix_len )
+ return miniroute;
+
+ /* If destination is unicast, then skip off-link
+ * entries with no router.
+ */
+ if ( ! ( IN6_IS_ADDR_MULTICAST ( *dest ) ||
+ ( miniroute->flags & IPV6_HAS_ROUTER ) ) )
+ continue;
+
+ /* Choose best route, defined as being the route with
+ * the smallest viable scope. If two routes both have
+ * the same scope, then prefer the route with the
+ * longest match length.
+ */
+ score = ( ( ( IPV6_SCOPE_MAX + 1 - miniroute->scope ) << 8 )
+ + match_len );
+ if ( score > best ) {
+ chosen = miniroute;
+ best = score;
}
}
+ /* Return chosen route, if any */
+ if ( chosen ) {
+ if ( ! IN6_IS_ADDR_MULTICAST ( *dest ) )
+ *dest = &chosen->router;
+ return chosen;
+ }
+
return NULL;
}
const char *netdev_name;
/* Identify network device, if applicable */
- if ( IN6_IS_ADDR_NONGLOBAL ( in ) ) {
+ if ( IN6_IS_ADDR_LINKLOCAL ( in ) || IN6_IS_ADDR_MULTICAST ( in ) ) {
netdev = find_netdev_by_index ( sin6->sin6_scope_id );
netdev_name = ( netdev ? netdev->name : "UNKNOWN" );
} else {
}
sin6->sin6_scope_id = netdev->index;
- } else if ( IN6_IS_ADDR_NONGLOBAL ( &in ) ) {
+ } else if ( IN6_IS_ADDR_LINKLOCAL ( &in ) ||
+ IN6_IS_ADDR_MULTICAST ( &in ) ) {
/* If no network device is explicitly specified for a
* link-local or multicast address, default to using
/** Define inline IPv6 address */
#define IPV6(...) { __VA_ARGS__ }
+/** An IPv6 test routing table entry */
+struct ipv6_test_route {
+ /** Local address */
+ const char *address;
+ /** Prefix length */
+ unsigned int prefix_len;
+ /** Router address (if any) */
+ const char *router;
+};
+
+/** An IPv6 test routing table */
+struct ipv6_test_table {
+ /** Test routing table entries */
+ const struct ipv6_test_route *routes;
+ /** Number of table entries */
+ unsigned int count;
+ /** Constructed routing table */
+ struct list_head list;
+};
+
+/** Define a test routing table */
+#define TABLE( name, ... ) \
+ static const struct ipv6_test_route name ## _routes[] = { \
+ __VA_ARGS__ \
+ }; \
+ static struct ipv6_test_table name = { \
+ .routes = name ## _routes, \
+ .count = ( sizeof ( name ## _routes ) / \
+ sizeof ( name ## _routes[0] ) ), \
+ .list = LIST_HEAD_INIT ( name.list ), \
+ };
+
/** The unspecified IPv6 address */
static const struct in6_addr sample_unspecified = {
.s6_addr = IPV6 ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x69, 0xff, 0xfe, 0x50, 0x58, 0x45 ),
};
+/** A sample site-local IPv6 address */
+static const struct in6_addr sample_site_local = {
+ .s6_addr = IPV6 ( 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 ),
+};
+
+/** A sample ULA IPv6 address */
+static const struct in6_addr sample_ula = {
+ .s6_addr = IPV6 ( 0xfd, 0x44, 0x91, 0x12, 0x64, 0x42, 0x00, 0x00,
+ 0x00, 0x00, 0x69, 0xff, 0xfe, 0x50, 0x58, 0x45 ),
+};
+
/** A sample global IPv6 address */
static const struct in6_addr sample_global = {
.s6_addr = IPV6 ( 0x20, 0x01, 0x0b, 0xa8, 0x00, 0x00, 0x01, 0xd4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ),
};
+/** Dummy network device used for routing tests */
+static struct net_device ipv6_test_netdev = {
+ .refcnt = REF_INIT ( ref_no_free ),
+ .index = 42,
+ .state = NETDEV_OPEN,
+};
+
+/** Routing table with only a link-local address */
+TABLE ( table_link_local,
+ { "fe80::69ff:fe50:5845", 64, NULL } );
+
+/** Routing table with a global address */
+TABLE ( table_normal,
+ { "fe80::69ff:fe50:5845", 64, NULL },
+ { "2001:db8:3::1", 64, "fe80::1" } );
+
+/** Routing table with multiple addresses and routers */
+TABLE ( table_multi,
+ { "fe80::69ff:fe50:5845", 64, NULL },
+ { "2001:db8:3::1", 64, "fe80::1" },
+ { "2001:db8:5::1", 64, NULL },
+ { "2001:db8:42::1", 64, "fe80::2" },
+ { "fd44:9112:6442::69ff:fe50:5845", 64, "fe80::1" },
+ { "fd70:6ba9:50ae::69ff:fe50:5845", 64, "fe80::3" } );
+
/**
* Report an inet6_ntoa() test result
*
#define inet6_aton_fail_ok( text ) \
inet6_aton_fail_okx ( text, __FILE__, __LINE__ )
+/**
+ * Create test routing table
+ *
+ * @v table Test routing table
+ * @v file Test code file
+ * @v line Test code line
+ */
+static void ipv6_table_okx ( struct ipv6_test_table *table, const char *file,
+ unsigned int line ) {
+ const struct ipv6_test_route *route;
+ struct in6_addr address;
+ struct in6_addr router;
+ struct list_head saved;
+ unsigned int i;
+
+ /* Sanity check */
+ okx ( list_empty ( &table->list ), file, line );
+
+ /* Save existing routing table */
+ INIT_LIST_HEAD ( &saved );
+ list_splice_init ( &ipv6_miniroutes, &saved );
+
+ /* Construct routing table */
+ for ( i = 0 ; i < table->count ; i++ ) {
+
+ /* Parse address and router (if applicable) */
+ route = &table->routes[i];
+ okx ( inet6_aton ( route->address, &address ) == 0,
+ file, line );
+ if ( route->router ) {
+ okx ( inet6_aton ( route->router, &router ) == 0,
+ file, line );
+ }
+
+ /* Add routing table entry */
+ okx ( ipv6_add_miniroute ( &ipv6_test_netdev, &address,
+ route->prefix_len,
+ ( route->router ?
+ &router : NULL ) ) == 0,
+ file, line );
+ }
+
+ /* Save constructed routing table */
+ list_splice_init ( &ipv6_miniroutes, &table->list );
+
+ /* Restore original routing table */
+ list_splice ( &saved, &ipv6_miniroutes );
+}
+#define ipv6_table_ok( table ) \
+ ipv6_table_okx ( table, __FILE__, __LINE__ )
+
+/**
+ * Report an ipv6_route() test result
+ *
+ * @v table Test routing table
+ * @v dest Destination address
+ * @v src Expected source address, or NULL to expect failure
+ * @v next Expected next hop address, or NULL to expect destination
+ * @v file Test code file
+ * @v line Test code line
+ */
+static void ipv6_route_okx ( struct ipv6_test_table *table, const char *dest,
+ const char *src, const char *next,
+ const char *file, unsigned int line ) {
+ struct in6_addr in_dest;
+ struct in6_addr in_src;
+ struct in6_addr in_next;
+ struct in6_addr *actual;
+ struct ipv6_miniroute *miniroute;
+ struct list_head saved;
+
+ /* Switch to test routing table */
+ INIT_LIST_HEAD ( &saved );
+ list_splice_init ( &ipv6_miniroutes, &saved );
+ list_splice_init ( &table->list, &ipv6_miniroutes );
+
+ /* Parse addresses */
+ okx ( inet6_aton ( dest, &in_dest ) == 0, file, line );
+ if ( src )
+ okx ( inet6_aton ( src, &in_src ) == 0, file, line );
+ if ( next ) {
+ okx ( inet6_aton ( next, &in_next ) == 0, file, line );
+ } else {
+ memcpy ( &in_next, &in_dest, sizeof ( in_next ) );
+ }
+
+ /* Perform routing */
+ actual = &in_dest;
+ miniroute = ipv6_route ( ipv6_test_netdev.index, &actual );
+
+ /* Validate result */
+ if ( src ) {
+
+ /* Check that a route was found */
+ okx ( miniroute != NULL, file, line );
+ DBG ( "ipv6_route ( %s ) = %s", dest, inet6_ntoa ( actual ) );
+ DBG ( " from %s\n", inet6_ntoa ( &miniroute->address ) );
+
+ /* Check that expected source address was used */
+ okx ( memcmp ( &miniroute->address, &in_src,
+ sizeof ( in_src ) ) == 0, file, line );
+
+ /* Check that expected next hop address was used */
+ okx ( memcmp ( actual, &in_next, sizeof ( *actual ) ) == 0,
+ file, line );
+
+ } else {
+
+ /* Routing is expected to fail */
+ okx ( miniroute == NULL, file, line );
+ }
+
+ /* Restore original routing table */
+ list_splice_init ( &ipv6_miniroutes, &table->list );
+ list_splice ( &saved, &ipv6_miniroutes );
+}
+#define ipv6_route_ok( table, dest, src, next ) \
+ ipv6_route_okx ( table, dest, src, next, __FILE__, __LINE__ )
+
+/**
+ * Destroy test routing table
+ *
+ * @v table Test routing table
+ */
+static void ipv6_table_del ( struct ipv6_test_table *table ) {
+ struct ipv6_miniroute *miniroute;
+ struct ipv6_miniroute *tmp;
+ struct list_head saved;
+
+ /* Switch to test routing table */
+ INIT_LIST_HEAD ( &saved );
+ list_splice_init ( &ipv6_miniroutes, &saved );
+ list_splice_init ( &table->list, &ipv6_miniroutes );
+
+ /* Delete all existing routes */
+ list_for_each_entry_safe ( miniroute, tmp, &ipv6_miniroutes, list )
+ ipv6_del_miniroute ( miniroute );
+
+ /* Restore original routing table */
+ list_splice ( &saved, &ipv6_miniroutes );
+}
+
/**
* Perform IPv6 self-tests
*
/* Address testing macros */
ok ( IN6_IS_ADDR_UNSPECIFIED ( &sample_unspecified ) );
ok ( ! IN6_IS_ADDR_UNSPECIFIED ( &sample_link_local ) );
+ ok ( ! IN6_IS_ADDR_UNSPECIFIED ( &sample_site_local ) );
+ ok ( ! IN6_IS_ADDR_UNSPECIFIED ( &sample_ula ) );
ok ( ! IN6_IS_ADDR_UNSPECIFIED ( &sample_global ) );
ok ( ! IN6_IS_ADDR_UNSPECIFIED ( &sample_multicast ) );
ok ( ! IN6_IS_ADDR_MULTICAST ( &sample_unspecified ) );
ok ( ! IN6_IS_ADDR_MULTICAST ( &sample_link_local ) );
+ ok ( ! IN6_IS_ADDR_MULTICAST ( &sample_site_local ) );
+ ok ( ! IN6_IS_ADDR_MULTICAST ( &sample_ula ) );
ok ( ! IN6_IS_ADDR_MULTICAST ( &sample_global ) );
ok ( IN6_IS_ADDR_MULTICAST ( &sample_multicast ) );
ok ( ! IN6_IS_ADDR_LINKLOCAL ( &sample_unspecified ) );
ok ( IN6_IS_ADDR_LINKLOCAL ( &sample_link_local ) );
+ ok ( ! IN6_IS_ADDR_LINKLOCAL ( &sample_site_local ) );
+ ok ( ! IN6_IS_ADDR_LINKLOCAL ( &sample_ula ) );
ok ( ! IN6_IS_ADDR_LINKLOCAL ( &sample_global ) );
ok ( ! IN6_IS_ADDR_LINKLOCAL ( &sample_multicast ) );
+ ok ( ! IN6_IS_ADDR_SITELOCAL ( &sample_unspecified ) );
+ ok ( ! IN6_IS_ADDR_SITELOCAL ( &sample_link_local ) );
+ ok ( IN6_IS_ADDR_SITELOCAL ( &sample_site_local ) );
+ ok ( ! IN6_IS_ADDR_SITELOCAL ( &sample_ula ) );
+ ok ( ! IN6_IS_ADDR_SITELOCAL ( &sample_global ) );
+ ok ( ! IN6_IS_ADDR_SITELOCAL ( &sample_multicast ) );
+ ok ( ! IN6_IS_ADDR_ULA ( &sample_unspecified ) );
+ ok ( ! IN6_IS_ADDR_ULA ( &sample_link_local ) );
+ ok ( ! IN6_IS_ADDR_ULA ( &sample_site_local ) );
+ ok ( IN6_IS_ADDR_ULA ( &sample_ula ) );
+ ok ( ! IN6_IS_ADDR_ULA ( &sample_global ) );
+ ok ( ! IN6_IS_ADDR_ULA ( &sample_multicast ) );
/* inet6_ntoa() tests */
inet6_ntoa_ok ( IPV6 ( 0x20, 0x01, 0x0b, 0xa8, 0x00, 0x00, 0x01, 0xd4,
inet6_aton_fail_ok ( "2001:db8::1::2" );
inet6_aton_fail_ok ( "2001:ba8:0:1d4:::6950:5845" );
inet6_aton_fail_ok ( ":::" );
+
+ /* Create test routing tables */
+ ipv6_table_ok ( &table_link_local );
+ ipv6_table_ok ( &table_normal );
+ ipv6_table_ok ( &table_multi );
+
+ /* Routing table with only a link-local address */
+ ipv6_route_ok ( &table_link_local, "fe80::1",
+ "fe80::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_link_local, "2001:db8:1::1",
+ NULL, NULL );
+ ipv6_route_ok ( &table_link_local, "ff02::1",
+ "fe80::69ff:fe50:5845", NULL );
+
+ /** Routing table with a global address */
+ ipv6_route_ok ( &table_normal, "fe80::1",
+ "fe80::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_normal, "2001:db8:3::42",
+ "2001:db8:3::1", NULL );
+ ipv6_route_ok ( &table_normal, "2001:ba8:0:1d4::6950:5845",
+ "2001:db8:3::1", "fe80::1" );
+ ipv6_route_ok ( &table_normal, "ff02::1",
+ "fe80::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_normal, "ff0e::1",
+ "2001:db8:3::1", NULL );
+
+ /** Routing table with multiple addresses and routers */
+ ipv6_route_ok ( &table_multi, "fe80::1",
+ "fe80::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_multi, "2001:db8:3::17",
+ "2001:db8:3::1", NULL );
+ ipv6_route_ok ( &table_multi, "2001:db8:5::92",
+ "2001:db8:5::1", NULL );
+ ipv6_route_ok ( &table_multi, "2001:db8:42::17",
+ "2001:db8:42::1", NULL );
+ ipv6_route_ok ( &table_multi, "2001:db8:5:1::17",
+ "2001:db8:3::1", "fe80::1" );
+ ipv6_route_ok ( &table_multi, "fd44:9112:6442::1",
+ "fd44:9112:6442::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_multi, "fd70:6ba9:50ae::1",
+ "fd70:6ba9:50ae::69ff:fe50:5845", NULL );
+ ipv6_route_ok ( &table_multi, "fd40::3",
+ "fd44:9112:6442::69ff:fe50:5845", "fe80::1" );
+ ipv6_route_ok ( &table_multi, "fd70::2",
+ "fd70:6ba9:50ae::69ff:fe50:5845", "fe80::3" );
+ ipv6_route_ok ( &table_multi, "ff02::1",
+ "fe80::69ff:fe50:5845", NULL );
+
+ /* Destroy test routing tables */
+ ipv6_table_del ( &table_link_local );
+ ipv6_table_del ( &table_normal );
+ ipv6_table_del ( &table_multi );
}
/** IPv6 self-test */