#!/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"