#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # # Test propagation of a real device's state to the VLANs stacked on top of it # when the real device is (or becomes) a bond member. # # The kernel mirrors a real device's UP/DOWN, MTU and feature changes onto its # VLANs. This is done asynchronously (netdev_work): doing it synchronously from # the real device's notifier could deadlock. If the real device is brought up # while enslaved to a bond - so its instance lock is held across NETDEV_UP - and # a VLAN on top of it is itself a bond member, the synchronous propagation # re-entered the stack and tried to take the same instance lock again. # # Cover both halves: # - the deferred UP/DOWN, MTU and feature propagation actually lands on the # VLAN (link state and MTU use an ops-locked dummy, i.e. the deferral path), # - the deadlock-prone topology - a VLAN on a dummy, with the VLAN and the # dummy each enslaved to a different bond - can be built without hanging. ALL_TESTS=" vlan_link_state vlan_mtu vlan_features vlan_real_dev_enslave " REQUIRE_MZ=no NUM_NETIFS=0 lib_dir=$(dirname "$0") source "$lib_dir"/../../../net/forwarding/lib.sh # Return 0 if $dev in netns $ns has flag $flag set (e.g. UP) in its <...> flags. link_has_flag() { local ns=$1 dev=$2 flag=$3 ip -n "$ns" link show dev "$dev" 2>/dev/null | grep -q "[<,]${flag}[,>]" } link_lacks_flag() { ! link_has_flag "$@" } link_mtu_is() { local ns=$1 dev=$2 want=$3 cur cur=$(ip -n "$ns" link show dev "$dev" 2>/dev/null | \ sed -n 's/.* mtu \([0-9]\+\).*/\1/p') [ "$cur" = "$want" ] } vlan_feature_is() { local ns=$1 dev=$2 feature=$3 value=$4 ip netns exec "$ns" ethtool -k "$dev" 2>/dev/null | \ grep -q "^$feature: $value" } link_has_master() { local ns=$1 dev=$2 master=$3 ip -n "$ns" -o link show dev "$dev" 2>/dev/null | grep -q "master $master" } vlan_link_state() { RET=0 ip -n "$NS" link add ls_dummy type dummy ip -n "$NS" link add link ls_dummy name ls_vlan type vlan id 100 # Bringing the real device up must propagate UP to the VLAN. ip -n "$NS" link set ls_dummy up busywait "$BUSYWAIT_TIMEOUT" link_has_flag "$NS" ls_vlan UP check_err $? "VLAN did not go UP after the real device went UP" # ... and likewise for DOWN. ip -n "$NS" link set ls_dummy down busywait "$BUSYWAIT_TIMEOUT" link_lacks_flag "$NS" ls_vlan UP check_err $? "VLAN did not go DOWN after the real device went DOWN" ip -n "$NS" link del ls_vlan ip -n "$NS" link del ls_dummy log_test "VLAN link state follows the real device" } vlan_mtu() { RET=0 # The VLAN inherits the real device's MTU (2000) at creation time. ip -n "$NS" link add mtu_dummy mtu 2000 type dummy ip -n "$NS" link add link mtu_dummy name mtu_vlan type vlan id 100 # Shrinking the real device's MTU must clamp the VLAN's MTU. ip -n "$NS" link set mtu_dummy mtu 1500 busywait "$BUSYWAIT_TIMEOUT" link_mtu_is "$NS" mtu_vlan 1500 check_err $? "VLAN MTU not clamped after the real device's MTU shrank" ip -n "$NS" link del mtu_vlan ip -n "$NS" link del mtu_dummy log_test "VLAN MTU clamped to the real device" } vlan_features() { RET=0 # Use veth as the real device: unlike dummy it exports vlan_features, so # the VLAN actually inherits a toggleable offload to assert on. ip -n "$NS" link add ft_veth type veth peer name ft_veth_pr ip -n "$NS" link add link ft_veth name ft_vlan type vlan id 100 vlan_feature_is "$NS" ft_vlan scatter-gather on check_err $? "VLAN did not inherit scatter-gather from the real device" # Toggling the offload on the real device must propagate to the VLAN. ip netns exec "$NS" ethtool -K ft_veth sg off busywait "$BUSYWAIT_TIMEOUT" \ vlan_feature_is "$NS" ft_vlan scatter-gather off check_err $? "VLAN scatter-gather still on after disabling it on real dev" ip netns exec "$NS" ethtool -K ft_veth sg on busywait "$BUSYWAIT_TIMEOUT" \ vlan_feature_is "$NS" ft_vlan scatter-gather on check_err $? "VLAN scatter-gather still off after enabling it on real dev" ip -n "$NS" link del ft_vlan ip -n "$NS" link del ft_veth log_test "VLAN features follow the real device" } vlan_real_dev_enslave() { RET=0 # dummy <- VLAN -> bond0, then enslave the dummy itself to bond1. The # last step brings the dummy up under bond1's instance lock, which used # to deadlock while synchronously propagating UP to the (bond-enslaved) # VLAN on top. ip -n "$NS" link add dl_dummy type dummy ip -n "$NS" link set dl_dummy up ip -n "$NS" link add link dl_dummy name dl_vlan type vlan id 100 ip -n "$NS" link add dl_bond0 type bond mode active-backup ip -n "$NS" link set dl_vlan down ip -n "$NS" link set dl_vlan master dl_bond0 check_err $? "could not enslave the VLAN to bond0" ip -n "$NS" link add dl_bond1 type bond mode active-backup ip -n "$NS" link set dl_dummy down ip -n "$NS" link set dl_dummy master dl_bond1 check_err $? "could not enslave the real device to bond1" # If we got here the kernel did not deadlock; make sure it is still # responsive and the enslave really took effect. link_has_master "$NS" dl_dummy dl_bond1 check_err $? "real device not enslaved to bond1" ip -n "$NS" link del dl_bond1 ip -n "$NS" link del dl_bond0 ip -n "$NS" link del dl_vlan ip -n "$NS" link del dl_dummy log_test "VLAN real device enslaved to a second bond" } setup_ns NS trap 'cleanup_ns $NS' EXIT tests_run exit "$EXIT_STATUS"