net: ethernet: bridging support
This adds the ability to create Ethernet bridges for connecting separate Ethernet segments together to appear as a single Ethernet network. This mimics the Linux functionality of the same name. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
This commit is contained in:
parent
d3f6b54f8e
commit
89482f0119
11 changed files with 519 additions and 3 deletions
|
@ -39,6 +39,7 @@ SECTION_DATA_PROLOGUE(net_if_area,,SUBALIGN(4)) \
|
||||||
_net_if_list_end = .; \
|
_net_if_list_end = .; \
|
||||||
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) \
|
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) \
|
||||||
Z_ITERABLE_SECTION_RAM(net_if_dev, 4) \
|
Z_ITERABLE_SECTION_RAM(net_if_dev, 4) \
|
||||||
Z_ITERABLE_SECTION_RAM(net_l2, 4)
|
Z_ITERABLE_SECTION_RAM(net_l2, 4) \
|
||||||
|
Z_ITERABLE_SECTION_RAM(eth_bridge, 4)
|
||||||
|
|
||||||
#include <arch/arm/aarch32/cortex_m/scripts/linker.ld>
|
#include <arch/arm/aarch32/cortex_m/scripts/linker.ld>
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
#define NETWORK_RAM_SECTIONS \
|
#define NETWORK_RAM_SECTIONS \
|
||||||
Z_ITERABLE_SECTION_RAM(net_if, 4) \
|
Z_ITERABLE_SECTION_RAM(net_if, 4) \
|
||||||
Z_ITERABLE_SECTION_RAM(net_if_dev, 4) \
|
Z_ITERABLE_SECTION_RAM(net_if_dev, 4) \
|
||||||
Z_ITERABLE_SECTION_RAM(net_l2, 4)
|
Z_ITERABLE_SECTION_RAM(net_l2, 4) \
|
||||||
|
Z_ITERABLE_SECTION_RAM(eth_bridge, 4)
|
||||||
#endif
|
#endif
|
||||||
#endif /* NETWORKING */
|
#endif /* NETWORKING */
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,10 @@
|
||||||
#include <net/dsa.h>
|
#include <net/dsa.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_NET_ETHERNET_BRIDGE)
|
||||||
|
#include <net/ethernet_bridge.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -521,6 +525,10 @@ struct ethernet_context {
|
||||||
ATOMIC_DEFINE(interfaces, NET_VLAN_MAX_COUNT);
|
ATOMIC_DEFINE(interfaces, NET_VLAN_MAX_COUNT);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_NET_ETHERNET_BRIDGE)
|
||||||
|
struct eth_bridge_iface_context bridge;
|
||||||
|
#endif
|
||||||
|
|
||||||
/** Carrier ON/OFF handler worker. This is used to create
|
/** Carrier ON/OFF handler worker. This is used to create
|
||||||
* network interface UP/DOWN event when ethernet L2 driver
|
* network interface UP/DOWN event when ethernet L2 driver
|
||||||
* notices carrier ON/OFF situation. We must not create another
|
* notices carrier ON/OFF situation. We must not create another
|
||||||
|
|
189
include/net/ethernet_bridge.h
Normal file
189
include/net/ethernet_bridge.h
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
/** @file
|
||||||
|
* @brief Ethernet Bridge public header file
|
||||||
|
*
|
||||||
|
* Ethernet Bridges connect two or more Ethernet networks together and
|
||||||
|
* transparently forward packets from one network to the others as if
|
||||||
|
* they were part of the same network.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 BayLibre SAS
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_NET_ETHERNET_BRIDGE_H_
|
||||||
|
#define ZEPHYR_INCLUDE_NET_ETHERNET_BRIDGE_H_
|
||||||
|
|
||||||
|
#include <sys/slist.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ethernet Bridging API
|
||||||
|
* @defgroup eth_bridge Ethernet Bridging API
|
||||||
|
* @ingroup networking
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @cond INTERNAL_HIDDEN */
|
||||||
|
|
||||||
|
struct eth_bridge {
|
||||||
|
struct k_mutex lock;
|
||||||
|
sys_slist_t interfaces;
|
||||||
|
sys_slist_t listeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ETH_BRIDGE_INITIALIZER(obj) \
|
||||||
|
{ \
|
||||||
|
.lock = { }, \
|
||||||
|
.interfaces = SYS_SLIST_STATIC_INIT(&obj.interfaces), \
|
||||||
|
.listeners = SYS_SLIST_STATIC_INIT(&obj.listeners), \
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @endcond */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Statically define and initialize a bridge instance.
|
||||||
|
*
|
||||||
|
* @param name Name of the bridge object
|
||||||
|
*/
|
||||||
|
#define ETH_BRIDGE_INIT(name) \
|
||||||
|
Z_STRUCT_SECTION_ITERABLE(eth_bridge, name) = \
|
||||||
|
ETH_BRIDGE_INITIALIZER(name)
|
||||||
|
|
||||||
|
struct eth_bridge_iface_context {
|
||||||
|
sys_snode_t node;
|
||||||
|
struct eth_bridge *instance;
|
||||||
|
bool allow_tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct eth_bridge_listener {
|
||||||
|
sys_snode_t node;
|
||||||
|
struct k_fifo pkt_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add an Ethernet network interface to a bridge
|
||||||
|
*
|
||||||
|
* This adds a network interface to a bridge. The interface is then put
|
||||||
|
* into promiscuous mode, all packets received by this interface are sent
|
||||||
|
* to the bridge, and any other packets sent to the bridge (with some
|
||||||
|
* exceptions) are transmitted via this interface.
|
||||||
|
*
|
||||||
|
* For transmission from the bridge to occur via this interface, it is
|
||||||
|
* necessary to enable TX mode with eth_bridge_iface_tx(). TX mode is
|
||||||
|
* initially disabled.
|
||||||
|
*
|
||||||
|
* Once an interface is added to a bridge, all its incoming traffic is
|
||||||
|
* diverted to the bridge. However, packets sent out with net_if_queue_tx()
|
||||||
|
* via this interface are not subjected to the bridge.
|
||||||
|
*
|
||||||
|
* @param br A pointer to an initialized bridge object
|
||||||
|
* @param iface Interface to add
|
||||||
|
*
|
||||||
|
* @return 0 if OK, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int eth_bridge_iface_add(struct eth_bridge *br, struct net_if *iface);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove an Ethernet network interface from a bridge
|
||||||
|
*
|
||||||
|
* @param br A pointer to an initialized bridge object
|
||||||
|
* @param iface Interface to remove
|
||||||
|
*
|
||||||
|
* @return 0 if OK, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int eth_bridge_iface_remove(struct eth_bridge *br, struct net_if *iface);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable/disable transmission mode for a bridged interface
|
||||||
|
*
|
||||||
|
* When TX mode is off, the interface may receive packets and send them to
|
||||||
|
* the bridge but no packets coming from the bridge will be sent through this
|
||||||
|
* interface. When TX mode is on, both incoming and outgoing packets are
|
||||||
|
* allowed.
|
||||||
|
*
|
||||||
|
* @param iface Interface to configure
|
||||||
|
* @param allow true to activate TX mode, false otherwise
|
||||||
|
*
|
||||||
|
* @return 0 if OK, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int eth_bridge_iface_allow_tx(struct net_if *iface, bool allow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add (register) a listener to the bridge
|
||||||
|
*
|
||||||
|
* This lets a software listener register a pointer to a provided FIFO for
|
||||||
|
* receiving packets sent to the bridge. The listener is responsible for
|
||||||
|
* emptying the FIFO with k_fifo_get() which will return a struct net_pkt
|
||||||
|
* pointer, and releasing the packet with net_pkt_unref() when done with it.
|
||||||
|
*
|
||||||
|
* The listener wishing not to receive any more packets should simply
|
||||||
|
* unregister itself with eth_bridge_listener_remove().
|
||||||
|
*
|
||||||
|
* @param br A pointer to an initialized bridge object
|
||||||
|
* @param l A pointer to an initialized listener instance.
|
||||||
|
*
|
||||||
|
* @return 0 if OK, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int eth_bridge_listener_add(struct eth_bridge *br, struct eth_bridge_listener *l);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove (unregister) a listener from the bridge
|
||||||
|
*
|
||||||
|
* @param br A pointer to an initialized bridge object
|
||||||
|
* @param l A pointer to the listener instance to be removed.
|
||||||
|
*
|
||||||
|
* @return 0 if OK, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int eth_bridge_listener_remove(struct eth_bridge *br, struct eth_bridge_listener *l);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get bridge index according to pointer
|
||||||
|
*
|
||||||
|
* @param br Pointer to bridge instance
|
||||||
|
*
|
||||||
|
* @return Bridge index
|
||||||
|
*/
|
||||||
|
int eth_bridge_get_index(struct eth_bridge *br);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get bridge instance according to index
|
||||||
|
*
|
||||||
|
* @param index Bridge instance index
|
||||||
|
*
|
||||||
|
* @return Pointer to bridge instance or NULL if not found.
|
||||||
|
*/
|
||||||
|
struct eth_bridge *eth_bridge_get_by_index(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef eth_bridge_cb_t
|
||||||
|
* @brief Callback used while iterating over bridge instances
|
||||||
|
*
|
||||||
|
* @param br Pointer to bridge instance
|
||||||
|
* @param user_data User supplied data
|
||||||
|
*/
|
||||||
|
typedef void (*eth_bridge_cb_t)(struct eth_bridge *br, void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Go through all the bridge instances in order to get
|
||||||
|
* information about them. This is mainly useful in
|
||||||
|
* net-shell to print data about currently active bridges.
|
||||||
|
*
|
||||||
|
* @param cb Callback to call for each bridge instance
|
||||||
|
* @param user_data User supplied data
|
||||||
|
*/
|
||||||
|
void net_eth_bridge_foreach(eth_bridge_cb_t cb, void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_NET_ETHERNET_BRIDGE_H_ */
|
|
@ -181,10 +181,17 @@ struct net_pkt {
|
||||||
* segment.
|
* segment.
|
||||||
*/
|
*/
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint8_t captured : 1; /* Set to 1 if this packet is already being
|
uint8_t captured : 1; /* Set to 1 if this packet is already being
|
||||||
* captured
|
* captured
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
uint8_t l2_bridged : 1; /* set to 1 if this packet comes from a bridge
|
||||||
|
* and already contains its L2 header to be
|
||||||
|
* preserved. Useful only if
|
||||||
|
* defined(CONFIG_NET_ETHERNET_BRIDGE).
|
||||||
|
*/
|
||||||
|
|
||||||
union {
|
union {
|
||||||
/* IPv6 hop limit or IPv4 ttl for this network packet.
|
/* IPv6 hop limit or IPv4 ttl for this network packet.
|
||||||
* The value is shared between IPv6 and IPv4.
|
* The value is shared between IPv6 and IPv4.
|
||||||
|
@ -349,6 +356,18 @@ static inline void net_pkt_set_captured(struct net_pkt *pkt, bool is_captured)
|
||||||
pkt->captured = is_captured;
|
pkt->captured = is_captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool net_pkt_is_l2_bridged(struct net_pkt *pkt)
|
||||||
|
{
|
||||||
|
return IS_ENABLED(CONFIG_NET_ETHERNET_BRIDGE) ? !!(pkt->l2_bridged) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void net_pkt_set_l2_bridged(struct net_pkt *pkt, bool is_l2_bridged)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_NET_ETHERNET_BRIDGE)) {
|
||||||
|
pkt->l2_bridged = is_l2_bridged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline uint8_t net_pkt_ip_hdr_len(struct net_pkt *pkt)
|
static inline uint8_t net_pkt_ip_hdr_len(struct net_pkt *pkt)
|
||||||
{
|
{
|
||||||
return pkt->ip_hdr_len;
|
return pkt->ip_hdr_len;
|
||||||
|
|
|
@ -55,7 +55,11 @@ LOG_MODULE_REGISTER(net_pkt, CONFIG_NET_PKT_LOG_LEVEL);
|
||||||
*/
|
*/
|
||||||
#define MAX_IP_PROTO_LEN 8
|
#define MAX_IP_PROTO_LEN 8
|
||||||
#else
|
#else
|
||||||
|
#if defined(CONFIG_NET_ETHERNET_BRIDGE)
|
||||||
|
#define MAX_IP_PROTO_LEN 0
|
||||||
|
#else
|
||||||
#error "Either IPv6 or IPv4 needs to be selected."
|
#error "Either IPv6 or IPv4 needs to be selected."
|
||||||
|
#endif /* ETHERNET_BRIDGE */
|
||||||
#endif /* SOCKETS_CAN */
|
#endif /* SOCKETS_CAN */
|
||||||
#endif /* IPv4 */
|
#endif /* IPv4 */
|
||||||
#endif /* IPv6 */
|
#endif /* IPv6 */
|
||||||
|
@ -1769,6 +1773,7 @@ static void clone_pkt_attributes(struct net_pkt *pkt, struct net_pkt *clone_pkt)
|
||||||
net_pkt_set_priority(clone_pkt, net_pkt_priority(pkt));
|
net_pkt_set_priority(clone_pkt, net_pkt_priority(pkt));
|
||||||
net_pkt_set_orig_iface(clone_pkt, net_pkt_orig_iface(pkt));
|
net_pkt_set_orig_iface(clone_pkt, net_pkt_orig_iface(pkt));
|
||||||
net_pkt_set_captured(clone_pkt, net_pkt_is_captured(pkt));
|
net_pkt_set_captured(clone_pkt, net_pkt_is_captured(pkt));
|
||||||
|
net_pkt_set_l2_bridged(clone_pkt, net_pkt_is_l2_bridged(pkt));
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
|
if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
|
||||||
net_pkt_set_ipv4_ttl(clone_pkt, net_pkt_ipv4_ttl(pkt));
|
net_pkt_set_ipv4_ttl(clone_pkt, net_pkt_ipv4_ttl(pkt));
|
||||||
|
|
|
@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_L2_ETHERNET_MGMT ethernet_mgmt.c)
|
||||||
if(CONFIG_NET_NATIVE)
|
if(CONFIG_NET_NATIVE)
|
||||||
zephyr_library_sources_ifdef(CONFIG_NET_ARP arp.c)
|
zephyr_library_sources_ifdef(CONFIG_NET_ARP arp.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS_ETHERNET ethernet_stats.c)
|
zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS_ETHERNET ethernet_stats.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_NET_ETHERNET_BRIDGE bridge.c)
|
||||||
|
|
||||||
if(CONFIG_NET_GPTP)
|
if(CONFIG_NET_GPTP)
|
||||||
add_subdirectory(gptp)
|
add_subdirectory(gptp)
|
||||||
|
|
|
@ -76,4 +76,19 @@ endif # NET_ARP
|
||||||
source "subsys/net/l2/ethernet/gptp/Kconfig"
|
source "subsys/net/l2/ethernet/gptp/Kconfig"
|
||||||
source "subsys/net/l2/ethernet/lldp/Kconfig"
|
source "subsys/net/l2/ethernet/lldp/Kconfig"
|
||||||
|
|
||||||
|
config NET_ETHERNET_BRIDGE
|
||||||
|
bool "Ethernet Bridging support"
|
||||||
|
select NET_PROMISCUOUS_MODE
|
||||||
|
help
|
||||||
|
Enables Ethernet bridging where packets can be transparently
|
||||||
|
forwarded across interfaces registered to a bridge.
|
||||||
|
|
||||||
|
if NET_ETHERNET_BRIDGE
|
||||||
|
module = NET_ETHERNET_BRIDGE
|
||||||
|
module-dep = NET_LOG
|
||||||
|
module-str = Log level for Ethernet Bridging
|
||||||
|
module-help = Enables Ethernet Bridge code to output debug messages.
|
||||||
|
source "subsys/net/Kconfig.template.log_config.net"
|
||||||
|
endif # NET_ETHERNET_BRIDGE
|
||||||
|
|
||||||
endif # NET_L2_ETHERNET
|
endif # NET_L2_ETHERNET
|
||||||
|
|
229
subsys/net/l2/ethernet/bridge.c
Normal file
229
subsys/net/l2/ethernet/bridge.c
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 BayLibre SAS
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(net_eth_bridge, CONFIG_NET_ETHERNET_BRIDGE_LOG_LEVEL);
|
||||||
|
|
||||||
|
#include <net/net_core.h>
|
||||||
|
#include <net/net_l2.h>
|
||||||
|
#include <net/net_if.h>
|
||||||
|
#include <net/ethernet.h>
|
||||||
|
#include <net/ethernet_bridge.h>
|
||||||
|
|
||||||
|
#include <sys/slist.h>
|
||||||
|
|
||||||
|
#include "bridge.h"
|
||||||
|
|
||||||
|
extern struct eth_bridge _eth_bridge_list_start[];
|
||||||
|
extern struct eth_bridge _eth_bridge_list_end[];
|
||||||
|
|
||||||
|
void net_eth_bridge_foreach(eth_bridge_cb_t cb, void *user_data)
|
||||||
|
{
|
||||||
|
Z_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&br->lock, K_FOREVER);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&br->lock, K_FOREVER);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&br->lock, K_FOREVER);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&br->lock, K_FOREVER);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&br->lock, K_FOREVER);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
22
subsys/net/l2/ethernet/bridge.h
Normal file
22
subsys/net/l2/ethernet/bridge.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 BayLibre SAS
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __BRIDGE_H
|
||||||
|
#define __BRIDGE_H
|
||||||
|
|
||||||
|
enum net_verdict net_eth_bridge_input(struct ethernet_context *ctx,
|
||||||
|
struct net_pkt *pkt);
|
||||||
|
|
||||||
|
static inline bool net_eth_iface_is_bridged(struct ethernet_context *ctx)
|
||||||
|
{
|
||||||
|
#if defined(CONFIG_NET_ETHERNET_BRIDGE)
|
||||||
|
return ctx->bridge.instance != NULL;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __BRIDGE_H */
|
|
@ -30,6 +30,7 @@ LOG_MODULE_REGISTER(net_ethernet, CONFIG_NET_L2_ETHERNET_LOG_LEVEL);
|
||||||
#include "net_private.h"
|
#include "net_private.h"
|
||||||
#include "ipv6.h"
|
#include "ipv6.h"
|
||||||
#include "ipv4_autoconf_internal.h"
|
#include "ipv4_autoconf_internal.h"
|
||||||
|
#include "bridge.h"
|
||||||
|
|
||||||
#define NET_BUF_TIMEOUT K_MSEC(100)
|
#define NET_BUF_TIMEOUT K_MSEC(100)
|
||||||
|
|
||||||
|
@ -187,6 +188,19 @@ static enum net_verdict ethernet_recv(struct net_if *iface,
|
||||||
goto drop;
|
goto drop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_NET_ETHERNET_BRIDGE) &&
|
||||||
|
net_eth_iface_is_bridged(ctx)) {
|
||||||
|
net_pkt_set_l2_bridged(pkt, true);
|
||||||
|
net_pkt_lladdr_src(pkt)->addr = hdr->src.addr;
|
||||||
|
net_pkt_lladdr_src(pkt)->len = sizeof(struct net_eth_addr);
|
||||||
|
net_pkt_lladdr_src(pkt)->type = NET_LINK_ETHERNET;
|
||||||
|
net_pkt_lladdr_dst(pkt)->addr = hdr->dst.addr;
|
||||||
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
||||||
|
net_pkt_lladdr_dst(pkt)->type = NET_LINK_ETHERNET;
|
||||||
|
ethernet_update_rx_stats(iface, pkt, net_pkt_get_len(pkt));
|
||||||
|
return net_eth_bridge_input(ctx, pkt);
|
||||||
|
}
|
||||||
|
|
||||||
type = ntohs(hdr->type);
|
type = ntohs(hdr->type);
|
||||||
|
|
||||||
if (net_eth_is_vlan_enabled(ctx, iface) &&
|
if (net_eth_is_vlan_enabled(ctx, iface) &&
|
||||||
|
@ -578,7 +592,19 @@ static int ethernet_send(struct net_if *iface, struct net_pkt *pkt)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_NET_IPV4) &&
|
if (IS_ENABLED(CONFIG_NET_ETHERNET_BRIDGE) &&
|
||||||
|
net_pkt_is_l2_bridged(pkt)) {
|
||||||
|
net_pkt_cursor_init(pkt);
|
||||||
|
ret = net_l2_send(api->send, net_if_get_device(iface), iface, pkt);
|
||||||
|
if (ret != 0) {
|
||||||
|
eth_stats_update_errors_tx(iface);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ethernet_update_tx_stats(iface, pkt);
|
||||||
|
ret = net_pkt_get_len(pkt);
|
||||||
|
net_pkt_unref(pkt);
|
||||||
|
return ret;
|
||||||
|
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
|
||||||
net_pkt_family(pkt) == AF_INET) {
|
net_pkt_family(pkt) == AF_INET) {
|
||||||
struct net_pkt *tmp;
|
struct net_pkt *tmp;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue