diff --git a/include/net/net_if.h b/include/net/net_if.h index d0f0d67b97..8bf1caa9d8 100644 --- a/include/net/net_if.h +++ b/include/net/net_if.h @@ -181,6 +181,12 @@ enum net_if_flag { /** Power management specific: interface is being suspended */ NET_IF_SUSPENDED, + /** Flag defines if received multicasts of other interface are + * forwarded on this interface. This activates multicast + * routing / forwarding for this interface. + */ + NET_IF_FORWARD_MULTICASTS, + /** @cond INTERNAL_HIDDEN */ /* Total number of flags - must be at the end of the enum */ NET_IF_NUM_FLAGS diff --git a/include/net/net_ip.h b/include/net/net_ip.h index 18832a35d0..4c62d5b3f9 100644 --- a/include/net/net_ip.h +++ b/include/net/net_ip.h @@ -904,13 +904,29 @@ static inline bool net_ipv6_is_addr_mcast_scope(const struct in6_addr *addr, return (addr->s6_addr[0] == 0xff) && (addr->s6_addr[1] == scope); } +/** + * @brief Check if the IPv6 addresses have the same multicast scope (FFyx::). + * + * @param addr_1 IPv6 address 1 + * @param addr_2 IPv6 address 2 + * + * @return True if both addresses have same multicast scope, + * false otherwise. + */ +static inline bool net_ipv6_is_same_mcast_scope(const struct in6_addr *addr_1, + const struct in6_addr *addr_2) +{ + return (addr_1->s6_addr[0] == 0xff) && (addr_2->s6_addr[0] == 0xff) && + (addr_1->s6_addr[1] == addr_2->s6_addr[1]); +} + /** * @brief Check if the IPv6 address is a global multicast address (FFxE::/16). * * @param addr IPv6 address. * * @return True if the address is global multicast address, false otherwise. -*/ + */ static inline bool net_ipv6_is_addr_mcast_global(const struct in6_addr *addr) { return net_ipv6_is_addr_mcast_scope(addr, 0x0e); @@ -930,6 +946,20 @@ static inline bool net_ipv6_is_addr_mcast_iface(const struct in6_addr *addr) return net_ipv6_is_addr_mcast_scope(addr, 0x01); } +/** + * @brief Check if the IPv6 address is a link local scope multicast + * address (FFx2::). + * + * @param addr IPv6 address. + * + * @return True if the address is a link local scope multicast address, + * false otherwise. + */ +static inline bool net_ipv6_is_addr_mcast_link(const struct in6_addr *addr) +{ + return net_ipv6_is_addr_mcast_scope(addr, 0x02); +} + /** * @brief Check if the IPv6 address is a mesh-local scope multicast * address (FFx3::). diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index e2fc46c0d9..f2fbe32c8d 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -265,8 +265,10 @@ config NET_MAX_NEXTHOPS This determines how many entries can be stored in nexthop table. config NET_ROUTE_MCAST - bool + bool "Enable Multicast Routing / Forwarding" depends on NET_ROUTE + help + Activates multicast routing/forwarding config NET_MAX_MCAST_ROUTES int "Max number of multicast routing entries stored." diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c index fc54fa2e57..440e52cffb 100644 --- a/subsys/net/ip/ipv6.c +++ b/subsys/net/ip/ipv6.c @@ -362,6 +362,31 @@ static inline enum net_verdict ipv6_route_packet(struct net_pkt *pkt, #endif /* CONFIG_NET_ROUTE */ + +static enum net_verdict ipv6_forward_mcast_packet(struct net_pkt *pkt, + struct net_ipv6_hdr *hdr) +{ +#if defined(CONFIG_NET_ROUTE_MCAST) + int routed; + + /* check if routing loop could be created or if the destination is of + * interface local scope or if from link local source + */ + if (net_ipv6_is_addr_mcast(&hdr->src) || + net_ipv6_is_addr_mcast_iface(&hdr->dst) || + net_ipv6_is_ll_addr(&hdr->src)) { + return NET_CONTINUE; + } + + routed = net_route_mcast_forward_packet(pkt, hdr); + + if (routed < 0) { + return NET_DROP; + } +#endif /*CONFIG_NET_ROUTE_MCAST*/ + return NET_CONTINUE; +} + enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); @@ -443,6 +468,20 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) goto drop; } + if (IS_ENABLED(CONFIG_NET_ROUTE_MCAST) && + net_ipv6_is_addr_mcast(&hdr->dst)) { + /* If the packet is a multicast packet and multicast routing + * is activated, we give the packet to the routing engine. + * + * But we only drop the packet if an error occurs, otherwise + * it might be eminent to respond on the packet on application + * layer. + */ + if (ipv6_forward_mcast_packet(pkt, hdr) == NET_DROP) { + goto drop; + } + } + /* If we receive a packet with ll source address fe80: and destination * address is one of ours, and if the packet would cross interface * boundary, then drop the packet. RFC 4291 ch 2.5.6 diff --git a/subsys/net/ip/net_shell.c b/subsys/net/ip/net_shell.c index 90f5019d6e..e790732a14 100644 --- a/subsys/net/ip/net_shell.c +++ b/subsys/net/ip/net_shell.c @@ -641,8 +641,9 @@ static void route_mcast_cb(struct net_route_entry_mcast *entry, PR("===========================================================" "%s\n", extra); - PR("IPv6 group : %s\n", net_sprint_ipv6_addr(&entry->group)); - PR("Lifetime : %u\n", entry->lifetime); + PR("IPv6 group : %s\n", net_sprint_ipv6_addr(&entry->group)); + PR("IPv6 group len : %d\n", entry->prefix_len); + PR("Lifetime : %u\n", entry->lifetime); } static void iface_per_mcast_route_cb(struct net_if *iface, void *user_data) diff --git a/subsys/net/ip/route.c b/subsys/net/ip/route.c index c361f36c20..6a4b478604 100644 --- a/subsys/net/ip/route.c +++ b/subsys/net/ip/route.c @@ -644,6 +644,48 @@ int net_route_foreach(net_route_cb_t cb, void *user_data) static struct net_route_entry_mcast route_mcast_entries[CONFIG_NET_MAX_MCAST_ROUTES]; +int net_route_mcast_forward_packet(struct net_pkt *pkt, + const struct net_ipv6_hdr *hdr) +{ + int i, ret = 0, err = 0; + + for (i = 0; i < CONFIG_NET_MAX_MCAST_ROUTES; ++i) { + struct net_route_entry_mcast *route = &route_mcast_entries[i]; + struct net_pkt *pkt_cpy = NULL; + + if (!route->is_used) { + continue; + } + + if (!net_if_flag_is_set(route->iface, + NET_IF_FORWARD_MULTICASTS) || + !net_ipv6_is_prefix(hdr->dst.s6_addr, + route->group.s6_addr, + route->prefix_len) || + (pkt->iface == route->iface)) { + 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_iface(pkt_cpy, route->iface); + + if (net_send_data(pkt_cpy) >= 0) { + ++ret; + } else { + --err; + } + } + + return (err == 0) ? ret : err; +} + int net_route_mcast_foreach(net_route_mcast_cb_t cb, struct in6_addr *skip, void *user_data) @@ -654,7 +696,9 @@ int net_route_mcast_foreach(net_route_mcast_cb_t cb, struct net_route_entry_mcast *route = &route_mcast_entries[i]; if (route->is_used) { - if (skip && net_ipv6_addr_cmp(skip, &route->group)) { + if (skip && net_ipv6_is_prefix(skip->s6_addr, + route->group.s6_addr, + route->prefix_len)) { continue; } @@ -668,16 +712,25 @@ int net_route_mcast_foreach(net_route_mcast_cb_t cb, } struct net_route_entry_mcast *net_route_mcast_add(struct net_if *iface, - struct in6_addr *group) + struct in6_addr *group, + uint8_t prefix_len) { int i; + 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))) { + return NULL; + } + 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->prefix_len = prefix_len; route->iface = iface; route->is_used = true; @@ -713,9 +766,13 @@ net_route_mcast_lookup(struct in6_addr *group) struct net_route_entry_mcast *route = &route_mcast_entries[i]; if (!route->is_used) { - if (net_ipv6_addr_cmp(group, &route->group)) { - return route; - } + continue; + } + + if (net_ipv6_is_prefix(group->s6_addr, + route->group.s6_addr, + route->prefix_len)) { + return route; } } diff --git a/subsys/net/ip/route.h b/subsys/net/ip/route.h index 1bd54def94..9fe1c0c190 100644 --- a/subsys/net/ip/route.h +++ b/subsys/net/ip/route.h @@ -186,13 +186,29 @@ struct net_route_entry_mcast { /** Routing entry lifetime in seconds. */ uint32_t lifetime; - /** Is this entry in user or not */ + /** Is this entry in use or not */ bool is_used; + + /** IPv6 multicast group prefix length. */ + uint8_t prefix_len; }; typedef void (*net_route_mcast_cb_t)(struct net_route_entry_mcast *entry, void *user_data); +/** + * @brief Forwards a multicast packet by checking the local multicast + * routing table + * + * @param pkt The original received ipv6 packet to forward + * @param hdr The IPv6 header of the packet + * + * @return Number of interfaces which forwarded the packet, or a negative + * value in case of an error. + */ +int net_route_mcast_forward_packet(struct net_pkt *pkt, + const struct net_ipv6_hdr *hdr); + /** * @brief Go through all the multicast routing entries and call callback * for each entry that is in use. @@ -212,11 +228,13 @@ int net_route_mcast_foreach(net_route_mcast_cb_t cb, * * @param iface Network interface to use. * @param group IPv6 multicast address. + * @param prefix_len Length of the IPv6 group that must match. * * @return Multicast routing entry. */ struct net_route_entry_mcast *net_route_mcast_add(struct net_if *iface, - struct in6_addr *group); + struct in6_addr *group, + uint8_t prefix_len); /** * @brief Delete a multicast routing entry. diff --git a/tests/net/route_mcast/CMakeLists.txt b/tests/net/route_mcast/CMakeLists.txt new file mode 100644 index 0000000000..84c48eacb9 --- /dev/null +++ b/tests/net/route_mcast/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(route_mcast) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/route_mcast/prj.conf b/tests/net/route_mcast/prj.conf new file mode 100644 index 0000000000..c655d805cd --- /dev/null +++ b/tests/net/route_mcast/prj.conf @@ -0,0 +1,25 @@ +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_NET_IPV6=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=n +CONFIG_NET_IPV4=n +CONFIG_NET_MAX_CONTEXTS=4 +CONFIG_NET_L2_DUMMY=y +CONFIG_NET_LOG=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_IPV6_DAD=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_PKT_TX_COUNT=10 +CONFIG_NET_PKT_RX_COUNT=5 +CONFIG_NET_BUF_RX_COUNT=5 +CONFIG_NET_BUF_TX_COUNT=5 +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=6 +CONFIG_NET_IF_MAX_IPV6_COUNT=10 +CONFIG_NET_MAX_ROUTES=4 +CONFIG_NET_MAX_NEXTHOPS=8 +CONFIG_NET_IPV6_MAX_NEIGHBORS=8 +CONFIG_ZTEST=y +CONFIG_NET_MAX_MCAST_ROUTES=10 +CONFIG_NET_ROUTE_MCAST=y diff --git a/tests/net/route_mcast/src/main.c b/tests/net/route_mcast/src/main.c new file mode 100644 index 0000000000..a6e7a1ab97 --- /dev/null +++ b/tests/net/route_mcast/src/main.c @@ -0,0 +1,665 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2020 Lemonbeat GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_ROUTE_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define NET_LOG_ENABLED 1 +#include "net_private.h" +#include "icmpv6.h" +#include "ipv6.h" +#include +#include "udp_internal.h" +#include "nbr.h" +#include "route.h" + +#if defined(CONFIG_NET_ROUTE_LOG_LEVEL_DBG) +#define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) +#else +#define DBG(fmt, ...) +#endif + +static struct in6_addr iface_1_addr = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; + +static struct in6_addr iface_2_addr = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0x0b, 0x0e, 0x0e, 0x3 } } }; + +static struct in6_addr iface_3_addr = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0x0e, 0x0e, 0x0e, 0x4 } } }; + +/* Extra address is assigned to ll_addr */ +static struct in6_addr ll_addr_1 = { { { 0xfe, 0x80, 0x43, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0xf2, + 0xaa, 0x29, 0x02, 0x04 } } }; + +static struct in6_addr ll_addr_2 = { { { 0xfe, 0x80, 0x43, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0xf2, + 0xaa, 0x29, 0x05, 0x06 } } }; + +static struct in6_addr ll_addr_3 = { { { 0xfe, 0x80, 0x43, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0xf2, + 0xaa, 0x29, 0x07, 0x08 } } }; + +static struct in6_addr in6addr_mcast = { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; + +static struct net_if *iface_1; +static struct net_if *iface_2; +static struct net_if *iface_3; + +#define WAIT_TIME K_MSEC(50) + +struct net_route_mcast_iface_cfg { + uint8_t mac_addr[sizeof(struct net_eth_addr)]; + struct net_linkaddr ll_addr; +}; + +#define MAX_MCAST_ROUTES CONFIG_NET_MAX_MCAST_ROUTES + +static struct net_route_entry_mcast *test_mcast_routes[MAX_MCAST_ROUTES]; + +static struct in6_addr mcast_prefix_iflocal = { { { + 0xFF, 0x01, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +static struct in6_addr mcast_prefix_llocal = { { { + 0xFF, 0x02, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +static struct in6_addr mcast_prefix_admin = { { { + 0xFF, 0x04, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +static struct in6_addr mcast_prefix_site_local = { { { + 0xFF, 0x05, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +static struct in6_addr mcast_prefix_orga = { { { + 0xFF, 0x08, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +static struct in6_addr mcast_prefix_global = { { { + 0xFF, 0x0E, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; +/* + * full network prefix based address, + * see RFC-3306 for details + * FF3F:40:FD01:101:: \128 + * network prefix FD01:101::\64 + */ +static struct in6_addr mcast_prefix_nw_based = { { { + 0xFF, 0x3F, 0, 0x40, + 0xFD, 0x01, 0x01, 0x01, + 0, 0, 0, 0, + 0, 0, 0, 0 } } }; + +static uint8_t forwarding_counter; +static bool iface_1_forwarded; +static bool iface_2_forwarded; +static bool iface_3_forwarded; + +struct net_route_mcast_scenario_cfg { + struct in6_addr src; + struct in6_addr mcast; + bool is_active; +}; + +static struct net_route_mcast_scenario_cfg active_scenario; + +int net_route_mcast_dev_init(struct device *dev) +{ + return 0; +} + +static uint8_t *net_route_mcast_get_mac(struct device *dev) +{ + struct net_route_mcast_iface_cfg *cfg = dev->data; + + if (cfg->mac_addr[2] == 0x00) { + /* 00-00-5E-00-53-xx Documentation RFC 7042 */ + cfg->mac_addr[0] = 0x00; + cfg->mac_addr[1] = 0x00; + cfg->mac_addr[2] = 0x5E; + cfg->mac_addr[3] = 0x00; + cfg->mac_addr[4] = 0x53; + cfg->mac_addr[5] = sys_rand32_get(); + } + + cfg->ll_addr.addr = cfg->mac_addr; + cfg->ll_addr.len = 6U; + + return cfg->mac_addr; +} + +static void net_route_mcast_add_addresses(struct net_if *iface, + struct in6_addr *ipv6, struct in6_addr *ll_addr) +{ + struct net_if_mcast_addr *maddr; + struct net_if_addr *ifaddr; + + uint8_t *mac = net_route_mcast_get_mac(net_if_get_device(iface)); + + net_if_set_link_addr(iface, mac, sizeof(struct net_eth_addr), + NET_LINK_ETHERNET); + + ifaddr = net_if_ipv6_addr_add(iface, ipv6, NET_ADDR_MANUAL, 0); + zassert_not_null(ifaddr, "Cannot add global IPv6 address"); + + ifaddr->addr_state = NET_ADDR_PREFERRED; + + ifaddr = net_if_ipv6_addr_add(iface, ll_addr, NET_ADDR_MANUAL, 0); + zassert_not_null(ifaddr, "Cannot add ll IPv6 address"); + + ifaddr->addr_state = NET_ADDR_PREFERRED; + + maddr = net_if_ipv6_maddr_add(iface, &in6addr_mcast); + zassert_not_null(maddr, "Cannot add multicast IPv6 address"); +} + +static void net_route_mcast_iface_init1(struct net_if *iface) +{ + iface_1 = iface; + net_route_mcast_add_addresses(iface, &iface_1_addr, &ll_addr_1); +} + +static void net_route_mcast_iface_init2(struct net_if *iface) +{ + iface_2 = iface; + net_route_mcast_add_addresses(iface, &iface_2_addr, &ll_addr_2); +} + +static void net_route_mcast_iface_init3(struct net_if *iface) +{ + iface_3 = iface; + net_route_mcast_add_addresses(iface, &iface_3_addr, &ll_addr_3); +} + +static bool check_packet_addresses(struct net_pkt *pkt) +{ + struct net_ipv6_hdr *ipv6_hdr = NET_IPV6_HDR(pkt); + + if ((memcmp(&active_scenario.src, + &ipv6_hdr->src, + sizeof(struct in6_addr)) != 0) || + (memcmp(&active_scenario.mcast, + &ipv6_hdr->dst, + sizeof(struct in6_addr)) != 0)) { + return false; + } + + return true; +} + +static int iface_send(struct device *dev, struct net_pkt *pkt) +{ + if (!active_scenario.is_active) { + return 0; + } + if (!check_packet_addresses(pkt)) { + return 0; + } + + forwarding_counter++; + + if (net_pkt_iface(pkt) == iface_1) { + iface_1_forwarded = true; + } else if (net_pkt_iface(pkt) == iface_2) { + iface_2_forwarded = true; + } else if (net_pkt_iface(pkt) == iface_3) { + iface_3_forwarded = true; + } + + return 0; +} + +struct net_route_mcast_iface_cfg net_route_data_if1; +struct net_route_mcast_iface_cfg net_route_data_if2; +struct net_route_mcast_iface_cfg net_route_data_if3; + +static struct dummy_api net_route_mcast_if_api_1 = { + .iface_api.init = net_route_mcast_iface_init1, + .send = iface_send, +}; + +static struct dummy_api net_route_mcast_if_api_2 = { + .iface_api.init = net_route_mcast_iface_init2, + .send = iface_send, +}; + +static struct dummy_api net_route_mcast_if_api_3 = { + .iface_api.init = net_route_mcast_iface_init3, + .send = iface_send, +}; + +#define _ETH_L2_LAYER DUMMY_L2 +#define _ETH_L2_CTX_TYPE NET_L2_GET_CTX_TYPE(DUMMY_L2) + +NET_DEVICE_INIT_INSTANCE(mcast_iface_1, "mcast_iface_1", iface_1, + net_route_mcast_dev_init, device_pm_control_nop, + &net_route_data_if1, NULL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &net_route_mcast_if_api_1, _ETH_L2_LAYER, + _ETH_L2_CTX_TYPE, 127); + +NET_DEVICE_INIT_INSTANCE(mcast_iface_2, "mcast_iface_2", iface_2, + net_route_mcast_dev_init, device_pm_control_nop, + &net_route_data_if2, NULL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &net_route_mcast_if_api_2, _ETH_L2_LAYER, + _ETH_L2_CTX_TYPE, 127); + +NET_DEVICE_INIT_INSTANCE(mcast_iface_3, "mcast_iface_3", iface_3, + net_route_mcast_dev_init, device_pm_control_nop, + &net_route_data_if3, NULL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &net_route_mcast_if_api_3, _ETH_L2_LAYER, + _ETH_L2_CTX_TYPE, 127); + +static struct net_pkt *setup_ipv6_udp(struct net_if *iface, + struct in6_addr *src_addr, + struct in6_addr *remote_addr, + uint16_t src_port, uint16_t remote_port) +{ + static const char payload[] = "foobar"; + struct net_pkt *pkt; + int res; + + pkt = net_pkt_alloc_with_buffer(iface, strlen(payload), AF_INET6, + IPPROTO_UDP, K_FOREVER); + if (!pkt) { + return NULL; + } + + net_pkt_set_ipv6_hop_limit(pkt, 2); + + res = net_ipv6_create(pkt, src_addr, remote_addr); + zassert_equal(0, res, "ipv6 create failed"); + + res = net_udp_create(pkt, htons(src_port), htons(remote_port)); + zassert_equal(0, res, "udp create failed"); + + res = net_pkt_write(pkt, (uint8_t *) payload, strlen(payload)); + zassert_equal(0, res, "pkt write failed"); + + net_pkt_cursor_init(pkt); + net_ipv6_finalize(pkt, IPPROTO_UDP); + net_pkt_cursor_init(pkt); + + return pkt; +} + +static void test_route_mcast_init(void) +{ + zassert_not_null(iface_1, "Interface is NULL"); + zassert_not_null(iface_2, "Interface is NULL"); + zassert_not_null(iface_3, "Interface is NULL"); + + net_if_flag_set(iface_1, NET_IF_FORWARD_MULTICASTS); + net_if_flag_set(iface_2, NET_IF_FORWARD_MULTICASTS); + /* iface_3 should not forward multicasts */ +} + +static void test_route_mcast_route_add(void) +{ + struct in6_addr nw_prefix_based_all_nodes; + struct net_route_entry_mcast *entry; + + entry = net_route_mcast_add(iface_1, &mcast_prefix_iflocal, 16); + zassert_is_null(entry, "add iface local should fail"); + + entry = net_route_mcast_add(iface_1, &mcast_prefix_llocal, 16); + zassert_is_null(entry, "add link local should fail"); + + test_mcast_routes[0] = net_route_mcast_add(iface_1, + &mcast_prefix_admin, 16); + zassert_not_null(test_mcast_routes[0], "mcast route add failed"); + + test_mcast_routes[1] = net_route_mcast_add(iface_2, + &mcast_prefix_site_local, 16); + zassert_not_null(test_mcast_routes[1], "mcast route add failed"); + + test_mcast_routes[2] = net_route_mcast_add(iface_1, + &mcast_prefix_orga, 16); + zassert_not_null(test_mcast_routes[2], "mcast route add failed"); + + test_mcast_routes[3] = net_route_mcast_add(iface_2, + &mcast_prefix_global, 16); + zassert_not_null(test_mcast_routes[3], "mcast route add failed"); + + /* check if route can be added + * if forwarding flag not set on iface + */ + test_mcast_routes[4] = net_route_mcast_add(iface_3, + &mcast_prefix_global, 16); + zassert_is_null(test_mcast_routes[4], "mcast route add should fail"); + + test_mcast_routes[4] = net_route_mcast_add(iface_1, + &mcast_prefix_nw_based, 96); + zassert_not_null(test_mcast_routes[4], + "add for nw prefix based failed"); + + memcpy(&nw_prefix_based_all_nodes, &mcast_prefix_nw_based, + sizeof(struct in6_addr)); + nw_prefix_based_all_nodes.s6_addr[15] = 0x01; + + test_mcast_routes[5] = net_route_mcast_add(iface_2, + &nw_prefix_based_all_nodes, 128); + zassert_not_null(test_mcast_routes[5], + "add for nw prefix based failed"); +} + +static void mcast_foreach_cb(struct net_route_entry_mcast *entry, + void *user_data) +{ + zassert_equal_ptr(user_data, &mcast_prefix_global, + "foreach failed, wrong user_data"); +} + +static void test_route_mcast_foreach(void) +{ + int executed_first = net_route_mcast_foreach(mcast_foreach_cb, + NULL, &mcast_prefix_global); + + int executed_skip = net_route_mcast_foreach(mcast_foreach_cb, + &mcast_prefix_admin, &mcast_prefix_global); + + zassert_true(executed_skip == (executed_first - 1), + "mcast foreach skip did not skip"); +} + +static void test_route_mcast_lookup(void) +{ + struct net_route_entry_mcast *route = + net_route_mcast_lookup(&mcast_prefix_admin); + + zassert_equal_ptr(test_mcast_routes[0], route, + "mcast lookup failed"); + + route = net_route_mcast_lookup(&mcast_prefix_site_local); + + zassert_equal_ptr(test_mcast_routes[1], route, + "mcast lookup failed"); + + route = net_route_mcast_lookup(&mcast_prefix_global); + + zassert_equal_ptr(test_mcast_routes[3], route, + "mcast lookup failed"); +} +static void test_route_mcast_route_del(void) +{ + struct net_route_entry_mcast *route; + bool success = net_route_mcast_del(test_mcast_routes[0]); + + zassert_true(success, "failed to delete mcast route"); + + route = net_route_mcast_lookup(&mcast_prefix_admin); + zassert_is_null(route, "lookup found deleted route"); + + success = net_route_mcast_del(test_mcast_routes[1]); + zassert_true(success, "failed to delete mcast route"); + + route = net_route_mcast_lookup(&mcast_prefix_site_local); + zassert_is_null(route, "lookup found deleted route"); + + success = net_route_mcast_del(test_mcast_routes[2]); + zassert_true(success, "failed to delete mcast route"); + + success = net_route_mcast_del(test_mcast_routes[3]); + zassert_true(success, "failed to delete mcast route"); + + success = net_route_mcast_del(test_mcast_routes[4]); + zassert_true(success, "failed to delete mcast route"); + + success = net_route_mcast_del(test_mcast_routes[5]); + zassert_true(success, "failed to delete mcast route"); +} + +static void reset_counters(void) +{ + iface_1_forwarded = false; + iface_2_forwarded = false; + iface_3_forwarded = false; + forwarding_counter = 0; +} + +static void test_route_mcast_scenario1(void) +{ + /* scenario 1 site local: + * 1. iface_1 receives site local + * only iface_2 forwards + * 2. iface_3 receives site_local + * only iface_2 forwards + */ + reset_counters(); + memcpy(&active_scenario.src, &iface_1_addr, sizeof(struct in6_addr)); + active_scenario.src.s6_addr[15] = 0x02; + + memcpy(&active_scenario.mcast, &mcast_prefix_site_local, + sizeof(struct in6_addr)); + active_scenario.mcast.s6_addr[15] = 0x01; + + struct net_pkt *pkt1 = setup_ipv6_udp(iface_1, &active_scenario.src, + &active_scenario.mcast, 20015, 20001); + + active_scenario.is_active = true; + if (net_recv_data(iface_1, pkt1) < 0) { + net_pkt_unref(pkt1); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt1); + + + zassert_true(iface_2_forwarded, "iface_2 did not forward"); + zassert_false(iface_1_forwarded, "iface_1 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 1, + "unexpected forwarded packet count"); + + reset_counters(); + + memcpy(&active_scenario.src, &iface_3_addr, sizeof(struct in6_addr)); + active_scenario.src.s6_addr[15] = 0x09; + + struct net_pkt *pkt2 = setup_ipv6_udp(iface_3, &active_scenario.src, + &active_scenario.mcast, 20015, 20001); + if (net_recv_data(iface_3, pkt2) < 0) { + net_pkt_unref(pkt2); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt2); + active_scenario.is_active = false; + zassert_true(iface_2_forwarded, "iface_2 did not forward"); + zassert_false(iface_1_forwarded, "iface_1 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 1, + "unexpected forwarded packet count"); + reset_counters(); +} + +static void test_route_mcast_scenario2(void) +{ + /* + * scenario 2 admin local: + * 1. iface_1 receives + * iface_2 must not forward due to missing routing entry. + * iface_3 must not forward due to missing + * routing entry and missing flag. + * iface_1 must not forward because itself + * received the packet! + * + * 2. iface_3 receives + * now iface_1 must forward due to routing entry + */ + reset_counters(); + memcpy(&active_scenario.src, &iface_1_addr, sizeof(struct in6_addr)); + active_scenario.src.s6_addr[15] = 0x08; + + memcpy(&active_scenario.mcast, &mcast_prefix_admin, + sizeof(struct in6_addr)); + active_scenario.mcast.s6_addr[15] = 0x01; + + struct net_pkt *pkt = setup_ipv6_udp(iface_1, &active_scenario.src, + &active_scenario.mcast, 215, 201); + + active_scenario.is_active = true; + if (net_recv_data(iface_1, pkt) < 0) { + net_pkt_unref(pkt); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt); + + zassert_false(iface_1_forwarded, "iface_1 forwarded"); + zassert_false(iface_2_forwarded, "iface_2 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 0, "wrong count forwarded packets"); + + reset_counters(); + memcpy(&active_scenario.src, &iface_3_addr, sizeof(struct in6_addr)); + active_scenario.src.s6_addr[15] = 0x08; + + struct net_pkt *pkt2 = setup_ipv6_udp(iface_3, &active_scenario.src, + &active_scenario.mcast, 215, 201); + if (net_recv_data(iface_3, pkt2) < 0) { + net_pkt_unref(pkt2); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + active_scenario.is_active = false; + net_pkt_unref(pkt2); + + zassert_true(iface_1_forwarded, "iface_1 did not forward"); + zassert_false(iface_2_forwarded, "iface_2 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 1, "wrong count forwarded packets"); +} + +static void test_route_mcast_scenario3(void) +{ + /* + * scenario 3: network prefix based forwarding + * 1. iface 3 receives nw prefix based all nodes + * iface 1 + 2 forwarding because all nodes group + * 2. iface 3 receives nw prefix based custom group + * only iface 1 forwards + * iface 3 route is set to all nodes + * 3. iface 3 receives all nodes group with different prefix + * no iface forwards + */ + reset_counters(); + memcpy(&active_scenario.src, &iface_3_addr, sizeof(struct in6_addr)); + active_scenario.src.s6_addr[15] = 0x08; + + memcpy(&active_scenario.mcast, &mcast_prefix_nw_based, + sizeof(struct in6_addr)); + active_scenario.mcast.s6_addr[15] = 0x01; + + struct net_pkt *pkt = setup_ipv6_udp(iface_3, &active_scenario.src, + &active_scenario.mcast, 215, 201); + + active_scenario.is_active = true; + if (net_recv_data(iface_3, pkt) < 0) { + net_pkt_unref(pkt); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt); + active_scenario.is_active = false; + + zassert_true(iface_1_forwarded, "iface_1 did not forward"); + zassert_true(iface_2_forwarded, "iface_2 did not forward"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 2, "wrong count forwarded packets"); + + reset_counters(); + /* set to custom group id */ + active_scenario.mcast.s6_addr[15] = 0x0F; + struct net_pkt *pkt2 = setup_ipv6_udp(iface_3, &active_scenario.src, + &active_scenario.mcast, 215, 201); + + active_scenario.is_active = true; + if (net_recv_data(iface_3, pkt2) < 0) { + net_pkt_unref(pkt2); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt2); + active_scenario.is_active = false; + + zassert_true(iface_1_forwarded, "iface_1 did not forward"); + zassert_false(iface_2_forwarded, "iface_2 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 1, "wrong count forwarded packets"); + + reset_counters(); + + /* set to all nodes but different prefix */ + active_scenario.mcast.s6_addr[11] = 0x0F; + active_scenario.mcast.s6_addr[15] = 0x01; + struct net_pkt *pkt3 = setup_ipv6_udp(iface_3, &active_scenario.src, + &active_scenario.mcast, 215, 201); + + active_scenario.is_active = true; + if (net_recv_data(iface_3, pkt3) < 0) { + net_pkt_unref(pkt3); + zassert_true(0, "failed to receive initial packet!"); + } + k_sleep(WAIT_TIME); + net_pkt_unref(pkt3); + active_scenario.is_active = false; + + zassert_false(iface_1_forwarded, "iface_1 forwarded"); + zassert_false(iface_2_forwarded, "iface_2 forwarded"); + zassert_false(iface_3_forwarded, "iface_3 forwarded"); + zassert_equal(forwarding_counter, 0, "wrong count forwarded packets"); +} + +/*test case main entry*/ +void test_main(void) +{ + ztest_test_suite(test_route_mcast, + ztest_unit_test(test_route_mcast_init), + ztest_unit_test(test_route_mcast_route_add), + ztest_unit_test(test_route_mcast_foreach), + ztest_unit_test(test_route_mcast_scenario1), + ztest_unit_test(test_route_mcast_scenario2), + ztest_unit_test(test_route_mcast_scenario3), + ztest_unit_test(test_route_mcast_lookup), + ztest_unit_test(test_route_mcast_route_del) + ); + ztest_run_test_suite(test_route_mcast); +} diff --git a/tests/net/route_mcast/testcase.yaml b/tests/net/route_mcast/testcase.yaml new file mode 100644 index 0000000000..c0ffce8a3c --- /dev/null +++ b/tests/net/route_mcast/testcase.yaml @@ -0,0 +1,6 @@ +common: + depends_on: netif +tests: + net.route_mcast: + min_ram: 21 + tags: net route_mcast