diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 959ce08314f..56c02e18dbb 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -776,6 +776,22 @@ Table=1234
+
+ IPv4ReversePathFilter=
+
+ Configure IPv4 Reverse Path Filtering. If enabled, when an IPv4 packet is received, the machine will first check
+ whether the source of the packet would be routed through the interface it came in. If there is no
+ route to the source on that interface, the machine will drop the packet. Takes one of
+ no, strict, or loose. When no,
+ no source validation will be done. When strict, mode each incoming packet is tested against the FIB and
+ if the incoming interface is not the best reverse path, the packet check will fail. By default failed packets are discarded.
+ When loose, mode each incoming packet's source address is tested against the FIB. The packet is dropped
+ only if the source address is not reachable via any interface on that router.
+ See RFC 3704.
+ When unset, the kernel's default will be used.
+
+
+
IPv4AcceptLocal=
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index a5d328e0378..4049993beb4 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -140,6 +140,7 @@ Network.PrimarySlave, config_parse_bool,
Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0
+Network.IPv4ReversePathFilter, config_parse_ip_reverse_path_filter, 0, offsetof(Network, ipv4_rp_filter)
Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier)
Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier)
Network.IgnoreCarrierLoss, config_parse_ignore_carrier_loss, 0, 0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index d4f4a9ed978..40148429851 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -474,6 +474,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.ipv6_hop_limit = -1,
.ipv6_proxy_ndp = -1,
.proxy_arp = -1,
+ .ipv4_rp_filter = _IP_REVERSE_PATH_FILTER_INVALID,
.ipv6_accept_ra = -1,
.ipv6_accept_ra_use_dns = true,
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 886729cd5a1..0b9775f0148 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -307,6 +307,7 @@ struct Network {
int proxy_arp;
uint32_t ipv6_mtu;
IPv6PrivacyExtensions ipv6_privacy_extensions;
+ IPReversePathFilter ipv4_rp_filter;
int ipv6_proxy_ndp;
Set *ipv6_proxy_ndp_addresses;
diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c
index 7c9a83ba579..0b8169a0176 100644
--- a/src/network/networkd-sysctl.c
+++ b/src/network/networkd-sysctl.c
@@ -89,6 +89,21 @@ static int link_set_ipv6_forward(Link *link) {
return sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1");
}
+static int link_set_ipv4_rp_filter(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ if (link->network->ipv4_rp_filter < 0)
+ return 0;
+
+ return sysctl_write_ip_property_int(AF_INET, link->ifname, "rp_filter", link->network->ipv4_rp_filter);
+}
+
static int link_set_ipv6_privacy_extensions(Link *link) {
IPv6PrivacyExtensions val;
@@ -302,6 +317,10 @@ int link_set_sysctl(Link *link) {
if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv4 route_localnet flag for interface, ignoring: %m");
+ r = link_set_ipv4_rp_filter(link);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot set IPv4 reverse path filtering for interface, ignoring: %m");
+
/* If promote_secondaries is not set, DHCP will work only as long as the IP address does not
* changes between leases. The kernel will remove all secondary IP addresses of an interface
* otherwise. The way systemd-networkd works is that the new IP of a lease is added as a
@@ -325,3 +344,13 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_privacy_extensions, IPv6PrivacyExte
IPV6_PRIVACY_EXTENSIONS_YES);
DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_privacy_extensions, ipv6_privacy_extensions, IPv6PrivacyExtensions,
"Failed to parse IPv6 privacy extensions option");
+
+static const char* const ip_reverse_path_filter_table[_IP_REVERSE_PATH_FILTER_MAX] = {
+ [IP_REVERSE_PATH_FILTER_NO] = "no",
+ [IP_REVERSE_PATH_FILTER_STRICT] = "strict",
+ [IP_REVERSE_PATH_FILTER_LOOSE] = "loose",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ip_reverse_path_filter, IPReversePathFilter);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ip_reverse_path_filter, ip_reverse_path_filter, IPReversePathFilter,
+ "Failed to parse IP reverse path filter option");
diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h
index df5d6e08ffa..ba6915719a1 100644
--- a/src/network/networkd-sysctl.h
+++ b/src/network/networkd-sysctl.h
@@ -17,10 +17,22 @@ typedef enum IPv6PrivacyExtensions {
_IPV6_PRIVACY_EXTENSIONS_INVALID = -EINVAL,
} IPv6PrivacyExtensions;
+typedef enum IPReversePathFilter {
+ IP_REVERSE_PATH_FILTER_NO,
+ IP_REVERSE_PATH_FILTER_STRICT,
+ IP_REVERSE_PATH_FILTER_LOOSE,
+ _IP_REVERSE_PATH_FILTER_MAX,
+ _IP_REVERSE_PATH_FILTER_INVALID = -EINVAL,
+} IPReversePathFilter;
+
int link_set_sysctl(Link *link);
int link_set_ipv6_mtu(Link *link);
const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_;
IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_;
+const char* ip_reverse_path_filter_to_string(IPReversePathFilter i) _const_;
+IPReversePathFilter ip_reverse_path_filter_from_string(const char *s) _pure_;
+
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions);
+CONFIG_PARSER_PROTOTYPE(config_parse_ip_reverse_path_filter);