</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>IPv6RetransmissionTimeSec=</varname></term>
+ <listitem>
+ <para>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.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>IPv4ReversePathFilter=</varname></term>
<listitem>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>UseRetransmissionTime=</varname></term>
+ <listitem>
+ <para>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.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>UseICMP6RateLimit=</varname></term>
<listitem>
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;
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)]; \
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))
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);
uint64_t flags;
unsigned preference;
uint64_t lifetime_usec;
+ usec_t retransmission_time_usec;
uint8_t hop_limit;
uint32_t mtu;
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;
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
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;
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;
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)
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
.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,
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;
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;
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;
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");
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 */
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[NetDev]
+Name=test25
+Kind=dummy
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=0
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=3
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=4
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=infinity
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=-2
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=test25
+
+[Network]
+IPv6AcceptRA=no
+IPv6RetransmissionTimeSec=999999999999999999
# 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 <class>.<test_function>. 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
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')
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:
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',