Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'net-bridge-add-stp_mode-attribute-for-stp-mode-selection'

Andy Roulin says:

====================
net: bridge: add stp_mode attribute for STP mode selection

The bridge-stp usermode helper is currently restricted to the initial
network namespace, preventing userspace STP daemons like mstpd from
operating on bridges in other namespaces. Since commit ff62198553e4
("bridge: Only call /sbin/bridge-stp for the initial network
namespace"), bridges in non-init namespaces silently fall back to
kernel STP with no way to request userspace STP.

This series adds a new IFLA_BR_STP_MODE bridge attribute that allows
explicit per-bridge control over STP mode selection. Three modes are
supported:

- auto (default): existing behavior, try /sbin/bridge-stp in
init_net, fall back to kernel STP otherwise
- user: directly enable BR_USER_STP without invoking the helper,
works in any network namespace
- kernel: directly enable BR_KERNEL_STP without invoking the helper

The user and kernel modes bypass call_usermodehelper() entirely,
addressing the security concerns discussed at [1]. Userspace is
responsible for ensuring an STP daemon manages the bridge, rather
than relying on the kernel to invoke /sbin/bridge-stp.

Patch 1 adds the kernel support. The mode can only be changed while
STP is disabled and is processed before IFLA_BR_STP_STATE in
br_changelink() so both can be set atomically in a single netlink
message.

Patch 2 adds documentation for the new attribute in the bridge docs.

Patch 3 adds a selftest with 9 test cases. The test requires iproute2
with IFLA_BR_STP_MODE support and can be run with virtme-ng:

vng --run arch/x86/boot/bzImage --skip-modules \
--overlay-rwdir /sbin --overlay-rwdir /tmp --overlay-rwdir /bin \
--exec 'cp /path/to/iproute2-next/ip/ip /bin/ip && \
cd tools/testing/selftests/net && \
bash bridge_stp_mode.sh'

iproute2 support can be found here [2].

[1] https://lore.kernel.org/netdev/565B7F7D.80208@nod.at/
[2] https://github.com/aroulin/iproute2-next/tree/bridge-stp-mode
====================

Link: https://patch.msgid.link/20260405205224.3163000-1-aroulin@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+400 -8
+12
Documentation/netlink/specs/rt-link.yaml
··· 840 840 entries: 841 841 - p2p 842 842 - mp 843 + - 844 + name: br-stp-mode 845 + type: enum 846 + enum-name: br-stp-mode 847 + entries: 848 + - auto 849 + - user 850 + - kernel 843 851 844 852 attribute-sets: 845 853 - ··· 1558 1550 - 1559 1551 name: fdb-max-learned 1560 1552 type: u32 1553 + - 1554 + name: stp-mode 1555 + type: u32 1556 + enum: br-stp-mode 1561 1557 - 1562 1558 name: linkinfo-brport-attrs 1563 1559 name-prefix: ifla-brport-
+22
Documentation/networking/bridge.rst
··· 148 148 stp_state <0|1>``). The kernel enables user_stp mode if that command returns 149 149 0, or enables kernel_stp mode if that command returns any other value. 150 150 151 + STP mode selection 152 + ------------------ 153 + 154 + The ``IFLA_BR_STP_MODE`` bridge attribute allows explicit control over how 155 + STP operates when enabled, bypassing the ``/sbin/bridge-stp`` helper 156 + entirely for the ``user`` and ``kernel`` modes. 157 + 158 + .. kernel-doc:: include/uapi/linux/if_link.h 159 + :doc: Bridge STP mode values 160 + 161 + The default mode is ``BR_STP_MODE_AUTO``, which preserves the traditional 162 + behavior of invoking the ``/sbin/bridge-stp`` helper. The ``user`` and 163 + ``kernel`` modes are particularly useful in network namespace environments 164 + where the helper mechanism is not available, as ``call_usermodehelper()`` 165 + is restricted to the initial network namespace. 166 + 167 + Example:: 168 + 169 + ip link set dev br0 type bridge stp_mode user stp_state 1 170 + 171 + The mode can only be changed while STP is disabled. 172 + 151 173 VLAN 152 174 ==== 153 175
+39
include/uapi/linux/if_link.h
··· 744 744 * @IFLA_BR_FDB_MAX_LEARNED 745 745 * Set the number of max dynamically learned FDB entries for the current 746 746 * bridge. 747 + * 748 + * @IFLA_BR_STP_MODE 749 + * Set the STP mode for the bridge, which controls how the bridge 750 + * selects between userspace and kernel STP. The valid values are 751 + * documented below in the ``BR_STP_MODE_*`` constants. 747 752 */ 748 753 enum { 749 754 IFLA_BR_UNSPEC, ··· 801 796 IFLA_BR_MCAST_QUERIER_STATE, 802 797 IFLA_BR_FDB_N_LEARNED, 803 798 IFLA_BR_FDB_MAX_LEARNED, 799 + IFLA_BR_STP_MODE, 804 800 __IFLA_BR_MAX, 805 801 }; 806 802 807 803 #define IFLA_BR_MAX (__IFLA_BR_MAX - 1) 804 + 805 + /** 806 + * DOC: Bridge STP mode values 807 + * 808 + * @BR_STP_MODE_AUTO 809 + * Default. The kernel invokes the ``/sbin/bridge-stp`` helper to hand 810 + * the bridge to a userspace STP daemon (e.g. mstpd). Only attempted in 811 + * the initial network namespace; in other namespaces this falls back to 812 + * kernel STP. 813 + * 814 + * @BR_STP_MODE_USER 815 + * Directly enable userspace STP (``BR_USER_STP``) without invoking the 816 + * ``/sbin/bridge-stp`` helper. Works in any network namespace. 817 + * Userspace is responsible for ensuring an STP daemon manages the 818 + * bridge. 819 + * 820 + * @BR_STP_MODE_KERNEL 821 + * Directly enable kernel STP (``BR_KERNEL_STP``) without invoking the 822 + * helper. 823 + * 824 + * The mode controls how the bridge selects between userspace and kernel 825 + * STP when STP is enabled via ``IFLA_BR_STP_STATE``. It can only be 826 + * changed while STP is disabled (``IFLA_BR_STP_STATE`` == 0), returns 827 + * ``-EBUSY`` otherwise. The default value is ``BR_STP_MODE_AUTO``. 828 + */ 829 + enum br_stp_mode { 830 + BR_STP_MODE_AUTO, 831 + BR_STP_MODE_USER, 832 + BR_STP_MODE_KERNEL, 833 + __BR_STP_MODE_MAX 834 + }; 835 + 836 + #define BR_STP_MODE_MAX (__BR_STP_MODE_MAX - 1) 808 837 809 838 struct ifla_bridge_id { 810 839 __u8 prio[2];
+1
net/bridge/br_device.c
··· 518 518 ether_addr_copy(br->group_addr, eth_stp_addr); 519 519 520 520 br->stp_enabled = BR_NO_STP; 521 + br->stp_mode = BR_STP_MODE_AUTO; 521 522 br->group_fwd_mask = BR_GROUPFWD_DEFAULT; 522 523 br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT; 523 524
+23 -1
net/bridge/br_netlink.c
··· 1270 1270 NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)), 1271 1271 [IFLA_BR_FDB_N_LEARNED] = { .type = NLA_REJECT }, 1272 1272 [IFLA_BR_FDB_MAX_LEARNED] = { .type = NLA_U32 }, 1273 + [IFLA_BR_STP_MODE] = NLA_POLICY_RANGE(NLA_U32, 1274 + BR_STP_MODE_AUTO, 1275 + BR_STP_MODE_MAX), 1273 1276 }; 1274 1277 1275 1278 static int br_changelink(struct net_device *brdev, struct nlattr *tb[], ··· 1307 1304 err = br_set_ageing_time(br, nla_get_u32(data[IFLA_BR_AGEING_TIME])); 1308 1305 if (err) 1309 1306 return err; 1307 + } 1308 + 1309 + if (data[IFLA_BR_STP_MODE]) { 1310 + u32 mode = nla_get_u32(data[IFLA_BR_STP_MODE]); 1311 + 1312 + if (mode != br->stp_mode) { 1313 + bool stp_off = br->stp_enabled == BR_NO_STP || 1314 + (data[IFLA_BR_STP_STATE] && 1315 + !nla_get_u32(data[IFLA_BR_STP_STATE])); 1316 + 1317 + if (!stp_off) { 1318 + NL_SET_ERR_MSG_MOD(extack, 1319 + "Can't change STP mode while STP is enabled"); 1320 + return -EBUSY; 1321 + } 1322 + } 1323 + br->stp_mode = mode; 1310 1324 } 1311 1325 1312 1326 if (data[IFLA_BR_STP_STATE]) { ··· 1654 1634 nla_total_size(sizeof(u8)) + /* IFLA_BR_NF_CALL_ARPTABLES */ 1655 1635 #endif 1656 1636 nla_total_size(sizeof(struct br_boolopt_multi)) + /* IFLA_BR_MULTI_BOOLOPT */ 1637 + nla_total_size(sizeof(u32)) + /* IFLA_BR_STP_MODE */ 1657 1638 0; 1658 1639 } 1659 1640 ··· 1707 1686 nla_put(skb, IFLA_BR_MULTI_BOOLOPT, sizeof(bm), &bm) || 1708 1687 nla_put_u32(skb, IFLA_BR_FDB_N_LEARNED, 1709 1688 atomic_read(&br->fdb_n_learned)) || 1710 - nla_put_u32(skb, IFLA_BR_FDB_MAX_LEARNED, br->fdb_max_learned)) 1689 + nla_put_u32(skb, IFLA_BR_FDB_MAX_LEARNED, br->fdb_max_learned) || 1690 + nla_put_u32(skb, IFLA_BR_STP_MODE, br->stp_mode)) 1711 1691 return -EMSGSIZE; 1712 1692 1713 1693 #ifdef CONFIG_BRIDGE_VLAN_FILTERING
+2
net/bridge/br_private.h
··· 523 523 unsigned char topology_change; 524 524 unsigned char topology_change_detected; 525 525 u16 root_port; 526 + u8 stp_mode; 527 + bool stp_helper_active; 526 528 unsigned long max_age; 527 529 unsigned long hello_time; 528 530 unsigned long forward_delay;
+12 -7
net/bridge/br_stp_if.c
··· 149 149 { 150 150 int err = -ENOENT; 151 151 152 - if (net_eq(dev_net(br->dev), &init_net)) 152 + /* AUTO mode: try bridge-stp helper in init_net only */ 153 + if (br->stp_mode == BR_STP_MODE_AUTO && 154 + net_eq(dev_net(br->dev), &init_net)) 153 155 err = br_stp_call_user(br, "start"); 154 156 155 157 if (err && err != -ENOENT) ··· 164 162 else if (br->bridge_forward_delay > BR_MAX_FORWARD_DELAY) 165 163 __br_set_forward_delay(br, BR_MAX_FORWARD_DELAY); 166 164 167 - if (!err) { 165 + if (br->stp_mode == BR_STP_MODE_USER || !err) { 168 166 br->stp_enabled = BR_USER_STP; 167 + br->stp_helper_active = !err; 169 168 br_debug(br, "userspace STP started\n"); 170 169 } else { 171 170 br->stp_enabled = BR_KERNEL_STP; ··· 183 180 184 181 static void br_stp_stop(struct net_bridge *br) 185 182 { 186 - int err; 187 - 188 183 if (br->stp_enabled == BR_USER_STP) { 189 - err = br_stp_call_user(br, "stop"); 190 - if (err) 191 - br_err(br, "failed to stop userspace STP (%d)\n", err); 184 + if (br->stp_helper_active) { 185 + int err = br_stp_call_user(br, "stop"); 186 + 187 + if (err) 188 + br_err(br, "failed to stop userspace STP (%d)\n", err); 189 + br->stp_helper_active = false; 190 + } 192 191 193 192 /* To start timers on any ports left in blocking */ 194 193 spin_lock_bh(&br->lock);
+1
tools/testing/selftests/net/Makefile
··· 15 15 big_tcp.sh \ 16 16 bind_bhash.sh \ 17 17 bpf_offload.py \ 18 + bridge_stp_mode.sh \ 18 19 bridge_vlan_dump.sh \ 19 20 broadcast_ether_dst.sh \ 20 21 broadcast_pmtu.sh \
+288
tools/testing/selftests/net/bridge_stp_mode.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + # shellcheck disable=SC2034,SC2154,SC2317,SC2329 4 + # 5 + # Test for bridge STP mode selection (IFLA_BR_STP_MODE). 6 + # 7 + # Verifies that: 8 + # - stp_mode defaults to auto on new bridges 9 + # - stp_mode can be toggled between user, kernel, and auto 10 + # - stp_mode change is rejected while STP is active (-EBUSY) 11 + # - stp_mode user in a netns yields userspace STP (stp_state=2) 12 + # - stp_mode kernel forces kernel STP (stp_state=1) 13 + # - stp_mode auto preserves traditional fallback to kernel STP 14 + # - stp_mode and stp_state can be set atomically in one message 15 + # - stp_mode persists across STP disable/enable cycles 16 + 17 + source lib.sh 18 + 19 + require_command jq 20 + 21 + ALL_TESTS=" 22 + test_default_auto 23 + test_set_modes 24 + test_reject_change_while_stp_active 25 + test_idempotent_mode_while_stp_active 26 + test_user_mode_in_netns 27 + test_kernel_mode 28 + test_auto_mode 29 + test_atomic_mode_and_state 30 + test_mode_persistence 31 + " 32 + 33 + bridge_info_get() 34 + { 35 + ip -n "$NS1" -d -j link show "$1" | \ 36 + jq -r ".[0].linkinfo.info_data.$2" 37 + } 38 + 39 + check_stp_mode() 40 + { 41 + local br=$1; shift 42 + local expected=$1; shift 43 + local msg=$1; shift 44 + local val 45 + 46 + val=$(bridge_info_get "$br" stp_mode) 47 + [ "$val" = "$expected" ] 48 + check_err $? "$msg: expected $expected, got $val" 49 + } 50 + 51 + check_stp_state() 52 + { 53 + local br=$1; shift 54 + local expected=$1; shift 55 + local msg=$1; shift 56 + local val 57 + 58 + val=$(bridge_info_get "$br" stp_state) 59 + [ "$val" = "$expected" ] 60 + check_err $? "$msg: expected $expected, got $val" 61 + } 62 + 63 + # Create a bridge in NS1, bring it up, and defer its deletion. 64 + bridge_create() 65 + { 66 + ip -n "$NS1" link add "$1" type bridge 67 + ip -n "$NS1" link set "$1" up 68 + defer ip -n "$NS1" link del "$1" 69 + } 70 + 71 + setup_prepare() 72 + { 73 + setup_ns NS1 74 + } 75 + 76 + cleanup() 77 + { 78 + defer_scopes_cleanup 79 + cleanup_all_ns 80 + } 81 + 82 + # Check that stp_mode defaults to auto when creating a bridge. 83 + test_default_auto() 84 + { 85 + RET=0 86 + 87 + ip -n "$NS1" link add br-test type bridge 88 + defer ip -n "$NS1" link del br-test 89 + 90 + check_stp_mode br-test auto "stp_mode default" 91 + 92 + log_test "stp_mode defaults to auto" 93 + } 94 + 95 + # Test setting stp_mode to user, kernel, and back to auto. 96 + test_set_modes() 97 + { 98 + RET=0 99 + 100 + ip -n "$NS1" link add br-test type bridge 101 + defer ip -n "$NS1" link del br-test 102 + 103 + ip -n "$NS1" link set dev br-test type bridge stp_mode user 104 + check_err $? "Failed to set stp_mode to user" 105 + check_stp_mode br-test user "after set user" 106 + 107 + ip -n "$NS1" link set dev br-test type bridge stp_mode kernel 108 + check_err $? "Failed to set stp_mode to kernel" 109 + check_stp_mode br-test kernel "after set kernel" 110 + 111 + ip -n "$NS1" link set dev br-test type bridge stp_mode auto 112 + check_err $? "Failed to set stp_mode to auto" 113 + check_stp_mode br-test auto "after set auto" 114 + 115 + log_test "stp_mode set user/kernel/auto" 116 + } 117 + 118 + # Verify that stp_mode cannot be changed while STP is active. 119 + test_reject_change_while_stp_active() 120 + { 121 + RET=0 122 + 123 + bridge_create br-test 124 + 125 + ip -n "$NS1" link set dev br-test type bridge stp_mode kernel 126 + check_err $? "Failed to set stp_mode to kernel" 127 + 128 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 129 + check_err $? "Failed to enable STP" 130 + 131 + # Changing stp_mode while STP is active should fail. 132 + ip -n "$NS1" link set dev br-test type bridge stp_mode auto 2>/dev/null 133 + check_fail $? "Changing stp_mode should fail while STP is active" 134 + 135 + check_stp_mode br-test kernel "mode unchanged after rejected change" 136 + 137 + # Disable STP, then change should succeed. 138 + ip -n "$NS1" link set dev br-test type bridge stp_state 0 139 + check_err $? "Failed to disable STP" 140 + 141 + ip -n "$NS1" link set dev br-test type bridge stp_mode auto 142 + check_err $? "Changing stp_mode should succeed after STP is disabled" 143 + 144 + log_test "reject stp_mode change while STP is active" 145 + } 146 + 147 + # Verify that re-setting the same stp_mode while STP is active succeeds. 148 + test_idempotent_mode_while_stp_active() 149 + { 150 + RET=0 151 + 152 + bridge_create br-test 153 + 154 + ip -n "$NS1" link set dev br-test type bridge stp_mode user stp_state 1 155 + check_err $? "Failed to enable STP with user mode" 156 + 157 + # Re-setting the same mode while STP is active should succeed. 158 + ip -n "$NS1" link set dev br-test type bridge stp_mode user 159 + check_err $? "Idempotent stp_mode set should succeed while STP is active" 160 + 161 + check_stp_state br-test 2 "stp_state after idempotent set" 162 + 163 + # Changing mode while disabling STP in the same message should succeed. 164 + ip -n "$NS1" link set dev br-test type bridge stp_mode auto stp_state 0 165 + check_err $? "Mode change with simultaneous STP disable should succeed" 166 + 167 + check_stp_mode br-test auto "mode changed after disable+change" 168 + check_stp_state br-test 0 "stp_state after disable+change" 169 + 170 + log_test "idempotent and simultaneous mode change while STP active" 171 + } 172 + 173 + # Test that stp_mode user in a non-init netns yields userspace STP 174 + # (stp_state == 2). This is the key use case: userspace STP without 175 + # needing /sbin/bridge-stp or being in init_net. 176 + test_user_mode_in_netns() 177 + { 178 + RET=0 179 + 180 + bridge_create br-test 181 + 182 + ip -n "$NS1" link set dev br-test type bridge stp_mode user 183 + check_err $? "Failed to set stp_mode to user" 184 + 185 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 186 + check_err $? "Failed to enable STP" 187 + 188 + check_stp_state br-test 2 "stp_state with user mode" 189 + 190 + log_test "stp_mode user in netns yields userspace STP" 191 + } 192 + 193 + # Test that stp_mode kernel forces kernel STP (stp_state == 1) 194 + # regardless of whether /sbin/bridge-stp exists. 195 + test_kernel_mode() 196 + { 197 + RET=0 198 + 199 + bridge_create br-test 200 + 201 + ip -n "$NS1" link set dev br-test type bridge stp_mode kernel 202 + check_err $? "Failed to set stp_mode to kernel" 203 + 204 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 205 + check_err $? "Failed to enable STP" 206 + 207 + check_stp_state br-test 1 "stp_state with kernel mode" 208 + 209 + log_test "stp_mode kernel forces kernel STP" 210 + } 211 + 212 + # Test that stp_mode auto preserves traditional behavior: in a netns 213 + # (non-init_net), bridge-stp is not called and STP falls back to 214 + # kernel mode (stp_state == 1). 215 + test_auto_mode() 216 + { 217 + RET=0 218 + 219 + bridge_create br-test 220 + 221 + # Auto mode is the default; enable STP in a netns. 222 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 223 + check_err $? "Failed to enable STP" 224 + 225 + # In a netns with auto mode, bridge-stp is skipped (init_net only), 226 + # so STP should fall back to kernel mode (stp_state == 1). 227 + check_stp_state br-test 1 "stp_state with auto mode in netns" 228 + 229 + log_test "stp_mode auto preserves traditional behavior" 230 + } 231 + 232 + # Test that stp_mode and stp_state can be set in a single netlink 233 + # message. This is the intended atomic usage pattern. 234 + test_atomic_mode_and_state() 235 + { 236 + RET=0 237 + 238 + bridge_create br-test 239 + 240 + # Set both stp_mode and stp_state in one command. 241 + ip -n "$NS1" link set dev br-test type bridge stp_mode user stp_state 1 242 + check_err $? "Failed to set stp_mode user and stp_state 1 atomically" 243 + 244 + check_stp_state br-test 2 "stp_state after atomic set" 245 + 246 + log_test "atomic stp_mode user + stp_state 1 in single message" 247 + } 248 + 249 + # Test that stp_mode persists across STP disable/enable cycles. 250 + test_mode_persistence() 251 + { 252 + RET=0 253 + 254 + bridge_create br-test 255 + 256 + # Set user mode and enable STP. 257 + ip -n "$NS1" link set dev br-test type bridge stp_mode user 258 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 259 + check_err $? "Failed to enable STP with user mode" 260 + 261 + # Disable STP. 262 + ip -n "$NS1" link set dev br-test type bridge stp_state 0 263 + check_err $? "Failed to disable STP" 264 + 265 + # Verify mode is still user. 266 + check_stp_mode br-test user "stp_mode after STP disable" 267 + 268 + # Re-enable STP -- should use user mode again. 269 + ip -n "$NS1" link set dev br-test type bridge stp_state 1 270 + check_err $? "Failed to re-enable STP" 271 + 272 + check_stp_state br-test 2 "stp_state after re-enable" 273 + 274 + log_test "stp_mode persists across STP disable/enable cycles" 275 + } 276 + 277 + # Check iproute2 support before setting up resources. 278 + if ! ip link add type bridge help 2>&1 | grep -q "stp_mode"; then 279 + echo "SKIP: iproute2 too old, missing stp_mode support" 280 + exit "$ksft_skip" 281 + fi 282 + 283 + trap cleanup EXIT 284 + 285 + setup_prepare 286 + tests_run 287 + 288 + exit "$EXIT_STATUS"