From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox 02/15] installer-types: answer: add options for configuring management bond
Date: Tue, 8 Sep 2026 12:16:16 +0200 [thread overview]
Message-ID: <20260908101647.1057780-3-c.heiss@proxmox.com> (raw)
In-Reply-To: <20260908101647.1057780-1-c.heiss@proxmox.com>
Adds a new subsection `bond` in [network]. This allows for configuring a
bond for the management network interface during the installation.
E.g.
[network.bond]
members = ["ab:cd:ef:12:34:56", "ab:cd:ef:12:34:57"]
mode = "active-backup"
hash-policy = "layer2+3"
primary-interface = "ab:cd:ef:12:34:57"
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Cargo.toml | 1 +
proxmox-installer-types/Cargo.toml | 1 +
proxmox-installer-types/debian/control | 2 +
proxmox-installer-types/src/answer.rs | 188 ++++++++++++++++++++++++-
4 files changed, 191 insertions(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index 16e91c94..13f62856 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -178,6 +178,7 @@ proxmox-lang = { version = "1.5", path = "proxmox-lang" }
proxmox-log = { version = "1.0.0", path = "proxmox-log" }
proxmox-login = { version = "1.0.0", path = "proxmox-login" }
proxmox-network-types = { version = "1.0.2", path = "proxmox-network-types" }
+proxmox-network-api = { version = "1.0.5", path = "proxmox-network-api" }
proxmox-parallel-handler = { version = "1.0.0", path = "proxmox-parallel-handler" }
proxmox-pgp = { version = "1.0.0", path = "proxmox-pgp" }
proxmox-procfs = { version = "0.1.0", path = "proxmox-procfs" }
diff --git a/proxmox-installer-types/Cargo.toml b/proxmox-installer-types/Cargo.toml
index 45f436e9..b208b0b7 100644
--- a/proxmox-installer-types/Cargo.toml
+++ b/proxmox-installer-types/Cargo.toml
@@ -17,6 +17,7 @@ serde = { workspace = true, features = ["derive"] }
serde_plain.workspace = true
regex = { workspace = true }
proxmox-network-types.workspace = true
+proxmox-network-api.workspace = true
proxmox-schema = { workspace = true, features = ["api-macro"] }
proxmox-section-config = { workspace = true, optional = true }
proxmox-node-status.workspace = true
diff --git a/proxmox-installer-types/debian/control b/proxmox-installer-types/debian/control
index e6f2ad6a..9dfe0017 100644
--- a/proxmox-installer-types/debian/control
+++ b/proxmox-installer-types/debian/control
@@ -7,6 +7,7 @@ Build-Depends-Arch: cargo:native <!nocheck>,
rustc:native (>= 1.85) <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
+ librust-proxmox-network-api-1+default-dev (>= 1.0.5-~~) <!nocheck>,
librust-proxmox-network-types-1+default-dev (>= 1.0.2-~~) <!nocheck>,
librust-proxmox-node-status-1+default-dev <!nocheck>,
librust-proxmox-schema-5+api-macro-dev (>= 5.3.0-~~) <!nocheck>,
@@ -28,6 +29,7 @@ Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
+ librust-proxmox-network-api-1+default-dev (>= 1.0.5-~~),
librust-proxmox-network-types-1+default-dev (>= 1.0.2-~~),
librust-proxmox-node-status-1+default-dev,
librust-proxmox-schema-5+api-macro-dev (>= 5.3.0-~~),
diff --git a/proxmox-installer-types/src/answer.rs b/proxmox-installer-types/src/answer.rs
index e608b4cb..93dc32a4 100644
--- a/proxmox-installer-types/src/answer.rs
+++ b/proxmox-installer-types/src/answer.rs
@@ -14,7 +14,8 @@ use std::{
str::FromStr,
};
-use proxmox_network_types::{fqdn::Fqdn, ip_address::Cidr};
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
+use proxmox_network_types::{MacAddress, fqdn::Fqdn, ip_address::Cidr};
#[cfg(feature = "api-types")]
use proxmox_schema::{
@@ -438,6 +439,106 @@ pub struct NetworkInterfacePinningOptionsAnswer {
pub mapping: HashMap<MacAddress, String>,
}
+#[cfg_attr(feature = "api-types", api(
+ properties: {
+ members: {
+ description: "List of interface MAC addresses to add as bond members.",
+ type: Array,
+ items: {
+ type: MacAddress,
+ },
+ },
+ },
+))]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Options for creating a bond for the management interface with the given
+/// physical interfaces and mode.
+pub struct NetworkBondOptions {
+ /// List of interface MAC addresses to add as bond members.
+ pub members: Vec<MacAddress>,
+ /// Mode for the bond to set.
+ pub mode: LinuxBondMode,
+ /// Bond hash policy. Only used with '802.3ad' and 'balance-xor' mode.
+ #[serde(default = "NetworkBondOptions::default_hash_policy")]
+ pub hash_policy: BondXmitHashPolicy,
+ /// Primary bond interface. Only used with 'active-backup' mode.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub primary_interface: Option<MacAddress>,
+}
+
+impl NetworkBondOptions {
+ /// List of all available bond modes.
+ pub const MODES: &[LinuxBondMode] = {
+ use LinuxBondMode::*;
+ &[
+ ActiveBackup,
+ BalanceAlb,
+ BalanceRr,
+ BalanceTlb,
+ BalanceXor,
+ Broadcast,
+ Ieee802_3ad,
+ ]
+ };
+
+ /// List of all available hash policies.
+ pub const HASH_POLICIES: &[BondXmitHashPolicy] = {
+ use BondXmitHashPolicy::*;
+ &[Layer2, Layer2_3, Layer3_4]
+ };
+
+ /// Returns the default hash policy for new bonds.
+ const fn default_hash_policy() -> BondXmitHashPolicy {
+ BondXmitHashPolicy::Layer2
+ }
+
+ /// Returns a bond configuration instance that disables any bonding setup in the low-level
+ /// installer.
+ pub const fn disabled() -> Self {
+ Self {
+ // an empty interface list is interpreted as disabled by the low-level installer
+ members: Vec::new(),
+ mode: LinuxBondMode::ActiveBackup,
+ hash_policy: BondXmitHashPolicy::Layer2,
+ primary_interface: None,
+ }
+ }
+
+ /// Does some basic checks on the options.
+ ///
+ /// This includes checks for:
+ /// - There are at least to members
+ /// - Whether the primary interface is applicable to the mode
+ /// - Whether the primary interface (if given) is a member
+ pub fn verify(&self) -> Result<()> {
+ if self.members.len() < 2 {
+ bail!(
+ "bond setup requires at least 2 interfaces, got {}",
+ self.members.len()
+ );
+ }
+
+ if let Some(primary_if) = &self.primary_interface {
+ if self.mode != LinuxBondMode::ActiveBackup {
+ bail!("primary bond interface can only be set in active-backup mode");
+ }
+
+ if !self.members.contains(primary_if) {
+ bail!(
+ "primary bond interface {primary_if} must be a member of the bond (members: {})",
+ self.members
+ .iter()
+ .fold(String::new(), |acc, mac| format!("{acc}, {mac}"))
+ .trim_start_matches(", ")
+ );
+ }
+ }
+
+ Ok(())
+ }
+}
+
#[cfg_attr(feature = "api-types", api(
properties: {
filter: {
@@ -464,6 +565,9 @@ pub struct NetworkConfigFromAnswer {
/// Off by default. Allowed for both `from-dhcp` and `from-answer` modes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interface_name_pinning: Option<NetworkInterfacePinningOptionsAnswer>,
+ /// If set, sets up a Linux bond for the management interface.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub bond: Option<NetworkBondOptions>,
}
#[cfg_attr(feature = "api-types", api)]
@@ -475,6 +579,9 @@ pub struct NetworkConfigFromDhcp {
/// Off by default. Allowed for both `from-dhcp` and `from-answer` modes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interface_name_pinning: Option<NetworkInterfacePinningOptionsAnswer>,
+ /// If set, sets up a Linux bond for the management interface.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub bond: Option<NetworkBondOptions>,
}
#[cfg_attr(feature = "api-types", api(
@@ -503,6 +610,14 @@ impl NetworkConfig {
Self::FromAnswer(answer) => answer.interface_name_pinning.as_ref(),
}
}
+
+ /// Returns the management network Linux bond options, if any.
+ pub const fn bond(&self) -> Option<&NetworkBondOptions> {
+ match self {
+ Self::FromDhcp(dhcp) => dhcp.bond.as_ref(),
+ Self::FromAnswer(answer) => answer.bond.as_ref(),
+ }
+ }
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
@@ -1475,4 +1590,75 @@ mod tests {
};
assert!(setup.filesystem_details().is_ok());
}
+
+ #[test]
+ fn network_bond_requires_at_least_two_members() {
+ let mut bond = NetworkBondOptions::disabled();
+ let res = bond.verify();
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ "bond setup requires at least 2 interfaces, got 0"
+ );
+
+ bond.members = vec!["12:34:56:ab:cd:df".parse().unwrap()];
+
+ let res = bond.verify();
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ "bond setup requires at least 2 interfaces, got 1"
+ );
+
+ bond.members = vec![
+ "12:34:56:ab:cd:df".parse().unwrap(),
+ "ab:cd:df:12:34:56".parse().unwrap(),
+ ];
+ assert!(bond.verify().is_ok());
+ }
+
+ #[test]
+ fn network_bond_primary_if_only_with_active_backup() {
+ let mut bond = NetworkBondOptions::disabled();
+ bond.members = vec![
+ "12:34:56:ab:cd:df".parse().unwrap(),
+ "ab:cd:df:12:34:56".parse().unwrap(),
+ ];
+ bond.primary_interface = Some("12:34:56:ab:cd:df".parse().unwrap());
+
+ for m in NetworkBondOptions::MODES {
+ bond.mode = *m;
+ match m {
+ LinuxBondMode::ActiveBackup => assert!(bond.verify().is_ok()),
+ _ => {
+ let res = bond.verify();
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ "primary bond interface can only be set in active-backup mode"
+ );
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn network_bond_primary_if_must_be_member() {
+ let mut bond = NetworkBondOptions::disabled();
+ bond.members = vec![
+ "12:34:56:ab:cd:df".parse().unwrap(),
+ "ab:cd:df:12:34:56".parse().unwrap(),
+ ];
+ bond.primary_interface = Some("12:34:56:ab:cd:df".parse().unwrap());
+ assert!(bond.verify().is_ok());
+
+ bond.primary_interface = Some("ab:12:cd:34:ef:56".parse().unwrap());
+
+ let res = bond.verify();
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ "primary bond interface AB:12:CD:34:EF:56 must be a member of the bond (members: 12:34:56:AB:CD:DF, AB:CD:DF:12:34:56)"
+ );
+ }
}
--
2.55.0
next prev parent reply other threads:[~2026-09-08 10:17 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 10:16 [PATCH installer/proxmox 00/15] partially fix #2164: add bond setup across all installers Christoph Heiss
2026-09-08 10:16 ` [PATCH proxmox 01/15] installer-types: use `MacAddress` type where applicable Christoph Heiss
2026-09-08 10:16 ` Christoph Heiss [this message]
2026-09-08 10:16 ` [PATCH installer 03/15] install: run make tidy Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 04/15] tree-wide: use `MacAddress` type instead of string for MAC addresses Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 05/15] install: factor out /etc/network/interfaces setup into own subroutine Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 06/15] install: do not rely on interface name map to be fully populated Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 07/15] install: config: add option for setting up bond on management interface Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 08/15] install: network: set up management bond if requested Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 09/15] gui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 10/15] gui: network: add bond setup options to " Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 11/15] auto: pass trough management network bond options to low-level installer Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 12/15] tui: drop long obsolete comment about logo formatting Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 13/15] tui: views: add widget for displaying a group of checkboxes Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 14/15] tui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 15/15] tui: network: add bond setup options to advanced network dialog Christoph Heiss
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=20260908101647.1057780-3-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/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.