From: Aritra Basu Date: Sun, 7 Jun 2026 06:04:11 +0000 (-0400) Subject: network: add IPv4ProxyARPAddress= and consolidate proxy ARP/NDP handling X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=08bebda611e21e56f89e8387b067776679424e98;p=thirdparty%2Fsystemd.git network: add IPv4ProxyARPAddress= and consolidate proxy ARP/NDP handling 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 `). 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 --- diff --git a/NEWS b/NEWS index 8461b44623d..8da1426b801 100644 --- 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: diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 6b988e0e474..24633a0265f 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1147,6 +1147,25 @@ DuplicateAddressDetection=none + + IPv4ProxyARPAddress= + + An IPv4 address, for which ARP requests will be proxied. This option may be specified + more than once. systemd-networkd will add the + IPv4ProxyARPAddress= entries to the kernel's IPv4 neighbor proxy table. + When IPv4ProxyARP= is unset, this setting implies + IPv4ProxyARP=yes: the per-interface proxy_arp 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 IPv4ProxyARP= has been set to false, the + IPv4ProxyARPAddress= entries listed here are discarded and not + programmed into the kernel. This mirrors the behaviour of + IPv6ProxyNDPAddress=/IPv6ProxyNDP=. + + + + + IPv6ProxyNDP= diff --git a/src/network/meson.build b/src/network/meson.build index 2a4120f7470..e4b4705b33c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -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 index 89f4418990f..00000000000 --- a/src/network/networkd-ipv6-proxy-ndp.c +++ /dev/null @@ -1,185 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#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 index 7a2953a001a..00000000000 --- a/src/network/networkd-ipv6-proxy-ndp.h +++ /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); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index f505031e1e1..1551f405865 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -47,13 +47,13 @@ #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; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 4c69ce7d027..9340ecab53f 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -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 index 00000000000..de01f38f2aa --- /dev/null +++ b/src/network/networkd-neighbor-proxy.c @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#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 index 00000000000..b7e4bb0f6a5 --- /dev/null +++ b/src/network/networkd-neighbor-proxy.h @@ -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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 0b598f131d9..0a58c728a89 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 087a6ba121f..02ded9ce8be 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -18,10 +18,10 @@ #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); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 893b3022bb4..6c8d6eee6d5 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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 */ diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index ea846327b0d..d2a2b831ab3 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -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", diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index 70c8b2ca9ee..172ff230b44 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -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, diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 7d12360fa84..887ef5d1796 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -18,10 +18,10 @@ #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)); } diff --git a/test/fuzz/fuzz-network-parser/sysctl b/test/fuzz/fuzz-network-parser/sysctl index 01b45a2b5db..68d0211c8c5 100644 --- a/test/fuzz/fuzz-network-parser/sysctl +++ b/test/fuzz/fuzz-network-parser/sysctl @@ -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 diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 0d6fadcc967..a3b3aa88c39 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -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 index 00000000000..3f0f615ea5b --- /dev/null +++ b/test/test-network/conf/25-ipv4-proxy-arp-disabled.network @@ -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 index 00000000000..9327559ed58 --- /dev/null +++ b/test/test-network/conf/25-ipv4-proxy-arp.network @@ -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 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index c07fb7a478c..ce0d5a8a150 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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')