Files
systemd/test/units/TEST-17-UDEV.rename-netif.sh
Chris Patterson 779a3a13ae udev: preserve rule-set properties on failed network interface rename
When rename_netif() failed for a reason other than -EBUSY (e.g. -EEXIST
because NAME= conflicts with an existing interface), the revert path
rewrote the on-disk udev database from dev_db_clone (the pre-rules
snapshot) and then returned the error. The caller in
udev_event_execute_rules() bailed out before reaching the final
device_update_db(dev), so every property the rules had attached to the
device during this event (including ENV{}= assignments made alongside
the failing NAME=) was silently dropped from /run/udev/data/.

Persist 'dev' from inside the revert block, after its syspath and
INTERFACE have been restored. The cloned snapshot is still written
first to clear ID_RENAMING/ID_PROCESSING; the subsequent dev write
keeps the rule-applied properties without resurrecting the new name.
Only persist when r < 0, since the -EBUSY path returns success and the
caller will write the DB itself.

Add a TEST-17-UDEV.rename-netif regression test that pre-creates the
rename target, then triggers rules that set three ENV{}= properties
(one on a rule line before the failing NAME=, one on the same line as
NAME=, and one on a rule line after) and asserts all three are still
visible via udevadm info on the source interface. The neighbouring
test_netif_renaming_conflict already exercises the same -EEXIST revert
path but only checks the SYSTEMD_ALIAS broadcast, which is why this
regression went unnoticed.

Verified against unpatched origin/main (b4aff10ac0): the new test
fails, udevadm reports only kernel-supplied fields for the source
interface, and /run/udev/data/n<ifindex> is 0 bytes. With this patch
applied all three properties survive regardless of whether they were
set before, on, or after the failing NAME= rule line.

Downstream impact (NetworkManager managing an interface that rules had
marked NM_UNMANAGED=1): https://redhat.atlassian.net/browse/RHEL-178481

Fixes: https://github.com/systemd/systemd/issues/42331

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 02:08:24 +09:00

239 lines
9.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# disable shellcheck warning about '"aaa"' type quotation
# shellcheck disable=SC2016
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
mkdir -p /run/udev/rules.d/
# test for ID_RENAMING= udev property and device unit state
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
ACTION=="remove", GOTO="hoge_end"
SUBSYSTEM!="net", GOTO="hoge_end"
KERNEL!="hoge", GOTO="hoge_end"
OPTIONS="log_level=debug"
# emulate renaming
ACTION=="online", ENV{ID_RENAMING}="1"
LABEL="hoge_end"
EOF
udevadm control --log-priority=debug --reload --timeout=30
ip link add hoge type dummy
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
udevadm trigger --action=online --settle /sys/devices/virtual/net/hoge
assert_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
udevadm trigger --action=move --settle /sys/devices/virtual/net/hoge
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
# test for renaming interface with NAME= (issue #25106)
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
ACTION!="add", GOTO="hoge_end"
SUBSYSTEM!="net", GOTO="hoge_end"
OPTIONS="log_level=debug"
KERNEL=="hoge", NAME="foobar"
KERNEL=="foobar", NAME="hoge"
LABEL="hoge_end"
EOF
udevadm control --log-priority=debug --reload --timeout=30
udevadm trigger --action=add --settle /sys/devices/virtual/net/hoge
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/foobar)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done'
udevadm trigger --action=add --settle /sys/devices/virtual/net/foobar
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "inactive" ]]; do sleep .5; done'
# cleanup
rm -f /run/udev/rules.d/50-testsuite.rules
udevadm control --reload --timeout=30
# test for renaming interface with an external tool (issue #16967)
ip link set hoge name foobar
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done'
ip link set foobar name hoge
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "inactive" ]]; do sleep .5; done'
timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "inactive" ]]; do sleep .5; done'
# cleanup
ip link del hoge
# shellcheck disable=SC2317,SC2329
teardown_netif_renaming_conflict() {
set +ex
if [[ -n "$KILL_PID" ]]; then
kill "$KILL_PID"
fi
rm -rf "$TMPDIR"
rm -f /run/udev/rules.d/50-testsuite.rules
udevadm control --reload --timeout=30
ip link del hoge
ip link del foobar
}
test_netif_renaming_conflict() {
local since found=
trap teardown_netif_renaming_conflict RETURN
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
ACTION!="add", GOTO="hoge_end"
SUBSYSTEM!="net", GOTO="hoge_end"
OPTIONS="log_level=debug"
KERNEL=="foobar", NAME="hoge"
LABEL="hoge_end"
EOF
udevadm control --log-priority=debug --reload --timeout=30
ip link add hoge type dummy
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
TMPDIR=$(mktemp -d -p /tmp udev-tests.XXXXXX)
udevadm monitor --udev --property --subsystem-match=net >"$TMPDIR"/monitor.txt &
KILL_PID="$!"
# make sure that 'udevadm monitor' actually monitor uevents
sleep 1
journalctl --sync
since="$(date '+%H:%M:%S')"
# add another interface which will conflict with an existing interface
ip link add foobar type dummy
for _ in {1..40}; do
if (
grep -q 'ACTION=add' "$TMPDIR"/monitor.txt
grep -q 'DEVPATH=/devices/virtual/net/foobar' "$TMPDIR"/monitor.txt
grep -q 'SUBSYSTEM=net' "$TMPDIR"/monitor.txt
grep -q 'INTERFACE=foobar' "$TMPDIR"/monitor.txt
grep -q 'ID_NET_DRIVER=dummy' "$TMPDIR"/monitor.txt
grep -q 'ID_NET_NAME=foobar' "$TMPDIR"/monitor.txt
# Even when network interface renaming is failed, SYSTEMD_ALIAS with the conflicting name will be broadcast.
grep -q 'SYSTEMD_ALIAS=/sys/subsystem/net/devices/hoge' "$TMPDIR"/monitor.txt
grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt
grep -q 'UDEV_WORKER_ERRNO=17' "$TMPDIR"/monitor.txt
grep -q 'UDEV_WORKER_ERRNO_NAME=EEXIST' "$TMPDIR"/monitor.txt
); then
cat "$TMPDIR"/monitor.txt
found=1
break
fi
sleep .5
done
test -n "$found"
journalctl --sync
timeout -v 30 bash -c "journalctl _PID=1 _COMM=systemd --since '$since' -n all --follow | grep -m 1 -q -F 'foobar: systemd-udevd failed to process the device, ignoring: File exists'"
# check if the invalid SYSTEMD_ALIAS property for the interface foobar is ignored by PID1
assert_eq "$(systemctl show --property=SysFSPath --value /sys/subsystem/net/devices/hoge)" "/sys/devices/virtual/net/hoge"
}
test_netif_renaming_conflict
# shellcheck disable=SC2317,SC2329
teardown_netif_renaming_keeps_properties() {
set +ex
rm -f /run/udev/rules.d/50-testsuite.rules
udevadm control --reload --timeout=30
ip link del rename-src 2>/dev/null || :
ip link del rename-dst 2>/dev/null || :
}
test_netif_renaming_keeps_properties() {
trap teardown_netif_renaming_keeps_properties RETURN
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
ACTION!="add", GOTO="end"
SUBSYSTEM!="net", GOTO="end"
OPTIONS="log_level=debug"
# Set properties before, during, and after a rename to a name that is already
# taken. The rename will fail with -EEXIST, but the property assignments must
# persist.
KERNEL=="rename-src", ENV{ID_TEST_PROP_BEFORE}="before"
KERNEL=="rename-src", ENV{ID_TEST_PROP_DURING}="during", NAME="rename-dst"
KERNEL=="rename-src", ENV{ID_TEST_PROP_AFTER}="after"
LABEL="end"
EOF
udevadm control --log-priority=debug --reload --timeout=30
# Pre-create the conflicting target so the rename is guaranteed to fail.
ip link add rename-dst type dummy
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/rename-dst
ip link add rename-src type dummy
udevadm wait --timeout=30 --settle /sys/devices/virtual/net/rename-src
# Sanity check: the rename did fail and the original interface still exists.
[[ -d /sys/devices/virtual/net/rename-src ]]
# The properties set by the rule alongside NAME= must be present in the udev
# database even though the rename itself failed. Check one assigned before,
# one assigned on the same line as the failing NAME= token, and one
# assigned after.
info="$(udevadm info /sys/devices/virtual/net/rename-src)"
assert_in "ID_TEST_PROP_BEFORE=before" "$info"
assert_in "ID_TEST_PROP_DURING=during" "$info"
assert_in "ID_TEST_PROP_AFTER=after" "$info"
}
test_netif_renaming_keeps_properties
exit 0