diff --git a/src/core/ipv6/nd6.c b/src/core/ipv6/nd6.c index d4673ca6..7c2d0c45 100644 --- a/src/core/ipv6/nd6.c +++ b/src/core/ipv6/nd6.c @@ -84,12 +84,19 @@ #if LWIP_ND6_NUM_ROUTERS > 127 #error LWIP_ND6_NUM_ROUTERS must fit into an s8_t (max value: 127) #endif +#if LWIP_ND6_SUPPORT_RIO && LWIP_ND6_NUM_ROUTES <= 0 +#error LWIP_ND6_NUM_ROUTES must be > 0 if LWIP_ND6_SUPPORT_RIO is on. +#endif + /* Router tables. */ struct nd6_neighbor_cache_entry neighbor_cache[LWIP_ND6_NUM_NEIGHBORS]; struct nd6_destination_cache_entry destination_cache[LWIP_ND6_NUM_DESTINATIONS]; struct nd6_prefix_list_entry prefix_list[LWIP_ND6_NUM_PREFIXES]; struct nd6_router_list_entry default_router_list[LWIP_ND6_NUM_ROUTERS]; +#if LWIP_ND6_SUPPORT_RIO +struct nd6_route_list_entry route_list[LWIP_ND6_NUM_ROUTES]; +#endif /* Default values, can be updated by a RA message. */ u32_t reachable_time = LWIP_ND6_REACHABLE_TIME; @@ -132,6 +139,10 @@ static s8_t nd6_get_onlink_prefix(const ip6_addr_t *prefix, struct netif *netif) static s8_t nd6_new_onlink_prefix(const ip6_addr_t *prefix, struct netif *netif); static s8_t nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif); static err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf *q); +#if LWIP_ND6_SUPPORT_RIO +static s8_t nd6_get_route(const ip6_addr_t *prefix, const u8_t prefix_len); +static s8_t nd6_new_route(const ip6_addr_t *prefix, const u8_t prefix_len); +#endif #define ND6_SEND_FLAG_MULTICAST_DEST 0x01 #define ND6_SEND_FLAG_ALLNODES_DEST 0x02 @@ -763,12 +774,60 @@ nd6_input(struct pbuf *p, struct netif *inp) break; } - case ND6_OPTION_TYPE_ROUTE_INFO: - /* @todo implement preferred routes. - struct route_option * route_opt; - route_opt = (struct route_option *)buffer;*/ - + case ND6_OPTION_TYPE_ROUTE_INFO: { +#if LWIP_ND6_SUPPORT_RIO + struct route_option *route_opt; + uint32_t route_lifetime; + uint8_t prefix_length; + uint8_t preference; + ip6_addr_t prefix; + ip6_addr_p_t packed_prefix; + int idx; + size_t addr_bytes; + if (option_len < sizeof(struct route_option)) { + goto lenerr_drop_free_return; + } + route_opt = (struct route_option *)buffer; + memcpy(&prefix_length, &route_opt->prefix_length, sizeof(prefix_length)); + memcpy(&preference, &route_opt->preference, sizeof(preference)); + memcpy(&route_lifetime, &route_opt->route_lifetime, sizeof(route_lifetime)); + + /* The struct definition contains only the first byte of the address. + The option should have enough bytes to encode prefix_length bits + of the address. */ + addr_bytes = (prefix_length + 7) >> 3; + if (addr_bytes > sizeof(ip6_addr_p_t) || + option_len < sizeof(struct route_option) + addr_bytes - 1) { + goto lenerr_drop_free_return; + } + /* For now, we only consider 64-bit prefix lengths. */ + if (prefix_length != 64) { + break; + } + memset(&packed_prefix, 0, sizeof(packed_prefix)); + memcpy(&packed_prefix, route_opt->prefix, addr_bytes); + ip6_addr_copy_from_packed(prefix, packed_prefix); + /* Link-local, any address prefix and multicast should not have defined + routes set through RIO options. If we got one of these, disregard it. */ + if (ip6_addr_isany(&prefix) || ip6_addr_islinklocal(&prefix) || + ip6_addr_ismulticast(&prefix)) { + break; + } + route_lifetime = lwip_ntohl(route_lifetime); + idx = nd6_get_route(&prefix, prefix_length); + if (idx < 0 && route_lifetime > 0) { + /* Create a new cache entry */ + idx = nd6_new_route(&prefix, prefix_length); + } + if (idx >= 0) { + route_list[idx].invalidation_timer = route_lifetime; + route_list[idx].preference = preference; + route_list[idx].router_list_entry_index = nd6_get_router(ip6_current_src_addr(), inp); + route_list[idx].netif = inp; + } +#endif break; + } #if LWIP_ND6_RDNSS_MAX_DNS_SERVERS case ND6_OPTION_TYPE_RDNSS: { @@ -1075,6 +1134,21 @@ nd6_tmr(void) } } +#if LWIP_ND6_SUPPORT_RIO + /* Process route entries. */ + for (i = 0; i < LWIP_ND6_NUM_ROUTES; ++i) { + if (ip6_addr_isany(&route_list[i].prefix)) { + continue; + } + if (route_list[i].invalidation_timer < ND6_TMR_INTERVAL / 1000) { + ip6_addr_set_any(&route_list[i].prefix); + route_list[i].invalidation_timer = 0; + } else { + route_list[i].invalidation_timer -= ND6_TMR_INTERVAL / 1000; + } + } +#endif + /* Process our own addresses, updating address lifetimes and/or DAD state. */ NETIF_FOREACH(netif) { for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) { @@ -1677,6 +1751,16 @@ nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif) return 1; } } + +#if LWIP_ND6_SUPPORT_RIO + for (i = 0; i < LWIP_ND6_NUM_ROUTES; i++) { + if ((route_list[i].netif == netif) && + (route_list[i].invalidation_timer > 0) && + ip6_addr_net_eq(ip6addr, &(route_list[i].prefix))) { + return 1; + } + } +#endif return 0; } @@ -1700,7 +1784,34 @@ nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif) s8_t i, j, valid_router; static s8_t last_router; - LWIP_UNUSED_ARG(ip6addr); /* @todo match preferred routes!! (must implement ND6_OPTION_TYPE_ROUTE_INFO) */ +#if LWIP_ND6_SUPPORT_RIO + /* Check first if we have a route explicitly set for this address from a + RIO. This works because we're only considering 64-bit prefixes in RIO + and the prefix option. If we later move to support non-64 prefixes, + these will need to generate candidate routers and rank based on prefix + length */ + for (i = 0; i < LWIP_ND6_NUM_ROUTES; ++i) { + /* For now, we assume all route prefixes are /64 */ + if (ip6_addr_isany(&route_list[i].prefix)) { + continue; + } + if (ip6_addr_netcmp(&route_list[i].prefix, ip6addr)) { + s8_t router_idx = route_list[i].router_list_entry_index; + router_netif = default_router_list[router_idx].neighbor_entry->netif; + /* TODO: Implement preferences. */ + if (netif != NULL && router_netif != netif) { + continue; + } + /* TODO: If we're up, but not reacheable, then what? */ + if (default_router_list[router_idx].neighbor_entry->state != + ND6_INCOMPLETE) { + return router_idx; + } + } + } +#else + LWIP_UNUSED_ARG(ip6addr); +#endif /* @todo: implement default router preference */ @@ -1770,7 +1881,6 @@ nd6_find_route(const ip6_addr_t *ip6addr) { struct netif *netif; s8_t i; - /* @todo decide if it makes sense to check the destination cache first */ /* Check if there is a matching on-link prefix. There may be multiple @@ -1935,6 +2045,57 @@ nd6_new_onlink_prefix(const ip6_addr_t *prefix, struct netif *netif) return -1; } +#if LWIP_ND6_SUPPORT_RIO +/** + * Find the cached entry for an RIO configured route. + * + * @param prefix the IPv6 prefix for the route. + * @param prefix_len the length of the prefix (currently assumed /64) + * @return the index on the route table, or -1 if not found + */ +static s8_t +nd6_get_route(const ip6_addr_t *prefix, const u8_t prefix_len) +{ + s8_t i; + /* TODO: add support for non- /64 prefixes */ + LWIP_UNUSED_ARG(prefix_len); + /* Look for prefix in list. */ + for (i = 0; i < LWIP_ND6_NUM_ROUTES; ++i) { + if (ip6_addr_netcmp(&route_list[i].prefix, prefix)) { + return i; + } + } + + /* Entry not available. */ + return -1; +} + +/** + * Creates a new entry for an on-link prefix. + * + * @param prefix the IPv6 prefix for the route. + * @param prefix_len the length of the prefix (currently assumed /64) + * @return the index on the prefix table, or -1 if not created + */ +static s8_t +nd6_new_route(const ip6_addr_t *prefix, const u8_t prefix_len) +{ + s8_t i; + + /* Create new entry. */ + for (i = 0; i < LWIP_ND6_NUM_ROUTES; ++i) { + if (ip6_addr_isany(&route_list[i].prefix) || route_list[i].invalidation_timer == 0) { + ip6_addr_set(&(route_list[i].prefix), prefix); + route_list[i].prefix_len = prefix_len; + return i; + } + } + + /* Entry not available. */ + return -1; +} +#endif + /** * Determine the next hop for a destination. Will determine if the * destination is on-link, else a suitable on-link router is selected. @@ -2394,7 +2555,7 @@ nd6_reachability_hint(const ip6_addr_t *ip6addr) #endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */ /** - * Remove all prefix, neighbor_cache and router entries of the specified netif. + * Remove all prefix, neighbor_cachei, route and router entries of the specified netif. * * @param netif points to a network interface */ @@ -2420,6 +2581,18 @@ nd6_cleanup_netif(struct netif *netif) nd6_free_neighbor_cache_entry(i); } } +#if LWIP_ND6_SUPPORT_RIO + for (i = 0; i < LWIP_ND6_NUM_ROUTES; i++) { + if (route_list[i].netif == netif) { + /* Remove the whole route - if this netif is not up, this is invalid */ + ip6_addr_set_any(&route_list[i].prefix); + route_list[i].prefix_len = 0; + route_list[i].invalidation_timer = 0; + route_list[i].netif = NULL; + } + } +#endif /* LWIP_ND6_SUPPORT_RIO */ + /* Clear the destination cache, since many entries may now have become * invalid for one of several reasons. As destination cache entries have no * netif association, use a sledgehammer approach (this can be improved). */ diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h index 9fe0bf04..297c2019 100644 --- a/src/include/lwip/opt.h +++ b/src/include/lwip/opt.h @@ -2625,6 +2625,20 @@ #define LWIP_ND6_NUM_PREFIXES 5 #endif +/** + * LWIP_ND6_SUPPORT_RIO: Support route information options in nd6 packets. + */ +#if !defined LWIP_ND6_SUPPORT_RIO || defined __DOXYGEN__ +#define LWIP_ND6_SUPPORT_RIO LWIP_IPV6 +#endif + +/** + * LWIP_ND6_NUM_ROUTES: number of entries in IPv6 route list. + */ +#if !defined LWIP_ND6_NUM_ROUTES || defined __DOXYGEN__ +#define LWIP_ND6_NUM_ROUTES 5 +#endif + /** * LWIP_ND6_NUM_ROUTERS: number of entries in IPv6 default router cache */ diff --git a/src/include/lwip/priv/nd6_priv.h b/src/include/lwip/priv/nd6_priv.h index 75d5f02d..6b4aea23 100644 --- a/src/include/lwip/priv/nd6_priv.h +++ b/src/include/lwip/priv/nd6_priv.h @@ -104,6 +104,18 @@ struct nd6_prefix_list_entry { u32_t invalidation_timer; /* in seconds */ }; +struct nd6_route_list_entry { + ip6_addr_t prefix; + u8_t prefix_len; + u8_t preference; + /* Router messages with this prefix should be sent to + use the neighbour entry in the router to set the + next hop */ + s8_t router_list_entry_index; + struct netif* netif; + u32_t invalidation_timer; +}; + struct nd6_router_list_entry { struct nd6_neighbor_cache_entry *neighbor_entry; u32_t invalidation_timer; /* in seconds */ @@ -129,6 +141,10 @@ extern struct nd6_neighbor_cache_entry neighbor_cache[]; extern struct nd6_destination_cache_entry destination_cache[]; extern struct nd6_prefix_list_entry prefix_list[]; extern struct nd6_router_list_entry default_router_list[]; +#if LWIP_ND6_SUPPORT_RIO +extern struct nd6_route_list_entry route_list[]; +#endif + /* Default values, can be updated by a RA message. */ extern u32_t reachable_time; diff --git a/src/include/lwip/prot/nd6.h b/src/include/lwip/prot/nd6.h index c270d07c..e97d47a7 100644 --- a/src/include/lwip/prot/nd6.h +++ b/src/include/lwip/prot/nd6.h @@ -240,7 +240,9 @@ struct route_option { PACK_STRUCT_FLD_8(u8_t prefix_length); PACK_STRUCT_FLD_8(u8_t preference); PACK_STRUCT_FIELD(u32_t route_lifetime); - PACK_STRUCT_FLD_S(ip6_addr_p_t prefix); + /* The prefix is formatted like a packed address, but is of a variable length + large enough to contain prefix_length bits. See RFC 4191 section 2.3. */ + PACK_STRUCT_FLD_S(u8_t prefix[1]); } PACK_STRUCT_STRUCT; PACK_STRUCT_END #ifdef PACK_STRUCT_USE_INCLUDES