]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: add IPv4ProxyARPAddress= and consolidate proxy ARP/NDP handling
authorAritra Basu <aritrbas+gh@cisco.com>
Sun, 7 Jun 2026 06:04:11 +0000 (02:04 -0400)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 15 Jul 2026 16:23:40 +0000 (01:23 +0900)
This adds an IPv4 counterpart to `IPv6ProxyNDPAddress=` for adding
manual entries to the kernel's IPv4 neighbour proxy table (check via
`ip -4 neighbour show proxy dev <dev>`). systemd-networkd only exposed
`IPv4ProxyARP=` for per-interface `proxy_arp` sysctl (automatic proxy
ARP) with no way to manage manual entries from a .network file.

To avoid duplicating the IPv6 proxy NDP code path, both families are
now combined into a single new `networkd-neighbor-proxy` module. The
IPv6 behaviour is preserved: `IPv6ProxyNDPAddress=` still implies
`IPv6ProxyNDP=yes` unless `IPv6ProxyNDP=` is explicitly disabled and
entries are still dropped if the kernel has no IPv6 support.
The same rule is applied to `IPv4ProxyARPAddress=`. It implies
`IPv4ProxyARP=yes` when the sysctl is not explicitly set and has no
effect if `IPv4ProxyARP=` has been set to false.

This keeps the user model symmetric and predictable across both
families: a single per-address setting that turns on the matching
per-interface sysctl automatically, while still letting system
administrators opt out by setting the boolean explicitly to false.

Note that the IPv4 manual NTF_PROXY entries installed here would
actually function without `proxy_arp` (unlike IPv6, where `proxy_ndp`
gates the manual entries); the implication is kept for symmetry with
`IPv6ProxyNDPAddress=` and is now called out explicitly in the man
page, together with the fact that enabling `proxy_arp` also activates
interface-wide automatic proxy ARP for routed-toward addresses on
connected subnets.

Parser-time validation rejects addresses the kernel would refuse:
the ANY/null address for both families, IPv4 and IPv6 multicast
and the IPv4 limited broadcast 255.255.255.255.

Signed-off-by: Aritra Basu <aritrbas+gh@cisco.com>
20 files changed:
NEWS
man/systemd.network.xml
src/network/meson.build
src/network/networkd-ipv6-proxy-ndp.c [deleted file]
src/network/networkd-ipv6-proxy-ndp.h [deleted file]
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-neighbor-proxy.c [new file with mode: 0644]
src/network/networkd-neighbor-proxy.h [new file with mode: 0644]
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-queue.c
src/network/networkd-queue.h
src/network/networkd-sysctl.c
test/fuzz/fuzz-network-parser/sysctl
test/fuzz/fuzz-unit-file/directives-all.service
test/test-network/conf/25-ipv4-proxy-arp-disabled.network [new file with mode: 0644]
test/test-network/conf/25-ipv4-proxy-arp.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py

diff --git a/NEWS b/NEWS
index 8461b44623dff742555ab8332957073b52142c92..8da1426b8012aa0eff0c13c2ece5a6025e259643 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -47,6 +47,15 @@ CHANGES WITH 262:
           should be provisioned as far as possible without ever blocking on a
           prompt.
 
+        Changes in systemd-networkd:
+
+        * A new IPv4ProxyARPAddress= setting has been added to the [Network]
+          section of .network files. It is the IPv4 counterpart to
+          IPv6ProxyNDPAddress= and allows manual entries to be added to the
+          kernel's IPv4 neighbor proxy table. The setting implies
+          IPv4ProxyARP=yes but has no effect if IPv4ProxyARP= has been set to
+          false. This mirrors the behavior of IPv6ProxyNDPAddress=.
+
 CHANGES WITH 261:
 
         Announcements of Future Feature Removals and Incompatible Changes:
index 6b988e0e4744a9742da98a0addb92e4e72f560b4..24633a0265fe558afbb4fb789a1ed4962a9eb821 100644 (file)
@@ -1147,6 +1147,25 @@ DuplicateAddressDetection=none</programlisting></para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>IPv4ProxyARPAddress=</varname></term>
+        <listitem>
+          <para>An IPv4 address, for which ARP requests will be proxied. This option may be specified
+          more than once. <command>systemd-networkd</command> will add the
+          <varname>IPv4ProxyARPAddress=</varname> entries to the kernel's IPv4 neighbor proxy table.
+          When <varname>IPv4ProxyARP=</varname> is unset, this setting implies
+          <varname>IPv4ProxyARP=yes</varname>: the per-interface <literal>proxy_arp</literal> sysctl
+          is enabled automatically, which also activates automatic proxy ARP for every address the
+          interface has a route toward on connected subnets, in addition to answering the listed
+          entries. If <varname>IPv4ProxyARP=</varname> has been set to false, the
+          <varname>IPv4ProxyARPAddress=</varname> entries listed here are discarded and not
+          programmed into the kernel. This mirrors the behaviour of
+          <varname>IPv6ProxyNDPAddress=</varname>/<varname>IPv6ProxyNDP=</varname>.</para>
+
+          <xi:include href="version-info.xml" xpointer="v262"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>IPv6ProxyNDP=</varname></term>
         <listitem>
index 2a4120f747016a8c05fb2028e968d58507499b1d..e4b4705b33c2c233a76d7c2cb6d9c6026cb52b96 100644 (file)
@@ -54,7 +54,6 @@ systemd_networkd_export_sources = files(
         'networkd-dns.c',
         'networkd-ipv4acd.c',
         'networkd-ipv4ll.c',
-        'networkd-ipv6-proxy-ndp.c',
         'networkd-ipv6ll.c',
         'networkd-json.c',
         'networkd-link-bus.c',
@@ -67,6 +66,7 @@ systemd_networkd_export_sources = files(
         'networkd-manager-varlink.c',
         'networkd-ndisc.c',
         'networkd-neighbor.c',
+        'networkd-neighbor-proxy.c',
         'networkd-netlabel.c',
         'networkd-network-bus.c',
         'networkd-network.c',
diff --git a/src/network/networkd-ipv6-proxy-ndp.c b/src/network/networkd-ipv6-proxy-ndp.c
deleted file mode 100644 (file)
index 89f4418..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include <netinet/in.h>
-
-#include "sd-netlink.h"
-
-#include "networkd-ipv6-proxy-ndp.h"
-#include "networkd-link.h"
-#include "networkd-manager.h"
-#include "networkd-network.h"
-#include "networkd-queue.h"
-#include "set.h"
-#include "socket-util.h"
-#include "string-util.h"
-
-void network_adjust_ipv6_proxy_ndp(Network *network) {
-        assert(network);
-
-        if (set_isempty(network->ipv6_proxy_ndp_addresses))
-                return;
-
-        if (!socket_ipv6_is_supported()) {
-                log_once(LOG_WARNING,
-                         "%s: IPv6 proxy NDP addresses are set, but IPv6 is not supported by kernel, "
-                         "Ignoring IPv6 proxy NDP addresses.", network->filename);
-                network->ipv6_proxy_ndp_addresses = set_free(network->ipv6_proxy_ndp_addresses);
-                return;
-        }
-
-        if (network->ipv6_proxy_ndp == 0) {
-                log_warning("%s: IPv6ProxyNDP= is disabled. Ignoring IPv6ProxyNDPAddress=.", network->filename);
-                network->ipv6_proxy_ndp_addresses = set_free(network->ipv6_proxy_ndp_addresses);
-        }
-}
-
-static int ipv6_proxy_ndp_address_configure_handler(
-                sd_netlink *rtnl,
-                sd_netlink_message *m,
-                Request *req,
-                Link *link,
-                struct in6_addr *address) {
-
-        int r;
-
-        assert(m);
-        assert(link);
-
-        r = sd_netlink_message_get_errno(m);
-        if (r < 0)
-                log_link_message_warning_errno(link, m, r, "Could not add IPv6 proxy ndp address entry, ignoring");
-
-        if (link->static_ipv6_proxy_ndp_messages == 0) {
-                log_link_debug(link, "IPv6 proxy NDP addresses set.");
-                link->static_ipv6_proxy_ndp_configured = true;
-                link_check_ready(link);
-        }
-
-        return 1;
-}
-
-/* send a request to the kernel to add an IPv6 Proxy entry to the neighbour table */
-static int ipv6_proxy_ndp_address_configure(const struct in6_addr *address, Link *link, Request *req) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-        int r;
-
-        assert(address);
-        assert(link);
-        assert(link->manager);
-        assert(link->manager->rtnl);
-        assert(req);
-
-        /* create new netlink message */
-        r = sd_rtnl_message_new_neigh(link->manager->rtnl, &m, RTM_NEWNEIGH, link->ifindex, AF_INET6);
-        if (r < 0)
-                return r;
-
-        r = sd_rtnl_message_neigh_set_flags(m, NTF_PROXY);
-        if (r < 0)
-                return r;
-
-        r = sd_netlink_message_append_in6_addr(m, NDA_DST, address);
-        if (r < 0)
-                return r;
-
-        return request_call_netlink_async(link->manager->rtnl, m, req);
-}
-
-static int ipv6_proxy_ndp_address_process_request(Request *req, Link *link, struct in6_addr *address) {
-        int r;
-
-        assert(req);
-        assert(link);
-        assert(address);
-
-        if (!link_is_ready_to_configure(link, false))
-                return 0;
-
-        r = ipv6_proxy_ndp_address_configure(address, link, req);
-        if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to configure IPv6 proxy NDP address: %m");
-
-        return 1;
-}
-
-int link_request_static_ipv6_proxy_ndp_addresses(Link *link) {
-        struct in6_addr *address;
-        int r;
-
-        assert(link);
-        assert(link->network);
-
-        link->static_ipv6_proxy_ndp_configured = false;
-
-        SET_FOREACH(address, link->network->ipv6_proxy_ndp_addresses) {
-                r = link_queue_request_safe(link, REQUEST_TYPE_IPV6_PROXY_NDP,
-                                            address, NULL,
-                                            in6_addr_hash_func,
-                                            in6_addr_compare_func,
-                                            ipv6_proxy_ndp_address_process_request,
-                                            &link->static_ipv6_proxy_ndp_messages,
-                                            ipv6_proxy_ndp_address_configure_handler,
-                                            NULL);
-                if (r < 0)
-                        return log_link_warning_errno(link, r, "Failed to request IPv6 proxy NDP address: %m");
-        }
-
-        if (link->static_ipv6_proxy_ndp_messages == 0) {
-                link->static_ipv6_proxy_ndp_configured = true;
-                link_check_ready(link);
-        } else {
-                log_link_debug(link, "Setting IPv6 proxy NDP addresses.");
-                link_set_state(link, LINK_STATE_CONFIGURING);
-        }
-
-        return 0;
-}
-
-int config_parse_ipv6_proxy_ndp_address(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        _cleanup_free_ struct in6_addr *address = NULL;
-        Network *network = ASSERT_PTR(userdata);
-        union in_addr_union buffer;
-        int r;
-
-        assert(filename);
-        assert(rvalue);
-
-        if (isempty(rvalue)) {
-                network->ipv6_proxy_ndp_addresses = set_free(network->ipv6_proxy_ndp_addresses);
-                return 0;
-        }
-
-        r = in_addr_from_string(AF_INET6, rvalue, &buffer);
-        if (r < 0) {
-                log_syntax(unit, LOG_WARNING, filename, line, r,
-                           "Failed to parse IPv6 proxy NDP address, ignoring: %s", rvalue);
-                return 0;
-        }
-
-        if (in_addr_is_null(AF_INET6, &buffer)) {
-                log_syntax(unit, LOG_WARNING, filename, line, 0,
-                           "IPv6 proxy NDP address cannot be the ANY address, ignoring: %s", rvalue);
-                return 0;
-        }
-
-        address = newdup(struct in6_addr, &buffer.in6, 1);
-        if (!address)
-                return log_oom();
-
-        r = set_ensure_consume(&network->ipv6_proxy_ndp_addresses, &in6_addr_hash_ops_free, TAKE_PTR(address));
-        if (r < 0)
-                return log_oom();
-
-        return 0;
-}
diff --git a/src/network/networkd-ipv6-proxy-ndp.h b/src/network/networkd-ipv6-proxy-ndp.h
deleted file mode 100644 (file)
index 7a2953a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include "networkd-forward.h"
-
-void network_adjust_ipv6_proxy_ndp(Network *network);
-
-int link_request_static_ipv6_proxy_ndp_addresses(Link *link);
-
-CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_proxy_ndp_address);
index f505031e1e1b7c6fec2664d4eb2d5db8fe7abce8..1551f4058659758864932c8a1767b61ee1b36110 100644 (file)
 #include "networkd-dhcp6.h"
 #include "networkd-ipv4acd.h"
 #include "networkd-ipv4ll.h"
-#include "networkd-ipv6-proxy-ndp.h"
 #include "networkd-link.h"
 #include "networkd-link-bus.h"
 #include "networkd-lldp-tx.h"
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
 #include "networkd-neighbor.h"
+#include "networkd-neighbor-proxy.h"
 #include "networkd-nexthop.h"
 #include "networkd-queue.h"
 #include "networkd-radv.h"
@@ -523,12 +523,12 @@ void link_check_ready(Link *link) {
         if (!link->static_bridge_mdb_configured)
                 return (void) log_link_debug(link, "%s(): static bridge MDB entries are not configured.", __func__);
 
-        if (!link->static_ipv6_proxy_ndp_configured)
-                return (void) log_link_debug(link, "%s(): static IPv6 proxy NDP addresses are not configured.", __func__);
-
         if (!link->static_neighbors_configured)
                 return (void) log_link_debug(link, "%s(): static neighbors are not configured.", __func__);
 
+        if (!link->static_neighbor_proxy_configured)
+                return (void) log_link_debug(link, "%s(): static neighbor proxy addresses are not configured.", __func__);
+
         if (!link->static_nexthops_configured)
                 return (void) log_link_debug(link, "%s(): static nexthops are not configured.", __func__);
 
@@ -649,11 +649,11 @@ static int link_request_static_configs(Link *link) {
         if (r < 0)
                 return r;
 
-        r = link_request_static_ipv6_proxy_ndp_addresses(link);
+        r = link_request_static_neighbors(link);
         if (r < 0)
                 return r;
 
-        r = link_request_static_neighbors(link);
+        r = link_request_static_neighbor_proxy_addresses(link);
         if (r < 0)
                 return r;
 
index 4c69ce7d027dc963ee23c669d04ee4a5fd9d4506..9340ecab53f135530923e3250c121efae00fa8e4 100644 (file)
@@ -98,8 +98,8 @@ typedef struct Link {
         unsigned static_address_label_messages;
         unsigned static_bridge_fdb_messages;
         unsigned static_bridge_mdb_messages;
-        unsigned static_ipv6_proxy_ndp_messages;
         unsigned static_neighbor_messages;
+        unsigned static_neighbor_proxy_messages;
         unsigned static_nexthop_messages;
         unsigned static_route_messages;
         unsigned static_routing_policy_rule_messages;
@@ -130,8 +130,8 @@ typedef struct Link {
         bool static_address_labels_configured:1;
         bool static_bridge_fdb_configured:1;
         bool static_bridge_mdb_configured:1;
-        bool static_ipv6_proxy_ndp_configured:1;
         bool static_neighbors_configured:1;
+        bool static_neighbor_proxy_configured:1;
         bool static_nexthops_configured:1;
         bool static_routes_configured:1;
         bool static_routing_policy_rules_configured:1;
diff --git a/src/network/networkd-neighbor-proxy.c b/src/network/networkd-neighbor-proxy.c
new file mode 100644 (file)
index 0000000..de01f38
--- /dev/null
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/in.h>
+
+#include "sd-netlink.h"
+
+#include "in-addr-util.h"
+#include "netlink-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-neighbor-proxy.h"
+#include "networkd-network.h"
+#include "networkd-queue.h"
+#include "set.h"
+#include "socket-util.h"
+#include "string-util.h"
+
+bool network_has_neighbor_proxy_address(const Network *network, int family) {
+        struct in_addr_data *a;
+
+        assert(network);
+        assert(IN_SET(family, AF_INET, AF_INET6));
+
+        SET_FOREACH(a, network->neighbor_proxy_addresses)
+                if (a->family == family)
+                        return true;
+
+        return false;
+}
+
+static void network_drop_neighbor_proxy_addresses(Network *network, int family) {
+        struct in_addr_data *a;
+
+        assert(network);
+        assert(IN_SET(family, AF_INET, AF_INET6));
+
+        SET_FOREACH(a, network->neighbor_proxy_addresses)
+                if (a->family == family)
+                        free(set_remove(network->neighbor_proxy_addresses, a));
+
+        if (set_isempty(network->neighbor_proxy_addresses))
+                network->neighbor_proxy_addresses = set_free(network->neighbor_proxy_addresses);
+}
+
+void network_adjust_neighbor_proxy(Network *network) {
+        assert(network);
+
+        if (set_isempty(network->neighbor_proxy_addresses))
+                return;
+
+        /* If IPv6 is not supported by the kernel, drop any IPv6 entries up front. */
+        if (!socket_ipv6_is_supported() &&
+            network_has_neighbor_proxy_address(network, AF_INET6)) {
+                log_once(LOG_WARNING,
+                         "%s: IPv6 proxy NDP addresses are set, but IPv6 is not supported by kernel, "
+                         "ignoring IPv6 proxy NDP addresses.", network->filename);
+                network_drop_neighbor_proxy_addresses(network, AF_INET6);
+        }
+
+        /* Drop per-family entries when the corresponding proxy sysctl was explicitly disabled.
+         * For IPv6 the proxy_ndp sysctl is required for manual entries to take effect; for IPv4 we
+         * apply the same rule for consistency so that an explicit IPv4ProxyARP=no is respected. */
+        int family;
+        FOREACH_ARGUMENT(family, AF_INET, AF_INET6) {
+                int tristate = family == AF_INET ? network->proxy_arp : network->ipv6_proxy_ndp;
+
+                if (tristate == 0 && network_has_neighbor_proxy_address(network, family)) {
+                        log_warning("%s: %s is disabled. Ignoring %s.",
+                                    network->filename,
+                                    family == AF_INET ? "IPv4ProxyARP=" : "IPv6ProxyNDP=",
+                                    family == AF_INET ? "IPv4ProxyARPAddress=" : "IPv6ProxyNDPAddress=");
+                        network_drop_neighbor_proxy_addresses(network, family);
+                }
+        }
+}
+
+static int neighbor_proxy_address_configure_handler(
+                sd_netlink *rtnl,
+                sd_netlink_message *m,
+                Request *req,
+                Link *link,
+                struct in_addr_data *address) {
+
+        int r;
+
+        assert(m);
+        assert(link);
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0)
+                log_link_message_warning_errno(link, m, r,
+                                               "Could not add neighbor proxy address entry, ignoring");
+
+        if (link->static_neighbor_proxy_messages == 0) {
+                log_link_debug(link, "Neighbor proxy addresses set.");
+                link->static_neighbor_proxy_configured = true;
+                link_check_ready(link);
+        }
+
+        return 1;
+}
+
+/* Send a request to the kernel to add a proxy entry to the neighbour table. */
+static int neighbor_proxy_address_configure(const struct in_addr_data *address, Link *link, Request *req) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        assert(address);
+        assert(IN_SET(address->family, AF_INET, AF_INET6));
+        assert(link);
+        assert(link->manager);
+        assert(link->manager->rtnl);
+        assert(req);
+
+        /* create new netlink message */
+        r = sd_rtnl_message_new_neigh(link->manager->rtnl, &m, RTM_NEWNEIGH, link->ifindex, address->family);
+        if (r < 0)
+                return r;
+
+        r = sd_rtnl_message_neigh_set_flags(m, NTF_PROXY);
+        if (r < 0)
+                return r;
+
+        r = netlink_message_append_in_addr_union(m, NDA_DST, address->family, &address->address);
+        if (r < 0)
+                return r;
+
+        return request_call_netlink_async(link->manager->rtnl, m, req);
+}
+
+static int neighbor_proxy_address_process_request(Request *req, Link *link, struct in_addr_data *address) {
+        int r;
+
+        assert(req);
+        assert(link);
+        assert(address);
+
+        if (!link_is_ready_to_configure(link, false))
+                return 0;
+
+        r = neighbor_proxy_address_configure(address, link, req);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to configure neighbor proxy address: %m");
+
+        return 1;
+}
+
+int link_request_static_neighbor_proxy_addresses(Link *link) {
+        struct in_addr_data *address;
+        int r;
+
+        assert(link);
+        assert(link->network);
+
+        link->static_neighbor_proxy_configured = false;
+
+        SET_FOREACH(address, link->network->neighbor_proxy_addresses) {
+                r = link_queue_request_safe(link, REQUEST_TYPE_NEIGHBOR_PROXY,
+                                            address, NULL,
+                                            in_addr_data_hash_func,
+                                            in_addr_data_compare_func,
+                                            neighbor_proxy_address_process_request,
+                                            &link->static_neighbor_proxy_messages,
+                                            neighbor_proxy_address_configure_handler,
+                                            NULL);
+                if (r < 0)
+                        return log_link_warning_errno(link, r, "Failed to request neighbor proxy address: %m");
+        }
+
+        if (link->static_neighbor_proxy_messages == 0) {
+                link->static_neighbor_proxy_configured = true;
+                link_check_ready(link);
+        } else {
+                log_link_debug(link, "Setting neighbor proxy addresses.");
+                link_set_state(link, LINK_STATE_CONFIGURING);
+        }
+
+        return 0;
+}
+
+int config_parse_neighbor_proxy_address(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_free_ struct in_addr_data *address = NULL;
+        Network *network = ASSERT_PTR(userdata);
+        int family = ltype;
+        union in_addr_union buffer = {};
+        int r;
+
+        assert(IN_SET(family, AF_INET, AF_INET6));
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                /* Drop only entries belonging to this family, so that
+                 * IPv4ProxyARPAddress= and IPv6ProxyNDPAddress= can be reset independently. */
+                network_drop_neighbor_proxy_addresses(network, family);
+                return 0;
+        }
+
+        r = in_addr_from_string(family, rvalue, &buffer);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        if (in_addr_is_null(family, &buffer)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "%s cannot be the ANY address, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        /* Reject address classes that do not qualify as proxy targets and that the kernel would
+         * reject: multicast for both families, plus the IPv4 limited broadcast 255.255.255.255. */
+        if (in_addr_is_multicast(family, &buffer) > 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "%s cannot be a multicast address, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        if (family == AF_INET && buffer.in.s_addr == htobe32(INADDR_BROADCAST)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "%s cannot be the limited broadcast address, ignoring: %s",
+                           lvalue, rvalue);
+                return 0;
+        }
+
+        address = new(struct in_addr_data, 1);
+        if (!address)
+                return log_oom();
+
+        *address = (struct in_addr_data) {
+                .family = family,
+                .address = buffer,
+        };
+
+        r = set_ensure_consume(&network->neighbor_proxy_addresses, &in_addr_data_hash_ops_free, TAKE_PTR(address));
+        if (r < 0)
+                return log_oom();
+
+        return 0;
+}
diff --git a/src/network/networkd-neighbor-proxy.h b/src/network/networkd-neighbor-proxy.h
new file mode 100644 (file)
index 0000000..b7e4bb0
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "networkd-forward.h"
+
+bool network_has_neighbor_proxy_address(const Network *network, int family);
+
+void network_adjust_neighbor_proxy(Network *network);
+
+int link_request_static_neighbor_proxy_addresses(Link *link);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_proxy_address);
index 0b598f131d9a567642ef0196e15af4f5bb7680d8..0a58c728a899855ed27164054275c9206ad875bc 100644 (file)
@@ -29,12 +29,12 @@ _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"")
 #include "networkd-dhcp6.h"
 #include "networkd-dns.h"
 #include "networkd-ipv4ll.h"
-#include "networkd-ipv6-proxy-ndp.h"
 #include "networkd-ipv6ll.h"
 #include "networkd-lldp-tx.h"
 #include "networkd-ndisc.h"
 #include "networkd-network.h"
 #include "networkd-neighbor.h"
+#include "networkd-neighbor-proxy.h"
 #include "networkd-nexthop.h"
 #include "networkd-ntp.h"
 #include "networkd-radv.h"
@@ -176,7 +176,8 @@ Network.PrimarySlave,                            config_parse_bool,
 Network.IPv4ProxyARP,                            config_parse_tristate,                          0,                                      offsetof(Network, proxy_arp)
 Network.IPv4ProxyARPPrivateVLAN,                 config_parse_tristate,                          0,                                      offsetof(Network, proxy_arp_pvlan)
 Network.ProxyARP,                                config_parse_tristate,                          0,                                      offsetof(Network, proxy_arp)
-Network.IPv6ProxyNDPAddress,                     config_parse_ipv6_proxy_ndp_address,            0,                                      0
+Network.IPv4ProxyARPAddress,                     config_parse_neighbor_proxy_address,            AF_INET,                                0
+Network.IPv6ProxyNDPAddress,                     config_parse_neighbor_proxy_address,            AF_INET6,                               0
 Network.IPv4ReversePathFilter,                   config_parse_ip_reverse_path_filter,            0,                                      offsetof(Network, ipv4_rp_filter)
 Network.MulticastIGMPVersion,                    config_parse_ipv4_force_igmp_version,           0,                                      offsetof(Network, ipv4_force_igmp_version)
 Network.MPLSRouting,                             config_parse_tristate,                          0,                                      offsetof(Network, mpls_input)
index 087a6ba121fd27b757309bcf6bf50f0886982c1d..02ded9ce8be43b8f5e29e63dae1fff8a4c6af9ca 100644 (file)
 #include "networkd-bridge-mdb.h"
 #include "networkd-dhcp-common.h"
 #include "networkd-dhcp-server-static-lease.h"
-#include "networkd-ipv6-proxy-ndp.h"
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
 #include "networkd-neighbor.h"
+#include "networkd-neighbor-proxy.h"
 #include "networkd-network.h"
 #include "networkd-nexthop.h"
 #include "networkd-radv.h"
@@ -230,7 +230,7 @@ int network_verify(Network *network) {
             network->ipv6ll_address_gen_mode < 0)
                 network->ipv6ll_address_gen_mode = IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_STABLE_PRIVACY;
 
-        network_adjust_ipv6_proxy_ndp(network);
+        network_adjust_neighbor_proxy(network);
         network_adjust_ndisc(network);
         network_adjust_dhcp(network);
         network_adjust_radv(network);
@@ -850,7 +850,7 @@ static Network *network_free(Network *network) {
         hashmap_free(network->stacked_netdevs);
 
         /* static configs */
-        set_free(network->ipv6_proxy_ndp_addresses);
+        set_free(network->neighbor_proxy_addresses);
         ordered_hashmap_free(network->addresses_by_section);
         hashmap_free(network->routes_by_section);
         ordered_hashmap_free(network->nexthops_by_section);
index 893b3022bb4b13261eddcdcc5c2ee3d7ff7b415f..6c8d6eee6d56d67af02ab08c9783935a1916239d 100644 (file)
@@ -355,7 +355,7 @@ typedef struct Network {
         IPReversePathFilter ipv4_rp_filter;
         IPv4ForceIgmpVersion ipv4_force_igmp_version;
         int ipv6_proxy_ndp;
-        Set *ipv6_proxy_ndp_addresses;
+        Set *neighbor_proxy_addresses;
         int mpls_input;
 
         /* NDisc support */
index ea846327b0db9f6124531a70585e5f79e2d97a66..d2a2b831ab310ded083649c657b3161fa043f6bd 100644 (file)
@@ -369,9 +369,9 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = {
         [REQUEST_TYPE_DHCP_SERVER]                      = "DHCP server",
         [REQUEST_TYPE_DHCP4_CLIENT]                     = "DHCPv4 client",
         [REQUEST_TYPE_DHCP6_CLIENT]                     = "DHCPv6 client",
-        [REQUEST_TYPE_IPV6_PROXY_NDP]                   = "IPv6 proxy NDP",
         [REQUEST_TYPE_NDISC]                            = "NDisc",
         [REQUEST_TYPE_NEIGHBOR]                         = "neighbor",
+        [REQUEST_TYPE_NEIGHBOR_PROXY]                   = "neighbor proxy",
         [REQUEST_TYPE_NETDEV_INDEPENDENT]               = "independent netdev",
         [REQUEST_TYPE_NETDEV_STACKED]                   = "stacked netdev",
         [REQUEST_TYPE_NEXTHOP]                          = "nexthop",
index 70c8b2ca9eedae592728ec8fb0c3d9fcbf69c6f7..172ff230b44879fdee7c2d2fef1c98632fe14e3a 100644 (file)
@@ -17,9 +17,9 @@ typedef enum RequestType {
         REQUEST_TYPE_DHCP_SERVER,
         REQUEST_TYPE_DHCP4_CLIENT,
         REQUEST_TYPE_DHCP6_CLIENT,
-        REQUEST_TYPE_IPV6_PROXY_NDP,
         REQUEST_TYPE_NDISC,
         REQUEST_TYPE_NEIGHBOR,
+        REQUEST_TYPE_NEIGHBOR_PROXY,
         REQUEST_TYPE_NETDEV_INDEPENDENT,
         REQUEST_TYPE_NETDEV_STACKED,
         REQUEST_TYPE_NEXTHOP,
index 7d12360fa8409b0dc566e9c0506d7083fc2a533b..887ef5d1796f997e5356a11ea02128f249ef1873 100644 (file)
 #include "networkd-lldp-tx.h"
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
+#include "networkd-neighbor-proxy.h"
 #include "networkd-network.h"
 #include "networkd-sysctl.h"
 #include "path-util.h"
-#include "set.h"
 #include "socket-util.h"
 #include "string-table.h"
 #include "string-util.h"
@@ -270,16 +270,25 @@ static int link_update_ipv6_sysctl(Link *link) {
 }
 
 static int link_set_proxy_arp(Link *link) {
+        bool v;
+
         assert(link);
         assert(link->manager);
 
         if (!link_is_configured_for_family(link, AF_INET))
                 return 0;
 
-        if (link->network->proxy_arp < 0)
+        if (link->network->proxy_arp >= 0)
+                v = link->network->proxy_arp;
+        else if (network_has_neighbor_proxy_address(link->network, AF_INET))
+                /* If IPv4ProxyARP= is not explicitly set, but per-address IPv4ProxyARPAddress=
+                 * entries are configured, implicitly enable the proxy_arp sysctl. This matches
+                 * the behavior of IPv6ProxyNDPAddress= which implies IPv6ProxyNDP=yes. */
+                v = true;
+        else
                 return 0;
 
-        return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0, manager_get_sysctl_shadow(link->manager));
+        return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", v, manager_get_sysctl_shadow(link->manager));
 }
 
 static int link_set_proxy_arp_pvlan(Link *link) {
@@ -513,7 +522,7 @@ static int link_set_ipv6_proxy_ndp(Link *link) {
         if (link->network->ipv6_proxy_ndp >= 0)
                 v = link->network->ipv6_proxy_ndp;
         else
-                v = !set_isempty(link->network->ipv6_proxy_ndp_addresses);
+                v = network_has_neighbor_proxy_address(link->network, AF_INET6);
 
         return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v, manager_get_sysctl_shadow(link->manager));
 }
index 01b45a2b5db28d01775545a6208b371d246c7045..68d0211c8c58595d83541bbadef03b6062a041e1 100644 (file)
@@ -7,5 +7,15 @@ IPv6PrivacyExtensions=true
 IPv6DuplicateAddressDetection=3
 IPv6HopLimit=5
 IPv4ProxyARP=true
+IPv4ProxyARPAddress=192.0.2.1
+IPv4ProxyARPAddress=192.0.2.2
+IPv4ProxyARPAddress=0.0.0.0
+IPv4ProxyARPAddress=224.0.0.1
+IPv4ProxyARPAddress=255.255.255.255
 IPv4ProxyARPPrivateVLAN=true
 IPv6ProxyNDP=true
+IPv6ProxyNDPAddress=2001:db8::1
+IPv6ProxyNDPAddress=2001:db8::2
+IPv6ProxyNDPAddress=::
+IPv6ProxyNDPAddress=ff02::1
+IPv6ProxyNDPAddress=fe80::1
index 0d6fadcc967edb94ee97179714a4aa696c79ecdf..a3b3aa88c39bd15e2a946a40d6c3dce91fbbc264 100644 (file)
@@ -478,6 +478,7 @@ IPForward=
 IPMasquerade=
 IPv4LLRoute=
 IPv4ProxyARP=
+IPv4ProxyARPAddress=
 IPv4ProxyARPPrivateVLAN=
 IPv6AcceptRA=
 IPv6DuplicateAddressDetection=
diff --git a/test/test-network/conf/25-ipv4-proxy-arp-disabled.network b/test/test-network/conf/25-ipv4-proxy-arp-disabled.network
new file mode 100644 (file)
index 0000000..3f0f615
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=dummy98
+
+[Network]
+IPv4ProxyARP=no
+IPv4ProxyARPAddress=192.0.2.1
+IPv4ProxyARPAddress=192.0.2.2
+IPv6AcceptRA=no
+Address=10.0.0.1/24
diff --git a/test/test-network/conf/25-ipv4-proxy-arp.network b/test/test-network/conf/25-ipv4-proxy-arp.network
new file mode 100644 (file)
index 0000000..9327559
--- /dev/null
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=dummy98
+
+[Network]
+IPv4ProxyARPAddress=192.0.2.1
+IPv4ProxyARPAddress=192.0.2.2
+IPv4ProxyARPAddress=192.0.2.3
+IPv4ProxyARPAddress=192.0.2.4
+IPv4ProxyARPAddress=192.0.2.5
+IPv6AcceptRA=no
+Address=10.0.0.1/24
index c07fb7a478cd2ca5f13a49c5214319cd3d194dec..ce0d5a8a150b01986ba919e9cf4f6867ce5795bf 100755 (executable)
@@ -5344,6 +5344,37 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         for i in range(1, 5):
             self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
 
+    def test_ipv4_proxy_arp(self):
+        copy_network_unit('25-ipv4-proxy-arp.network', '12-dummy.netdev')
+        start_networkd()
+
+        self.wait_online('dummy98:routable')
+
+        output = check_output('ip -4 neighbor show proxy dev dummy98')
+        print(output)
+        for i in range(1, 6):
+            self.assertRegex(output, f'192.0.2.{i} *proxy')
+
+        # IPv4ProxyARPAddress= implies IPv4ProxyARP=yes, mirroring IPv6ProxyNDPAddress=.
+        self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp', '1')
+
+        # Explicit IPv4ProxyARP=no must suppress all IPv4ProxyARPAddress= entries and
+        # must not force proxy_arp=1. The module is add-only (no reconcile/remove pass),
+        # so phase 2 starts from a clean interface: stop networkd, delete the dummy to
+        # flush the kernel neighbor-proxy table, swap the .network file, and restart.
+        stop_networkd()
+        remove_link('dummy98')
+        remove_network_unit('25-ipv4-proxy-arp.network')
+        copy_network_unit('25-ipv4-proxy-arp-disabled.network', '12-dummy.netdev')
+        start_networkd()
+        self.wait_online('dummy98:routable')
+
+        output = check_output('ip -4 neighbor show proxy dev dummy98')
+        print(output)
+        for i in range(1, 6):
+            self.assertNotIn(f'192.0.2.{i}', output)
+        self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp', '0')
+
     def test_ipv6_neigh_retrans_time(self):
         link = 'test25'
         copy_network_unit('25-dummy.netdev', '25-dummy.network')