From cdbd9cff73adb24d6691d0296d4f51059036b7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BCthing?= Date: Mon, 4 May 2026 10:13:42 +0200 Subject: [PATCH] Provide NetworkInterface and NetworkInterfaceCollection classes Today we just have a loose collection of functions to handle LwIP network interfaces. This does not feel like a nice interface to use and when using it the first time I had to do quite some research on which LwIP functions are already called through abstractions and which ones I still have to call directly. This change tries to introduce an easy to use class based abstraction of a LwIP interface and also provide a collection of interfaces with some logic to properly route frames to the matching interface based on its VLAN ID. --- libs/bsw/lwipSocket/CMakeLists.txt | 2 + .../lwipSocket/netif/NetworkInterface.h | 184 ++++++++++++++++++ .../netif/NetworkInterfaceCollection.h | 126 ++++++++++++ .../src/lwipSocket/netif/NetworkInterface.cpp | 1 + .../netif/NetworkInterfaceCollection.cpp | 1 + 5 files changed, 314 insertions(+) create mode 100644 libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterface.h create mode 100644 libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterfaceCollection.h create mode 100644 libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterface.cpp create mode 100644 libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterfaceCollection.cpp diff --git a/libs/bsw/lwipSocket/CMakeLists.txt b/libs/bsw/lwipSocket/CMakeLists.txt index 6d4022896a0..56924bd422b 100644 --- a/libs/bsw/lwipSocket/CMakeLists.txt +++ b/libs/bsw/lwipSocket/CMakeLists.txt @@ -1,5 +1,7 @@ add_library( lwipSocket + src/lwipSocket/netif/NetworkInterface.cpp + src/lwipSocket/netif/NetworkInterfaceCollection.cpp src/lwipSocket/netif/LwipNetworkInterface.cpp src/lwipSocket/tcp/LwipServerSocket.cpp src/lwipSocket/tcp/LwipSocket.cpp diff --git a/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterface.h b/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterface.h new file mode 100644 index 00000000000..d65a0590a3a --- /dev/null +++ b/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterface.h @@ -0,0 +1,184 @@ +#pragma once + +#include "ip/IPAddress.h" + +#include "lwip/err.h" +#include "lwip/netif.h" +#include "netif/etharp.h" +#include "netif/ethernet.h" + +namespace openbsw::lwip +{ + +/** + * Representation of a single LwIP network interface. + * + * This class encapsulates a single LwIP network interface with an associated VLAN ID and IPv4 + * address. Furthermore, it provides an integration with a low-level network driver, which needs to + * implement two methods: + * + * - getMac() returning the MAC address of the physical device + * - writeFrame(struct netif*, struct pbuf*) + * + * To instantiate a network interface you need to instantiate an object and call the up() and down() + * methods at the appropriate points in your lifecycle. Furthermore, you need to communicate link + * up/down events by calling linkUp() and linkDown() respectively. + * + * Outgoing frames are handled via the LwIP linkoutput callback and passed to you low-level network + * driver using the writeFrame() method. For incoming frames you have to call input(struct pbuf*) + * for each frame received. + * + * Note, that VLANs are not handled explicitly. For outgoing frames you need to register a proper + * LWIP_HOOK_VLAN_SET as shown below. Since LwIP only passes it's netif structure, we register the + * pointer to our own instance in the free-to-use state member. + * + * \code{.cpp} + * int32_t vlanForNetif(const struct netif* netif) + * { + * auto* interface = reinterpret_cast(netif->state); + * return interface->hasVlanId() ? -1 : static_cast(interface->getVlanId()); + * } + * \endcode + * + * For incoming frames you also need to implement the filtering for the VLAN on your own. For an + * already existing implementation you can check the NetworkInterfaceCollection. + */ +template +class NetworkInterface +{ +public: + using DriverType = D; + + static constexpr uint16_t UNTAGGED_VLAN_ID = 0xffffU; + +public: + /** + * Constructor to create a network interface with the given VLAN ID and IPv4 address. + */ + NetworkInterface( + DriverType& driver, + uint16_t vlanId, + ::ip::IPAddress address, + ::ip::IPAddress netmask, + ::ip::IPAddress gateway) + : _netif() + , _driver(driver) + , _vlanId(vlanId) + , _address(address) + , _netmask(netmask) + , _gateway(gateway) + { + _netif.name[0] = '\0'; + } + + /** + * Constructor to create a network interface with the given VLAN ID and no IPv4 address. + */ + NetworkInterface(DriverType& driver, uint16_t vlanId) + : _netif() + , _driver(driver) + , _vlanId(vlanId) + , _address(::ip::make_ip4(0U, 0U, 0U, 0U)) + , _netmask(::ip::make_ip4(0U, 0U, 0U, 0U)) + , _gateway(::ip::make_ip4(0U, 0U, 0U, 0U)) + { + _netif.name[0] = '\0'; + } + + /** + * Returns whether the network interface has a VLAN ID assigned. + * + * In case the network interface has a VLAN ID assigned, it should only handle incoming frames + * of this VLAN. Furthermore, outgoing frames need to be tagged with this VLAN ID. + */ + bool hasVlanId() const { return _vlanId != UNTAGGED_VLAN_ID; } + + /** + * Returns the VLAN ID assigned to this network interface. + */ + uint16_t getVlanId() const { return _vlanId; } + + /** + * Sets the interface up and adds it to the LwIP stack. + */ + void up() + { + _netif.linkoutput = &linkoutput; + + ip4_addr address; + ::etl::copy( + ip::packed(_address), + ::etl::span(reinterpret_cast(&address.addr), sizeof(address.addr))); + + ip4_addr netmask; + ::etl::copy( + ip::packed(_netmask), + ::etl::span(reinterpret_cast(&netmask.addr), sizeof(netmask.addr))); + + ip4_addr gateway; + ::etl::copy( + ip::packed(_gateway), + ::etl::span(reinterpret_cast(&gateway.addr), sizeof(gateway.addr))); + + netif_add(&_netif, &address, &netmask, &gateway, this, &initnetif, nullptr); + netif_set_up(&_netif); + } + + /** + * Sets the interface down and removes it from the LwIP stack. + */ + void down() + { + netif_set_down(&_netif); + netif_remove(&_netif); + } + + /** + * Notifies about the link state of the underlying physical interface. + */ + void linkUp() { netif_set_link_up(&_netif); } + + /** + * Notifies about the link state of the underlying physical interface. + */ + void linkDown() { netif_set_link_down(&_netif); } + + /** + * Processes a frame received from the low-lever device driver. + */ + void input(struct pbuf* buffer) { _netif.input(buffer, &_netif); } + +private: + static err_t linkoutput(struct netif* netif, struct pbuf* buffer) + { + auto* interface = reinterpret_cast(netif->state); + return interface->_driver.writeFrame(&interface->_netif, buffer); + } + + static err_t initnetif(struct netif* netif) + { + auto const* interface = reinterpret_cast(netif->state); + + netif->input = ðernet_input; + netif->output = ðarp_output; + + netif->flags + |= (NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHERNET | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP); + netif->mtu = 1500U; + + netif->hwaddr_len = 6U; + interface->_driver.getMac().copyTo(netif->hwaddr); + + return ERR_OK; + } + +private: + netif _netif; + DriverType& _driver; + uint16_t _vlanId; + ::ip::IPAddress _address; + ::ip::IPAddress _netmask; + ::ip::IPAddress _gateway; +}; + +} // namespace openbsw::lwip diff --git a/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterfaceCollection.h b/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterfaceCollection.h new file mode 100644 index 00000000000..10fbe4eb650 --- /dev/null +++ b/libs/bsw/lwipSocket/include/lwipSocket/netif/NetworkInterfaceCollection.h @@ -0,0 +1,126 @@ +#pragma once + +#include "lwipSocket/netif/NetworkInterface.h" + +#include + +namespace openbsw::lwip +{ + +/** + * Collection of multiple network interfaces with VLAN handling. + * + * In case you want to manage multiple network interfaces with different VLAN IDs you need to + * implement proper routing to decide to which interface to route a frame. LwIP does not provide any + * read-to-use implementation for this. + */ +template +class NetworkInterfaceCollection +{ +public: + using DriverType = D; + using InterfaceType = NetworkInterface; + + static constexpr size_t InterfaceCount = C; + +public: + /** + * Constructor to create an empty collection for the given driver. + */ + NetworkInterfaceCollection(D& driver) : _driver(driver) {} + + /** + * Adds a single network interface with the given IP address and netmask. + */ + void add(uint16_t vlan, ::ip::IPAddress address, ::ip::IPAddress mask) + { + add(vlan, address, mask, ::ip::make_ip4(0U, 0U, 0U, 0U)); + } + + /** + * Adds a single network interface with the given IP address, netmask and gateway. + */ + void add(uint16_t vlan, ::ip::IPAddress address, ::ip::IPAddress mask, ::ip::IPAddress gateway) + { + _interfaces.emplace_back(_driver, vlan, address, mask, gateway); + } + + /** + * Sets all interfaces up and adds them to the LwIP stack. + */ + void up() + { + for (auto& interface : _interfaces) + { + interface.up(); + } + } + + /** + * Sets all interfaces down and removes them from the LwIP stack. + */ + void down() + { + for (auto& interface : _interfaces) + { + interface.down(); + } + } + + /** + * Notifies about the link state of the underlying physical interface. + */ + void linkUp() + { + for (auto& interface : _interfaces) + { + interface.linkUp(); + } + } + + /** + * Notifies about the link state of the underlying physical interface. + */ + void linkDown() + { + for (auto& interface : _interfaces) + { + interface.linkDown(); + } + } + + /** + * Processes a frame received from the low-lever device driver. + * + * When having multiple network interfaces with different VLAN IDs we need to decide to which + * network interface to route a frame. Therefore, we parse the incoming frame, extract it's VLAN + * ID and handle it for the matching interface. + */ + void input(struct pbuf* buffer) + { + auto payload = etl::span(static_cast(buffer->payload), buffer->len); + if (payload.size() < 16U) + { + return; + } + payload.advance(12U); + auto const type = payload.take(); + auto const vlanId + = (type == 0x8100U) ? (payload.take() & 0x0FFFU) : 0xFFFFU; + + for (auto& interface : _interfaces) + { + if (interface.getVlanId() == vlanId) + { + interface.input(buffer); + return; + } + } + } + +private: + DriverType& _driver; + ::etl::vector _interfaces; +}; + +} // namespace openbsw::lwip diff --git a/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterface.cpp b/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterface.cpp new file mode 100644 index 00000000000..ad4539eebcf --- /dev/null +++ b/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterface.cpp @@ -0,0 +1 @@ +#include "lwipSocket/netif/NetworkInterface.h" diff --git a/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterfaceCollection.cpp b/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterfaceCollection.cpp new file mode 100644 index 00000000000..9c81278275a --- /dev/null +++ b/libs/bsw/lwipSocket/src/lwipSocket/netif/NetworkInterfaceCollection.cpp @@ -0,0 +1 @@ +#include "lwipSocket/netif/NetworkInterfaceCollection.h"