zephyr/subsys/net/l2/ethernet/bridge.c

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