From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 09A6E1FF09B for ; Mon, 09 Nov 2026 15:58:43 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 88758215EF; Mon, 09 Nov 2026 15:58:39 +0100 (CET) From: Gabriel Goller Date: Mon, 09 Nov 2026 15:58:28 +0100 Subject: [PATCH ifupdown2] d/patches: backport upstream fix for missing bond-primary device MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261109-backport_upstream_fix-v1-1-9b6f76633849@proxmox.com> X-B4-Tracking: v=1; b=H4sIAJPf8WoC/z2NW07DMBBFtxL5G0sz49SJs5WqivyYgIHEwU4Kq OrecSHi887ce85NFM6Rixiam8h8jSWmpQZ8aoR/scszyxhqFgSkEcFIZ/3bmvI27mvZMtt5nOK XBG5ZkUIAjaJu18z1/Ms9izjta0ifC4nL36vs7pX99uAe5cwfe3Vvx+JfPTSHWMvKG3syZkzL+ 7fsdYsmhJasD8OVHhhnC0uf5jluQ9PBKRhLoCwr29veaNdODhRh1wF6fXIIPBGKy/3+A0cGiNc FAQAA X-Change-ID: 20261109-backport_upstream_fix-0e4e32310061 To: pve-devel@lists.proxmox.com X-Mailer: b4 0.17-dev X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1794236313225 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.233 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: WKPXW7TMHB2Y75Z3RWMU44AM7CB24YGF X-Message-ID-Hash: WKPXW7TMHB2Y75Z3RWMU44AM7CB24YGF X-MailFrom: g.goller@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Shay Sevet X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: If the device named in bond-primary does not exist, e.g. an unplugged USB NIC, bond setup aborts and every interface on top of the bond fails with it. Backport upstream PR #358 to set up the bond without a primary and warn instead. This completes my earlier PR and backport which fixed this for bond-slaves: https://github.com/CumulusNetworks/ifupdown2/pull/355 and https://lore.proxmox.com/pve-devel/20261008102322.95273-1-g.goller@proxmox.com/ Upstream-Link: https://github.com/CumulusNetworks/ifupdown2/pull/358 Signed-off-by: Gabriel Goller --- This is based on top of the other ifupdown2 patches. This is just for convenience, no real dependancy. --- debian/patches/series | 1 + ...-fail-the-bond-when-bond-primary-does-not.patch | 148 +++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/debian/patches/series b/debian/patches/series index 48bb4adae7f5..52e66559b188 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -23,3 +23,4 @@ pve/0016-addons-bond-skip-mac-check-for-bonds-without-slaves.patch pve/0017-addons-vxlan-don-t-fail-syntax-check-on-missing-vxla.patch pve/0018-addons-vxlan-add-vxlan-gbp-attribute.patch pve/0019-vxlan-match-IPv6-peers-when-reading-the-fdb.patch +upstream/0006-bond-do-not-fail-the-bond-when-bond-primary-does-not.patch diff --git a/debian/patches/upstream/0006-bond-do-not-fail-the-bond-when-bond-primary-does-not.patch b/debian/patches/upstream/0006-bond-do-not-fail-the-bond-when-bond-primary-does-not.patch new file mode 100644 index 000000000000..75d559bd847e --- /dev/null +++ b/debian/patches/upstream/0006-bond-do-not-fail-the-bond-when-bond-primary-does-not.patch @@ -0,0 +1,148 @@ +From cf1f500bc2f8d18d7831a830cbe81675b51c0825 Mon Sep 17 00:00:00 2001 +From: Shay Sevet <4628214+sevet@users.noreply.github.com> +Date: Sat, 7 Nov 2026 17:42:30 +0000 +Subject: [PATCH] bond: do not fail the bond when bond-primary does not exist + +bond-primary is translated to an ifindex through the netlink cache. If +the named device does not exist (an unplugged or failed USB NIC, a NIC +whose driver loads late, a replacement NIC that came up under a new +name), get_ifindex() raises NetlinkCacheIfnameNotFoundError. Only +KeyError is handled there, so the exception aborts the whole bond setup: + + error: ifname nic9 not present in cache + error: br0: bridge port bond0 does not exist + +The bond is never created and every interface stacked on it (bridges, +VLANs, addresses) fails with it, at boot and on ifreload. For an +active-backup bond this defeats the point of the redundancy: losing the +preferred device takes down the bond instead of failing over. + +Treat a missing bond-primary like an invalid value: log a warning and +set up the bond without a primary. The kernel then picks the active +slave as for any bond without a primary. The configuration is left +untouched; a later ifreload sets the primary once the device exists. + +Missing bond-slaves are handled separately by #355. + +Reported-by: Shay Sevet <4628214+sevet@users.noreply.github.com> +[patch developed with AI assistance (Claude), reviewed and tested by the author] +Upstream-Link: https://github.com/CumulusNetworks/ifupdown2/pull/358 +Signed-off-by: Shay Sevet <4628214+sevet@users.noreply.github.com> +Signed-off-by: Gabriel Goller + +Backport notes: adjust context for the fallback import block. +--- + ifupdown2/addons/bond.py | 9 +++++ + tests/unit/__init__.py | 0 + tests/unit/test_bond_primary.py | 64 +++++++++++++++++++++++++++++++++ + 3 files changed, 73 insertions(+) + create mode 100644 tests/unit/__init__.py + create mode 100644 tests/unit/test_bond_primary.py + +diff --git a/ifupdown2/addons/bond.py b/ifupdown2/addons/bond.py +index 4db62ee8ac1a..7d2bc9ac401b 100644 +--- a/ifupdown2/addons/bond.py ++++ b/ifupdown2/addons/bond.py +@@ -15,6 +15,7 @@ from contextlib import suppress + try: + from ifupdown2.nlmanager.ipnetwork import IPv4Address + from ifupdown2.lib.addon import Addon ++ from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError + from ifupdown2.nlmanager.nlmanager import Link + + from ifupdown2.ifupdown.iface import ifaceRole, ifaceLinkKind, ifaceLinkPrivFlags, ifaceLinkType, ifaceDependencyType, ifaceStatus +@@ -28,6 +29,7 @@ try: + except (ImportError, ModuleNotFoundError): + from nlmanager.ipnetwork import IPv4Address + from lib.addon import Addon ++ from lib.nlcache import NetlinkCacheIfnameNotFoundError + from nlmanager.nlmanager import Link + + from ifupdown.iface import ifaceRole, ifaceLinkKind, ifaceLinkPrivFlags, ifaceLinkType, ifaceDependencyType, ifaceStatus +@@ -714,6 +716,13 @@ class bond(Addon, moduleBase): + except KeyError: + self.logger.warning('%s: invalid %s value %s' % (ifname, attr_name, user_config)) + ++ except NetlinkCacheIfnameNotFoundError: ++ # bond-primary names a device that does not exist (e.g. an ++ # unplugged or failed USB NIC). Set up the bond without a ++ # primary instead of failing it and every interface above it. ++ self.log_warn('%s: ignoring %s %s: device does not exist' ++ % (ifname, attr_name, user_config), ifaceobj) ++ + self._check_bond_mode_user_config(ifname, link_exists, ifla_info_data) + return ifla_info_data + +diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py +new file mode 100644 +index 000000000000..e69de29bb2d1 +diff --git a/tests/unit/test_bond_primary.py b/tests/unit/test_bond_primary.py +new file mode 100644 +index 000000000000..5d82f8f0c402 +--- /dev/null ++++ b/tests/unit/test_bond_primary.py +@@ -0,0 +1,64 @@ ++"""Bond primary regression tests; run with python3 -m unittest discover -s tests/unit.""" ++ ++import unittest ++from unittest.mock import Mock, patch ++ ++from ifupdown2.addons.bond import bond ++from ifupdown2.ifupdown import ifupdownflags ++from ifupdown2.ifupdown.iface import iface, ifaceStatus ++from ifupdown2.lib.nlcache import NetlinkCacheIfnameNotFoundError ++from ifupdown2.nlmanager.nlmanager import Link ++ ++ ++class BondPrimaryTests(unittest.TestCase): ++ def setUp(self): ++ # Avoid initializing netlink sockets or touching the host network. ++ self.addon = bond.__new__(bond) ++ self.addon.logger = Mock() ++ self.addon.cache = Mock() ++ self.addon.get_saved_ifaceobj = Mock(return_value=None) ++ self.addon._check_bond_mode_user_config = Mock() ++ self.addon._bond_attr_set_list = bond._bond_attr_set_list + ( ++ ("bond-primary", Link.IFLA_BOND_PRIMARY, self.get_ifindex), ++ ) ++ self.existing = {"nic0": 10, "nic1": 11} ++ self.ifaceobj = iface({"name": "bond0"}) ++ self.ifaceobj.config = { ++ "bond-slaves": ["nic0 nic1"], ++ "bond-mode": ["active-backup"], ++ } ++ self.addCleanup(patch.stopall) ++ patch.object(ifupdownflags.flags, "IGNORE_ERRORS", False).start() ++ patch( ++ "ifupdown2.addons.bond.policymanager.policymanager_api", Mock( ++ get_iface_default=Mock(return_value=None)) ++ ).start() ++ ++ def get_ifindex(self, ifname): ++ try: ++ return self.existing[ifname] ++ except KeyError: ++ raise NetlinkCacheIfnameNotFoundError("ifname %s not present in cache" % ifname) ++ ++ def attrs(self, primary): ++ self.ifaceobj.config["bond-primary"] = [primary] ++ return self.addon.get_ifla_bond_attr_from_user_config(self.ifaceobj, False) ++ ++ def test_existing_primary_is_set(self): ++ info = self.attrs("nic1") ++ self.assertEqual(info[Link.IFLA_BOND_PRIMARY], 11) ++ self.assertNotEqual(self.ifaceobj.status, ifaceStatus.WARNING) ++ ++ def test_missing_primary_is_skipped_with_warning(self): ++ info = self.attrs("usb0") ++ self.assertNotIn(Link.IFLA_BOND_PRIMARY, info) ++ # the remaining attributes are still applied ++ self.assertIn(Link.IFLA_BOND_MODE, info) ++ self.assertEqual(self.ifaceobj.status, ifaceStatus.WARNING) ++ self.addon.logger.warning.assert_called_with( ++ "bond0: ignoring bond-primary usb0: device does not exist" ++ ) ++ ++ ++if __name__ == "__main__": ++ unittest.main() --- base-commit: 705d9a203ae3a8a896b4fb03217701c65b10ef21 change-id: 20261109-backport_upstream_fix-0e4e32310061 prerequisite-change-id: 20261106-fix_8299_only-86419dd42acd:v2 prerequisite-patch-id: 54aca45ba1edc2781ea6b4843b662da577b08cdf prerequisite-patch-id: bf8fa8dc4eb0f688930f892c5bf60a1cac22dc4b Best regards, -- Gabriel Goller