From 39283a1f2710da43d57ef400a5320b8f9b03e369 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Oct 2024 16:43:15 +0900 Subject: [PATCH] network: wait for IPv6 MTU being synced to link MTU MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 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 5469bee5516..59240bbc369 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 27bc299bc5f..e86839af8eb 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 6caa7201fdd..c0762b9b705 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 446b8355554..1c19fbd2d6f 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_; -- 2.47.3