]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: wait for IPv6 MTU being synced to link MTU 34736/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 12 Oct 2024 07:43:15 +0000 (16:43 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 14 Oct 2024 21:56:06 +0000 (06:56 +0900)
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
src/network/networkd-link.h
src/network/networkd-sysctl.c
src/network/networkd-sysctl.h

index 5469bee551616cef423f3713e95dcab94b2e548e..59240bbc369728f6268635ca1d77d9596bff9992 100644 (file)
@@ -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);
index 27bc299bc5fa1d4c01effe330887d962a323dca7..e86839af8eb6fe6805bcb0a1c6f10c91a85407c2 100644 (file)
@@ -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;
index 6caa7201fddd3d46c7cb9a32456cea849380b35a..c0762b9b705998888f66c2985fd8f5866e9f0244 100644 (file)
@@ -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", &current_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", &current_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);
index 446b8355554b737b8a5b760cb6858d235d2e3851..1c19fbd2d6f2b2a1d8c7fc667a0524dda26ef027 100644 (file)
@@ -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_;