zephyr/subsys/net/ip/route.c
Florian Vaussard 1f23d2681f ip: route: disable LL address check when using dummy L2
The dummy L2 does not setup the link layer address. Do not check the
source and destination link layer addresses when routing packets
otherwise packet routing will not work when using a dummy L2.

Signed-off-by: Florian Vaussard <florian.vaussard@gmail.com>
Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
2018-08-02 16:42:47 +03:00

833 lines
18 KiB
C

/** @file
* @brief Route handling.
*
*/
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_ROUTE)
#define SYS_LOG_DOMAIN "net/route"
#define NET_LOG_ENABLED 1
#endif
#include <kernel.h>
#include <limits.h>
#include <zephyr/types.h>
#include <misc/slist.h>
#include <net/net_pkt.h>
#include <net/net_core.h>
#include <net/net_stats.h>
#include <net/net_mgmt.h>
#include <net/net_ip.h>
#include "net_private.h"
#include "ipv6.h"
#include "icmpv6.h"
#include "nbr.h"
#include "route.h"
#include "rpl.h"
#if !defined(NET_ROUTE_EXTRA_DATA_SIZE)
#define NET_ROUTE_EXTRA_DATA_SIZE 0
#endif
/* 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;
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,
0);
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_INFO(idx < CONFIG_NET_MAX_NEXTHOPS, "idx %d >= max %d",
idx, CONFIG_NET_MAX_NEXTHOPS);
return (struct net_nbr *)((void *)start +
((sizeof(struct net_nbr) + start->size) * idx));
}
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_ROUTE_EXTRA_DATA_SIZE);
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)
{
int i;
NET_ASSERT(route);
for (i = 0; i < CONFIG_NET_MAX_ROUTES; i++) {
struct net_nbr *nbr = get_nbr(i);
if (!nbr->ref) {
continue;
}
if (nbr->data == (u8_t *)route) {
if (!nbr->ref) {
return NULL;
}
return nbr;
}
}
return NULL;
}
#if defined(CONFIG_NET_DEBUG_ROUTE)
void net_routes_print(void)
{
int i;
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 iface %p idx %d "
"ll %s",
i, nbr, nbr->ref,
net_sprint_ipv6_addr(&net_route_data(nbr)->addr),
net_route_data(nbr)->prefix_len,
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));
}
}
#else
#define net_routes_print(...)
#endif /* CONFIG_NET_DEBUG_ROUTE */
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,
u8_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 = net_ipv6_nbr_lookup(iface, addr);
NET_ASSERT_INFO(nbr, "Next hop neighbor not found!");
NET_ASSERT_INFO(nbr->idx != NET_NBR_LLADDR_UNKNOWN,
"Nexthop %s not in neighbor cache!",
net_sprint_ipv6_addr(addr));
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;
}
#if defined(CONFIG_NET_DEBUG_ROUTE)
#define net_route_info(str, route, dst) \
do { \
char out[NET_IPV6_ADDR_LEN]; \
struct in6_addr *naddr = net_route_get_nexthop(route); \
\
NET_ASSERT_INFO(naddr, "Unknown nexthop address"); \
\
snprintk(out, sizeof(out), "%s", \
net_sprint_ipv6_addr(dst)); \
NET_DBG("%s route to %s via %s (iface %p)", str, out, \
net_sprint_ipv6_addr(naddr), route->iface); \
} while (0)
#else
#define net_route_info(...)
#endif /* CONFIG_NET_DEBUG_ROUTE */
/* 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;
u8_t longest_match = 0;
int i;
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_is_ipv6_prefix((u8_t *)dst,
(u8_t *)&route->addr,
route->prefix_len)) {
found = route;
longest_match = route->prefix_len;
}
}
if (found) {
net_route_info("Found", found, dst);
update_route_access(found);
}
return found;
}
struct net_route_entry *net_route_add(struct net_if *iface,
struct in6_addr *addr,
u8_t prefix_len,
struct in6_addr *nexthop)
{
struct net_linkaddr_storage *nexthop_lladdr;
struct net_nbr *nbr, *nbr_nexthop, *tmp;
struct net_route_nexthop *nexthop_route;
struct net_route_entry *route;
#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;
}
nbr_nexthop = net_ipv6_nbr_lookup(iface, nexthop);
if (!nbr_nexthop) {
NET_DBG("No such neighbor %s found",
net_sprint_ipv6_addr(nexthop));
return NULL;
}
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);
return route;
}
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 defined(CONFIG_NET_DEBUG_ROUTE)
do {
char out[NET_IPV6_ADDR_LEN];
struct in6_addr *tmp;
struct net_linkaddr_storage *llstorage;
snprintk(out, sizeof(out), "%s",
net_sprint_ipv6_addr(&route->addr));
tmp = net_route_get_nexthop(route);
nbr = net_ipv6_nbr_lookup(iface, tmp);
llstorage = net_nbr_get_lladdr(nbr->idx);
NET_DBG("Removing the oldest route %s via %s [%s]",
out, net_sprint_ipv6_addr(tmp),
net_sprint_ll_addr(llstorage->addr,
llstorage->len));
} while (0);
#endif /* CONFIG_NET_DEBUG_ROUTE */
net_route_del(route);
nbr = nbr_new(iface, addr, prefix_len);
if (!nbr) {
NET_ERR("Neighbor route alloc failed!");
return NULL;
}
}
tmp = get_nexthop_route();
if (!tmp) {
NET_ERR("No nexthop route available!");
return NULL;
}
nexthop_route = net_nexthop_data(tmp);
route = net_route_data(nbr);
route->iface = iface;
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
return route;
}
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;
}
#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
sys_slist_find_and_remove(&routes, &route->node);
nbr = net_route_get_nbr(route);
if (!nbr) {
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);
}
nbr_free(nbr);
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);
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;
}
}
}
if (count) {
return count;
} else if (status < 0) {
return status;
}
return 0;
}
int net_route_del_by_nexthop_data(struct net_if *iface,
struct in6_addr *nexthop,
void *data)
{
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);
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);
SYS_SLIST_FOR_EACH_CONTAINER(&route->nexthop, nexthop_route,
node) {
void *extra_data;
if (nexthop_route->nbr != nbr_nexthop) {
continue;
}
if (nbr->extra_data_size == 0) {
continue;
}
/* Routing engine specific extra data needs
* to match too.
*/
extra_data = net_nbr_extra_data(nbr_nexthop);
if (extra_data != data) {
continue;
}
ret = net_route_del(route);
if (!ret) {
count++;
} else {
status = ret;
}
break;
}
}
if (count) {
return count;
}
return status;
}
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;
}
SYS_SLIST_FOR_EACH_CONTAINER(&route->nexthop, nexthop_route, node) {
struct in6_addr *addr;
NET_ASSERT(nexthop_route->nbr->idx != NET_NBR_LLADDR_UNKNOWN);
if (nexthop_route->nbr->idx == NET_NBR_LLADDR_UNKNOWN) {
continue;
}
ipv6_nbr_data = net_ipv6_nbr_data(nexthop_route->nbr);
if (ipv6_nbr_data) {
addr = &ipv6_nbr_data->addr;
NET_ASSERT(addr);
return addr;
} else {
NET_ERR("could not get neighbor data from next hop");
}
}
return NULL;
}
int net_route_foreach(net_route_cb_t cb, void *user_data)
{
int i, ret = 0;
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;
}
route = net_route_data(nbr);
if (!route) {
continue;
}
cb(route, user_data);
ret++;
}
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];
int net_route_mcast_foreach(net_route_mcast_cb_t cb,
struct in6_addr *skip,
void *user_data)
{
int i, ret = 0;
for (i = 0; i < CONFIG_NET_MAX_MCAST_ROUTES; i++) {
struct net_route_entry_mcast *route = &route_mcast_entries[i];
if (route->is_used) {
if (skip && net_ipv6_addr_cmp(skip, &route->group)) {
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)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_MCAST_ROUTES; i++) {
struct net_route_entry_mcast *route = &route_mcast_entries[i];
if (!route->is_used) {
net_ipaddr_copy(&route->group, group);
route->iface = iface;
route->is_used = true;
return route;
}
}
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_INFO(route->is_used,
"Multicast route %p to %s was already removed", route,
net_sprint_ipv6_addr(&route->group));
route->is_used = false;
return true;
}
struct net_route_entry_mcast *
net_route_mcast_lookup(struct in6_addr *group)
{
int i;
for (i = 0; i < CONFIG_NET_MAX_MCAST_ROUTES; i++) {
struct net_route_entry_mcast *route = &route_mcast_entries[i];
if (!route->is_used) {
if (net_ipv6_addr_cmp(group, &route->group)) {
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;
/* 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;
return true;
}
*route = net_route_lookup(iface, dst);
if (*route) {
*nexthop = net_route_get_nexthop(*route);
if (!*nexthop) {
return false;
}
return true;
} else {
/* No specific route to this host, use the default
* route instead.
*/
router = net_if_ipv6_router_find_default(NULL, dst);
if (!router) {
return false;
}
*nexthop = &router->address.in6_addr;
return true;
}
return false;
}
int net_route_packet(struct net_pkt *pkt, struct in6_addr *nexthop)
{
struct net_linkaddr_storage *lladdr;
struct net_nbr *nbr;
nbr = net_ipv6_nbr_lookup(NULL, nexthop);
if (!nbr) {
NET_DBG("Cannot find %s neighbor",
net_sprint_ipv6_addr(nexthop));
return -ENOENT;
}
lladdr = net_nbr_get_lladdr(nbr->idx);
if (!lladdr) {
NET_DBG("Cannot find %s neighbor link layer address.",
net_sprint_ipv6_addr(nexthop));
return -ESRCH;
}
#if defined(CONFIG_NET_L2_DUMMY)
/* No need to do this check for dummy L2 as it does not have any
* link layer. This is done at runtime because we can have multiple
* network technologies enabled.
*/
if (net_if_l2(net_pkt_iface(pkt)) != &NET_L2_GET_NAME(DUMMY)) {
#endif
if (!net_pkt_ll_src(pkt)->addr) {
NET_DBG("Link layer source address not set");
return -EINVAL;
}
/* Sanitycheck: If src and dst ll addresses are going to be
* same, then something went wrong in route lookup.
*/
if (!memcmp(net_pkt_ll_src(pkt)->addr, lladdr->addr,
lladdr->len)) {
NET_ERR("Src ll and Dst ll are same");
return -EINVAL;
}
#if defined(CONFIG_NET_L2_DUMMY)
}
#endif
net_pkt_set_forwarding(pkt, true);
/* Set the destination and source ll address in the packet.
* We set the destination address to be the nexthop recipient.
*/
net_pkt_ll_src(pkt)->addr = net_pkt_ll_if(pkt)->addr;
net_pkt_ll_src(pkt)->type = net_pkt_ll_if(pkt)->type;
net_pkt_ll_src(pkt)->len = net_pkt_ll_if(pkt)->len;
net_pkt_ll_dst(pkt)->addr = lladdr->addr;
net_pkt_ll_dst(pkt)->type = lladdr->type;
net_pkt_ll_dst(pkt)->len = lladdr->len;
net_pkt_set_iface(pkt, nbr->iface);
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));
}