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