From cec6bc4ea609e2ba61f2ac789046aa22cc5ce498 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 08:24:29 +0000 Subject: [PATCH] imds: warn if net.ipv4.conf.all.rp_filter breaks locked mode Make that caveat from the previous commit more obvious with a logged warning, and document the workaround (or really, the fix) of disabling `net.ipv4.conf.all.rp_filter` globally. Signed-off-by: Martin Pitt --- man/systemd-imdsd@.service.xml | 10 ++++++++++ src/imds/imdsd.c | 18 +++++++++++++++++- test/units/TEST-74-AUX-UTILS.imds.sh | 21 ++++++++++++++++++--- units/systemd-imds-early-network.service.in | 2 +- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/man/systemd-imdsd@.service.xml b/man/systemd-imdsd@.service.xml index 76d94dd68ac..d1670c02dcc 100644 --- a/man/systemd-imdsd@.service.xml +++ b/man/systemd-imdsd@.service.xml @@ -78,6 +78,16 @@ set to unlocked (the default) this "prohibit" route is not created, and regular unprivileged processes can directly contact IMDS. + Some systems set net.ipv4.conf.all.rp_filter. With + locked, IMDS then cannot be reached. systemd-imdsd warns about + this when setting up the network. To allow IMDS access, turn it off with a + sysctl.d5 + drop-in, e.g. /etc/sysctl.d/90-imds.conf: + net.ipv4.conf.all.rp_filter = 0 + + systemd's 50-default.conf already turns on reverse path filtering for every + individual link, so all other links keep it. + diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 4c8bb60afb0..7473bbc21aa 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -47,6 +47,7 @@ #include "socket-util.h" #include "string-util.h" #include "strv.h" +#include "sysctl-util.h" #include "time-util.h" #include "tmpfile-util.h" #include "utf8.h" @@ -1637,6 +1638,19 @@ static int imds_configured(int level) { return 0; } +static void warn_rp_filter_all(void) { + int r, v; + + /* The kernel applies the maximum of the link's and the "all" setting, so turning off reverse path + * filtering on the link does not help if "all" turns it on. */ + r = sysctl_read_ip_property_int(AF_INET, "all", "rp_filter", &v); + if (r < 0) + return (void) log_debug_errno(r, "Failed to read net.ipv4.conf.all.rp_filter, ignoring: %m"); + if (v != 0) + log_warning("net.ipv4.conf.all.rp_filter=%i turns on IPv4 reverse path filtering on all links, " + "which drops IMDS replies in locked mode. Set it to 0 to allow IMDS access.", v); +} + static int setup_network(void) { int r; @@ -1691,9 +1705,11 @@ static int setup_network(void) { /* IMDS's replies to imdsd carry no firewall mark. Reverse path filtering would check their source * address against the main table, find the prohibit route below, and drop them. IPv6 has no * reverse path filter. */ - if (arg_network_mode != IMDS_NETWORK_UNLOCKED && in4_addr_is_set(&arg_address_ipv4)) + if (arg_network_mode != IMDS_NETWORK_UNLOCKED && in4_addr_is_set(&arg_address_ipv4)) { fputs("# Reverse path filtering would drop IMDS's replies due to the prohibit route\n" "IPv4ReversePathFilter=no\n", f); + warn_rp_filter_all(); + } fputs("\n" "[DHCP]\n" diff --git a/test/units/TEST-74-AUX-UTILS.imds.sh b/test/units/TEST-74-AUX-UTILS.imds.sh index 5046b38eda8..97650affa15 100755 --- a/test/units/TEST-74-AUX-UTILS.imds.sh +++ b/test/units/TEST-74-AUX-UTILS.imds.sh @@ -25,6 +25,10 @@ at_exit() { rmdir /run/systemd/system/systemd-imdsd@.service.d # cached negative entries would short-circuit the /flaky retry on a rerun rm -rf /run/systemd/imds/cache/veth-imds + if [[ -v RP_FILTER_ALL ]]; then + rm -f /run/sysctl.d/90-imds.conf + sysctl -w net.ipv4.conf.all.rp_filter="$RP_FILTER_ALL" + fi rm -rf "$WORK_DIR" } @@ -168,11 +172,12 @@ ip -n imds addr add 169.254.169.254/32 dev veth-imds-peer IMDSD_LOCKED=("${IMDSD[@]}" --data-url=http://169.254.169.254:8088 --address-ipv4=169.254.169.254) # Unlocked mode has no prohibit route, and keeps reverse path filtering -SYSTEMD_PROC_CMDLINE=systemd.imds.network=unlocked "${IMDSD[@]}" --address-ipv4=169.254.169.254 --setup-network +SYSTEMD_PROC_CMDLINE=systemd.imds.network=unlocked "${IMDSD[@]}" --address-ipv4=169.254.169.254 --setup-network 2>&1 | tee "$WORK_DIR/setup-network-unlocked.log" cat /run/systemd/network/85-imds-early.network (! grep -F -e "Type=prohibit" -e "IPv4ReversePathFilter=" /run/systemd/network/85-imds-early.network >/dev/null) +(! grep -F "net.ipv4.conf.all.rp_filter" "$WORK_DIR/setup-network-unlocked.log" >/dev/null) -SYSTEMD_PROC_CMDLINE=systemd.imds.network=locked "${IMDSD_LOCKED[@]}" --setup-network +SYSTEMD_PROC_CMDLINE=systemd.imds.network=locked "${IMDSD_LOCKED[@]}" --setup-network 2>&1 | tee "$WORK_DIR/setup-network.log" cat /run/systemd/network/85-imds-early.network grep -Fx "Type=prohibit" /run/systemd/network/85-imds-early.network >/dev/null grep -Fx "IPv4ReversePathFilter=no" /run/systemd/network/85-imds-early.network >/dev/null @@ -204,15 +209,25 @@ assert_eq "$(sysctl -n net.ipv4.conf.veth-imds.rp_filter)" 0 # applies the maximum of that and net.ipv4.conf.all.rp_filter, which some distributions turn on (e.g. # Ubuntu). Then the reverse path filter still drops the replies. if [[ "$(sysctl -n net.ipv4.conf.all.rp_filter)" == 0 ]]; then + (! grep -F "net.ipv4.conf.all.rp_filter" "$WORK_DIR/setup-network.log" >/dev/null) assert_eq "$("${IMDSD_LOCKED[@]}" --interface=veth-imds /hostname)" "piff" else - echo "WARNING: net.ipv4.conf.all.rp_filter is on, imdsd cannot reach IMDS in locked mode" + grep -F "net.ipv4.conf.all.rp_filter=" "$WORK_DIR/setup-network.log" >/dev/null # Do not wait for imdsd's overall 40s timeout (too annoying); instead, check that reverse path # filtering drops the replies rp_filter_drops() { nstat -asz TcpExtIPReversePathFilter | awk '/IPReversePathFilter/ { print $2 }'; } DROPS="$(rp_filter_drops)" assert_rc 124 timeout 5 "${IMDSD_LOCKED[@]}" --interface=veth-imds /hostname (( "$(rp_filter_drops)" > DROPS )) + + # The documented workaround. Only apply this file: all sysctl.d files would also reset the link's + # setting, which networkd configures after them during boot. + RP_FILTER_ALL="$(sysctl -n net.ipv4.conf.all.rp_filter)" + mkdir -p /run/sysctl.d + echo "net.ipv4.conf.all.rp_filter = 0" >/run/sysctl.d/90-imds.conf + /usr/lib/systemd/systemd-sysctl /run/sysctl.d/90-imds.conf + sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.veth-imds.rp_filter + assert_eq "$("${IMDSD_LOCKED[@]}" --interface=veth-imds /hostname)" "piff" fi # Everybody else gets blocked diff --git a/units/systemd-imds-early-network.service.in b/units/systemd-imds-early-network.service.in index 23b35e6337b..0804dd3a833 100644 --- a/units/systemd-imds-early-network.service.in +++ b/units/systemd-imds-early-network.service.in @@ -15,7 +15,7 @@ Before=network-pre.target Wants=network-pre.target Conflicts=shutdown.target initrd-switch-root.target Before=shutdown.target initrd-switch-root.target -After=sys-devices-virtual-dmi-id.device +After=sys-devices-virtual-dmi-id.device systemd-sysctl.service [Service] ExecStart={{LIBEXECDIR}}/systemd-imdsd --setup-network