summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/net/bridge_stp_mode.sh
blob: 0c81fd029d794f09bb78e2b4c6821cd612b70a7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2034,SC2154,SC2317,SC2329
#
# Test for bridge STP mode selection (IFLA_BR_STP_MODE).
#
# Verifies that:
# - stp_mode defaults to auto on new bridges
# - stp_mode can be toggled between user, kernel, and auto
# - stp_mode change is rejected while STP is active (-EBUSY)
# - stp_mode user in a netns yields userspace STP (stp_state=2)
# - stp_mode kernel forces kernel STP (stp_state=1)
# - stp_mode auto preserves traditional fallback to kernel STP
# - stp_mode and stp_state can be set atomically in one message
# - stp_mode persists across STP disable/enable cycles

source lib.sh

require_command jq

ALL_TESTS="
	test_default_auto
	test_set_modes
	test_reject_change_while_stp_active
	test_idempotent_mode_while_stp_active
	test_user_mode_in_netns
	test_kernel_mode
	test_auto_mode
	test_atomic_mode_and_state
	test_mode_persistence
"

bridge_info_get()
{
	ip -n "$NS1" -d -j link show "$1" | \
		jq -r ".[0].linkinfo.info_data.$2"
}

check_stp_mode()
{
	local br=$1; shift
	local expected=$1; shift
	local msg=$1; shift
	local val

	val=$(bridge_info_get "$br" stp_mode)
	[ "$val" = "$expected" ]
	check_err $? "$msg: expected $expected, got $val"
}

check_stp_state()
{
	local br=$1; shift
	local expected=$1; shift
	local msg=$1; shift
	local val

	val=$(bridge_info_get "$br" stp_state)
	[ "$val" = "$expected" ]
	check_err $? "$msg: expected $expected, got $val"
}

# Create a bridge in NS1, bring it up, and defer its deletion.
bridge_create()
{
	ip -n "$NS1" link add "$1" type bridge
	ip -n "$NS1" link set "$1" up
	defer ip -n "$NS1" link del "$1"
}

setup_prepare()
{
	setup_ns NS1
}

cleanup()
{
	defer_scopes_cleanup
	cleanup_all_ns
}

# Check that stp_mode defaults to auto when creating a bridge.
test_default_auto()
{
	RET=0

	ip -n "$NS1" link add br-test type bridge
	defer ip -n "$NS1" link del br-test

	check_stp_mode br-test auto "stp_mode default"

	log_test "stp_mode defaults to auto"
}

# Test setting stp_mode to user, kernel, and back to auto.
test_set_modes()
{
	RET=0

	ip -n "$NS1" link add br-test type bridge
	defer ip -n "$NS1" link del br-test

	ip -n "$NS1" link set dev br-test type bridge stp_mode user
	check_err $? "Failed to set stp_mode to user"
	check_stp_mode br-test user "after set user"

	ip -n "$NS1" link set dev br-test type bridge stp_mode kernel
	check_err $? "Failed to set stp_mode to kernel"
	check_stp_mode br-test kernel "after set kernel"

	ip -n "$NS1" link set dev br-test type bridge stp_mode auto
	check_err $? "Failed to set stp_mode to auto"
	check_stp_mode br-test auto "after set auto"

	log_test "stp_mode set user/kernel/auto"
}

# Verify that stp_mode cannot be changed while STP is active.
test_reject_change_while_stp_active()
{
	RET=0

	bridge_create br-test

	ip -n "$NS1" link set dev br-test type bridge stp_mode kernel
	check_err $? "Failed to set stp_mode to kernel"

	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to enable STP"

	# Changing stp_mode while STP is active should fail.
	ip -n "$NS1" link set dev br-test type bridge stp_mode auto 2>/dev/null
	check_fail $? "Changing stp_mode should fail while STP is active"

	check_stp_mode br-test kernel "mode unchanged after rejected change"

	# Disable STP, then change should succeed.
	ip -n "$NS1" link set dev br-test type bridge stp_state 0
	check_err $? "Failed to disable STP"

	ip -n "$NS1" link set dev br-test type bridge stp_mode auto
	check_err $? "Changing stp_mode should succeed after STP is disabled"

	log_test "reject stp_mode change while STP is active"
}

# Verify that re-setting the same stp_mode while STP is active succeeds.
test_idempotent_mode_while_stp_active()
{
	RET=0

	bridge_create br-test

	ip -n "$NS1" link set dev br-test type bridge stp_mode user stp_state 1
	check_err $? "Failed to enable STP with user mode"

	# Re-setting the same mode while STP is active should succeed.
	ip -n "$NS1" link set dev br-test type bridge stp_mode user
	check_err $? "Idempotent stp_mode set should succeed while STP is active"

	check_stp_state br-test 2 "stp_state after idempotent set"

	# Changing mode while disabling STP in the same message should succeed.
	ip -n "$NS1" link set dev br-test type bridge stp_mode auto stp_state 0
	check_err $? "Mode change with simultaneous STP disable should succeed"

	check_stp_mode br-test auto "mode changed after disable+change"
	check_stp_state br-test 0 "stp_state after disable+change"

	log_test "idempotent and simultaneous mode change while STP active"
}

# Test that stp_mode user in a non-init netns yields userspace STP
# (stp_state == 2). This is the key use case: userspace STP without
# needing /sbin/bridge-stp or being in init_net.
test_user_mode_in_netns()
{
	RET=0

	bridge_create br-test

	ip -n "$NS1" link set dev br-test type bridge stp_mode user
	check_err $? "Failed to set stp_mode to user"

	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to enable STP"

	check_stp_state br-test 2 "stp_state with user mode"

	log_test "stp_mode user in netns yields userspace STP"
}

# Test that stp_mode kernel forces kernel STP (stp_state == 1)
# regardless of whether /sbin/bridge-stp exists.
test_kernel_mode()
{
	RET=0

	bridge_create br-test

	ip -n "$NS1" link set dev br-test type bridge stp_mode kernel
	check_err $? "Failed to set stp_mode to kernel"

	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to enable STP"

	check_stp_state br-test 1 "stp_state with kernel mode"

	log_test "stp_mode kernel forces kernel STP"
}

# Test that stp_mode auto preserves traditional behavior: in a netns
# (non-init_net), bridge-stp is not called and STP falls back to
# kernel mode (stp_state == 1).
test_auto_mode()
{
	RET=0

	bridge_create br-test

	# Auto mode is the default; enable STP in a netns.
	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to enable STP"

	# In a netns with auto mode, bridge-stp is skipped (init_net only),
	# so STP should fall back to kernel mode (stp_state == 1).
	check_stp_state br-test 1 "stp_state with auto mode in netns"

	log_test "stp_mode auto preserves traditional behavior"
}

# Test that stp_mode and stp_state can be set in a single netlink
# message. This is the intended atomic usage pattern.
test_atomic_mode_and_state()
{
	RET=0

	bridge_create br-test

	# Set both stp_mode and stp_state in one command.
	ip -n "$NS1" link set dev br-test type bridge stp_mode user stp_state 1
	check_err $? "Failed to set stp_mode user and stp_state 1 atomically"

	check_stp_state br-test 2 "stp_state after atomic set"

	log_test "atomic stp_mode user + stp_state 1 in single message"
}

# Test that stp_mode persists across STP disable/enable cycles.
test_mode_persistence()
{
	RET=0

	bridge_create br-test

	# Set user mode and enable STP.
	ip -n "$NS1" link set dev br-test type bridge stp_mode user
	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to enable STP with user mode"

	# Disable STP.
	ip -n "$NS1" link set dev br-test type bridge stp_state 0
	check_err $? "Failed to disable STP"

	# Verify mode is still user.
	check_stp_mode br-test user "stp_mode after STP disable"

	# Re-enable STP -- should use user mode again.
	ip -n "$NS1" link set dev br-test type bridge stp_state 1
	check_err $? "Failed to re-enable STP"

	check_stp_state br-test 2 "stp_state after re-enable"

	log_test "stp_mode persists across STP disable/enable cycles"
}

# Check iproute2 support before setting up resources.
if ! ip link add type bridge help 2>&1 | grep -q "stp_mode"; then
	echo "SKIP: iproute2 too old, missing stp_mode support"
	exit "$ksft_skip"
fi

trap cleanup EXIT

setup_prepare
tests_run

exit "$EXIT_STATUS"