/* * Copyright (c) 2021 BayLibre SAS * Copyright (c) 2024 Nordic Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #define NET_LOG_LEVEL CONFIG_NET_ETHERNET_BRIDGE_LOG_LEVEL #include LOG_MODULE_REGISTER(net_test, NET_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #if NET_LOG_LEVEL >= LOG_LEVEL_DBG #define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) #else #define DBG(fmt, ...) #endif static struct net_if *bridge; struct eth_fake_context { struct net_if *iface; struct net_pkt *sent_pkt; uint8_t mac_address[6]; bool promisc_mode; }; static void eth_fake_iface_init(struct net_if *iface) { const struct device *dev = net_if_get_device(iface); struct eth_fake_context *ctx = dev->data; ctx->iface = iface; ctx->mac_address[0] = 0xc2; ctx->mac_address[1] = 0xaa; ctx->mac_address[2] = 0xbb; ctx->mac_address[3] = 0xcc; ctx->mac_address[4] = 0xdd; ctx->mac_address[5] = 0xee; net_if_set_link_addr(iface, ctx->mac_address, sizeof(ctx->mac_address), NET_LINK_ETHERNET); ethernet_init(iface); } static int eth_fake_send(const struct device *dev, struct net_pkt *pkt) { struct eth_fake_context *ctx = dev->data; struct net_eth_hdr *eth_hdr = NET_ETH_HDR(pkt); /* * Ignore packets we don't care about for this test, like * the IP autoconfig related ones, etc. */ if (eth_hdr->type != htons(NET_ETH_PTYPE_ALL)) { DBG("Fake send ignoring pkt %p\n", pkt); return 0; } if (ctx->sent_pkt != NULL) { DBG("Fake send found pkt %p while sending %p\n", ctx->sent_pkt, pkt); return -EBUSY; } ctx->sent_pkt = net_pkt_shallow_clone(pkt, K_NO_WAIT); if (ctx->sent_pkt == NULL) { DBG("Fake send out of mem while sending pkt %p\n", pkt); return -ENOMEM; } DBG("Fake send pkt %p kept locally as %p\n", pkt, ctx->sent_pkt); return 0; } static enum ethernet_hw_caps eth_fake_get_capabilities(const struct device *dev) { return ETHERNET_PROMISC_MODE; } static int eth_fake_set_config(const struct device *dev, enum ethernet_config_type type, const struct ethernet_config *config) { struct eth_fake_context *ctx = dev->data; switch (type) { case ETHERNET_CONFIG_TYPE_PROMISC_MODE: if (config->promisc_mode == ctx->promisc_mode) { return -EALREADY; } ctx->promisc_mode = config->promisc_mode; break; default: return -EINVAL; } return 0; } static const struct ethernet_api eth_fake_api_funcs = { .iface_api.init = eth_fake_iface_init, .get_capabilities = eth_fake_get_capabilities, .set_config = eth_fake_set_config, .send = eth_fake_send, }; static int eth_fake_init(const struct device *dev) { struct eth_fake_context *ctx = dev->data; ctx->promisc_mode = false; return 0; } static struct eth_fake_context eth_fake_data[3]; ETH_NET_DEVICE_INIT(eth_fake0, "eth_fake0", eth_fake_init, NULL, ð_fake_data[0], NULL, CONFIG_ETH_INIT_PRIORITY, ð_fake_api_funcs, NET_ETH_MTU); ETH_NET_DEVICE_INIT(eth_fake1, "eth_fake1", eth_fake_init, NULL, ð_fake_data[1], NULL, CONFIG_ETH_INIT_PRIORITY, ð_fake_api_funcs, NET_ETH_MTU); ETH_NET_DEVICE_INIT(eth_fake2, "eth_fake2", eth_fake_init, NULL, ð_fake_data[2], NULL, CONFIG_ETH_INIT_PRIORITY, ð_fake_api_funcs, NET_ETH_MTU); static struct net_if *fake_iface[3]; static void iface_cb(struct net_if *iface, void *user_data) { static int if_count; if (if_count >= ARRAY_SIZE(fake_iface)) { return; } DBG("Interface %p [%d]\n", iface, net_if_get_by_iface(iface)); if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { const struct ethernet_api *api = net_if_get_device(iface)->api; /* * We want to only use struct net_if devices defined in * this test as board on which it is run can have its * own set of interfaces. */ if (api->get_capabilities == eth_fake_api_funcs.get_capabilities) { fake_iface[if_count++] = iface; } } if (net_if_l2(iface) == &NET_L2_GET_NAME(VIRTUAL)) { enum virtual_interface_caps caps; caps = net_virtual_get_iface_capabilities(iface); if (caps & VIRTUAL_INTERFACE_BRIDGE) { bridge = iface; } } } static int orig_rx_num_blocks; static int orig_tx_num_blocks; static void get_free_packet_count(void) { struct k_mem_slab *rx, *tx; net_pkt_get_info(&rx, &tx, NULL, NULL); orig_rx_num_blocks = rx->info.num_blocks; orig_tx_num_blocks = tx->info.num_blocks; } static void check_free_packet_count(void) { struct k_mem_slab *rx, *tx; net_pkt_get_info(&rx, &tx, NULL, NULL); zassert_equal(rx->info.num_blocks, orig_rx_num_blocks, ""); zassert_equal(tx->info.num_blocks, orig_tx_num_blocks, ""); } static void test_iface_setup(void) { net_if_foreach(iface_cb, NULL); zassert_not_null(fake_iface[0], ""); zassert_not_null(fake_iface[1], ""); zassert_not_null(fake_iface[2], ""); DBG("Interfaces: [%d] iface0 %p, [%d] iface1 %p, [%d] iface2 %p\n", net_if_get_by_iface(fake_iface[0]), fake_iface[0], net_if_get_by_iface(fake_iface[1]), fake_iface[1], net_if_get_by_iface(fake_iface[2]), fake_iface[2]); net_if_up(fake_iface[0]); net_if_up(fake_iface[1]); net_if_up(fake_iface[2]); /* Remember the initial number of free packets in the pool. */ get_free_packet_count(); } /* * Simulate a packet reception from the outside world */ static void _recv_data(struct net_if *iface) { struct net_pkt *pkt; struct net_eth_hdr eth_hdr; static uint8_t data[] = { 't', 'e', 's', 't', '\0' }; int ret; pkt = net_pkt_rx_alloc_with_buffer(iface, sizeof(eth_hdr) + sizeof(data), AF_UNSPEC, 0, K_FOREVER); zassert_not_null(pkt, ""); /* * The source and destination MAC addresses are completely arbitrary * except for the U/L and I/G bits. However, the index of the faked * incoming interface is mixed in as well to create some variation, * and to help with validation on the transmit side. */ eth_hdr.dst.addr[0] = 0xb2; eth_hdr.dst.addr[1] = 0x11; eth_hdr.dst.addr[2] = 0x22; eth_hdr.dst.addr[3] = 0x33; eth_hdr.dst.addr[4] = net_if_get_by_iface(iface); eth_hdr.dst.addr[5] = 0x55; eth_hdr.src.addr[0] = 0xa2; eth_hdr.src.addr[1] = 0x11; eth_hdr.src.addr[2] = 0x22; eth_hdr.src.addr[3] = net_if_get_by_iface(iface); eth_hdr.src.addr[4] = 0x77; eth_hdr.src.addr[5] = 0x88; eth_hdr.type = htons(NET_ETH_PTYPE_ALL); ret = net_pkt_write(pkt, ð_hdr, sizeof(eth_hdr)); zassert_equal(ret, 0, ""); ret = net_pkt_write(pkt, data, sizeof(data)); zassert_equal(ret, 0, ""); DBG("[%d] Fake recv pkt %p\n", net_if_get_by_iface(iface), pkt); ret = net_recv_data(iface, pkt); zassert_equal(ret, 0, ""); } static void test_recv_before_bridging(void) { /* fake some packet reception */ _recv_data(fake_iface[0]); _recv_data(fake_iface[1]); _recv_data(fake_iface[2]); /* give time to the processing threads to run */ k_sleep(K_MSEC(100)); /* nothing should have been transmitted at this point */ zassert_is_null(eth_fake_data[0].sent_pkt, ""); zassert_is_null(eth_fake_data[1].sent_pkt, ""); zassert_is_null(eth_fake_data[2].sent_pkt, ""); /* and everything already dropped. */ check_free_packet_count(); } static void test_setup_bridge(void) { int ret; /* add our interfaces to the bridge */ ret = eth_bridge_iface_add(bridge, fake_iface[0]); zassert_equal(ret, 0, ""); ret = eth_bridge_iface_add(bridge, fake_iface[1]); zassert_equal(ret, 0, ""); /* Try to add the bridge twice, there should be no error */ ret = eth_bridge_iface_add(bridge, fake_iface[1]); zassert_equal(ret, 0, ""); ret = eth_bridge_iface_add(bridge, fake_iface[2]); zassert_equal(ret, 0, ""); ret = net_if_up(bridge); zassert_equal(ret, 0, ""); } static void test_recv_with_bridge(void) { int i, j; for (i = 0; i < 3; i++) { int src_if_idx = net_if_get_by_iface(fake_iface[i]); /* fake reception of packets */ _recv_data(fake_iface[i]); /* give time to the processing threads to run */ k_sleep(K_MSEC(100)); /* * fake_iface[0] and fake_iface[2] should have sent the packet * but only if it didn't come from them. * We skip fake_iface[1] handled above. */ for (j = 0; j < 3; j += 2) { struct net_pkt *pkt = eth_fake_data[j].sent_pkt; struct net_eth_hdr *hdr; if (eth_fake_data[j].iface == fake_iface[i]) { zassert_is_null(pkt, ""); continue; } eth_fake_data[j].sent_pkt = NULL; zassert_not_null(pkt, ""); /* make sure nothing messed up our ethernet header */ hdr = NET_ETH_HDR(pkt); zassert_equal(hdr->dst.addr[0], 0xb2, ""); zassert_equal(hdr->src.addr[0], 0xa2, ""); zassert_equal(hdr->dst.addr[4], src_if_idx, ""); zassert_equal(hdr->src.addr[3], src_if_idx, ""); net_pkt_unref(pkt); } } check_free_packet_count(); } static void test_recv_after_bridging(void) { int ret; ret = net_if_down(bridge); zassert_equal(ret, 0, ""); /* remove our interfaces from the bridge */ ret = eth_bridge_iface_remove(bridge, fake_iface[0]); zassert_equal(ret, 0, ""); ret = eth_bridge_iface_remove(bridge, fake_iface[1]); zassert_equal(ret, 0, ""); ret = eth_bridge_iface_remove(bridge, fake_iface[2]); zassert_equal(ret, 0, ""); /* If there are not enough interfaces in the bridge, it is not created */ ret = net_if_up(bridge); zassert_equal(ret, -ENOENT, ""); eth_fake_data[0].sent_pkt = eth_fake_data[1].sent_pkt = eth_fake_data[2].sent_pkt = NULL; /* things should have returned to the pre-bridging state */ test_recv_before_bridging(); } /* Make sure bridge interface support promiscuous API */ ZTEST(net_eth_bridge, test_verify_promisc_mode) { int ret; ret = net_promisc_mode_on(bridge); zassert_equal(ret, 0, ""); } ZTEST(net_eth_bridge, test_net_eth_bridge) { DBG("Before bridging\n"); test_iface_setup(); test_recv_before_bridging(); DBG("With bridging\n"); test_setup_bridge(); test_recv_with_bridge(); DBG("After bridging\n"); test_recv_after_bridging(); } ZTEST_SUITE(net_eth_bridge, NULL, NULL, NULL, NULL, NULL);