diff --git a/man/systemd.network.xml b/man/systemd.network.xml index ef4a0fd430..c1b0eec1a7 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -882,6 +882,18 @@ Table=1234 + + IPv6RetransmissionTimeSec= + + Configures IPv6 Retransmission Time. The time between retransmitted Neighbor + Solicitation messages. Used by address resolution and the Neighbor Unreachability + Detection algorithm. A value of zero is ignored and the kernel's current value + will be used. Defaults to unset, and the kernel's current value will be used. + + + + + IPv4ReversePathFilter= @@ -3306,6 +3318,18 @@ Token=prefixstable:2002:da8:1:: + + UseRetransmissionTime= + + Takes a boolean. When true, the retransmission time received in the Router Advertisement will be set + on the interface receiving the advertisement. It is used as the time between retransmissions of Neighbor + Solicitation messages to a neighbor when resolving the address or when probing the reachability of a neighbor. + Defaults to true. + + + + + UseICMP6RateLimit= diff --git a/src/basic/sysctl-util.c b/src/basic/sysctl-util.c index b66a6622ae..9a1933f579 100644 --- a/src/basic/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -96,6 +96,26 @@ int sysctl_write_ip_property(int af, const char *ifname, const char *property, c return sysctl_write(p, value); } +int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value) { + const char *p; + + assert(property); + assert(value); + assert(ifname); + + if (!IN_SET(af, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (ifname) { + if (!ifname_valid_full(ifname, IFNAME_VALID_SPECIAL)) + return -EINVAL; + p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/", ifname, "/", property); + } else + p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/default/", property); + + return sysctl_write(p, value); +} + int sysctl_read(const char *property, char **ret) { char *p; int r; diff --git a/src/basic/sysctl-util.h b/src/basic/sysctl-util.h index 32364196f9..7192e8c0b0 100644 --- a/src/basic/sysctl-util.h +++ b/src/basic/sysctl-util.h @@ -19,6 +19,13 @@ static inline int sysctl_write_ip_property_boolean(int af, const char *ifname, c return sysctl_write_ip_property(af, ifname, property, one_zero(value)); } +int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value); +static inline int sysctl_write_ip_neighbor_property_uint32(int af, const char *ifname, const char *property, uint32_t value) { + char buf[DECIMAL_STR_MAX(uint32_t)]; + xsprintf(buf, "%u", value); + return sysctl_write_ip_neighbor_property(af, ifname, property, buf); +} + #define DEFINE_SYSCTL_WRITE_IP_PROPERTY(name, type, format) \ static inline int sysctl_write_ip_property_##name(int af, const char *ifname, const char *property, type value) { \ char buf[DECIMAL_STR_MAX(type)]; \ diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c index 5162df799c..89681d0075 100644 --- a/src/libsystemd-network/ndisc-router.c +++ b/src/libsystemd-network/ndisc-router.c @@ -144,6 +144,7 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */ rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false); rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); + rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); rt->preference = (rt->flags >> 3) & 3; if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) @@ -275,6 +276,14 @@ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { return 0; } +int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->retransmission_time_usec; + return 0; +} + int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h index 0a55e1ac57..63d4f90ba9 100644 --- a/src/libsystemd-network/ndisc-router.h +++ b/src/libsystemd-network/ndisc-router.h @@ -24,6 +24,7 @@ struct sd_ndisc_router { uint64_t flags; unsigned preference; uint64_t lifetime_usec; + usec_t retransmission_time_usec; uint8_t hop_limit; uint32_t mtu; diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index d94cc1ceb7..d0648b95fa 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -28,7 +28,7 @@ static sd_ndisc *test_timeout_nd; static void router_dump(sd_ndisc_router *rt) { struct in6_addr addr; uint8_t hop_limit; - usec_t t, lifetime; + usec_t t, lifetime, retrans_time; uint64_t flags; uint32_t mtu; unsigned preference; @@ -65,6 +65,9 @@ static void router_dump(sd_ndisc_router *rt) { assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0); log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t)); + assert_se(sd_ndisc_router_get_retransmission_time(rt, &retrans_time) >= 0); + log_info("Retransmission Time: %s", FORMAT_TIMESPAN(retrans_time, USEC_PER_SEC)); + if (sd_ndisc_router_get_mtu(rt, &mtu) < 0) log_info("No MTU set"); else diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 9f56fd23d4..ca8bbb2d5e 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -393,6 +393,42 @@ static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) return 0; } +static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) { + usec_t retrans_time, msec; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ipv6_accept_ra_use_retransmission_time) + return 0; + + r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time); + if (r < 0) { + log_link_debug_errno(link, r, "Failed to get retransmission time from RA, ignoring: %m"); + return 0; + } + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ + if (!timestamp_is_set(retrans_time)) + return 0; + + msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC); + if (msec <= 0 || msec > UINT32_MAX) { + log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec); + return 0; + } + + /* Set the retransmission time for Neigbor Solicitations. */ + r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec); + if (r < 0) + log_link_warning_errno( + link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec); + + return 0; +} + static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { usec_t lifetime_valid_usec, lifetime_preferred_usec; _cleanup_set_free_ Set *addresses = NULL; @@ -1354,6 +1390,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + r = ndisc_router_process_retransmission_time(link, rt); + if (r < 0) + return r; + r = ndisc_router_process_options(link, rt); if (r < 0) return r; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index f0650a0c88..ce37450938 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -131,6 +131,7 @@ Network.IPv6AcceptRA, config_parse_tristate, Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit) +Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time) Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp) Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) @@ -297,6 +298,7 @@ IPv6AcceptRA.UseDNS, config_parse_bool, IPv6AcceptRA.UseDomains, config_parse_ipv6_accept_ra_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_mtu) IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_hop_limit) +IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_retransmission_time) IPv6AcceptRA.UseICMP6RateLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_icmp6_ratelimit) IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client) IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 08c7da5699..16c679b343 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -483,6 +483,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ipv6_accept_ra_use_onlink_prefix = true, .ipv6_accept_ra_use_mtu = true, .ipv6_accept_ra_use_hop_limit = true, + .ipv6_accept_ra_use_retransmission_time = true, .ipv6_accept_ra_use_icmp6_ratelimit = true, .ipv6_accept_ra_route_table = RT_TABLE_MAIN, .ipv6_accept_ra_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 1d7a7da798..3ab115c3b9 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -324,6 +324,7 @@ struct Network { int ipv4_route_localnet; int ipv6_dad_transmits; uint8_t ipv6_hop_limit; + usec_t ipv6_retransmission_time; int proxy_arp; int proxy_arp_pvlan; uint32_t ipv6_mtu; @@ -341,6 +342,7 @@ struct Network { bool ipv6_accept_ra_use_onlink_prefix; bool ipv6_accept_ra_use_mtu; bool ipv6_accept_ra_use_hop_limit; + bool ipv6_accept_ra_use_retransmission_time; bool ipv6_accept_ra_use_icmp6_ratelimit; bool ipv6_accept_ra_quickack; bool ipv6_accept_ra_use_captive_portal; diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 9d188c022e..8fa0ede5c2 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -179,6 +179,24 @@ static int link_set_ipv6_hop_limit(Link *link) { return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit); } +static int link_set_ipv6_retransmission_time(Link *link) { + usec_t retrans_time_ms; + + assert(link); + + if (!link_is_configured_for_family(link, AF_INET6)) + return 0; + + if (!timestamp_is_set(link->network->ipv6_retransmission_time)) + return 0; + + retrans_time_ms = DIV_ROUND_UP(link->network->ipv6_retransmission_time, USEC_PER_MSEC); + if (retrans_time_ms <= 0 || retrans_time_ms > UINT32_MAX) + return 0; + + return sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", retrans_time_ms); +} + static int link_set_ipv6_proxy_ndp(Link *link) { bool v; @@ -297,6 +315,10 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m"); + r = link_set_ipv6_retransmission_time(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 retransmission time for interface, ignoring: %m"); + r = link_set_ipv6_proxy_ndp(link); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index 3f93e3a406..a5ccd5f644 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -96,6 +96,7 @@ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret); int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); /* Generic option access */ diff --git a/test/test-network/conf/25-dummy.netdev b/test/test-network/conf/25-dummy.netdev new file mode 100644 index 0000000000..d7cf7b4878 --- /dev/null +++ b/test/test-network/conf/25-dummy.netdev @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Name=test25 +Kind=dummy diff --git a/test/test-network/conf/25-dummy.network b/test/test-network/conf/25-dummy.network new file mode 100644 index 0000000000..a6e93fd0a0 --- /dev/null +++ b/test/test-network/conf/25-dummy.network @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network new file mode 100644 index 0000000000..04c7c494a9 --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-0s.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=0 diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network new file mode 100644 index 0000000000..b4dbd06ead --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-3s.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=3 diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network new file mode 100644 index 0000000000..cbdf4f306a --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-4s.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=4 diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network new file mode 100644 index 0000000000..085cb30cd7 --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-infinity.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=infinity diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network new file mode 100644 index 0000000000..8a0bf8320b --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-invalid.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=-2 diff --git a/test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network b/test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network new file mode 100644 index 0000000000..0976bae8c7 --- /dev/null +++ b/test/test-network/conf/25-ipv6-neigh-retrans-time-toobig.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=test25 + +[Network] +IPv6AcceptRA=no +IPv6RetransmissionTimeSec=999999999999999999 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 3bb0166f4e..064ca53193 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -4,6 +4,16 @@ # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM, # simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. +# +# To run an individual test, specify it as a command line argument in the form +# of .. E.g. the NetworkdMTUTests class has a test +# function called test_ipv6_mtu(). To run just that test use: +# +# sudo ./systemd-networkd-tests.py NetworkdMTUTests.test_ipv6_mtu +# +# Similarly, other indivdual tests can be run, eg.: +# +# sudo ./systemd-networkd-tests.py NetworkdNetworkTests.test_ipv6_neigh_retrans_time import argparse import datetime @@ -582,9 +592,16 @@ def read_ip_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f: return f.readline().strip() +def read_ip_neigh_sysctl_attr(link, attribute, ipv): + with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f: + return f.readline().strip() + def read_ipv6_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv6') +def read_ipv6_neigh_sysctl_attr(link, attribute): + return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6') + def read_ipv4_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv4') @@ -915,6 +932,9 @@ class Utilities(): def check_ipv6_sysctl_attr(self, link, attribute, expected): self.assertEqual(read_ipv6_sysctl_attr(link, attribute), expected) + def check_ipv6_neigh_sysctl_attr(self, link, attribute, expected): + self.assertEqual(read_ipv6_neigh_sysctl_attr(link, attribute), expected) + def wait_links(self, *links, timeout=20, fail_assert=True): def links_exist(*links): for link in links: @@ -3505,6 +3525,56 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): for i in range(1, 5): self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy') + def test_ipv6_neigh_retrans_time(self): + link='test25' + copy_network_unit('25-dummy.netdev', '25-dummy.network') + start_networkd() + + self.wait_online([f'{link}:degraded']) + remove_network_unit('25-dummy.network') + + # expect retrans_time_ms updated + copy_network_unit('25-ipv6-neigh-retrans-time-3s.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000') + remove_network_unit('25-ipv6-neigh-retrans-time-3s.network') + + # expect retrans_time_ms unchanged + copy_network_unit('25-ipv6-neigh-retrans-time-0s.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000') + remove_network_unit('25-ipv6-neigh-retrans-time-0s.network') + + # expect retrans_time_ms unchanged + copy_network_unit('25-ipv6-neigh-retrans-time-toobig.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000') + remove_network_unit('25-ipv6-neigh-retrans-time-toobig.network') + + # expect retrans_time_ms unchanged + copy_network_unit('25-ipv6-neigh-retrans-time-infinity.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000') + remove_network_unit('25-ipv6-neigh-retrans-time-infinity.network') + + # expect retrans_time_ms unchanged + copy_network_unit('25-ipv6-neigh-retrans-time-invalid.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '3000') + remove_network_unit('25-ipv6-neigh-retrans-time-invalid.network') + + # expect retrans_time_ms updated + copy_network_unit('25-ipv6-neigh-retrans-time-4s.network') + networkctl_reload() + self.wait_online([f'{link}:degraded']) + self.check_ipv6_neigh_sysctl_attr(link, 'retrans_time_ms', '4000') + remove_network_unit('25-ipv6-neigh-retrans-time-4s.network') + def test_neighbor(self): copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf', '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network',