diff options
Diffstat (limited to 'tools/testing/selftests/drivers/net/team')
12 files changed, 1274 insertions, 4 deletions
diff --git a/tools/testing/selftests/drivers/net/team/Makefile b/tools/testing/selftests/drivers/net/team/Makefile index 2d5a76d99181..7c58cf82121e 100644 --- a/tools/testing/selftests/drivers/net/team/Makefile +++ b/tools/testing/selftests/drivers/net/team/Makefile @@ -1,11 +1,24 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for net selftests -TEST_PROGS := dev_addr_lists.sh +TEST_PROGS := \ + decoupled_enablement.sh \ + dev_addr_lists.sh \ + non_ether_header_ops.sh \ + options.sh \ + propagation.sh \ + refleak.sh \ + teamd_activebackup.sh \ + transmit_failover.sh \ +# end of TEST_PROGS TEST_INCLUDES := \ + team_lib.sh \ ../bonding/lag_lib.sh \ ../../../net/forwarding/lib.sh \ - ../../../net/lib.sh + ../../../net/in_netns.sh \ + ../../../net/lib.sh \ + ../../../net/lib/sh/defer.sh \ +# end of TEST_INCLUDES include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/net/team/config b/tools/testing/selftests/drivers/net/team/config index b5e3a3aad4bf..8f04ae419c53 100644 --- a/tools/testing/selftests/drivers/net/team/config +++ b/tools/testing/selftests/drivers/net/team/config @@ -1,5 +1,13 @@ +CONFIG_BONDING=y CONFIG_DUMMY=y CONFIG_IPV6=y CONFIG_MACVLAN=y +CONFIG_NETDEVSIM=m +CONFIG_NET_IPGRE=y CONFIG_NET_TEAM=y +CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=y +CONFIG_NET_TEAM_MODE_BROADCAST=y CONFIG_NET_TEAM_MODE_LOADBALANCE=y +CONFIG_NET_TEAM_MODE_RANDOM=y +CONFIG_NET_TEAM_MODE_ROUNDROBIN=y +CONFIG_VETH=y diff --git a/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh b/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh new file mode 100755 index 000000000000..0d3d9c98e9f5 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh @@ -0,0 +1,249 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# These tests verify the decoupled RX and TX enablement of team driver member +# interfaces. +# +# Topology +# +# +---------------------+ NS1 +# | test_team1 | +# | | | +# | eth0 | +# | | | +# | | | +# +---------------------+ +# | +# +---------------------+ NS2 +# | | | +# | | | +# | eth0 | +# | | | +# | test_team2 | +# +---------------------+ + +export ALL_TESTS=" + team_test_tx_enablement + team_test_rx_enablement +" + +test_dir="$(dirname "$0")" +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/lib.sh" +# shellcheck disable=SC1091 +source "${test_dir}/team_lib.sh" + +NS1="" +NS2="" +export NODAD="nodad" +PREFIX_LENGTH="64" +NS1_IP="fd00::1" +NS2_IP="fd00::2" +NS1_IP4="192.168.0.1" +NS2_IP4="192.168.0.2" +MEMBERS=("eth0") +PING_COUNT=5 +PING_TIMEOUT_S=1 +PING_INTERVAL=0.1 + +while getopts "4" opt; do + case $opt in + 4) + echo "IPv4 mode selected." + export NODAD= + PREFIX_LENGTH="24" + NS1_IP="${NS1_IP4}" + NS2_IP="${NS2_IP4}" + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# This has to be sourced after opts are gathered... +export REQUIRE_MZ=no +export NUM_NETIFS=0 +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/forwarding/lib.sh" + +# Create the network namespaces, veth pair, and team devices in the specified +# mode. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# mode - The team driver mode to use for the team devices. +environment_create() +{ + trap cleanup_all_ns EXIT + setup_ns ns1 ns2 + NS1="${NS_LIST[0]}" + NS2="${NS_LIST[1]}" + + # Create the interfaces. + ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}" + ip -n "${NS1}" link add test_team1 type team + ip -n "${NS2}" link add test_team2 type team + + # Set up the receiving network namespace's team interface. + setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \ + "${PREFIX_LENGTH}" "${MEMBERS[@]}" +} + +# Set a particular option value for team or team port. +# Arguments: +# namespace - The namespace name that has the team. +# option_name - The option name to set. +# option_value - The value to set the option to. +# team_name - The name of team to set the option for. +# member_name - The (optional) optional name of the member port. +set_option_value() +{ + local namespace="$1" + local option_name="$2" + local option_value="$3" + local team_name="$4" + local member_name="$5" + local port_flag="--port=${member_name}" + + ip netns exec "${namespace}" teamnl "${team_name}" setoption \ + "${option_name}" "${option_value}" "${port_flag}" + return $? +} + +# Send some pings and return the ping command return value. +try_ping() +{ + ip netns exec "${NS1}" ping -i "${PING_INTERVAL}" -c "${PING_COUNT}" \ + "${NS2_IP}" -W "${PING_TIMEOUT_S}" +} + +# Checks tcpdump output from net/forwarding lib, and checks if there are any +# ICMP(4 or 6) packets. +# Arguments: +# interface - The interface name to search for. +# ip_address - The destination IP address (4 or 6) to search for. +did_interface_receive_icmp() +{ + local interface="$1" + local ip_address="$2" + local packet_count + + packet_count=$(tcpdump_show "$interface" | grep -c \ + "> ${ip_address}: ICMP") + echo "Packet count for ${interface} was ${packet_count}" + + if [[ "$packet_count" -gt 0 ]]; then + true + else + false + fi +} + +# Test JUST tx enablement with a given mode. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# mode - The mode to set the team interfaces to. +team_test_mode_tx_enablement() +{ + local mode="$1" + export RET=0 + + # Set up the sender team with the correct mode. + setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \ + "${PREFIX_LENGTH}" "${MEMBERS[@]}" + check_err $? "Failed to set up sender team" + + ### Scenario 1: Member interface initially enabled. + # Expect ping to pass + try_ping + check_err $? "Ping failed when TX enabled" + + ### Scenario 2: One tx-side interface disabled. + # Expect ping to fail. + set_option_value "${NS1}" tx_enabled false test_team1 eth0 + check_err $? "Failed to disable TX" + tcpdump_start eth0 "${NS2}" + try_ping + check_fail $? "Ping succeeded when TX disabled" + tcpdump_stop eth0 + # Expect no packets to be transmitted, since TX is disabled. + did_interface_receive_icmp eth0 "${NS2_IP}" + check_fail $? "eth0 IS transmitting when TX disabled" + tcpdump_cleanup eth0 + + ### Scenario 3: The interface has tx re-enabled. + # Expect ping to pass. + set_option_value "${NS1}" tx_enabled true test_team1 eth0 + check_err $? "Failed to reenable TX" + try_ping + check_err $? "Ping failed when TX reenabled" + + log_test "TX failover of '${mode}' test" +} + +# Test JUST rx enablement with a given mode. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# mode - The mode to set the team interfaces to. +team_test_mode_rx_enablement() +{ + local mode="$1" + export RET=0 + + # Set up the sender team with the correct mode. + setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \ + "${PREFIX_LENGTH}" "${MEMBERS[@]}" + check_err $? "Failed to set up sender team" + + ### Scenario 1: Member interface initially enabled. + # Expect ping to pass + try_ping + check_err $? "Ping failed when RX enabled" + + ### Scenario 2: One rx-side interface disabled. + # Expect ping to fail. + set_option_value "${NS1}" rx_enabled false test_team1 eth0 + check_err $? "Failed to disable RX" + tcpdump_start eth0 "${NS2}" + try_ping + check_fail $? "Ping succeeded when RX disabled" + tcpdump_stop eth0 + # Expect packets to be transmitted, since only RX is disabled. + did_interface_receive_icmp eth0 "${NS2_IP}" + check_err $? "eth0 not transmitting when RX disabled" + tcpdump_cleanup eth0 + + ### Scenario 3: The interface has rx re-enabled. + # Expect ping to pass. + set_option_value "${NS1}" rx_enabled true test_team1 eth0 + check_err $? "Failed to reenable RX" + try_ping + check_err $? "Ping failed when RX reenabled" + + log_test "RX failover of '${mode}' test" +} + +team_test_tx_enablement() +{ + team_test_mode_tx_enablement broadcast + team_test_mode_tx_enablement roundrobin + team_test_mode_tx_enablement random +} + +team_test_rx_enablement() +{ + team_test_mode_rx_enablement broadcast + team_test_mode_rx_enablement roundrobin + team_test_mode_rx_enablement random +} + +require_command teamnl +require_command tcpdump +require_command ping +environment_create +tests_run +exit "${EXIT_STATUS}" diff --git a/tools/testing/selftests/drivers/net/team/dev_addr_lists.sh b/tools/testing/selftests/drivers/net/team/dev_addr_lists.sh index b1ec7755b783..26469f3be022 100755 --- a/tools/testing/selftests/drivers/net/team/dev_addr_lists.sh +++ b/tools/testing/selftests/drivers/net/team/dev_addr_lists.sh @@ -42,8 +42,6 @@ team_cleanup() } -require_command teamd - trap cleanup EXIT tests_run diff --git a/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh b/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh new file mode 100755 index 000000000000..948a43576bdc --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# shellcheck disable=SC2154 +# +# Reproduce the non-Ethernet header_ops confusion scenario with: +# g0 (gre) -> b0 (bond) -> t0 (team) +# +# Before the fix, direct header_ops inheritance in this stack could call +# callbacks with the wrong net_device context and crash. + +lib_dir=$(dirname "$0") +source "$lib_dir"/../../../net/lib.sh + +trap cleanup_all_ns EXIT + +setup_ns ns1 + +ip -n "$ns1" link add d0 type dummy +ip -n "$ns1" addr add 10.10.10.1/24 dev d0 +ip -n "$ns1" link set d0 up + +ip -n "$ns1" link add g0 type gre local 10.10.10.1 +ip -n "$ns1" link add b0 type bond mode active-backup +ip -n "$ns1" link add t0 type team + +ip -n "$ns1" link set g0 master b0 +ip -n "$ns1" link set b0 master t0 + +ip -n "$ns1" link set g0 up +ip -n "$ns1" link set b0 up +ip -n "$ns1" link set t0 up + +# IPv6 address assignment triggers MLD join reports that call +# dev_hard_header() on t0, exercising the inherited header_ops path. +ip -n "$ns1" -6 addr add 2001:db8:1::1/64 dev t0 nodad +for i in $(seq 1 20); do + ip netns exec "$ns1" ping -6 -I t0 ff02::1 -c1 -W1 &>/dev/null || true +done + +echo "PASS: non-Ethernet header_ops stacking did not crash" +exit "$EXIT_STATUS" diff --git a/tools/testing/selftests/drivers/net/team/options.sh b/tools/testing/selftests/drivers/net/team/options.sh new file mode 100755 index 000000000000..66c0cb896dad --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/options.sh @@ -0,0 +1,285 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# These tests verify basic set and get functionality of the team +# driver options over netlink. + +# Run in private netns. +test_dir="$(dirname "$0")" +if [[ $# -eq 0 ]]; then + "${test_dir}"/../../../net/in_netns.sh "$0" __subprocess + exit $? +fi + +export ALL_TESTS=" + team_test_options + team_test_enabled_implicit_changes + team_test_rx_enabled_implicit_changes + team_test_tx_enabled_implicit_changes +" + +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/lib.sh" + +TEAM_PORT="team0" +MEMBER_PORT="dummy0" + +setup() +{ + ip link add name "${MEMBER_PORT}" type dummy + ip link add name "${TEAM_PORT}" type team +} + +get_and_check_value() +{ + local option_name="$1" + local expected_value="$2" + local port_flag="$3" + + local value_from_get + + if ! value_from_get=$(teamnl "${TEAM_PORT}" getoption "${option_name}" \ + "${port_flag}"); then + echo "Could not get option '${option_name}'" >&2 + return 1 + fi + + if [[ "${value_from_get}" != "${expected_value}" ]]; then + echo "Incorrect value for option '${option_name}'" >&2 + echo "get (${value_from_get}) != set (${expected_value})" >&2 + return 1 + fi +} + +set_and_check_get() +{ + local option_name="$1" + local option_value="$2" + local port_flag="$3" + + local value_from_get + + if ! teamnl "${TEAM_PORT}" setoption "${option_name}" \ + "${option_value}" "${port_flag}"; then + echo "'setoption ${option_name} ${option_value}' failed" >&2 + return 1 + fi + + get_and_check_value "${option_name}" "${option_value}" "${port_flag}" + return $? +} + +# Get a "port flag" to pass to the `teamnl` command. +# E.g. $1="dummy0" -> "port=dummy0", +# $1="" -> "" +get_port_flag() +{ + local port_name="$1" + + if [[ -n "${port_name}" ]]; then + echo "--port=${port_name}" + fi +} + +attach_port_if_specified() +{ + local port_name="$1" + + if [[ -n "${port_name}" ]]; then + ip link set dev "${port_name}" master "${TEAM_PORT}" + return $? + fi +} + +detach_port_if_specified() +{ + local port_name="$1" + + if [[ -n "${port_name}" ]]; then + ip link set dev "${port_name}" nomaster + return $? + fi +} + +# Test that an option's get value matches its set value. +# Globals: +# RET - Used by testing infra like `check_err`. +# EXIT_STATUS - Used by `log_test` for whole script exit value. +# Arguments: +# option_name - The name of the option. +# value_1 - The first value to try setting. +# value_2 - The second value to try setting. +# port_name - The (optional) name of the attached port. +team_test_option() +{ + local option_name="$1" + local value_1="$2" + local value_2="$3" + local possible_values="$2 $3 $2" + local port_name="$4" + local port_flag + + RET=0 + + echo "Setting '${option_name}' to '${value_1}' and '${value_2}'" + + attach_port_if_specified "${port_name}" + check_err $? "Couldn't attach ${port_name} to master" + port_flag=$(get_port_flag "${port_name}") + + # Set and get both possible values. + for value in ${possible_values}; do + set_and_check_get "${option_name}" "${value}" "${port_flag}" + check_err $? "Failed to set '${option_name}' to '${value}'" + done + + detach_port_if_specified "${port_name}" + check_err $? "Couldn't detach ${port_name} from its master" + + log_test "Set + Get '${option_name}' test" +} + +# Test that getting a non-existant option fails. +# Globals: +# RET - Used by testing infra like `check_err`. +# EXIT_STATUS - Used by `log_test` for whole script exit value. +# Arguments: +# option_name - The name of the option. +# port_name - The (optional) name of the attached port. +team_test_get_option_fails() +{ + local option_name="$1" + local port_name="$2" + local port_flag + + RET=0 + + attach_port_if_specified "${port_name}" + check_err $? "Couldn't attach ${port_name} to master" + port_flag=$(get_port_flag "${port_name}") + + # Just confirm that getting the value fails. + teamnl "${TEAM_PORT}" getoption "${option_name}" "${port_flag}" + check_fail $? "Shouldn't be able to get option '${option_name}'" + + detach_port_if_specified "${port_name}" + + log_test "Get '${option_name}' fails" +} + +team_test_options() +{ + # Wrong option name behavior. + team_test_get_option_fails fake_option1 + team_test_get_option_fails fake_option2 "${MEMBER_PORT}" + + # Correct set and get behavior. + team_test_option mode activebackup loadbalance + team_test_option notify_peers_count 0 5 + team_test_option notify_peers_interval 0 5 + team_test_option mcast_rejoin_count 0 5 + team_test_option mcast_rejoin_interval 0 5 + team_test_option enabled true false "${MEMBER_PORT}" + team_test_option rx_enabled true false "${MEMBER_PORT}" + team_test_option tx_enabled true false "${MEMBER_PORT}" + team_test_option user_linkup true false "${MEMBER_PORT}" + team_test_option user_linkup_enabled true false "${MEMBER_PORT}" + team_test_option priority 10 20 "${MEMBER_PORT}" + team_test_option queue_id 0 1 "${MEMBER_PORT}" +} + +team_test_enabled_implicit_changes() +{ + export RET=0 + + attach_port_if_specified "${MEMBER_PORT}" + check_err $? "Couldn't attach ${MEMBER_PORT} to master" + + # Set enabled to true. + set_and_check_get enabled true "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'enabled' to true" + + # Show that both rx enabled and tx enabled are true. + get_and_check_value rx_enabled true "--port=${MEMBER_PORT}" + check_err $? "'Rx_enabled' wasn't implicitly set to true" + get_and_check_value tx_enabled true "--port=${MEMBER_PORT}" + check_err $? "'Tx_enabled' wasn't implicitly set to true" + + # Set enabled to false. + set_and_check_get enabled false "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'enabled' to false" + + # Show that both rx enabled and tx enabled are false. + get_and_check_value rx_enabled false "--port=${MEMBER_PORT}" + check_err $? "'Rx_enabled' wasn't implicitly set to false" + get_and_check_value tx_enabled false "--port=${MEMBER_PORT}" + check_err $? "'Tx_enabled' wasn't implicitly set to false" + + log_test "'Enabled' implicit changes" +} + +team_test_rx_enabled_implicit_changes() +{ + export RET=0 + + attach_port_if_specified "${MEMBER_PORT}" + check_err $? "Couldn't attach ${MEMBER_PORT} to master" + + # Set enabled to true. + set_and_check_get enabled true "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'enabled' to true" + + # Set rx_enabled to false. + set_and_check_get rx_enabled false "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'rx_enabled' to false" + + # Show that enabled is false. + get_and_check_value enabled false "--port=${MEMBER_PORT}" + check_err $? "'enabled' wasn't implicitly set to false" + + # Set rx_enabled to true. + set_and_check_get rx_enabled true "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'rx_enabled' to true" + + # Show that enabled is true. + get_and_check_value enabled true "--port=${MEMBER_PORT}" + check_err $? "'enabled' wasn't implicitly set to true" + + log_test "'Rx_enabled' implicit changes" +} + +team_test_tx_enabled_implicit_changes() +{ + export RET=0 + + attach_port_if_specified "${MEMBER_PORT}" + check_err $? "Couldn't attach ${MEMBER_PORT} to master" + + # Set enabled to true. + set_and_check_get enabled true "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'enabled' to true" + + # Set tx_enabled to false. + set_and_check_get tx_enabled false "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'tx_enabled' to false" + + # Show that enabled is false. + get_and_check_value enabled false "--port=${MEMBER_PORT}" + check_err $? "'enabled' wasn't implicitly set to false" + + # Set tx_enabled to true. + set_and_check_get tx_enabled true "--port=${MEMBER_PORT}" + check_err $? "Failed to set 'tx_enabled' to true" + + # Show that enabled is true. + get_and_check_value enabled true "--port=${MEMBER_PORT}" + check_err $? "'enabled' wasn't implicitly set to true" + + log_test "'Tx_enabled' implicit changes" +} + + +require_command teamnl +setup +tests_run +exit "${EXIT_STATUS}" diff --git a/tools/testing/selftests/drivers/net/team/propagation.sh b/tools/testing/selftests/drivers/net/team/propagation.sh new file mode 100755 index 000000000000..4bea75b79878 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/propagation.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +set -e + +NSIM_LRO_ID=$((256 + RANDOM % 256)) +NSIM_LRO_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_LRO_ID + +NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device +NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device + +cleanup() +{ + set +e + ip link del dummyteam &>/dev/null + ip link del team0 &>/dev/null + echo $NSIM_LRO_ID > $NSIM_DEV_SYS_DEL + modprobe -r netdevsim +} + +# Trigger LRO propagation to the lower. +# https://lore.kernel.org/netdev/aBvOpkIoxcr9PfDg@mini-arch/ +team_lro() +{ + # using netdevsim because it supports NETIF_F_LRO + NSIM_LRO_NAME=$(find $NSIM_LRO_SYS/net -maxdepth 1 -type d ! \ + -path $NSIM_LRO_SYS/net -exec basename {} \;) + + ip link add name team0 type team + ip link set $NSIM_LRO_NAME down + ip link set dev $NSIM_LRO_NAME master team0 + ip link set team0 up + ethtool -K team0 large-receive-offload off + + ip link del team0 +} + +# Trigger promisc propagation to the lower during IFLA_MASTER. +# https://lore.kernel.org/netdev/20250506032328.3003050-1-sdf@fomichev.me/ +team_promisc() +{ + ip link add name dummyteam type dummy + ip link add name team0 type team + ip link set dummyteam down + ip link set team0 promisc on + ip link set dev dummyteam master team0 + ip link set team0 up + + ip link del team0 + ip link del dummyteam +} + +# Trigger promisc propagation to the lower via netif_change_flags (aka +# ndo_change_rx_flags). +# https://lore.kernel.org/netdev/20250514220319.3505158-1-stfomichev@gmail.com/ +team_change_flags() +{ + ip link add name dummyteam type dummy + ip link add name team0 type team + ip link set dummyteam down + ip link set dev dummyteam master team0 + ip link set team0 up + ip link set team0 promisc on + + # Make sure we can add more L2 addresses without any issues. + ip link add link team0 address 00:00:00:00:00:01 team0.1 type macvlan + ip link set team0.1 up + + ip link del team0.1 + ip link del team0 + ip link del dummyteam +} + +trap cleanup EXIT +modprobe netdevsim || : +echo $NSIM_LRO_ID > $NSIM_DEV_SYS_NEW +udevadm settle +team_lro +team_promisc +team_change_flags diff --git a/tools/testing/selftests/drivers/net/team/refleak.sh b/tools/testing/selftests/drivers/net/team/refleak.sh new file mode 100755 index 000000000000..ef08213ab964 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/refleak.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# shellcheck disable=SC2154 + +lib_dir=$(dirname "$0") +source "$lib_dir"/../../../net/lib.sh + +trap cleanup_all_ns EXIT + +# Test that there is no reference count leak and that dummy1 can be deleted. +# https://lore.kernel.org/netdev/4d69abe1-ca8d-4f0b-bcf8-13899b211e57@I-love.SAKURA.ne.jp/ +setup_ns ns1 ns2 +ip -n "$ns1" link add name team1 type team +ip -n "$ns1" link add name dummy1 mtu 1499 type dummy +ip -n "$ns1" link set dev dummy1 master team1 +ip -n "$ns1" link set dev dummy1 netns "$ns2" +ip -n "$ns2" link del dev dummy1 diff --git a/tools/testing/selftests/drivers/net/team/settings b/tools/testing/selftests/drivers/net/team/settings new file mode 100644 index 000000000000..694d70710ff0 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/settings @@ -0,0 +1 @@ +timeout=300 diff --git a/tools/testing/selftests/drivers/net/team/team_lib.sh b/tools/testing/selftests/drivers/net/team/team_lib.sh new file mode 100644 index 000000000000..02ef0ee02d6a --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/team_lib.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +test_dir="$(dirname "$0")" +export REQUIRE_MZ=no +export NUM_NETIFS=0 +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/forwarding/lib.sh" + +TCP_PORT="43434" + +# Create a team interface inside of a given network namespace with a given +# mode, members, and IP address. +# Arguments: +# namespace - Network namespace to put the team interface into. +# team - The name of the team interface to setup. +# mode - The team mode of the interface. +# ip_address - The IP address to assign to the team interface. +# prefix_length - The prefix length for the IP address subnet. +# $@ - members - The member interfaces of the aggregation. +setup_team() +{ + local namespace=$1 + local team=$2 + local mode=$3 + local ip_address=$4 + local prefix_length=$5 + shift 5 + local members=("$@") + + # Prerequisite: team must have no members + for member in "${members[@]}"; do + ip -n "${namespace}" link set "${member}" nomaster + done + + # Prerequisite: team must have no address in order to set it + # shellcheck disable=SC2086 + ip -n "${namespace}" addr del "${ip_address}/${prefix_length}" \ + ${NODAD} dev "${team}" + + echo "Setting team in ${namespace} to mode ${mode}" + + if ! ip -n "${namespace}" link set "${team}" down; then + echo "Failed to bring team device down" + return 1 + fi + if ! ip netns exec "${namespace}" teamnl "${team}" setoption mode \ + "${mode}"; then + echo "Failed to set ${team} mode to '${mode}'" + return 1 + fi + + # Aggregate the members into teams. + for member in "${members[@]}"; do + ip -n "${namespace}" link set "${member}" master "${team}" + done + + # Bring team devices up and give them addresses. + if ! ip -n "${namespace}" link set "${team}" up; then + echo "Failed to set ${team} up" + return 1 + fi + + # shellcheck disable=SC2086 + if ! ip -n "${namespace}" addr add "${ip_address}/${prefix_length}" \ + ${NODAD} dev "${team}"; then + echo "Failed to give ${team} IP address in ${namespace}" + return 1 + fi +} + +# This is global used to keep track of the sender's iperf3 process, so that it +# can be terminated. +declare sender_pid + +# Start sending and receiving TCP traffic with iperf3. +# Globals: +# sender_pid - The process ID of the iperf3 sender process. Used to kill it +# later. +start_listening_and_sending() +{ + ip netns exec "${NS2}" iperf3 -s -p "${TCP_PORT}" --logfile /dev/null & + # Wait for server to become reachable before starting client. + slowwait 5 ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p \ + "${TCP_PORT}" -t 1 --logfile /dev/null + ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p "${TCP_PORT}" -b 1M -l \ + 1K -t 0 --logfile /dev/null & + sender_pid=$! +} + +# Stop sending TCP traffic with iperf3. +# Globals: +# sender_pid - The process ID of the iperf3 sender process. +stop_sending_and_listening() +{ + kill "${sender_pid}" && wait "${sender_pid}" 2>/dev/null || true +} + +# Monitor for TCP traffic with Tcpdump, save results to temp files. +# Arguments: +# namespace - The network namespace to run tcpdump inside of. +# $@ - interfaces - The interfaces to listen to. +save_tcpdump_outputs() +{ + local namespace=$1 + shift 1 + local interfaces=("$@") + + for interface in "${interfaces[@]}"; do + tcpdump_start "${interface}" "${namespace}" + done + + sleep 1 + + for interface in "${interfaces[@]}"; do + tcpdump_stop_nosleep "${interface}" + done +} + +clear_tcpdump_outputs() +{ + local interfaces=("$@") + + for interface in "${interfaces[@]}"; do + tcpdump_cleanup "${interface}" + done +} + +# Read Tcpdump output, determine packet counts. +# Arguments: +# interface - The name of the interface to count packets for. +# ip_address - The destination IP address. +did_interface_receive() +{ + local interface="$1" + local ip_address="$2" + local packet_count + + packet_count=$(tcpdump_show "$interface" | grep -c \ + "> ${ip_address}.${TCP_PORT}") + echo "Packet count for ${interface} was ${packet_count}" + + if [[ "${packet_count}" -gt 0 ]]; then + true + else + false + fi +} + +# Return true if the given interface in the given namespace does NOT receive +# traffic over a 1 second period. +# Arguments: +# interface - The name of the interface. +# ip_address - The destination IP address. +# namespace - The name of the namespace that the interface is in. +check_no_traffic() +{ + local interface="$1" + local ip_address="$2" + local namespace="$3" + local rc + + save_tcpdump_outputs "${namespace}" "${interface}" + did_interface_receive "${interface}" "${ip_address}" + rc=$? + + clear_tcpdump_outputs "${interface}" + + if [[ "${rc}" -eq 0 ]]; then + return 1 + else + return 0 + fi +} diff --git a/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh b/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh new file mode 100755 index 000000000000..2b26a697e179 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# These tests verify that teamd is able to enable and disable ports via the +# active backup runner. +# +# Topology: +# +# +-------------------------+ NS1 +# | test_team1 | +# | + | +# | eth0 | eth1 | +# | +---+---+ | +# | | | | +# +-------------------------+ +# | | +# +-------------------------+ NS2 +# | | | | +# | +-------+ | +# | eth0 | eth1 | +# | + | +# | test_team2 | +# +-------------------------+ + +export ALL_TESTS="teamd_test_active_backup" + +test_dir="$(dirname "$0")" +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/lib.sh" +# shellcheck disable=SC1091 +source "${test_dir}/team_lib.sh" + +NS1="" +NS2="" +export NODAD="nodad" +PREFIX_LENGTH="64" +NS1_IP="fd00::1" +NS2_IP="fd00::2" +NS1_IP4="192.168.0.1" +NS2_IP4="192.168.0.2" +NS1_TEAMD_CONF="" +NS2_TEAMD_CONF="" +NS1_TEAMD_PID="" +NS2_TEAMD_PID="" + +while getopts "4" opt; do + case $opt in + 4) + echo "IPv4 mode selected." + export NODAD= + PREFIX_LENGTH="24" + NS1_IP="${NS1_IP4}" + NS2_IP="${NS2_IP4}" + ;; + \?) + echo "Invalid option: -${OPTARG}" >&2 + exit 1 + ;; + esac +done + +teamd_config_create() +{ + local runner=$1 + local dev=$2 + local conf + + conf=$(mktemp) + + cat > "${conf}" <<-EOF + { + "device": "${dev}", + "runner": {"name": "${runner}"}, + "ports": { + "eth0": {}, + "eth1": {} + } + } + EOF + echo "${conf}" +} + +# Create the network namespaces, veth pair, and team devices in the specified +# runner. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# runner - The Teamd runner to use for the Team devices. +environment_create() +{ + local runner=$1 + + echo "Setting up two-link aggregation for runner ${runner}" + echo "Teamd version is: $(teamd --version)" + trap environment_destroy EXIT + + setup_ns ns1 ns2 + NS1="${NS_LIST[0]}" + NS2="${NS_LIST[1]}" + + for link in $(seq 0 1); do + ip -n "${NS1}" link add "eth${link}" type veth peer name \ + "eth${link}" netns "${NS2}" + check_err $? "Failed to create veth pair" + done + + NS1_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team1") + NS2_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team2") + echo "Conf files are ${NS1_TEAMD_CONF} and ${NS2_TEAMD_CONF}" + + ip netns exec "${NS1}" teamd -d -f "${NS1_TEAMD_CONF}" + check_err $? "Failed to create team device in ${NS1}" + NS1_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS1_TEAMD_CONF}") + + ip netns exec "${NS2}" teamd -d -f "${NS2_TEAMD_CONF}" + check_err $? "Failed to create team device in ${NS2}" + NS2_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS2_TEAMD_CONF}") + + echo "Created team devices" + echo "Teamd PIDs are ${NS1_TEAMD_PID} and ${NS2_TEAMD_PID}" + + ip -n "${NS1}" link set test_team1 up + check_err $? "Failed to set test_team1 up in ${NS1}" + ip -n "${NS2}" link set test_team2 up + check_err $? "Failed to set test_team2 up in ${NS2}" + + ip -n "${NS1}" addr add "${NS1_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \ + test_team1 + check_err $? "Failed to add address to team device in ${NS1}" + ip -n "${NS2}" addr add "${NS2_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \ + test_team2 + check_err $? "Failed to add address to team device in ${NS2}" + + slowwait 2 timeout 0.5 ip netns exec "${NS1}" ping -W 1 -c 1 "${NS2_IP}" +} + +# Tear down the environment: kill teamd and delete network namespaces. +environment_destroy() +{ + echo "Tearing down two-link aggregation" + + rm "${NS1_TEAMD_CONF}" + rm "${NS2_TEAMD_CONF}" + + # First, try graceful teamd teardown. + ip netns exec "${NS1}" teamd -k -t test_team1 + ip netns exec "${NS2}" teamd -k -t test_team2 + + # If teamd can't be killed gracefully, then sigkill. + if kill -0 "${NS1_TEAMD_PID}" 2>/dev/null; then + echo "Sending sigkill to teamd for test_team1" + kill -9 "${NS1_TEAMD_PID}" + rm -f /var/run/teamd/test_team1.{pid,sock} + fi + if kill -0 "${NS2_TEAMD_PID}" 2>/dev/null; then + echo "Sending sigkill to teamd for test_team2" + kill -9 "${NS2_TEAMD_PID}" + rm -f /var/run/teamd/test_team2.{pid,sock} + fi + cleanup_all_ns +} + +# Change the active port for an active-backup mode team. +# Arguments: +# namespace - The network namespace that the team is in. +# team - The name of the team. +# active_port - The port to make active. +set_active_port() +{ + local namespace=$1 + local team=$2 + local active_port=$3 + + ip netns exec "${namespace}" teamdctl "${team}" state item set \ + runner.active_port "${active_port}" + slowwait 2 bash -c "ip netns exec ${namespace} teamdctl ${team} state \ + item get runner.active_port | grep -q ${active_port}" +} + +# Wait for an interface to stop receiving traffic. If it keeps receiving traffic +# for the duration of the timeout, then return an error. +# Arguments: +# - namespace - The network namespace that the interface is in. +# - interface - The name of the interface. +wait_to_stop_receiving() +{ + local namespace=$1 + local interface=$2 + + echo "Waiting for ${interface} in ${namespace} to stop receiving" + slowwait 10 check_no_traffic "${interface}" "${NS2_IP}" \ + "${namespace}" +} + +# Test that active backup runner can change active ports. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +teamd_test_active_backup() +{ + export RET=0 + + start_listening_and_sending + + ### Scenario 1: Don't manually set active port, just make sure team + # works. + save_tcpdump_outputs "${NS2}" test_team2 + did_interface_receive test_team2 "${NS2_IP}" + check_err $? "Traffic did not reach team interface in NS2." + clear_tcpdump_outputs test_team2 + + ### Scenario 2: Choose active port. + set_active_port "${NS1}" test_team1 eth1 + set_active_port "${NS2}" test_team2 eth1 + + wait_to_stop_receiving "${NS2}" eth0 + save_tcpdump_outputs "${NS2}" eth0 eth1 + did_interface_receive eth0 "${NS2_IP}" + check_fail $? "eth0 IS transmitting when inactive" + did_interface_receive eth1 "${NS2_IP}" + check_err $? "eth1 not transmitting when active" + clear_tcpdump_outputs eth0 eth1 + + ### Scenario 3: Change active port. + set_active_port "${NS1}" test_team1 eth0 + set_active_port "${NS2}" test_team2 eth0 + + wait_to_stop_receiving "${NS2}" eth1 + save_tcpdump_outputs "${NS2}" eth0 eth1 + did_interface_receive eth0 "${NS2_IP}" + check_err $? "eth0 not transmitting when active" + did_interface_receive eth1 "${NS2_IP}" + check_fail $? "eth1 IS transmitting when inactive" + clear_tcpdump_outputs eth0 eth1 + + log_test "teamd active backup runner test" + + stop_sending_and_listening +} + +require_command teamd +require_command teamdctl +require_command iperf3 +require_command tcpdump +environment_create activebackup +tests_run +exit "${EXIT_STATUS}" diff --git a/tools/testing/selftests/drivers/net/team/transmit_failover.sh b/tools/testing/selftests/drivers/net/team/transmit_failover.sh new file mode 100755 index 000000000000..b2bdcd27bc98 --- /dev/null +++ b/tools/testing/selftests/drivers/net/team/transmit_failover.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# These tests verify the basic failover capability of the team driver via the +# `enabled` team driver option across different team driver modes. This does not +# rely on teamd, and instead just uses teamnl to set the `enabled` option +# directly. +# +# Topology: +# +# +-------------------------+ NS1 +# | test_team1 | +# | + | +# | eth0 | eth1 | +# | +---+---+ | +# | | | | +# +-------------------------+ +# | | +# +-------------------------+ NS2 +# | | | | +# | +-------+ | +# | eth0 | eth1 | +# | + | +# | test_team2 | +# +-------------------------+ + +export ALL_TESTS="team_test_failover" + +test_dir="$(dirname "$0")" +# shellcheck disable=SC1091 +source "${test_dir}/../../../net/lib.sh" +# shellcheck disable=SC1091 +source "${test_dir}/team_lib.sh" + +NS1="" +NS2="" +export NODAD="nodad" +PREFIX_LENGTH="64" +NS1_IP="fd00::1" +NS2_IP="fd00::2" +NS1_IP4="192.168.0.1" +NS2_IP4="192.168.0.2" +MEMBERS=("eth0" "eth1") + +while getopts "4" opt; do + case $opt in + 4) + echo "IPv4 mode selected." + export NODAD= + PREFIX_LENGTH="24" + NS1_IP="${NS1_IP4}" + NS2_IP="${NS2_IP4}" + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# Create the network namespaces, veth pair, and team devices in the specified +# mode. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# mode - The team driver mode to use for the team devices. +environment_create() +{ + trap cleanup_all_ns EXIT + setup_ns ns1 ns2 + NS1="${NS_LIST[0]}" + NS2="${NS_LIST[1]}" + + # Create the interfaces. + ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}" + ip -n "${NS1}" link add eth1 type veth peer name eth1 netns "${NS2}" + ip -n "${NS1}" link add test_team1 type team + ip -n "${NS2}" link add test_team2 type team + + # Set up the receiving network namespace's team interface. + setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \ + "${PREFIX_LENGTH}" "${MEMBERS[@]}" +} + + +# Check that failover works for a specific team driver mode. +# Globals: +# RET - Used by test infra, set by `check_err` functions. +# Arguments: +# mode - The mode to set the team interfaces to. +team_test_mode_failover() +{ + local mode="$1" + export RET=0 + + # Set up the sender team with the correct mode. + setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \ + "${PREFIX_LENGTH}" "${MEMBERS[@]}" + check_err $? "Failed to set up sender team" + + start_listening_and_sending + + ### Scenario 1: All interfaces initially enabled. + save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}" + did_interface_receive eth0 "${NS2_IP}" + check_err $? "eth0 not transmitting when both links enabled" + did_interface_receive eth1 "${NS2_IP}" + check_err $? "eth1 not transmitting when both links enabled" + clear_tcpdump_outputs "${MEMBERS[@]}" + + ### Scenario 2: One tx-side interface disabled. + ip netns exec "${NS1}" teamnl test_team1 setoption enabled false \ + --port=eth1 + slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \ + enabled --port=eth1 | grep -q false" + + save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}" + did_interface_receive eth0 "${NS2_IP}" + check_err $? "eth0 not transmitting when enabled" + did_interface_receive eth1 "${NS2_IP}" + check_fail $? "eth1 IS transmitting when disabled" + clear_tcpdump_outputs "${MEMBERS[@]}" + + ### Scenario 3: The interface is re-enabled. + ip netns exec "${NS1}" teamnl test_team1 setoption enabled true \ + --port=eth1 + slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \ + enabled --port=eth1 | grep -q true" + + save_tcpdump_outputs "${NS2}" "${MEMBERS[@]}" + did_interface_receive eth0 "${NS2_IP}" + check_err $? "eth0 not transmitting when both links enabled" + did_interface_receive eth1 "${NS2_IP}" + check_err $? "eth1 not transmitting when both links enabled" + clear_tcpdump_outputs "${MEMBERS[@]}" + + log_test "Failover of '${mode}' test" + + # Clean up + stop_sending_and_listening +} + +team_test_failover() +{ + team_test_mode_failover broadcast + team_test_mode_failover roundrobin + team_test_mode_failover random + # Don't test `activebackup` or `loadbalance` modes, since they are too + # complicated for just setting `enabled` to work. They use more than + # the `enabled` option for transmit. +} + +require_command teamnl +require_command iperf3 +require_command tcpdump +environment_create +tests_run +exit "${EXIT_STATUS}" |
