From: Gabriel Goller <g.goller@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Shay Sevet <shay@sevet.net>
Subject: [PATCH ifupdown2] d/patches: backport upstream fix for missing bond-primary device
Date: Mon, 09 Nov 2026 15:58:28 +0100 [thread overview]
Message-ID: <20261109-backport_upstream_fix-v1-1-9b6f76633849@proxmox.com> (raw)
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 <g.goller@proxmox.com>
---
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 <g.goller@proxmox.com>
+
+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 <g.goller@proxmox.com>
next reply other threads:[~2026-11-09 14:58 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 14:58 Gabriel Goller [this message]
2026-11-10 20:43 ` applied: [PATCH ifupdown2] d/patches: backport upstream fix for missing bond-primary device Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109-backport_upstream_fix-v1-1-9b6f76633849@proxmox.com \
--to=g.goller@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
--cc=shay@sevet.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.