| /* |
| * Copyright (c) 2021 BayLibre SAS |
| * Copyright (c) 2024 Nordic Semiconductor |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_eth_bridge, CONFIG_NET_ETHERNET_BRIDGE_LOG_LEVEL); |
| |
| #include <zephyr/net/net_core.h> |
| #include <zephyr/net/net_l2.h> |
| #include <zephyr/net/net_if.h> |
| #include <zephyr/net/virtual.h> |
| #include <zephyr/net/ethernet.h> |
| #include <zephyr/net/ethernet_bridge.h> |
| #include <zephyr/sys/slist.h> |
| #include <zephyr/random/random.h> |
| |
| #include "net_private.h" |
| #include "bridge.h" |
| |
| #if defined(CONFIG_NET_ETHERNET_BRIDGE_TXRX_DEBUG) |
| #define DEBUG_TX 1 |
| #define DEBUG_RX 1 |
| #else |
| #define DEBUG_TX 0 |
| #define DEBUG_RX 0 |
| #endif |
| |
| #define MAX_BRIDGE_NAME_LEN MIN(sizeof("bridge##"), CONFIG_NET_INTERFACE_NAME_LEN) |
| #define MAX_VIRT_NAME_LEN MIN(sizeof("<no config>"), CONFIG_NET_L2_VIRTUAL_MAX_NAME_LEN) |
| |
| static void lock_bridge(struct eth_bridge_iface_context *ctx) |
| { |
| k_mutex_lock(&ctx->lock, K_FOREVER); |
| } |
| |
| static void unlock_bridge(struct eth_bridge_iface_context *ctx) |
| { |
| k_mutex_unlock(&ctx->lock); |
| } |
| |
| struct ud { |
| eth_bridge_cb_t cb; |
| void *user_data; |
| }; |
| |
| static void iface_cb(struct net_if *iface, void *user_data) |
| { |
| struct ud *br_user_data = user_data; |
| struct eth_bridge_iface_context *ctx; |
| enum virtual_interface_caps caps; |
| |
| if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { |
| return; |
| } |
| |
| caps = net_virtual_get_iface_capabilities(iface); |
| if (!(caps & VIRTUAL_INTERFACE_BRIDGE)) { |
| return; |
| } |
| |
| ctx = net_if_get_device(iface)->data; |
| |
| br_user_data->cb(ctx, br_user_data->user_data); |
| } |
| |
| void net_eth_bridge_foreach(eth_bridge_cb_t cb, void *user_data) |
| { |
| struct ud br_user_data = { |
| .cb = cb, |
| .user_data = user_data, |
| }; |
| |
| net_if_foreach(iface_cb, &br_user_data); |
| } |
| |
| int eth_bridge_get_index(struct net_if *br) |
| { |
| return net_if_get_by_iface(br); |
| } |
| |
| struct net_if *eth_bridge_get_by_index(int index) |
| { |
| return net_if_get_by_index(index); |
| } |
| |
| int eth_bridge_iface_add(struct net_if *br, struct net_if *iface) |
| { |
| struct eth_bridge_iface_context *ctx = net_if_get_device(br)->data; |
| struct ethernet_context *eth_ctx = net_if_l2_data(iface); |
| bool found = false; |
| int count = 0; |
| int ret; |
| |
| #if defined(CONFIG_NET_DSA) && !defined(CONFIG_NET_DSA_DEPRECATED) |
| if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET) || |
| (eth_ctx->dsa_port != DSA_USER_PORT && |
| !(net_eth_get_hw_capabilities(iface) & ETHERNET_PROMISC_MODE))) { |
| return -EINVAL; |
| } |
| #else |
| if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET) || |
| !(net_eth_get_hw_capabilities(iface) & ETHERNET_PROMISC_MODE)) { |
| return -EINVAL; |
| } |
| #endif |
| if (net_if_l2(br) != &NET_L2_GET_NAME(VIRTUAL) || |
| !(net_virtual_get_iface_capabilities(br) & VIRTUAL_INTERFACE_BRIDGE)) { |
| return -EINVAL; |
| } |
| |
| lock_bridge(ctx); |
| |
| if (eth_ctx->bridge == br) { |
| /* This Ethernet interface was already added to the bridge */ |
| found = true; |
| } |
| |
| ARRAY_FOR_EACH(ctx->eth_iface, i) { |
| if (!found && ctx->eth_iface[i] == NULL) { |
| ctx->eth_iface[i] = iface; |
| eth_ctx->bridge = br; |
| found = true; |
| } |
| |
| /* Calculate how many interfaces are added to this bridge */ |
| if (ctx->eth_iface[i] != NULL) { |
| struct ethernet_context *tmp = net_if_l2_data(ctx->eth_iface[i]); |
| |
| if (tmp->bridge == br) { |
| count++; |
| } |
| } |
| } |
| |
| unlock_bridge(ctx); |
| |
| if (!found) { |
| return -ENOMEM; |
| } |
| |
| #if defined(CONFIG_NET_DSA) && !defined(CONFIG_NET_DSA_DEPRECATED) |
| if (eth_ctx->dsa_port != DSA_USER_PORT) { |
| ret = net_eth_promisc_mode(iface, true); |
| if (ret != 0 && ret != -EALREADY) { |
| NET_DBG("iface %d promiscuous mode failed: %d", |
| net_if_get_by_iface(iface), ret); |
| eth_bridge_iface_remove(br, iface); |
| return ret; |
| } |
| } |
| #else |
| ret = net_eth_promisc_mode(iface, true); |
| if (ret != 0 && ret != -EALREADY) { |
| /* Ignore any errors when using native-sim driver, |
| * we do not need host promiscuous working when testing |
| * bridging using native-sim. |
| */ |
| if (!IS_ENABLED(CONFIG_ETH_NATIVE_TAP)) { |
| NET_DBG("iface %d promiscuous mode failed: %d", |
| net_if_get_by_iface(iface), ret); |
| eth_bridge_iface_remove(br, iface); |
| return ret; |
| } |
| } |
| |
| #endif |
| NET_DBG("iface %d added to bridge %d", net_if_get_by_iface(iface), |
| net_if_get_by_iface(br)); |
| |
| if (count > 1) { |
| ctx->is_setup = true; |
| |
| NET_INFO("Bridge %d is %ssetup", net_if_get_by_iface(eth_ctx->bridge), ""); |
| |
| net_virtual_set_name(ctx->iface, "<config ok>"); |
| } |
| |
| ctx->count = count; |
| |
| return 0; |
| } |
| |
| int eth_bridge_iface_remove(struct net_if *br, struct net_if *iface) |
| { |
| struct eth_bridge_iface_context *ctx = net_if_get_device(br)->data; |
| struct ethernet_context *eth_ctx = net_if_l2_data(iface); |
| bool found = false; |
| int count = 0; |
| |
| if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET)) { |
| return -EINVAL; |
| } |
| |
| if (net_if_l2(br) != &NET_L2_GET_NAME(VIRTUAL) || |
| !(net_virtual_get_iface_capabilities(br) & VIRTUAL_INTERFACE_BRIDGE)) { |
| return -EINVAL; |
| } |
| |
| lock_bridge(ctx); |
| |
| ARRAY_FOR_EACH(ctx->eth_iface, i) { |
| if (!found && ctx->eth_iface[i] == iface) { |
| ctx->eth_iface[i] = NULL; |
| eth_ctx->bridge = NULL; |
| found = true; |
| } |
| |
| /* Calculate how many interfaces are added to this bridge */ |
| if (ctx->eth_iface[i] != NULL) { |
| struct ethernet_context *tmp = net_if_l2_data(ctx->eth_iface[i]); |
| |
| if (tmp->bridge == br) { |
| count++; |
| } |
| } |
| } |
| |
| unlock_bridge(ctx); |
| |
| NET_DBG("iface %d removed from bridge %d", net_if_get_by_iface(iface), |
| net_if_get_by_iface(br)); |
| |
| if (count < 2) { |
| ctx->is_setup = false; |
| |
| NET_INFO("Bridge %d is %ssetup", net_if_get_by_iface(br), "not "); |
| |
| net_virtual_set_name(ctx->iface, "<no config>"); |
| } |
| |
| ctx->count = count; |
| |
| return 0; |
| } |
| |
| static inline bool is_link_local_addr(struct net_eth_addr *addr) |
| { |
| if (addr->addr[0] == 0x01 && |
| addr->addr[1] == 0x80 && |
| addr->addr[2] == 0xc2 && |
| addr->addr[3] == 0x00 && |
| addr->addr[4] == 0x00 && |
| (addr->addr[5] & 0x0f) == 0x00) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void random_linkaddr(uint8_t *linkaddr, size_t len) |
| { |
| sys_rand_get(linkaddr, len); |
| } |
| |
| static void bridge_iface_init(struct net_if *iface) |
| { |
| struct eth_bridge_iface_context *ctx = net_if_get_device(iface)->data; |
| struct virtual_interface_context *vctx = net_if_l2_data(iface); |
| char name[MAX_BRIDGE_NAME_LEN]; |
| |
| if (ctx->is_init) { |
| return; |
| } |
| |
| k_mutex_init(&ctx->lock); |
| |
| ctx->iface = iface; |
| |
| net_if_flag_set(iface, NET_IF_NO_AUTO_START); |
| net_if_flag_clear(iface, NET_IF_IPV4); |
| net_if_flag_clear(iface, NET_IF_IPV6); |
| net_if_flag_clear(iface, NET_IF_FORWARD_MULTICASTS); |
| |
| net_virtual_set_flags(iface, NET_L2_PROMISC_MODE); |
| |
| snprintk(name, sizeof(name), "bridge%d", ctx->id); |
| net_if_set_name(iface, name); |
| |
| net_virtual_set_name(iface, "<no config>"); |
| |
| /* We need to set the link address here as normally it would be set in |
| * virtual interface API attach function but we do not use that in |
| * bridging. |
| */ |
| random_linkaddr(vctx->lladdr.addr, sizeof(vctx->lladdr.addr)); |
| |
| vctx->lladdr.len = sizeof(vctx->lladdr.addr); |
| vctx->lladdr.type = NET_LINK_UNKNOWN; |
| |
| net_if_set_link_addr(iface, vctx->lladdr.addr, |
| vctx->lladdr.len, vctx->lladdr.type); |
| |
| ctx->is_init = true; |
| ctx->is_setup = false; |
| } |
| |
| static enum virtual_interface_caps bridge_get_capabilities(struct net_if *iface) |
| { |
| ARG_UNUSED(iface); |
| |
| return VIRTUAL_INTERFACE_BRIDGE; |
| } |
| |
| static int bridge_iface_start(const struct device *dev) |
| { |
| struct eth_bridge_iface_context *ctx = dev->data; |
| |
| if (!ctx->is_setup) { |
| NET_DBG("Bridge interface %d not configured yet.", |
| net_if_get_by_iface(ctx->iface)); |
| return -ENOENT; |
| } |
| |
| if (ctx->status) { |
| return -EALREADY; |
| } |
| |
| ctx->status = true; |
| |
| NET_DBG("Starting iface %d", net_if_get_by_iface(ctx->iface)); |
| |
| NET_INFO("Bridge %d is %sactive", net_if_get_by_iface(ctx->iface), ""); |
| |
| net_virtual_set_name(ctx->iface, "<enabled>"); |
| |
| return 0; |
| } |
| |
| static int bridge_iface_stop(const struct device *dev) |
| { |
| struct eth_bridge_iface_context *ctx = dev->data; |
| |
| if (!ctx->status) { |
| return -EALREADY; |
| } |
| |
| ctx->status = false; |
| |
| NET_DBG("Stopping iface %d", net_if_get_by_iface(ctx->iface)); |
| |
| NET_INFO("Bridge %d is %sactive", net_if_get_by_iface(ctx->iface), "not "); |
| |
| if (ctx->is_setup) { |
| net_virtual_set_name(ctx->iface, "<disabled>"); |
| } else { |
| net_virtual_set_name(ctx->iface, "<no config>"); |
| } |
| |
| return 0; |
| } |
| |
| static enum net_verdict bridge_iface_process(struct net_if *iface, |
| struct net_pkt *pkt, |
| bool is_send) |
| { |
| struct eth_bridge_iface_context *ctx = net_if_get_device(iface)->data; |
| struct net_if *orig_iface; |
| struct net_pkt *send_pkt = pkt; |
| int fwd_iface_num = 0; |
| |
| /* Drop all link-local packets for now. */ |
| if (is_link_local_addr((struct net_eth_addr *)net_pkt_lladdr_dst(pkt))) { |
| NET_DBG("DROP: lladdr"); |
| net_pkt_unref(pkt); |
| return NET_OK; |
| } |
| |
| lock_bridge(ctx); |
| |
| orig_iface = net_pkt_orig_iface(pkt); |
| |
| /* Get interface number to forward */ |
| ARRAY_FOR_EACH(ctx->eth_iface, i) { |
| if (ctx->eth_iface[i] != NULL && ctx->eth_iface[i] != orig_iface) { |
| /* Skip it if not up */ |
| if (!net_if_flag_is_set(ctx->eth_iface[i], NET_IF_UP)) { |
| continue; |
| } |
| fwd_iface_num++; |
| } |
| } |
| |
| /* Forward pkt to other interfaces */ |
| ARRAY_FOR_EACH(ctx->eth_iface, i) { |
| if (ctx->eth_iface[i] != NULL && ctx->eth_iface[i] != orig_iface) { |
| /* Skip it if not up */ |
| if (!net_if_flag_is_set(ctx->eth_iface[i], NET_IF_UP)) { |
| continue; |
| } |
| |
| /* Clone the packet if we have more than two interfaces in the bridge |
| * because the first send might mess the data part of the message. |
| */ |
| if (fwd_iface_num > 1) { |
| send_pkt = net_pkt_clone(pkt, K_NO_WAIT); |
| if (send_pkt == NULL) { |
| NET_DBG("DROP: clone failed"); |
| break; |
| } |
| } |
| |
| net_pkt_set_family(send_pkt, AF_UNSPEC); |
| net_pkt_set_iface(send_pkt, ctx->eth_iface[i]); |
| net_if_queue_tx(ctx->eth_iface[i], send_pkt); |
| |
| NET_DBG("%s iface %d pkt %p (ref %d)", |
| is_send ? "Send" : "Recv", |
| net_if_get_by_iface(ctx->eth_iface[i]), |
| send_pkt, (int)atomic_get(&send_pkt->atomic_ref)); |
| } |
| } |
| |
| /* Free pkt if cloned pkt is used to send */ |
| if (send_pkt != pkt) { |
| net_pkt_unref(pkt); |
| } |
| |
| unlock_bridge(ctx); |
| |
| return NET_OK; |
| } |
| |
| int bridge_iface_send(struct net_if *iface, struct net_pkt *pkt) |
| { |
| if (DEBUG_TX) { |
| char str[sizeof("TX iface xx")]; |
| |
| snprintk(str, sizeof(str), "TX iface %d", |
| net_if_get_by_iface(net_pkt_iface(pkt))); |
| |
| net_pkt_hexdump(pkt, str); |
| } |
| |
| (void)bridge_iface_process(iface, pkt, true); |
| |
| return 0; |
| } |
| |
| static enum net_verdict bridge_iface_recv(struct net_if *iface, |
| struct net_pkt *pkt) |
| { |
| if (DEBUG_RX) { |
| char str[sizeof("RX iface xx")]; |
| |
| snprintk(str, sizeof(str), "RX iface %d", |
| net_if_get_by_iface(net_pkt_iface(pkt))); |
| |
| net_pkt_hexdump(pkt, str); |
| } |
| |
| return bridge_iface_process(iface, pkt, false); |
| } |
| |
| /* We cannot attach the bridge interface to Ethernet interface because |
| * the attachment can be done to only one Ethernet interface and we |
| * need to "attach" at least two Ethernet interfaces to the bridge interface. |
| * So we return -ENOTSUP here so that the attachment fails if it is tried. |
| */ |
| static int bridge_iface_attach(struct net_if *br, |
| struct net_if *iface) |
| { |
| ARG_UNUSED(br); |
| ARG_UNUSED(iface); |
| |
| return -ENOTSUP; |
| } |
| |
| static const struct virtual_interface_api bridge_iface_api = { |
| .iface_api.init = bridge_iface_init, |
| |
| .get_capabilities = bridge_get_capabilities, |
| .start = bridge_iface_start, |
| .stop = bridge_iface_stop, |
| .send = bridge_iface_send, |
| .recv = bridge_iface_recv, |
| .attach = bridge_iface_attach, |
| }; |
| |
| #define ETH_DEFINE_BRIDGE(x, _) \ |
| static struct eth_bridge_iface_context bridge_context_data_##x = { \ |
| .id = x, \ |
| }; \ |
| NET_VIRTUAL_INTERFACE_INIT_INSTANCE(bridge_##x, \ |
| "BRIDGE_" #x, \ |
| x, \ |
| NULL, \ |
| NULL, \ |
| &bridge_context_data_##x, \ |
| NULL, /* config */ \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
| &bridge_iface_api, \ |
| NET_ETH_MTU) |
| |
| LISTIFY(CONFIG_NET_ETHERNET_BRIDGE_COUNT, ETH_DEFINE_BRIDGE, (;), _); |