Originally reported in the community forum [0]. The sysfs-based methods do not handle altnames at all. Instead, get/set interface MTUs through netlink directly, which also has transparent altname support, and is a more robust way in general.
At the same time, this simplifies some things as it means that the MTU will always be present in the Netlink cache. [0] https://forum.proxmox.com/threads/wrong-mtu-after-upgrade-to-9.169887/ Signed-off-by: Christoph Heiss <c.he...@proxmox.com> --- ...et-interface-mtu-through-netlink-ins.patch | 318 ++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 319 insertions(+) create mode 100644 debian/patches/pve/0012-addons-nlcache-set-interface-mtu-through-netlink-ins.patch diff --git a/debian/patches/pve/0012-addons-nlcache-set-interface-mtu-through-netlink-ins.patch b/debian/patches/pve/0012-addons-nlcache-set-interface-mtu-through-netlink-ins.patch new file mode 100644 index 0000000..e40e666 --- /dev/null +++ b/debian/patches/pve/0012-addons-nlcache-set-interface-mtu-through-netlink-ins.patch @@ -0,0 +1,318 @@ +From f7d6aa4769a86980ce0e93cc55f2ef0c6b4682a9 Mon Sep 17 00:00:00 2001 +From: Christoph Heiss <c.he...@proxmox.com> +Date: Wed, 20 Aug 2025 14:02:22 +0200 +Subject: [PATCH] addons, nlcache: set interface mtu through netlink instead of + sysfs + +The sysfs-based methods do not handle altnames at all. Instead, get/set +interface MTUs through netlink directly, which also has transparent +altname support, and is a more robust way in general. + +At the same time, this simplifies some things as it means that the MTU +will always be present in the netlink cache. + +Along the way also clean up some weird mtu_{int,str}, where the MTU is +passed around in the `address` as both string and integer. + +Signed-off-by: Christoph Heiss <c.he...@proxmox.com> +--- + ifupdown2/addons/address.py | 65 +++++++++++++++++++----------- + ifupdown2/addons/addressvirtual.py | 2 +- + ifupdown2/addons/bridge.py | 2 +- + ifupdown2/lib/iproute2.py | 5 +-- + ifupdown2/lib/nlcache.py | 33 +++++++++++++++ + ifupdown2/lib/sysfs.py | 16 -------- + 6 files changed, 78 insertions(+), 45 deletions(-) + +diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py +index 3196a5e..25270c3 100644 +--- a/ifupdown2/addons/address.py ++++ b/ifupdown2/addons/address.py +@@ -430,7 +430,7 @@ class address(AddonWithIpBlackList, moduleBase): + except ValueError as e: + self.logger.warning("%s: invalid mtu %s: %s" % (ifaceobj.name, mtu_str, str(e))) + return False +- return self._check_mtu_config(ifaceobj, mtu_str, mtu_int, ifaceobj_getfunc, syntaxcheck=True) ++ return self._check_mtu_config(ifaceobj, mtu_int, ifaceobj_getfunc, syntaxcheck=True) + return True + + def syntax_check_addr_allowed_on(self, ifaceobj, syntax_check=False): +@@ -792,7 +792,14 @@ class address(AddonWithIpBlackList, moduleBase): + return ipv + return prev_gateways + +- def _check_mtu_config(self, ifaceobj, mtu_str, mtu_int, ifaceobj_getfunc, syntaxcheck=False): ++ def _check_mtu_config(self, ifaceobj, mtu, ifaceobj_getfunc, syntaxcheck=False): ++ """ ++ :param ifaceobj: ++ :param mtu: integer ++ :param ifaceobj_getfunc: ++ :param syntaxcheck: boolean ++ """ ++ + retval = True + if (ifaceobj.link_kind & ifaceLinkKind.BRIDGE): + if syntaxcheck: +@@ -806,10 +813,10 @@ class address(AddonWithIpBlackList, moduleBase): + masterobj = ifaceobj_getfunc(ifaceobj.upperifaces[0]) + if masterobj: + master_mtu = masterobj[0].get_attr_value_first('mtu') +- if master_mtu and master_mtu != mtu_str: ++ if master_mtu and master_mtu != str(mtu): + log_msg = ("%s: bond slave mtu %s is different from bond master %s mtu %s. " + "There is no need to configure mtu on a bond slave." % +- (ifaceobj.name, mtu_str, masterobj[0].name, master_mtu)) ++ (ifaceobj.name, mtu, masterobj[0].name, master_mtu)) + if syntaxcheck: + self.logger.warning(log_msg) + retval = False +@@ -823,24 +830,30 @@ class address(AddonWithIpBlackList, moduleBase): + lowerdev_mtu = int(lowerobj[0].get_attr_value_first('mtu') or 0) + else: + lowerdev_mtu = self.cache.get_link_mtu(lowerobj[0].name) # return type: int +- if lowerdev_mtu and mtu_int > lowerdev_mtu: ++ if lowerdev_mtu and mtu > lowerdev_mtu: + self.logger.warning('%s: vlan dev mtu %s is greater than lower realdev %s mtu %s' +- %(ifaceobj.name, mtu_str, lowerobj[0].name, lowerdev_mtu)) ++ %(ifaceobj.name, mtu, lowerobj[0].name, lowerdev_mtu)) + retval = False + elif (not lowerobj[0].link_kind and + not (lowerobj[0].link_privflags & ifaceLinkPrivFlags.LOOPBACK) and +- not lowerdev_mtu and self.default_mtu and (mtu_int > self.default_mtu_int)): ++ not lowerdev_mtu and self.default_mtu and (mtu > self.default_mtu_int)): + # only check default mtu on lower device which is a physical interface + self.logger.warning('%s: vlan dev mtu %s is greater than lower realdev %s mtu %s' +- %(ifaceobj.name, mtu_str, lowerobj[0].name, self.default_mtu)) ++ %(ifaceobj.name, mtu, lowerobj[0].name, self.default_mtu)) + retval = False +- if self.max_mtu and mtu_int > self.max_mtu: ++ if self.max_mtu and mtu > self.max_mtu: + self.logger.warning('%s: specified mtu %s is greater than max mtu %s' +- %(ifaceobj.name, mtu_str, self.max_mtu)) ++ %(ifaceobj.name, mtu, self.max_mtu)) + retval = False + return retval + +- def _propagate_mtu_to_upper_devs(self, ifaceobj, mtu_str, mtu_int, ifaceobj_getfunc): ++ def _propagate_mtu_to_upper_devs(self, ifaceobj, mtu, ifaceobj_getfunc): ++ """ ++ :param ifaceobj: ++ :param mtu: integer ++ :param ifaceobj_getfunc: ++ """ ++ + if (not ifaceobj.upperifaces or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BOND_SLAVE) or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE) or +@@ -855,15 +868,21 @@ class address(AddonWithIpBlackList, moduleBase): + umtu = upperobjs[0].get_attr_value_first('mtu') + if not umtu: + running_mtu = self.cache.get_link_mtu(upperobjs[0].name) +- if not running_mtu or running_mtu != mtu_int: +- self.sysfs.link_set_mtu(u, mtu_str=mtu_str, mtu_int=mtu_int) ++ if not running_mtu or running_mtu != mtu: ++ self.netlink.link_set_mtu(u, mtu) + +- def _process_mtu_config_mtu_valid(self, ifaceobj, ifaceobj_getfunc, mtu_str, mtu_int): +- if not self._check_mtu_config(ifaceobj, mtu_str, mtu_int, ifaceobj_getfunc): ++ def _process_mtu_config_mtu_valid(self, ifaceobj, ifaceobj_getfunc, mtu): ++ """ ++ :param ifaceobj: ++ :param ifaceobj_getfunc: ++ :param mtu: integer ++ """ ++ ++ if not self._check_mtu_config(ifaceobj, mtu, ifaceobj_getfunc): + return + +- if mtu_int != self.cache.get_link_mtu(ifaceobj.name): +- self.sysfs.link_set_mtu(ifaceobj.name, mtu_str=mtu_str, mtu_int=mtu_int) ++ if mtu != self.cache.get_link_mtu(ifaceobj.name): ++ self.netlink.link_set_mtu(ifaceobj.name, mtu) + + if (not ifupdownflags.flags.ALL and + not ifaceobj.link_kind and +@@ -871,7 +890,7 @@ class address(AddonWithIpBlackList, moduleBase): + # This is additional cost to us, so do it only when + # ifupdown2 is called on a particular interface and + # it is a physical interface +- self._propagate_mtu_to_upper_devs(ifaceobj, mtu_str, mtu_int, ifaceobj_getfunc) ++ self._propagate_mtu_to_upper_devs(ifaceobj, mtu, ifaceobj_getfunc) + return + + def _process_mtu_config_mtu_none(self, ifaceobj): +@@ -889,7 +908,7 @@ class address(AddonWithIpBlackList, moduleBase): + or ifaceobj.link_kind & ifaceLinkKind.BRIDGE \ + or ifaceobj.link_kind & ifaceLinkKind.OTHER: + if cached_link_mtu != self.default_mtu_int: +- self.sysfs.link_set_mtu(ifaceobj.name, mtu_str=self.default_mtu, mtu_int=self.default_mtu_int) ++ self.netlink.link_set_mtu(ifaceobj.name, self.default_mtu_int) + return + + # set vlan interface mtu to lower device mtu +@@ -902,7 +921,7 @@ class address(AddonWithIpBlackList, moduleBase): + lower_iface_mtu_int = self.cache.get_link_mtu(lower_iface) + + if lower_iface_mtu_int != cached_link_mtu: +- self.sysfs.link_set_mtu(ifaceobj.name, mtu_str=str(lower_iface_mtu_int), mtu_int=lower_iface_mtu_int) ++ self.netlink.link_set_mtu(ifaceobj.name, lower_iface_mtu_int) + + elif ( + ifaceobj.name != 'lo' +@@ -919,7 +938,7 @@ class address(AddonWithIpBlackList, moduleBase): + # config by the kernel in play, we try to be cautious here + # on which devices we want to reset mtu to default. + # essentially only physical interfaces which are not bond slaves +- self.sysfs.link_set_mtu(ifaceobj.name, mtu_str=self.default_mtu, mtu_int=self.default_mtu_int) ++ self.netlink.link_set_mtu(ifaceobj.name, self.default_mtu_int) + + def _set_bridge_forwarding(self, ifaceobj): + """ set ip forwarding to 0 if bridge interface does not have a +@@ -1105,7 +1124,7 @@ class address(AddonWithIpBlackList, moduleBase): + self.logger.warning("%s: invalid MTU value: %s" % (ifaceobj.name, str(e))) + return + +- self._process_mtu_config_mtu_valid(ifaceobj, ifaceobj_getfunc, mtu_str, mtu_int) ++ self._process_mtu_config_mtu_valid(ifaceobj, ifaceobj_getfunc, mtu_int) + else: + self._process_mtu_config_mtu_none(ifaceobj) + +@@ -1373,7 +1392,7 @@ class address(AddonWithIpBlackList, moduleBase): + # ifupdown2. If this MTU is different from our default mtu, + # if so we need to reset it back to default. + if not ifaceobj.link_kind and self.default_mtu and ifaceobj.get_attr_value_first('mtu') and self.cache.get_link_mtu(ifaceobj.name) != self.default_mtu_int: +- self.sysfs.link_set_mtu(ifaceobj.name, mtu_str=self.default_mtu, mtu_int=self.default_mtu_int) ++ self.netlink.link_set_mtu(ifaceobj.name, self.default_mtu_int) + + # + # alias +diff --git a/ifupdown2/addons/addressvirtual.py b/ifupdown2/addons/addressvirtual.py +index 587f39f..80cd820 100644 +--- a/ifupdown2/addons/addressvirtual.py ++++ b/ifupdown2/addons/addressvirtual.py +@@ -540,7 +540,7 @@ class addressvirtual(AddonWithIpBlackList, moduleBase): + update_mtu = False + + try: +- self.sysfs.link_set_mtu(macvlan_ifname, mtu_str=lower_iface_mtu_str, mtu_int=lower_iface_mtu) ++ self.netlink.link_set_mtu(macvlan_ifname, lower_iface_mtu) + except Exception as e: + self.logger.info('%s: failed to set mtu %s: %s' % (macvlan_ifname, lower_iface_mtu, e)) + +diff --git a/ifupdown2/addons/bridge.py b/ifupdown2/addons/bridge.py +index 1da8de7..dee6f7b 100644 +--- a/ifupdown2/addons/bridge.py ++++ b/ifupdown2/addons/bridge.py +@@ -2765,7 +2765,7 @@ class bridge(Bridge, moduleBase): + + bridge_mtu = self.get_bridge_mtu(ifaceobj) + if bridge_mtu: +- self.sysfs.link_set_mtu(ifname, bridge_mtu, int(bridge_mtu)) ++ self.netlink.link_set_mtu(ifname, int(bridge_mtu)) + else: + link_just_created = False + self.logger.info('%s: bridge already exists' % ifname) +diff --git a/ifupdown2/lib/iproute2.py b/ifupdown2/lib/iproute2.py +index 7e7366c..3760963 100644 +--- a/ifupdown2/lib/iproute2.py ++++ b/ifupdown2/lib/iproute2.py +@@ -518,10 +518,7 @@ class IPRoute2(Cache, Requirements): + self.logger.info("%s: cannot set addrgen: ipv6 is disabled on this device" % ifname) + return False + +- if link_created: +- link_mtu = self.sysfs.link_get_mtu(ifname) +- else: +- link_mtu = self.cache.get_link_mtu(ifname) ++ link_mtu = self.cache.get_link_mtu(ifname) + + if link_mtu < 1280: + self.logger.info("%s: ipv6 addrgen is disabled on device with MTU " +diff --git a/ifupdown2/lib/nlcache.py b/ifupdown2/lib/nlcache.py +index e5a42ef..a36e610 100644 +--- a/ifupdown2/lib/nlcache.py ++++ b/ifupdown2/lib/nlcache.py +@@ -60,6 +60,7 @@ try: + NLM_F_REQUEST, \ + NLM_F_CREATE, \ + NLM_F_ACK, \ ++ NLM_F_REPLACE, \ + RT_SCOPES, \ + INFINITY_LIFE_TIME + +@@ -91,6 +92,7 @@ except (ImportError, ModuleNotFoundError): + NLM_F_REQUEST, \ + NLM_F_CREATE, \ + NLM_F_ACK, \ ++ NLM_F_REPLACE, \ + RT_SCOPES, \ + INFINITY_LIFE_TIME + +@@ -3302,6 +3304,37 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject + def link_translate_altnames(self, ifnames): + return self.cache.link_translate_altnames(ifnames) + ++ ### ++ ++ """ ++ Sets the MTU of the given link, updating the cache on success. ++ ++ :param ifname: str - Name of the interface to update ++ :param mtu: int - New MTU to set for the interface. ++ :return: True if the operation was successful ++ """ ++ def link_set_mtu(self, ifname, mtu): ++ if self.cache.get_link_mtu(ifname) == mtu: ++ # no need to update ++ return ++ ++ self.logger.info(f'{ifname}: netlink: ip link set dev {ifname} mtu {mtu}') ++ ++ debug = RTM_SETLINK in self.debug ++ try: ++ link = Link(RTM_SETLINK, debug, use_color=self.use_color) ++ link.flags = NLM_F_REPLACE | NLM_F_REQUEST | NLM_F_ACK ++ link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) ++ link.add_attribute(Link.IFLA_IFNAME, ifname) ++ link.add_attribute(Link.IFLA_MTU, mtu) ++ link.build_message(next(self.sequence), self.pid) ++ result = self.tx_nlpacket_get_response_with_error(link) ++ self.cache.override_link_mtu(ifname, mtu) ++ ++ return result ++ except Exception as e: ++ raise Exception(f'{ifname}: netlink: failed to set mtu to {mtu}: {str(e)}') ++ + ############################################################################ + # ADDRESS + ############################################################################ +diff --git a/ifupdown2/lib/sysfs.py b/ifupdown2/lib/sysfs.py +index dd9f361..3ac678d 100644 +--- a/ifupdown2/lib/sysfs.py ++++ b/ifupdown2/lib/sysfs.py +@@ -106,22 +106,6 @@ class __Sysfs(IO, Requirements): + """ + return self.read_file_oneline("/sys/class/net/%s/address" % ifname) + +- # +- # MTU +- # +- +- def link_get_mtu(self, ifname): +- return int(self.read_file_oneline("/sys/class/net/%s/mtu" % ifname) or 0) +- +- def link_set_mtu(self, ifname, mtu_str, mtu_int): +- if self.cache.get_link_mtu(ifname) != mtu_int: +- if self.write_to_file('/sys/class/net/%s/mtu' % ifname, mtu_str): +- self.cache.override_link_mtu(ifname, mtu_int) +- +- def link_set_mtu_dry_run(self, ifname, mtu_str, mtu_int): +- # we can remove the cache check in DRYRUN mode +- self.write_to_file('/sys/class/net/%s/mtu' % ifname, mtu_str) +- + # + # ALIAS + # +-- +2.50.1 + diff --git a/debian/patches/series b/debian/patches/series index 6955322..e8aa870 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -9,5 +9,6 @@ pve/0008-lacp-bond-remove-bond-min-links-0-warning.patch pve/0009-gvgeb-fix-python-interpreter-shebang.patch pve/0010-main-ignore-dpkg-files-when-running-hook-scripts.patch pve/0011-nlmanager-addons-add-transparent-support-interface-a.patch +pve/0012-addons-nlcache-set-interface-mtu-through-netlink-ins.patch upstream/0001-add-ipv6-slaac-support-inet6-auto-accept_ra.patch upstream/0001-use-raw-strings-for-regex-to-fix-backslash-interpret.patch -- 2.50.1 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel