From d7de242ce78ae2782ac483da76204d305ff49ac7 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Tue, 10 Dec 2024 16:45:20 +0100 Subject: [PATCH 1/3] network: bridge: add support for NO_LL_LEARN When using locked ports on a bridge link-local learning needs to be disabled to prevent the kernel from learning and automatically unlocking hosts based on link-local traffic. So add support for enabling NO_LL_LEARN for bridges. --- man/systemd.netdev.xml | 10 ++++++++++ src/network/netdev/bridge.c | 13 +++++++++++++ src/network/netdev/bridge.h | 1 + src/network/netdev/netdev-gperf.gperf | 1 + test/test-network/conf/25-bridge.netdev | 1 + test/test-network/systemd-networkd-tests.py | 1 + 6 files changed, 27 insertions(+) diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index d072501a45..b809bd220e 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -456,6 +456,16 @@ + + LinkLocalLearning= + + Takes a boolean. This enables learning source addresses from link local frames. When unset, the + kernel's default will be used. + + + + + diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c index d3ba4989d9..06f9840d1c 100644 --- a/src/network/netdev/bridge.c +++ b/src/network/netdev/bridge.c @@ -47,6 +47,7 @@ static int netdev_bridge_set_handler(sd_netlink *rtnl, sd_netlink_message *m, Ne static int netdev_bridge_post_create_message(NetDev *netdev, sd_netlink_message *req) { Bridge *b = BRIDGE(netdev); + struct br_boolopt_multi bm = {}; int r; r = sd_netlink_message_open_container(req, IFLA_LINKINFO); @@ -142,6 +143,17 @@ static int netdev_bridge_post_create_message(NetDev *netdev, sd_netlink_message return r; } + if (b->linklocal_learn >= 0) { + bm.optmask |= 1 << BR_BOOLOPT_NO_LL_LEARN; + SET_FLAG(bm.optval, 1 << BR_BOOLOPT_NO_LL_LEARN, !b->linklocal_learn); + } + + if (bm.optmask != 0) { + r = sd_netlink_message_append_data(req, IFLA_BR_MULTI_BOOLOPT, &bm, sizeof(bm)); + if (r < 0) + return r; + } + r = sd_netlink_message_close_container(req); if (r < 0) return r; @@ -279,6 +291,7 @@ static void bridge_init(NetDev *netdev) { b->default_pvid = VLANID_INVALID; b->forward_delay = USEC_INFINITY; b->ageing_time = USEC_INFINITY; + b->linklocal_learn = -1; } static bool bridge_can_set_mac(NetDev *netdev, const struct hw_addr_data *hw_addr) { diff --git a/src/network/netdev/bridge.h b/src/network/netdev/bridge.h index 7be00e1063..a1d682bf8f 100644 --- a/src/network/netdev/bridge.h +++ b/src/network/netdev/bridge.h @@ -21,6 +21,7 @@ typedef struct Bridge { uint8_t igmp_version; uint32_t fdb_max_learned; bool fdb_max_learned_set; + int linklocal_learn; usec_t forward_delay; usec_t hello_time; diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index be61e208a0..dcd786d87f 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -236,6 +236,7 @@ Bridge.VLANProtocol, config_parse_vlanprotocol, Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp) Bridge.MulticastIGMPVersion, config_parse_uint8, 0, offsetof(Bridge, igmp_version) Bridge.FDBMaxLearned, config_parse_bridge_fdb_max_learned, 0, offsetof(Bridge, fdb_max_learned) +Bridge.LinkLocalLearning, config_parse_tristate, 0, offsetof(Bridge, linklocal_learn) VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ VRF.Table, config_parse_uint32, 0, offsetof(Vrf, table) BareUDP.DestinationPort, config_parse_ip_port, 0, offsetof(BareUDP, dest_port) diff --git a/test/test-network/conf/25-bridge.netdev b/test/test-network/conf/25-bridge.netdev index 9e7fa545b2..398637b643 100644 --- a/test/test-network/conf/25-bridge.netdev +++ b/test/test-network/conf/25-bridge.netdev @@ -18,3 +18,4 @@ VLANProtocol=802.1ad STP=true MulticastIGMPVersion=3 FDBMaxLearned=4 +LinkLocalLearning=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index a151e00a2f..533945ed0d 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1682,6 +1682,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) output = networkctl_status('bridge99') print(output) From a434de60568b0f34c07de4f97af6cdc33d4fd2a2 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Mon, 2 Dec 2024 11:54:09 +0100 Subject: [PATCH 2/3] network: bridge: add support for IFLA_BRPORT_LOCKED Since linux commit a21d9a670d81103db7f788de1a4a4a6e4b891a0b ("net: bridge: Add support for bridge port in locked mode"), included since v5.18, it is possible to set bridge ports to locked. Locked ports do not learn automatically, and discard any traffic from unknown source MACs. To allow traffic, the userspace authenticator is expected to create fdb entries for authenticated hosts. Add support to systemd-network for setting the new attribute for bridge ports. --- man/systemd.network.xml | 9 +++++++++ src/libsystemd/sd-netlink/netlink-types-rtnl.c | 1 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-setlink.c | 6 ++++++ .../conf/26-bridge-slave-interface-2.network | 1 + test/test-network/systemd-networkd-tests.py | 1 + 8 files changed, 21 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 126accaca9..edb15cf4aa 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -4624,6 +4624,15 @@ ServerAddress=192.168.0.1/24 + + Locked= + + Takes a boolean. Configures whether the port is "locked" and does not allow traffic forwarded + until fully authenticated, e.g. via 802.1x. When unset, the kernel's default will be used. + + + + diff --git a/src/libsystemd/sd-netlink/netlink-types-rtnl.c b/src/libsystemd/sd-netlink/netlink-types-rtnl.c index a85b4b16c3..c0e820486d 100644 --- a/src/libsystemd/sd-netlink/netlink-types-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-rtnl.c @@ -485,6 +485,7 @@ static const struct NLAPolicy rtnl_bridge_port_policies[] = { [IFLA_BRPORT_MRP_IN_OPEN] = BUILD_POLICY(U8), [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = BUILD_POLICY(U32), [IFLA_BRPORT_MCAST_EHT_HOSTS_CNT] = BUILD_POLICY(U32), + [IFLA_BRPORT_LOCKED] = BUILD_POLICY(U8), }; static const NLAPolicySetUnionElement rtnl_link_info_slave_data_policy_set_union_elements[] = { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 83ef927b8b..30f7148029 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -383,6 +383,7 @@ Bridge.ProxyARP, config_parse_tristate, Bridge.ProxyARPWiFi, config_parse_tristate, 0, offsetof(Network, bridge_proxy_arp_wifi) Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority) Bridge.MulticastRouter, config_parse_multicast_router, 0, offsetof(Network, multicast_router) +Bridge.Locked, config_parse_tristate, 0, offsetof(Network, bridge_locked) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 BridgeFDB.Destination, config_parse_fdb_destination, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 7de0027aae..82f39e2f80 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -456,6 +456,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .bridge_proxy_arp_wifi = -1, .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, .multicast_router = _MULTICAST_ROUTER_INVALID, + .bridge_locked = -1, .bridge_vlan_pvid = BRIDGE_VLAN_KEEP_PVID, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index b61914ea7a..95407279e3 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -297,6 +297,7 @@ struct Network { uint32_t cost; uint16_t priority; MulticastRouter multicast_router; + int bridge_locked; /* Bridge VLAN */ uint16_t bridge_vlan_pvid; diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index 467fadb3ea..b973ffea98 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -320,6 +320,12 @@ static int link_configure_fill_message( return r; } + if (link->network->bridge_locked >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_LOCKED, link->network->bridge_locked); + if (r < 0) + return r; + } + r = sd_netlink_message_close_container(req); if (r < 0) return r; diff --git a/test/test-network/conf/26-bridge-slave-interface-2.network b/test/test-network/conf/26-bridge-slave-interface-2.network index 42b197eeef..6eb955dc4b 100644 --- a/test/test-network/conf/26-bridge-slave-interface-2.network +++ b/test/test-network/conf/26-bridge-slave-interface-2.network @@ -10,3 +10,4 @@ Bridge=bridge99 [Bridge] Priority=0 +Locked=true diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 533945ed0d..2d1309da28 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5790,6 +5790,7 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities): output = check_output('bridge -d link show test1') print(output) self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') + self.assertIn('locked on', output) def test_bridge_property(self): copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', From 08a26ecc4733a04fcd763cebd889da1c49672e0e Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 24 Jan 2025 13:15:06 +0100 Subject: [PATCH 3/3] network: bridge: add support for IFLA_BRPORT_MAB Since linux commit a35ec8e38cdd1766f29924ca391a01de20163931 ("bridge: Add MAC Authentication Bypass (MAB) support"), included since v6.2, it is possible to enable MAC Authentication Bypass for bridge ports. In this mode the locked port learns again, but the learned fdb entries are locked, allowing user space to unlock hosts based seen MAC addresses. This requires learning to be enabled on the port, and link-local learning disabled for the bridge. Add support to systemd-network for setting the new attribute for bridge ports. --- man/systemd.network.xml | 9 +++++++++ src/libsystemd/sd-netlink/netlink-types-rtnl.c | 1 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-setlink.c | 6 ++++++ .../conf/26-bridge-slave-interface-2.network | 1 + test/test-network/systemd-networkd-tests.py | 2 ++ 8 files changed, 22 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index edb15cf4aa..f2777e640f 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -4630,6 +4630,15 @@ ServerAddress=192.168.0.1/24 Takes a boolean. Configures whether the port is "locked" and does not allow traffic forwarded until fully authenticated, e.g. via 802.1x. When unset, the kernel's default will be used. + + + MACAuthenticationBypass= + + Takes a boolean. Configures whether a locked port has "MAC Authentication Bypass" enabled and + creates newly learned fdb entries in a "locked" state. User space can authenticate these entries by + clearing the locked flag. Requires Learning to be enabled. When unset, the kernel's default will be + used. + diff --git a/src/libsystemd/sd-netlink/netlink-types-rtnl.c b/src/libsystemd/sd-netlink/netlink-types-rtnl.c index c0e820486d..187d9b6756 100644 --- a/src/libsystemd/sd-netlink/netlink-types-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-rtnl.c @@ -486,6 +486,7 @@ static const struct NLAPolicy rtnl_bridge_port_policies[] = { [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = BUILD_POLICY(U32), [IFLA_BRPORT_MCAST_EHT_HOSTS_CNT] = BUILD_POLICY(U32), [IFLA_BRPORT_LOCKED] = BUILD_POLICY(U8), + [IFLA_BRPORT_MAB] = BUILD_POLICY(U8), }; static const NLAPolicySetUnionElement rtnl_link_info_slave_data_policy_set_union_elements[] = { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 30f7148029..bdbb3ad2c8 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -384,6 +384,7 @@ Bridge.ProxyARPWiFi, config_parse_tristate, Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority) Bridge.MulticastRouter, config_parse_multicast_router, 0, offsetof(Network, multicast_router) Bridge.Locked, config_parse_tristate, 0, offsetof(Network, bridge_locked) +Bridge.MACAuthenticationBypass, config_parse_tristate, 0, offsetof(Network, bridge_mac_authentication_bypass) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 BridgeFDB.Destination, config_parse_fdb_destination, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 82f39e2f80..080e184ae1 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -457,6 +457,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, .multicast_router = _MULTICAST_ROUTER_INVALID, .bridge_locked = -1, + .bridge_mac_authentication_bypass = -1, .bridge_vlan_pvid = BRIDGE_VLAN_KEEP_PVID, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 95407279e3..cec5f98d5b 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -298,6 +298,7 @@ struct Network { uint16_t priority; MulticastRouter multicast_router; int bridge_locked; + int bridge_mac_authentication_bypass; /* Bridge VLAN */ uint16_t bridge_vlan_pvid; diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index b973ffea98..2f4a6c6e03 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -326,6 +326,12 @@ static int link_configure_fill_message( return r; } + if (link->network->bridge_mac_authentication_bypass >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MAB, link->network->bridge_mac_authentication_bypass); + if (r < 0) + return r; + } + r = sd_netlink_message_close_container(req); if (r < 0) return r; diff --git a/test/test-network/conf/26-bridge-slave-interface-2.network b/test/test-network/conf/26-bridge-slave-interface-2.network index 6eb955dc4b..c76f17201f 100644 --- a/test/test-network/conf/26-bridge-slave-interface-2.network +++ b/test/test-network/conf/26-bridge-slave-interface-2.network @@ -11,3 +11,4 @@ Bridge=bridge99 [Bridge] Priority=0 Locked=true +MACAuthenticationBypass=true diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 2d1309da28..d073e007af 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5791,6 +5791,8 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities): print(output) self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') self.assertIn('locked on', output) + if ' mab ' in output: # This is new in kernel and iproute2 v6.2 + self.assertIn('mab on', output) def test_bridge_property(self): copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',