dacb3dbfeb
Until now iterable sections APIs have been part of the toolchain (common) headers. They are not strictly related to a toolchain, they just rely on linker providing support for sections. Most files relied on indirect includes to access the API, now, it is included as needed. Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
244 lines
5.5 KiB
C
244 lines
5.5 KiB
C
/*
|
|
* 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;
|
|
}
|