zephyr/subsys/net/l2/ethernet/bridge.c
Jukka Rissanen c6a1af5c17 net: ethernet: bridge: Drop the cloned packet in error
We need to drop the cloned packet that was fed to the bridge instead of
returning directly from the function. Without this change we have a
buffer leak.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
2024-11-28 12:50:36 +01:00

479 lines
11 KiB
C

/*
* 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 (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, "<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;
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, (;), _);