/* * Copyright (c) 2021 BayLibre SAS * Copyright (c) 2024 Nordic Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_eth_bridge, CONFIG_NET_ETHERNET_BRIDGE_LOG_LEVEL); #include #include #include #include #include #include #include #include #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(""), 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 (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET) || !(net_eth_get_hw_capabilities(iface) & ETHERNET_PROMISC_MODE)) { 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); 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; } 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_POSIX)) { NET_DBG("iface %d promiscuous mode failed: %d", net_if_get_by_iface(iface), ret); eth_bridge_iface_remove(br, iface); return ret; } } 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, ""); } 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, ""); } 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, ""); /* 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, ""); 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, ""); } else { net_virtual_set_name(ctx->iface, ""); } 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; size_t count; /* 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"); goto out; } lock_bridge(ctx); /* Keep the original packet interface so that we can send to each * bridged interface. */ orig_iface = net_pkt_orig_iface(pkt); count = ctx->count; /* Pass the data to all the Ethernet interface except the originator * Ethernet interface. */ 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 (count > 2) { send_pkt = net_pkt_clone(pkt, K_NO_WAIT); if (send_pkt == NULL) { NET_DBG("DROP: clone failed"); break; } net_pkt_ref(send_pkt); } else { send_pkt = net_pkt_ref(pkt); } 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)); net_pkt_unref(send_pkt); } } unlock_bridge(ctx); out: /* The packet was cloned by the caller so remove it here. */ net_pkt_unref(pkt); 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, (;), _);