/** @file * @brief Route handling. * */ /* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_route, CONFIG_NET_ROUTE_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include "net_private.h" #include "ipv6.h" #include "icmpv6.h" #include "nbr.h" #include "route.h" /* We keep track of the routes in a separate list so that we can remove * the oldest routes (at tail) if needed. */ static sys_slist_t routes; /* Track currently active route lifetime timers */ static sys_slist_t active_route_lifetime_timers; /* Timer that manages expired route entries. */ static struct k_work_delayable route_lifetime_timer; static void net_route_nexthop_remove(struct net_nbr *nbr) { NET_DBG("Nexthop %p removed", nbr); } /* * This pool contains information next hop neighbors. */ NET_NBR_POOL_INIT(net_route_nexthop_pool, CONFIG_NET_MAX_NEXTHOPS, sizeof(struct net_route_nexthop), net_route_nexthop_remove); static inline struct net_route_nexthop *net_nexthop_data(struct net_nbr *nbr) { return (struct net_route_nexthop *)nbr->data; } static inline struct net_nbr *get_nexthop_nbr(struct net_nbr *start, int idx) { NET_ASSERT(idx < CONFIG_NET_MAX_NEXTHOPS, "idx %d >= max %d", idx, CONFIG_NET_MAX_NEXTHOPS); return (struct net_nbr *)((uint8_t *)start + ((sizeof(struct net_nbr) + start->size) * idx)); } static void release_nexthop_route(struct net_route_nexthop *route_nexthop) { struct net_nbr *nbr = CONTAINER_OF((uint8_t *)route_nexthop, struct net_nbr, __nbr[0]); net_nbr_unref(nbr); } static struct net_nbr *get_nexthop_route(void) { int i; for (i = 0; i < CONFIG_NET_MAX_NEXTHOPS; i++) { struct net_nbr *nbr = get_nexthop_nbr( (struct net_nbr *)net_route_nexthop_pool, i); if (!nbr->ref) { nbr->data = nbr->__nbr; nbr->idx = NET_NBR_LLADDR_UNKNOWN; return net_nbr_ref(nbr); } } return NULL; } static void net_route_entry_remove(struct net_nbr *nbr) { NET_DBG("Route %p removed", nbr); } static void net_route_entries_table_clear(struct net_nbr_table *table) { NET_DBG("Route table %p cleared", table); } /* * This pool contains routing table entries. */ NET_NBR_POOL_INIT(net_route_entries_pool, CONFIG_NET_MAX_ROUTES, sizeof(struct net_route_entry), net_route_entry_remove); NET_NBR_TABLE_INIT(NET_NBR_LOCAL, nbr_routes, net_route_entries_pool, net_route_entries_table_clear); static inline struct net_nbr *get_nbr(int idx) { return &net_route_entries_pool[idx].nbr; } static inline struct net_route_entry *net_route_data(struct net_nbr *nbr) { return (struct net_route_entry *)nbr->data; } struct net_nbr *net_route_get_nbr(struct net_route_entry *route) { struct net_nbr *ret = NULL; int i; NET_ASSERT(route); net_ipv6_nbr_lock(); for (i = 0; i < CONFIG_NET_MAX_ROUTES; i++) { struct net_nbr *nbr = get_nbr(i); if (!nbr->ref) { continue; } if (nbr->data == (uint8_t *)route) { if (!nbr->ref) { break; } ret = nbr; break; } } net_ipv6_nbr_unlock(); return ret; } void net_routes_print(void) { int i; net_ipv6_nbr_lock(); for (i = 0; i < CONFIG_NET_MAX_ROUTES; i++) { struct net_nbr *nbr = get_nbr(i); if (!nbr->ref) { continue; } NET_DBG("[%d] %p %d addr %s/%d", i, nbr, nbr->ref, net_sprint_ipv6_addr(&net_route_data(nbr)->addr), net_route_data(nbr)->prefix_len); NET_DBG(" iface %p idx %d ll %s", nbr->iface, nbr->idx, nbr->idx == NET_NBR_LLADDR_UNKNOWN ? "?" : net_sprint_ll_addr( net_nbr_get_lladdr(nbr->idx)->addr, net_nbr_get_lladdr(nbr->idx)->len)); } net_ipv6_nbr_unlock(); } static inline void nbr_free(struct net_nbr *nbr) { NET_DBG("nbr %p", nbr); net_nbr_unref(nbr); } static struct net_nbr *nbr_new(struct net_if *iface, struct in6_addr *addr, uint8_t prefix_len) { struct net_nbr *nbr = net_nbr_get(&net_nbr_routes.table); if (!nbr) { return NULL; } nbr->iface = iface; net_ipaddr_copy(&net_route_data(nbr)->addr, addr); net_route_data(nbr)->prefix_len = prefix_len; NET_DBG("[%d] nbr %p iface %p IPv6 %s/%d", nbr->idx, nbr, iface, net_sprint_ipv6_addr(&net_route_data(nbr)->addr), prefix_len); return nbr; } static struct net_nbr *nbr_nexthop_get(struct net_if *iface, struct in6_addr *addr) { /* Note that the nexthop host must be already in the neighbor * cache. We just increase the ref count of an existing entry. */ struct net_nbr *nbr; nbr = net_ipv6_nbr_lookup(iface, addr); if (nbr == NULL) { NET_DBG("Next hop neighbor not found!"); return NULL; } net_nbr_ref(nbr); NET_DBG("[%d] nbr %p iface %p IPv6 %s", nbr->idx, nbr, iface, net_sprint_ipv6_addr(addr)); return nbr; } static int nbr_nexthop_put(struct net_nbr *nbr) { NET_ASSERT(nbr); NET_DBG("[%d] nbr %p iface %p", nbr->idx, nbr, nbr->iface); net_nbr_unref(nbr); return 0; } #define net_route_info(str, route, dst) \ do { \ if (CONFIG_NET_ROUTE_LOG_LEVEL >= LOG_LEVEL_DBG) { \ struct in6_addr *naddr = net_route_get_nexthop(route); \ \ NET_ASSERT(naddr, "Unknown nexthop address"); \ \ NET_DBG("%s route to %s via %s (iface %p)", str, \ net_sprint_ipv6_addr(dst), \ net_sprint_ipv6_addr(naddr), \ route->iface); \ } } while (false) /* Route was accessed, so place it in front of the routes list */ static inline void update_route_access(struct net_route_entry *route) { sys_slist_find_and_remove(&routes, &route->node); sys_slist_prepend(&routes, &route->node); } struct net_route_entry *net_route_lookup(struct net_if *iface, struct in6_addr *dst) { struct net_route_entry *route, *found = NULL; uint8_t longest_match = 0U; int i; net_ipv6_nbr_lock(); for (i = 0; i < CONFIG_NET_MAX_ROUTES && longest_match < 128; i++) { struct net_nbr *nbr = get_nbr(i); if (!nbr->ref) { continue; } if (iface && nbr->iface != iface) { continue; } route = net_route_data(nbr); if (route->prefix_len >= longest_match && net_ipv6_is_prefix(dst->s6_addr, route->addr.s6_addr, route->prefix_len)) { found = route; longest_match = route->prefix_len; } } if (found) { net_route_info("Found", found, dst); update_route_access(found); } net_ipv6_nbr_unlock(); return found; } static inline bool route_preference_is_lower(uint8_t old, uint8_t new) { if (new == NET_ROUTE_PREFERENCE_RESERVED || (new & 0xfc) != 0) { return true; } /* Transform valid preference values into comparable integers */ old = (old + 1) & 0x3; new = (new + 1) & 0x3; return new < old; } struct net_route_entry *net_route_add(struct net_if *iface, struct in6_addr *addr, uint8_t prefix_len, struct in6_addr *nexthop, uint32_t lifetime, uint8_t preference) { struct net_linkaddr_storage *nexthop_lladdr; struct net_nbr *nbr, *nbr_nexthop, *tmp; struct net_route_nexthop *nexthop_route; struct net_route_entry *route = NULL; #if defined(CONFIG_NET_MGMT_EVENT_INFO) struct net_event_ipv6_route info; #endif NET_ASSERT(addr); NET_ASSERT(iface); NET_ASSERT(nexthop); if (net_ipv6_addr_cmp(addr, net_ipv6_unspecified_address())) { NET_DBG("Route cannot be towards unspecified address"); return NULL; } net_ipv6_nbr_lock(); nbr_nexthop = net_ipv6_nbr_lookup(iface, nexthop); if (!nbr_nexthop) { NET_DBG("No such neighbor %s found", net_sprint_ipv6_addr(nexthop)); goto exit; } if (nbr_nexthop && nbr_nexthop->idx != NET_NBR_LLADDR_UNKNOWN) { nexthop_lladdr = net_nbr_get_lladdr(nbr_nexthop->idx); NET_ASSERT(nexthop_lladdr); NET_DBG("Nexthop %s lladdr is %s", net_sprint_ipv6_addr(nexthop), net_sprint_ll_addr(nexthop_lladdr->addr, nexthop_lladdr->len)); } route = net_route_lookup(iface, addr); if (route) { /* Update nexthop if not the same */ struct in6_addr *nexthop_addr; nexthop_addr = net_route_get_nexthop(route); if (nexthop_addr && net_ipv6_addr_cmp(nexthop, nexthop_addr)) { NET_DBG("No changes, return old route %p", route); /* Reset lifetime timer. */ net_route_update_lifetime(route, lifetime); route->preference = preference; goto exit; } if (route_preference_is_lower(route->preference, preference)) { NET_DBG("No changes, ignoring route with lower preference"); route = NULL; goto exit; } NET_DBG("Old route to %s found", net_sprint_ipv6_addr(nexthop_addr)); net_route_del(route); } nbr = nbr_new(iface, addr, prefix_len); if (!nbr) { /* Remove the oldest route and try again */ sys_snode_t *last = sys_slist_peek_tail(&routes); sys_slist_find_and_remove(&routes, last); route = CONTAINER_OF(last, struct net_route_entry, node); if (CONFIG_NET_ROUTE_LOG_LEVEL >= LOG_LEVEL_DBG) { struct in6_addr *in6_addr_tmp; struct net_linkaddr_storage *llstorage; in6_addr_tmp = net_route_get_nexthop(route); nbr = net_ipv6_nbr_lookup(iface, in6_addr_tmp); if (nbr) { llstorage = net_nbr_get_lladdr(nbr->idx); NET_DBG("Removing the oldest route %s " "via %s [%s]", net_sprint_ipv6_addr(&route->addr), net_sprint_ipv6_addr(in6_addr_tmp), net_sprint_ll_addr(llstorage->addr, llstorage->len)); } } net_route_del(route); nbr = nbr_new(iface, addr, prefix_len); if (!nbr) { NET_ERR("Neighbor route alloc failed!"); route = NULL; goto exit; } } tmp = get_nexthop_route(); if (!tmp) { NET_ERR("No nexthop route available!"); route = NULL; goto exit; } nexthop_route = net_nexthop_data(tmp); route = net_route_data(nbr); route->iface = iface; route->preference = preference; net_route_update_lifetime(route, lifetime); sys_slist_prepend(&routes, &route->node); tmp = nbr_nexthop_get(iface, nexthop); NET_ASSERT(tmp == nbr_nexthop); nexthop_route->nbr = tmp; sys_slist_init(&route->nexthop); sys_slist_prepend(&route->nexthop, &nexthop_route->node); net_route_info("Added", route, addr); #if defined(CONFIG_NET_MGMT_EVENT_INFO) net_ipaddr_copy(&info.addr, addr); net_ipaddr_copy(&info.nexthop, nexthop); info.prefix_len = prefix_len; net_mgmt_event_notify_with_info(NET_EVENT_IPV6_ROUTE_ADD, iface, (void *) &info, sizeof(struct net_event_ipv6_route)); #else net_mgmt_event_notify(NET_EVENT_IPV6_ROUTE_ADD, iface); #endif exit: net_ipv6_nbr_unlock(); return route; } static void route_expired(struct net_route_entry *route) { NET_DBG("Route to %s expired", net_sprint_ipv6_addr(&route->addr)); sys_slist_find_and_remove(&active_route_lifetime_timers, &route->lifetime.node); net_route_del(route); } static void route_lifetime_timeout(struct k_work *work) { uint32_t next_update = UINT32_MAX; uint32_t current_time = k_uptime_get_32(); struct net_route_entry *current, *next; ARG_UNUSED(work); net_ipv6_nbr_lock(); SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&active_route_lifetime_timers, current, next, lifetime.node) { struct net_timeout *timeout = ¤t->lifetime; uint32_t this_update = net_timeout_evaluate(timeout, current_time); if (this_update == 0U) { route_expired(current); continue; } if (this_update < next_update) { next_update = this_update; } } if (next_update != UINT32_MAX) { k_work_reschedule(&route_lifetime_timer, K_MSEC(next_update)); } net_ipv6_nbr_unlock(); } void net_route_update_lifetime(struct net_route_entry *route, uint32_t lifetime) { NET_DBG("Updating route lifetime of %s to %u secs", net_sprint_ipv6_addr(&route->addr), lifetime); if (!route) { return; } net_ipv6_nbr_lock(); if (lifetime == NET_IPV6_ND_INFINITE_LIFETIME) { route->is_infinite = true; (void)sys_slist_find_and_remove(&active_route_lifetime_timers, &route->lifetime.node); } else { route->is_infinite = false; net_timeout_set(&route->lifetime, lifetime, k_uptime_get_32()); (void)sys_slist_find_and_remove(&active_route_lifetime_timers, &route->lifetime.node); sys_slist_append(&active_route_lifetime_timers, &route->lifetime.node); k_work_reschedule(&route_lifetime_timer, K_NO_WAIT); } net_ipv6_nbr_unlock(); } int net_route_del(struct net_route_entry *route) { struct net_nbr *nbr; struct net_route_nexthop *nexthop_route; #if defined(CONFIG_NET_MGMT_EVENT_INFO) struct net_event_ipv6_route info; #endif if (!route) { return -EINVAL; } net_ipv6_nbr_lock(); #if defined(CONFIG_NET_MGMT_EVENT_INFO) net_ipaddr_copy(&info.addr, &route->addr); info.prefix_len = route->prefix_len; net_ipaddr_copy(&info.nexthop, net_route_get_nexthop(route)); net_mgmt_event_notify_with_info(NET_EVENT_IPV6_ROUTE_DEL, route->iface, (void *) &info, sizeof(struct net_event_ipv6_route)); #else net_mgmt_event_notify(NET_EVENT_IPV6_ROUTE_DEL, route->iface); #endif if (!route->is_infinite) { sys_slist_find_and_remove(&active_route_lifetime_timers, &route->lifetime.node); if (sys_slist_is_empty(&active_route_lifetime_timers)) { k_work_cancel_delayable(&route_lifetime_timer); } } sys_slist_find_and_remove(&routes, &route->node); nbr = net_route_get_nbr(route); if (!nbr) { net_ipv6_nbr_unlock(); return -ENOENT; } net_route_info("Deleted", route, &route->addr); SYS_SLIST_FOR_EACH_CONTAINER(&route->nexthop, nexthop_route, node) { if (!nexthop_route->nbr) { continue; } nbr_nexthop_put(nexthop_route->nbr); release_nexthop_route(nexthop_route); } nbr_free(nbr); net_ipv6_nbr_unlock(); return 0; } int net_route_del_by_nexthop(struct net_if *iface, struct in6_addr *nexthop) { int count = 0, status = 0; struct net_nbr *nbr_nexthop; struct net_route_nexthop *nexthop_route; int i, ret; NET_ASSERT(iface); NET_ASSERT(nexthop); net_ipv6_nbr_lock(); nbr_nexthop = net_ipv6_nbr_lookup(iface, nexthop); for (i = 0; i < CONFIG_NET_MAX_ROUTES; i++) { struct net_nbr *nbr = get_nbr(i); struct net_route_entry *route = net_route_data(nbr); if (!route) { continue; } SYS_SLIST_FOR_EACH_CONTAINER(&route->nexthop, nexthop_route, node) { if (nexthop_route->nbr == nbr_nexthop) { /* This route contains this nexthop */ ret = net_route_del(route); if (!ret) { count++; } else { status = ret; } break; } } } net_ipv6_nbr_unlock(); if (count) { return count; } else if (status < 0) { return status; } return 0; } struct in6_addr *net_route_get_nexthop(struct net_route_entry *route) { struct net_route_nexthop *nexthop_route; struct net_ipv6_nbr_data *ipv6_nbr_data; if (!route) { return NULL; } net_ipv6_nbr_lock(); SYS_SLIST_FOR_EACH_CONTAINER(&route->nexthop, nexthop_route, node) { struct in6_addr *addr; ipv6_nbr_data = net_ipv6_nbr_data(nexthop_route->nbr); if (ipv6_nbr_data) { addr = &ipv6_nbr_data->addr; NET_ASSERT(addr); net_ipv6_nbr_unlock(); return addr; } else { NET_ERR("could not get neighbor data from next hop"); } } net_ipv6_nbr_unlock(); return NULL; } int net_route_foreach(net_route_cb_t cb, void *user_data) { int i, ret = 0; net_ipv6_nbr_lock(); for (i = 0; i < CONFIG_NET_MAX_ROUTES; i++) { struct net_route_entry *route; struct net_nbr *nbr; nbr = get_nbr(i); if (!nbr) { continue; } if (!nbr->ref) { continue; } route = net_route_data(nbr); if (!route) { continue; } cb(route, user_data); ret++; } net_ipv6_nbr_unlock(); return ret; } #if defined(CONFIG_NET_ROUTE_MCAST) /* * This array contains multicast routing entries. */ static struct net_route_entry_mcast route_mcast_entries[CONFIG_NET_MAX_MCAST_ROUTES]; static int mcast_route_iface_lookup(struct net_route_entry_mcast *entry, struct net_if *iface) { ARRAY_FOR_EACH(entry->ifaces, i) { if (entry->ifaces[i] == iface) { return i; } } return -1; } bool net_route_mcast_iface_add(struct net_route_entry_mcast *entry, struct net_if *iface) { if (!net_if_flag_is_set(iface, NET_IF_FORWARD_MULTICASTS)) { return false; } if (mcast_route_iface_lookup(entry, iface) >= 0) { /* Interface is already added */ return true; } ARRAY_FOR_EACH(entry->ifaces, i) { if (entry->ifaces[i] == NULL) { entry->ifaces[i] = iface; return true; } } /* There are no empty slots */ return false; } bool net_route_mcast_iface_del(struct net_route_entry_mcast *entry, struct net_if *iface) { int pos = mcast_route_iface_lookup(entry, iface); if (pos < 0) { return false; } entry->ifaces[pos] = NULL; return true; } #if defined(CONFIG_NET_MCAST_ROUTE_MLD_REPORTS) struct mcast_route_mld_event { struct in6_addr *addr; uint8_t mode; }; static void send_mld_event(struct net_if *iface, void *user_data) { struct mcast_route_mld_event *event = (struct mcast_route_mld_event *)user_data; /* Do not send events for ifaces without IPv6, without MLD, already or still in * a given group */ if (!iface->config.ip.ipv6 || net_if_flag_is_set(iface, NET_IF_IPV6_NO_MLD) || net_if_ipv6_maddr_lookup(event->addr, &iface)) { return; } net_ipv6_mld_send_single(iface, event->addr, event->mode); } static void propagate_mld_event(struct net_route_entry_mcast *route, bool route_added) { struct mcast_route_mld_event mld_event; /* Apply only for complete addresses */ if (route->prefix_len == 128) { mld_event.addr = &route->group; mld_event.mode = route_added ? NET_IPV6_MLDv2_CHANGE_TO_EXCLUDE_MODE : NET_IPV6_MLDv2_CHANGE_TO_INCLUDE_MODE; net_if_foreach(send_mld_event, &mld_event); } } #else #define propagate_mld_event(...) #endif /* CONFIG_NET_MCAST_ROUTE_MLD_REPORTS */ int net_route_mcast_forward_packet(struct net_pkt *pkt, struct net_ipv6_hdr *hdr) { int ret = 0, err = 0; /* At this point, the original pkt has already stored the hop limit in its metadata. * Change its value in a common buffer so the forwardee has a proper count. As we have * a direct access to the buffer there is no need to perform read/write operations. */ hdr->hop_limit--; ARRAY_FOR_EACH_PTR(route_mcast_entries, route) { struct net_pkt *pkt_cpy = NULL; if (!route->is_used || !net_ipv6_is_prefix(hdr->dst, route->group.s6_addr, route->prefix_len)) { continue; } ARRAY_FOR_EACH(route->ifaces, i) { if (!route->ifaces[i] || pkt->iface == route->ifaces[i] || !net_if_flag_is_set(route->ifaces[i], NET_IF_FORWARD_MULTICASTS)) { continue; } pkt_cpy = net_pkt_shallow_clone(pkt, K_NO_WAIT); if (pkt_cpy == NULL) { err--; continue; } net_pkt_set_forwarding(pkt_cpy, true); net_pkt_set_orig_iface(pkt_cpy, pkt->iface); net_pkt_set_iface(pkt_cpy, route->ifaces[i]); if (net_send_data(pkt_cpy) >= 0) { ++ret; } else { net_pkt_unref(pkt_cpy); --err; } } } return (err == 0) ? ret : err; } int net_route_mcast_foreach(net_route_mcast_cb_t cb, struct in6_addr *skip, void *user_data) { int ret = 0; ARRAY_FOR_EACH_PTR(route_mcast_entries, route) { if (route->is_used) { if (skip && net_ipv6_is_prefix(skip->s6_addr, route->group.s6_addr, route->prefix_len)) { continue; } cb(route, user_data); ret++; } } return ret; } struct net_route_entry_mcast *net_route_mcast_add(struct net_if *iface, struct in6_addr *group, uint8_t prefix_len) { net_ipv6_nbr_lock(); if ((!net_if_flag_is_set(iface, NET_IF_FORWARD_MULTICASTS)) || (!net_ipv6_is_addr_mcast(group)) || (net_ipv6_is_addr_mcast_iface(group)) || (net_ipv6_is_addr_mcast_link(group))) { net_ipv6_nbr_unlock(); return NULL; } ARRAY_FOR_EACH_PTR(route_mcast_entries, route) { if (!route->is_used) { net_ipaddr_copy(&route->group, group); ARRAY_FOR_EACH(route->ifaces, i) { route->ifaces[i] = NULL; } route->prefix_len = prefix_len; route->ifaces[0] = iface; route->is_used = true; propagate_mld_event(route, true); net_ipv6_nbr_unlock(); return route; } } net_ipv6_nbr_unlock(); return NULL; } bool net_route_mcast_del(struct net_route_entry_mcast *route) { if (route > &route_mcast_entries[CONFIG_NET_MAX_MCAST_ROUTES - 1] || route < &route_mcast_entries[0]) { return false; } NET_ASSERT(route->is_used, "Multicast route %p to %s was already removed", route, net_sprint_ipv6_addr(&route->group)); propagate_mld_event(route, false); route->is_used = false; return true; } struct net_route_entry_mcast * net_route_mcast_lookup(struct in6_addr *group) { ARRAY_FOR_EACH_PTR(route_mcast_entries, route) { if (!route->is_used) { continue; } if (net_ipv6_is_prefix(group->s6_addr, route->group.s6_addr, route->prefix_len)) { return route; } } return NULL; } #endif /* CONFIG_NET_ROUTE_MCAST */ bool net_route_get_info(struct net_if *iface, struct in6_addr *dst, struct net_route_entry **route, struct in6_addr **nexthop) { struct net_if_router *router; bool ret = false; net_ipv6_nbr_lock(); /* Search in neighbor table first, if not search in routing table. */ if (net_ipv6_nbr_lookup(iface, dst)) { /* Found nexthop, no need to look into routing table. */ *route = NULL; *nexthop = dst; ret = true; goto exit; } *route = net_route_lookup(iface, dst); if (*route) { *nexthop = net_route_get_nexthop(*route); if (!*nexthop) { goto exit; } ret = true; goto exit; } else { /* No specific route to this host, use the default * route instead. */ router = net_if_ipv6_router_find_default(NULL, dst); if (!router) { goto exit; } *nexthop = &router->address.in6_addr; ret = true; goto exit; } exit: net_ipv6_nbr_unlock(); return ret; } static bool is_ll_addr_supported(struct net_if *iface) { #if defined(CONFIG_NET_L2_DUMMY) if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { return false; } #endif #if defined(CONFIG_NET_L2_PPP) if (net_if_l2(iface) == &NET_L2_GET_NAME(PPP)) { return false; } #endif #if defined(CONFIG_NET_L2_OPENTHREAD) if (net_if_l2(iface) == &NET_L2_GET_NAME(OPENTHREAD)) { return false; } #endif return true; } int net_route_packet(struct net_pkt *pkt, struct in6_addr *nexthop) { struct net_linkaddr_storage *lladdr = NULL; struct net_nbr *nbr; int err; net_ipv6_nbr_lock(); nbr = net_ipv6_nbr_lookup(NULL, nexthop); if (!nbr) { NET_DBG("Cannot find %s neighbor", net_sprint_ipv6_addr(nexthop)); err = -ENOENT; goto error; } if (is_ll_addr_supported(nbr->iface) && is_ll_addr_supported(net_pkt_iface(pkt)) && is_ll_addr_supported(net_pkt_orig_iface(pkt))) { lladdr = net_nbr_get_lladdr(nbr->idx); if (!lladdr) { NET_DBG("Cannot find %s neighbor link layer address.", net_sprint_ipv6_addr(nexthop)); err = -ESRCH; goto error; } if (!net_pkt_lladdr_src(pkt)->addr) { NET_DBG("Link layer source address not set"); err = -EINVAL; goto error; } /* Sanitycheck: If src and dst ll addresses are going * to be same, then something went wrong in route * lookup. */ if (!memcmp(net_pkt_lladdr_src(pkt)->addr, lladdr->addr, lladdr->len)) { NET_ERR("Src ll and Dst ll are same"); err = -EINVAL; goto error; } } net_pkt_set_forwarding(pkt, true); /* Set the source ll address of the iface (if relevant) and the * destination address to be the nexthop recipient. */ if (is_ll_addr_supported(net_pkt_iface(pkt))) { net_pkt_lladdr_src(pkt)->addr = net_pkt_lladdr_if(pkt)->addr; net_pkt_lladdr_src(pkt)->type = net_pkt_lladdr_if(pkt)->type; net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_if(pkt)->len; } if (lladdr) { net_pkt_lladdr_dst(pkt)->addr = lladdr->addr; net_pkt_lladdr_dst(pkt)->type = lladdr->type; net_pkt_lladdr_dst(pkt)->len = lladdr->len; } net_pkt_set_iface(pkt, nbr->iface); net_ipv6_nbr_unlock(); return net_send_data(pkt); error: net_ipv6_nbr_unlock(); return err; } int net_route_packet_if(struct net_pkt *pkt, struct net_if *iface) { /* The destination is reachable via iface. But since no valid nexthop * is known, net_pkt_lladdr_dst(pkt) cannot be set here. */ net_pkt_set_orig_iface(pkt, net_pkt_iface(pkt)); net_pkt_set_iface(pkt, iface); net_pkt_set_forwarding(pkt, true); /* Set source LL address if only if relevant */ if (is_ll_addr_supported(iface)) { net_pkt_lladdr_src(pkt)->addr = net_pkt_lladdr_if(pkt)->addr; net_pkt_lladdr_src(pkt)->type = net_pkt_lladdr_if(pkt)->type; net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_if(pkt)->len; } return net_send_data(pkt); } void net_route_init(void) { NET_DBG("Allocated %d routing entries (%zu bytes)", CONFIG_NET_MAX_ROUTES, sizeof(net_route_entries_pool)); NET_DBG("Allocated %d nexthop entries (%zu bytes)", CONFIG_NET_MAX_NEXTHOPS, sizeof(net_route_nexthop_pool)); #if defined(CONFIG_NET_ROUTE_MCAST) memset(route_mcast_entries, 0, sizeof(route_mcast_entries)); #endif k_work_init_delayable(&route_lifetime_timer, route_lifetime_timeout); }