xref: /linux/tools/testing/selftests/net/bridge_stp_mode.sh (revision e46ff213f7a5f5aaebd6bca589517844aa0fe73a)
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
17source lib.sh
18
19require_command jq
20
21ALL_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
33bridge_info_get()
34{
35	ip -n "$NS1" -d -j link show "$1" | \
36		jq -r ".[0].linkinfo.info_data.$2"
37}
38
39check_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
51check_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.
64bridge_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
71setup_prepare()
72{
73	setup_ns NS1
74}
75
76cleanup()
77{
78	defer_scopes_cleanup
79	cleanup_all_ns
80}
81
82# Check that stp_mode defaults to auto when creating a bridge.
83test_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.
96test_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.
119test_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.
148test_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.
176test_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.
195test_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).
215test_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.
234test_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.
250test_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.
278if ! 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"
281fi
282
283trap cleanup EXIT
284
285setup_prepare
286tests_run
287
288exit "$EXIT_STATUS"
289