From d56d07fc1ed3ce5f5353b9d73772dcafe7d03bad Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Oct 2024 16:40:19 +0900 Subject: [PATCH 1/4] sysctl-util: introduce sysctl_read_ip_property_int() and _uint32() Currently not used, but will be used later. --- src/basic/sysctl-util.c | 27 +++++++++++++++++++++++++++ src/basic/sysctl-util.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/src/basic/sysctl-util.c b/src/basic/sysctl-util.c index 8a73dc6b3e..2feb4917d7 100644 --- a/src/basic/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -10,6 +10,7 @@ #include "fileio.h" #include "log.h" #include "macro.h" +#include "parse-util.h" #include "path-util.h" #include "socket-util.h" #include "string-util.h" @@ -193,3 +194,29 @@ int sysctl_read_ip_property(int af, const char *ifname, const char *property, ch return sysctl_read(p, ret); } + +int sysctl_read_ip_property_int(int af, const char *ifname, const char *property, int *ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(ret); + + r = sysctl_read_ip_property(af, ifname, property, &s); + if (r < 0) + return r; + + return safe_atoi(s, ret); +} + +int sysctl_read_ip_property_uint32(int af, const char *ifname, const char *property, uint32_t *ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(ret); + + r = sysctl_read_ip_property(af, ifname, property, &s); + if (r < 0) + return r; + + return safe_atou32(s, ret); +} diff --git a/src/basic/sysctl-util.h b/src/basic/sysctl-util.h index 041292f693..bbfc4ce1ea 100644 --- a/src/basic/sysctl-util.h +++ b/src/basic/sysctl-util.h @@ -17,6 +17,8 @@ static inline int sysctl_write(const char *property, const char *value) { } int sysctl_read_ip_property(int af, const char *ifname, const char *property, char **ret); +int sysctl_read_ip_property_int(int af, const char *ifname, const char *property, int *ret); +int sysctl_read_ip_property_uint32(int af, const char *ifname, const char *property, uint32_t *ret); int sysctl_write_ip_property(int af, const char *ifname, const char *property, const char *value, Hashmap **shadow); static inline int sysctl_write_ip_property_boolean(int af, const char *ifname, const char *property, bool value, Hashmap **shadow) { return sysctl_write_ip_property(af, ifname, property, one_zero(value), shadow); From ea684ccb6df5a7ffcfa118b9862e60c8567be9a0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 15 Oct 2024 06:52:19 +0900 Subject: [PATCH 2/4] network/route: use sysctl_read_ip_property_int() for reading route/max_size --- src/network/networkd-route-util.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index 9bb1e0f707..dc9663e24b 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -16,23 +16,26 @@ #include "strv.h" #include "sysctl-util.h" -#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U +#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096 unsigned routes_max(void) { static thread_local unsigned cached = 0; - _cleanup_free_ char *s4 = NULL, *s6 = NULL; - unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; + int val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; if (cached > 0) return cached; - if (sysctl_read_ip_property(AF_INET, NULL, "route/max_size", &s4) >= 0) - if (safe_atou(s4, &val4) >= 0 && val4 == 2147483647U) + /* The kernel internally stores these maximum size in int. */ + + if (sysctl_read_ip_property_int(AF_INET, /* ifname = */ NULL, "route/max_size", &val4) >= 0) + if (val4 == INT_MAX) /* This is the default "no limit" value in the kernel */ val4 = ROUTES_DEFAULT_MAX_PER_FAMILY; - if (sysctl_read_ip_property(AF_INET6, NULL, "route/max_size", &s6) >= 0) - (void) safe_atou(s6, &val6); + if (sysctl_read_ip_property_int(AF_INET6, /* ifname = */ NULL, "route/max_size", &val6) >= 0) + if (val6 == INT_MAX) + /* This is the default "no limit" value in the kernel */ + val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) + MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6); From 7edfbf4130ccdb517a127a3ad3748dfc82739b9e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Oct 2024 16:45:05 +0900 Subject: [PATCH 3/4] network/sysctl: make link_set_ipv6_mtu() log failures No functional change, just refactoring. --- src/network/networkd-link.c | 13 +++---------- src/network/networkd-ndisc.c | 4 +--- src/network/networkd-sysctl.c | 11 +++++++---- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 234e08680b..5469bee551 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1869,8 +1869,6 @@ static int link_carrier_lost(Link *link) { } static int link_admin_state_up(Link *link) { - int r; - assert(link); /* This is called every time an interface admin state changes to up; @@ -1886,9 +1884,7 @@ static int link_admin_state_up(Link *link) { /* We set the ipv6 mtu after the device mtu, but the kernel resets * ipv6 mtu on NETDEV_UP, so we need to reset it. */ - r = link_set_ipv6_mtu(link, LOG_INFO); - if (r < 0) - log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); + (void) link_set_ipv6_mtu(link, LOG_INFO); return 0; } @@ -2439,12 +2435,9 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { link->mtu = mtu; - if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) /* The kernel resets IPv6 MTU after changing device MTU. So, we need to re-set IPv6 MTU again. */ - r = link_set_ipv6_mtu(link, LOG_INFO); - if (r < 0) - log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); - } + (void) link_set_ipv6_mtu(link, LOG_INFO); if (link->dhcp_client) { r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 529311f4a4..fc06e5c38b 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1083,9 +1083,7 @@ static int ndisc_router_process_mtu(Link *link, sd_ndisc_router *rt) { link->ndisc_mtu = mtu; - r = link_set_ipv6_mtu(link, LOG_DEBUG); - if (r < 0) - log_link_warning_errno(link, r, "Failed to apply IPv6 MTU (%"PRIu32"), ignoring: %m", mtu); + (void) link_set_ipv6_mtu(link, LOG_DEBUG); return 0; } diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 7330c2cc76..6caa7201fd 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -503,6 +503,7 @@ static int link_set_ipv6_proxy_ndp(Link *link) { int link_set_ipv6_mtu(Link *link, int log_level) { uint32_t mtu = 0; + int r; assert(link); assert(link->manager); @@ -526,7 +527,11 @@ int link_set_ipv6_mtu(Link *link, int log_level) { mtu = link->mtu; } - return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu, manager_get_sysctl_shadow(link->manager)); + r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu, manager_get_sysctl_shadow(link->manager)); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to set IPv6 MTU to %"PRIu32": %m", mtu); + + return 0; } static int link_set_ipv4_accept_local(Link *link) { @@ -616,9 +621,7 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); - r = link_set_ipv6_mtu(link, LOG_INFO); - if (r < 0) - log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); + (void) link_set_ipv6_mtu(link, LOG_INFO); r = link_set_ipv6ll_stable_secret(link); if (r < 0) From 39283a1f2710da43d57ef400a5320b8f9b03e369 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Oct 2024 16:43:15 +0900 Subject: [PATCH 4/4] network: wait for IPv6 MTU being synced to link MTU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel resets the IPv6 MTU of an interface when its link MTU is changed. But it seems the operation is asynchronous, and even when we detect that the link MTU is changed, the IPv6 MTU may not be reset yet. ==== [ 2257.067613] systemd-networkd[447122]: veth99: MTU is changed: 1500 →1600 (min: 68, max: 65535) [ 2257.067641] systemd-networkd[447122]: Setting '/proc/sys/net/ipv6/conf/veth99/mtu' to '1410' [ 2257.067711] systemd-networkd[447122]: No change in value '1410', suppressing write ==== As you can see, even if the link MTU is changed to 1600, the IPv6 MTU is unchanged (in this case, still 1410). --- src/network/networkd-link.c | 3 +- src/network/networkd-link.h | 3 ++ src/network/networkd-sysctl.c | 86 +++++++++++++++++++++++++++++++++++ src/network/networkd-sysctl.h | 1 + 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 5469bee551..59240bbc36 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -288,6 +288,7 @@ static Link *link_free(Link *link) { network_unref(link->network); sd_event_source_disable_unref(link->carrier_lost_timer); + sd_event_source_disable_unref(link->ipv6_mtu_wait_synced_event_source); return mfree(link); } @@ -2437,7 +2438,7 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) /* The kernel resets IPv6 MTU after changing device MTU. So, we need to re-set IPv6 MTU again. */ - (void) link_set_ipv6_mtu(link, LOG_INFO); + (void) link_set_ipv6_mtu_async(link); if (link->dhcp_client) { r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 27bc299bc5..e86839af8e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -74,6 +74,9 @@ typedef struct Link { sd_device *dev; char *driver; + sd_event_source *ipv6_mtu_wait_synced_event_source; + unsigned ipv6_mtu_wait_trial_count; + /* bridge vlan */ uint16_t bridge_vlan_pvid; bool bridge_vlan_pvid_is_untagged; diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 6caa7201fd..c0762b9b70 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -8,6 +8,7 @@ #include "af-list.h" #include "cgroup-util.h" +#include "event-util.h" #include "fd-util.h" #include "format-util.h" #include "missing_network.h" @@ -511,6 +512,14 @@ int link_set_ipv6_mtu(Link *link, int log_level) { if (!link_is_configured_for_family(link, AF_INET6)) return 0; + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + if (sd_event_source_get_enabled(link->ipv6_mtu_wait_synced_event_source, /* ret = */ NULL) > 0) { + log_link_debug(link, "Waiting for IPv6 MTU is synced to link MTU, delaying to set IPv6 MTU."); + return 0; + } + assert(link->network); if (link->network->ndisc_use_mtu) @@ -534,6 +543,83 @@ int link_set_ipv6_mtu(Link *link, int log_level) { return 0; } +static int ipv6_mtu_wait_synced_handler(sd_event_source *s, uint64_t usec, void *userdata); + +static int link_set_ipv6_mtu_async_impl(Link *link) { + uint32_t current_mtu; + int r; + + assert(link); + + /* When the link MTU is updated, it seems that the kernel IPv6 MTU of the interface is asynchronously + * reset to the link MTU. Hence, we need to check if it is already reset, and wait for a while if not. */ + + if (++link->ipv6_mtu_wait_trial_count >= 10) { + log_link_debug(link, "Timed out waiting for IPv6 MTU being synced to link MTU, proceeding anyway."); + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + return r; + + return 1; /* done */ + } + + /* Check if IPv6 MTU is synced. */ + r = sysctl_read_ip_property_uint32(AF_INET6, link->ifname, "mtu", ¤t_mtu); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to read IPv6 MTU: %m"); + + if (current_mtu == link->mtu) { + /* Already synced. Update IPv6 MTU now. */ + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + return r; + + return 1; /* done */ + } + + /* If not, set up a timer event source. */ + r = event_reset_time_relative( + link->manager->event, &link->ipv6_mtu_wait_synced_event_source, + CLOCK_BOOTTIME, 100 * USEC_PER_MSEC, 0, + ipv6_mtu_wait_synced_handler, link, + /* priority = */ 0, "ipv6-mtu-wait-synced", /* force = */ true); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure timer event source for waiting for IPv6 MTU being synced: %m"); + + /* Check again. */ + r = sysctl_read_ip_property_uint32(AF_INET6, link->ifname, "mtu", ¤t_mtu); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to read IPv6 MTU: %m"); + + if (current_mtu == link->mtu) { + /* Synced while setting up the timer event source. Disable it and update IPv6 MTU now. */ + r = sd_event_source_set_enabled(link->ipv6_mtu_wait_synced_event_source, SD_EVENT_OFF); + if (r < 0) + log_link_debug_errno(link, r, "Failed to disable timer event source for IPv6 MTU, ignoring: %m"); + + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + return r; + + return 1; /* done */ + } + + log_link_debug(link, "IPv6 MTU is not synced to the link MTU after it is changed. Waiting for a while."); + return 0; /* waiting */ +} + +static int ipv6_mtu_wait_synced_handler(sd_event_source *s, uint64_t usec, void *userdata) { + (void) link_set_ipv6_mtu_async_impl(ASSERT_PTR(userdata)); + return 0; +} + +int link_set_ipv6_mtu_async(Link *link) { + assert(link); + + link->ipv6_mtu_wait_trial_count = 0; + return link_set_ipv6_mtu_async_impl(link); +} + static int link_set_ipv4_accept_local(Link *link) { assert(link); assert(link->manager); diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h index 446b835555..1c19fbd2d6 100644 --- a/src/network/networkd-sysctl.h +++ b/src/network/networkd-sysctl.h @@ -42,6 +42,7 @@ void manager_set_sysctl(Manager *manager); int link_get_ip_forwarding(Link *link, int family); int link_set_sysctl(Link *link); int link_set_ipv6_mtu(Link *link, int log_level); +int link_set_ipv6_mtu_async(Link *link); const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_;