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