diff --git a/boards/arm/ip_k66f/linker.ld b/boards/arm/ip_k66f/linker.ld index 9641df553e1..b8aa54b678b 100644 --- a/boards/arm/ip_k66f/linker.ld +++ b/boards/arm/ip_k66f/linker.ld @@ -39,6 +39,7 @@ SECTION_DATA_PROLOGUE(net_if_area,,SUBALIGN(4)) \ _net_if_list_end = .; \ } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) \ 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 diff --git a/include/linker/common-ram.ld b/include/linker/common-ram.ld index 9fb4292a21b..a324f4dbcad 100644 --- a/include/linker/common-ram.ld +++ b/include/linker/common-ram.ld @@ -5,7 +5,8 @@ #define NETWORK_RAM_SECTIONS \ Z_ITERABLE_SECTION_RAM(net_if, 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 /* NETWORKING */ diff --git a/include/net/ethernet.h b/include/net/ethernet.h index 47cc0ae0dfc..f12bffa1daf 100644 --- a/include/net/ethernet.h +++ b/include/net/ethernet.h @@ -34,6 +34,10 @@ #include #endif +#if defined(CONFIG_NET_ETHERNET_BRIDGE) +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -521,6 +525,10 @@ struct ethernet_context { ATOMIC_DEFINE(interfaces, NET_VLAN_MAX_COUNT); #endif +#if defined(CONFIG_NET_ETHERNET_BRIDGE) + struct eth_bridge_iface_context bridge; +#endif + /** Carrier ON/OFF handler worker. This is used to create * network interface UP/DOWN event when ethernet L2 driver * notices carrier ON/OFF situation. We must not create another diff --git a/include/net/ethernet_bridge.h b/include/net/ethernet_bridge.h new file mode 100644 index 00000000000..ad546c56d0a --- /dev/null +++ b/include/net/ethernet_bridge.h @@ -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 + +#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_ */ diff --git a/include/net/net_pkt.h b/include/net/net_pkt.h index d29ca218256..1c2ef1dd0a7 100644 --- a/include/net/net_pkt.h +++ b/include/net/net_pkt.h @@ -181,10 +181,17 @@ struct net_pkt { * segment. */ #endif + uint8_t captured : 1; /* Set to 1 if this packet is already being * 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 { /* IPv6 hop limit or IPv4 ttl for this network packet. * 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; } +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) { return pkt->ip_hdr_len; diff --git a/subsys/net/ip/net_pkt.c b/subsys/net/ip/net_pkt.c index e295201b8d3..cd17fd1ce67 100644 --- a/subsys/net/ip/net_pkt.c +++ b/subsys/net/ip/net_pkt.c @@ -55,7 +55,11 @@ LOG_MODULE_REGISTER(net_pkt, CONFIG_NET_PKT_LOG_LEVEL); */ #define MAX_IP_PROTO_LEN 8 #else +#if defined(CONFIG_NET_ETHERNET_BRIDGE) +#define MAX_IP_PROTO_LEN 0 +#else #error "Either IPv6 or IPv4 needs to be selected." +#endif /* ETHERNET_BRIDGE */ #endif /* SOCKETS_CAN */ #endif /* IPv4 */ #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_orig_iface(clone_pkt, net_pkt_orig_iface(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) { net_pkt_set_ipv4_ttl(clone_pkt, net_pkt_ipv4_ttl(pkt)); diff --git a/subsys/net/l2/ethernet/CMakeLists.txt b/subsys/net/l2/ethernet/CMakeLists.txt index 7d8c4eb89ab..4d76f4c16d7 100644 --- a/subsys/net/l2/ethernet/CMakeLists.txt +++ b/subsys/net/l2/ethernet/CMakeLists.txt @@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_L2_ETHERNET_MGMT ethernet_mgmt.c) if(CONFIG_NET_NATIVE) 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_ETHERNET_BRIDGE bridge.c) if(CONFIG_NET_GPTP) add_subdirectory(gptp) diff --git a/subsys/net/l2/ethernet/Kconfig b/subsys/net/l2/ethernet/Kconfig index 9fd0049de7a..fba8ab0e4c9 100644 --- a/subsys/net/l2/ethernet/Kconfig +++ b/subsys/net/l2/ethernet/Kconfig @@ -76,4 +76,19 @@ endif # NET_ARP source "subsys/net/l2/ethernet/gptp/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 diff --git a/subsys/net/l2/ethernet/bridge.c b/subsys/net/l2/ethernet/bridge.c new file mode 100644 index 00000000000..2df15dba44a --- /dev/null +++ b/subsys/net/l2/ethernet/bridge.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2021 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_eth_bridge, CONFIG_NET_ETHERNET_BRIDGE_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/subsys/net/l2/ethernet/bridge.h b/subsys/net/l2/ethernet/bridge.h new file mode 100644 index 00000000000..a4836fdc9d1 --- /dev/null +++ b/subsys/net/l2/ethernet/bridge.h @@ -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 */ diff --git a/subsys/net/l2/ethernet/ethernet.c b/subsys/net/l2/ethernet/ethernet.c index 7089d406c9b..f656d066e73 100644 --- a/subsys/net/l2/ethernet/ethernet.c +++ b/subsys/net/l2/ethernet/ethernet.c @@ -30,6 +30,7 @@ LOG_MODULE_REGISTER(net_ethernet, CONFIG_NET_L2_ETHERNET_LOG_LEVEL); #include "net_private.h" #include "ipv6.h" #include "ipv4_autoconf_internal.h" +#include "bridge.h" #define NET_BUF_TIMEOUT K_MSEC(100) @@ -187,6 +188,19 @@ static enum net_verdict ethernet_recv(struct net_if *iface, 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); 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; } - 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) { struct net_pkt *tmp;