summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-05-09 02:50:29 +0300
committerJakub Kicinski <kuba@kernel.org>2026-05-09 02:50:30 +0300
commitd3f65237fa68ec83f025a718e11e9c503ff715c8 (patch)
tree543e8622d5eec102bc7570fc262c85b584214147
parentdfc86291d99b1fe513fd0b470592eb4ed58212d5 (diff)
parentc0375944d99781e13285a62a3485257ad488b30c (diff)
downloadlinux-d3f65237fa68ec83f025a718e11e9c503ff715c8.tar.xz
Merge branch 'net-fix-protodown-with-macvlan'
Ido Schimmel says: ==================== net: Fix protodown with macvlan When protodown is enabled on a macvlan, two bugs cause the macvlan to incorrectly gain carrier: 1. Toggling the lower device's carrier while protodown is enabled on the macvlan causes the macvlan to gain carrier, effectively bypassing the protodown mechanism. 2. Toggling protodown on and then off on the macvlan while the lower device has no carrier causes the macvlan to gain carrier, since netif_change_proto_down() unconditionally turns the carrier on. Patch #1 is a preparation. Patch #2 solves the first problem by making netif_carrier_on() return early when protodown is on. Patch #3 solves the second problem by only calling netif_carrier_on() when protodown is turned off if there is no linked net device or if the linked net device has a carrier. Patch #4 adds a selftest covering both bugs and the basic protodown functionality. Targeting at net-next since these are not regressions (i.e., never worked). Note that while these changes are in the core, they should only affect macvlan as protodown is only supported by macvlan and vxlan and only the former has a linked net device. ==================== Link: https://patch.msgid.link/20260507105906.891817-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--net/core/dev.c23
-rw-r--r--net/sched/sch_generic.c3
-rw-r--r--tools/testing/selftests/net/Makefile1
-rwxr-xr-xtools/testing/selftests/net/protodown.sh182
4 files changed, 207 insertions, 2 deletions
diff --git a/net/core/dev.c b/net/core/dev.c
index 8bfa8313ef62..b0691e03dd6b 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -10141,17 +10141,36 @@ bool netdev_port_same_parent_id(struct net_device *a, struct net_device *b)
}
EXPORT_SYMBOL(netdev_port_same_parent_id);
+static struct net_device *dev_get_iflink_dev(struct net_device *dev)
+{
+ struct net *net;
+
+ ASSERT_RTNL();
+
+ if (!dev->netdev_ops->ndo_get_iflink || !dev->rtnl_link_ops ||
+ !dev->rtnl_link_ops->get_link_net)
+ return dev;
+
+ net = dev->rtnl_link_ops->get_link_net(dev);
+ return __dev_get_by_index(net, dev_get_iflink(dev));
+}
+
int netif_change_proto_down(struct net_device *dev, bool proto_down)
{
+ struct net_device *iflink_dev;
+
if (!dev->change_proto_down)
return -EOPNOTSUPP;
if (!netif_device_present(dev))
return -ENODEV;
+ iflink_dev = dev_get_iflink_dev(dev);
+ if (!iflink_dev)
+ return -ENODEV;
+ WRITE_ONCE(dev->proto_down, proto_down);
if (proto_down)
netif_carrier_off(dev);
- else
+ else if (dev == iflink_dev || netif_carrier_ok(iflink_dev))
netif_carrier_on(dev);
- WRITE_ONCE(dev->proto_down, proto_down);
return 0;
}
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index a93321db8fd7..05c250c483f0 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -609,6 +609,9 @@ static void netdev_watchdog_down(struct net_device *dev)
*/
void netif_carrier_on(struct net_device *dev)
{
+ if (READ_ONCE(dev->proto_down))
+ return;
+
if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
if (dev->reg_state == NETREG_UNINITIALIZED)
return;
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index f966efedfde6..5ca6c557fc3f 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -69,6 +69,7 @@ TEST_PROGS := \
nl_netdev.py \
nl_nlctrl.py \
pmtu.sh \
+ protodown.sh \
psock_snd.sh \
reuseaddr_ports_exhausted.sh \
reuseport_addr_any.sh \
diff --git a/tools/testing/selftests/net/protodown.sh b/tools/testing/selftests/net/protodown.sh
new file mode 100755
index 000000000000..0a7b78c63c37
--- /dev/null
+++ b/tools/testing/selftests/net/protodown.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test the protodown mechanism. Verify basic protodown toggling, protodown
+# reasons, operational state when the lower device carrier changes, and correct
+# operational state when the lower device has no carrier.
+
+# shellcheck disable=SC1091,SC2034,SC2154,SC2317
+source lib.sh
+
+require_command jq
+
+ALL_TESTS="
+ protodown_basic_macvlan
+ protodown_basic_vxlan
+ protodown_reasons
+ protodown_lower_toggle
+ protodown_lower_down
+"
+
+operstate_get()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+
+ ip -n "$ns" -j link show dev "$dev" | jq -r '.[].operstate'
+}
+
+operstate_check()
+{
+ local ns=$1; shift
+ local dev=$1; shift
+ local expected=$1; shift
+
+ local current
+ current=$(operstate_get "$ns" "$dev")
+
+ [ "$current" = "$expected" ]
+}
+
+setup_prepare()
+{
+ setup_ns NS
+ defer cleanup_all_ns
+
+ ip -n "$NS" link add name dummy0 up type dummy
+
+ ip -n "$NS" link add name macvlan0 link dummy0 up type macvlan mode bridge
+
+ ip -n "$NS" link add name vxlan0 up type vxlan id 10010 dstport 4789
+}
+
+protodown_basic()
+{
+ local dev=$1; shift
+
+ ip -n "$NS" link set dev "$dev" protodown on
+ check_err $? "Failed to set protodown on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" DOWN
+ check_err $? "Operational state is not DOWN after setting protodown"
+
+ ip -n "$NS" link set dev "$dev" protodown off
+ check_err $? "Failed to set protodown off"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" UP
+ check_err $? "Operational state is not UP after clearing protodown"
+}
+
+protodown_basic_macvlan()
+{
+ RET=0
+
+ protodown_basic macvlan0
+
+ log_test "Basic protodown on/off with macvlan"
+}
+
+protodown_basic_vxlan()
+{
+ RET=0
+
+ protodown_basic vxlan0
+
+ log_test "Basic protodown on/off with vxlan"
+}
+
+protodown_reasons()
+{
+ RET=0
+
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ ip -n "$NS" link set dev macvlan0 protodown_reason 0 on
+ check_err $? "Failed to set protodown reason bit 0"
+
+ # Cannot clear protodown while reasons are active.
+ ip -n "$NS" link set dev macvlan0 protodown off 2>/dev/null
+ check_fail $? "Clearing protodown succeeded with active reasons"
+
+ ip -n "$NS" link set dev macvlan0 protodown_reason 0 off
+ check_err $? "Failed to clear protodown reason bit 0"
+
+ # Can clear protodown when no reasons are active.
+ ip -n "$NS" link set dev macvlan0 protodown off
+ check_err $? "Failed to clear protodown with no active reasons"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Operational state is not UP after clearing protodown"
+
+ log_test "Protodown reasons"
+}
+
+protodown_lower_toggle()
+{
+ RET=0
+
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+ check_err $? "Operational state is not DOWN after setting protodown"
+
+ # Toggle carrier on the lower device. The macvlan should stay DOWN
+ # because protodown is on.
+ ip -n "$NS" link set dev dummy0 carrier off
+ ip -n "$NS" link set dev dummy0 carrier on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+ check_err $? "Lower device is not UP after carrier on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+ check_err $? "Macvlan operational state is not DOWN despite protodown"
+
+ # Clear protodown and verify the macvlan comes back up.
+ ip -n "$NS" link set dev macvlan0 protodown off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Operational state is not UP after clearing protodown"
+
+ log_test "Protodown with lower device toggled"
+}
+
+protodown_lower_down()
+{
+ RET=0
+
+ # Bring the lower device carrier down first.
+ ip -n "$NS" link set dev dummy0 carrier off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN with lower carrier off"
+
+ # Toggle protodown on and off while lower has no carrier. The macvlan
+ # should not transition to UP.
+ ip -n "$NS" link set dev macvlan0 protodown on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN after setting protodown"
+
+ ip -n "$NS" link set dev macvlan0 protodown off
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+ check_err $? "Macvlan is not LOWERLAYERDOWN after clearing protodown"
+
+ # Bring the lower device carrier up. The macvlan should transition to
+ # UP.
+ ip -n "$NS" link set dev dummy0 carrier on
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+ check_err $? "Lower device is not UP after carrier on"
+
+ busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+ check_err $? "Macvlan is not UP after lower device is UP"
+
+ log_test "Protodown with lower device down"
+}
+
+trap defer_scopes_cleanup EXIT
+setup_prepare
+tests_run
+
+exit "$EXIT_STATUS"