blob: 8fe4f8ef9897269abc90a48395bf41b6b08a11a9 [file] [log] [blame]
/*
* Copyright (c) 2021 BayLibre SAS
*
* 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/ethernet.h>
#include <zephyr/net/ethernet_bridge.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/sys/slist.h>
#include "bridge.h"
extern struct eth_bridge _eth_bridge_list_start[];
extern struct eth_bridge _eth_bridge_list_end[];
static void lock_bridge(struct eth_bridge *br)
{
/* Lazy-evaluate initialization. The ETH_BRIDGE_INITIALIZER()
* macro assumed that k_mutex can be statically initialized,
* and it can't. Post-zync, this will actually be possible
* and we can come back and fix this.
*/
if (!br->initialized) {
k_mutex_init(&br->lock);
br->initialized = true;
}
k_mutex_lock(&br->lock, K_FOREVER);
}
void net_eth_bridge_foreach(eth_bridge_cb_t cb, void *user_data)
{
STRUCT_SECTION_FOREACH(eth_bridge, br) {
cb(br, user_data);
}
}
int eth_bridge_get_index(struct eth_bridge *br)
{
if (!(br >= _eth_bridge_list_start && br < _eth_bridge_list_end)) {
return -1;
}
return (br - _eth_bridge_list_start) + 1;
}
struct eth_bridge *eth_bridge_get_by_index(int index)
{
if (index <= 0) {
return NULL;
}
if (&_eth_bridge_list_start[index - 1] >= _eth_bridge_list_end) {
NET_DBG("Index %d is too large", index);
return NULL;
}
return &_eth_bridge_list_start[index - 1];
}
int eth_bridge_iface_add(struct eth_bridge *br, struct net_if *iface)
{
struct ethernet_context *ctx = net_if_l2_data(iface);
if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET) ||
!(net_eth_get_hw_capabilities(iface) & ETHERNET_PROMISC_MODE)) {
return -EINVAL;
}
lock_bridge(br);
if (ctx->bridge.instance != NULL) {
k_mutex_unlock(&br->lock);
return -EBUSY;
}
ctx->bridge.instance = br;
ctx->bridge.allow_tx = false;
sys_slist_append(&br->interfaces, &ctx->bridge.node);
k_mutex_unlock(&br->lock);
int ret = net_eth_promisc_mode(iface, true);
if (ret != 0) {
NET_DBG("iface %p promiscuous mode failed: %d", iface, ret);
eth_bridge_iface_remove(br, iface);
return ret;
}
NET_DBG("iface %p added to bridge %p", iface, br);
return 0;
}
int eth_bridge_iface_remove(struct eth_bridge *br, struct net_if *iface)
{
struct ethernet_context *ctx = net_if_l2_data(iface);
if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET)) {
return -EINVAL;
}
lock_bridge(br);
if (ctx->bridge.instance != br) {
k_mutex_unlock(&br->lock);
return -EINVAL;
}
sys_slist_find_and_remove(&br->interfaces, &ctx->bridge.node);
ctx->bridge.instance = NULL;
k_mutex_unlock(&br->lock);
NET_DBG("iface %p removed from bridge %p", iface, br);
return 0;
}
int eth_bridge_iface_allow_tx(struct net_if *iface, bool allow)
{
struct ethernet_context *ctx = net_if_l2_data(iface);
if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET) ||
ctx->bridge.instance == NULL) {
return -EINVAL;
}
ctx->bridge.allow_tx = allow;
return 0;
}
int eth_bridge_listener_add(struct eth_bridge *br, struct eth_bridge_listener *l)
{
lock_bridge(br);
sys_slist_append(&br->listeners, &l->node);
k_mutex_unlock(&br->lock);
return 0;
}
int eth_bridge_listener_remove(struct eth_bridge *br, struct eth_bridge_listener *l)
{
lock_bridge(br);
sys_slist_find_and_remove(&br->listeners, &l->node);
k_mutex_unlock(&br->lock);
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;
}
enum net_verdict net_eth_bridge_input(struct ethernet_context *ctx,
struct net_pkt *pkt)
{
struct eth_bridge *br = ctx->bridge.instance;
sys_snode_t *node;
NET_DBG("new pkt %p", pkt);
/* Drop all link-local packets for now. */
if (is_link_local_addr((struct net_eth_addr *)net_pkt_lladdr_dst(pkt))) {
return NET_DROP;
}
lock_bridge(br);
/*
* Send packet to all registered interfaces for now.
* Eventually we could get smarter with a MAC address cache.
*/
SYS_SLIST_FOR_EACH_NODE(&br->interfaces, node) {
struct ethernet_context *out_ctx;
struct net_pkt *out_pkt;
out_ctx = CONTAINER_OF(node, struct ethernet_context, bridge.node);
/* Don't xmit on the same interface as the incoming packet's */
if (ctx == out_ctx) {
continue;
}
/* Skip it if not allowed to transmit */
if (!out_ctx->bridge.allow_tx) {
continue;
}
/* Skip it if not up */
if (!net_if_flag_is_set(out_ctx->iface, NET_IF_UP)) {
continue;
}
out_pkt = net_pkt_shallow_clone(pkt, K_NO_WAIT);
if (out_pkt == NULL) {
continue;
}
NET_DBG("sending pkt %p as %p on iface %p", pkt, out_pkt, out_ctx->iface);
/*
* Use AF_UNSPEC to avoid interference, set the output
* interface and send the packet.
*/
net_pkt_set_family(out_pkt, AF_UNSPEC);
net_pkt_set_orig_iface(out_pkt, net_pkt_iface(pkt));
net_pkt_set_iface(out_pkt, out_ctx->iface);
net_if_queue_tx(out_ctx->iface, out_pkt);
}
SYS_SLIST_FOR_EACH_NODE(&br->listeners, node) {
struct eth_bridge_listener *l;
struct net_pkt *out_pkt;
l = CONTAINER_OF(node, struct eth_bridge_listener, node);
out_pkt = net_pkt_shallow_clone(pkt, K_NO_WAIT);
if (out_pkt == NULL) {
continue;
}
k_fifo_put(&l->pkt_queue, out_pkt);
}
k_mutex_unlock(&br->lock);
net_pkt_unref(pkt);
return NET_OK;
}