From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id A35E61FF0B2 for ; Fri, 25 Sep 2026 14:35:21 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 37F8D216D9; Fri, 25 Sep 2026 14:35:21 +0200 (CEST) From: Christoph Heiss To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox 2/3] fix #6121: network-api: config: add support for `bond-miimon` option Date: Fri, 25 Sep 2026 14:34:31 +0200 Message-ID: <20260925123459.424903-3-c.heiss@proxmox.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260925123459.424903-1-c.heiss@proxmox.com> References: <20260925123459.424903-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1790339716459 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.313 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: VQO6MCVVKKH3NUHIYFVRLBVJTT6AZA2Q X-Message-ID-Hash: VQO6MCVVKKH3NUHIYFVRLBVJTT6AZA2Q X-MailFrom: c.heiss@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 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Fixes #6121 [0]. Adds support for the 'bond-miimon' option to the ifupdown(2) config writer and parser. The option is always written out for bonds, with a default value of 100 - much like PVE does it. This is needed so that (at least) in active-backup mode, the bond correctly switches over to the backup link if the active one goes down. [0] https://bugzilla.proxmox.com/show_bug.cgi?id=6121 Signed-off-by: Christoph Heiss --- proxmox-network-api/src/config/lexer.rs | 3 + proxmox-network-api/src/config/mod.rs | 70 ++++++++++++++++++++++++ proxmox-network-api/src/config/parser.rs | 23 ++++++++ 3 files changed, 96 insertions(+) diff --git a/proxmox-network-api/src/config/lexer.rs b/proxmox-network-api/src/config/lexer.rs index 4729d462..2a646508 100644 --- a/proxmox-network-api/src/config/lexer.rs +++ b/proxmox-network-api/src/config/lexer.rs @@ -30,6 +30,7 @@ pub enum Token { BondMode, BondPrimary, BondXmitHashPolicy, + BondMiimon, EOF, } @@ -62,6 +63,8 @@ static KEYWORDS: LazyLock> = LazyLock::new(|| { map.insert("bond_primary", Token::BondPrimary); map.insert("bond_xmit_hash_policy", Token::BondXmitHashPolicy); map.insert("bond-xmit-hash-policy", Token::BondXmitHashPolicy); + map.insert("bond-miimon", Token::BondMiimon); + map.insert("bond_miimon", Token::BondMiimon); map }); diff --git a/proxmox-network-api/src/config/mod.rs b/proxmox-network-api/src/config/mod.rs index 09165bfb..3eff9284 100644 --- a/proxmox-network-api/src/config/mod.rs +++ b/proxmox-network-api/src/config/mod.rs @@ -89,6 +89,9 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er } } + let miimon = iface.bond_miimon.unwrap_or(100); + writeln!(w, "\tbond-miimon {miimon}")?; + let slaves = iface.slaves.as_ref().unwrap_or(&EMPTY_LIST); if slaves.is_empty() { writeln!(w, "\tbond-slaves none")?; @@ -791,4 +794,71 @@ iface individual_name inet manual assert_eq!(parse_vlan_raw_device_from_name("vmbr0"), None); assert_eq!(parse_vlan_raw_device_from_name("vmbr0.200"), Some("vmbr0")); } + + #[test] + fn test_write_network_config_bond_default_miimon() { + let mut bond = Interface::new("bond0".to_owned()); + bond.interface_type = NetworkInterfaceType::Bond; + bond.method = Some(NetworkConfigMethod::Manual); + bond.slaves = Some(vec![String::from("eth0"), String::from("eth1")]); + bond.bond_mode = Some(LinuxBondMode::ActiveBackup); + + let mut eth0 = Interface::new("eth0".to_owned()); + eth0.interface_type = NetworkInterfaceType::Eth; + + let mut eth1 = Interface::new("eth1".to_owned()); + eth1.interface_type = NetworkInterfaceType::Eth; + + let nw_config = NetworkConfig { + interfaces: BTreeMap::from([ + ("eth0".into(), eth0), + ("eth1".into(), eth1), + ("bond0".into(), bond), + ]), + order: vec![NetworkOrderEntry::Iface("bond0".to_owned())], + }; + assert_eq!( + String::try_from(nw_config).unwrap().trim(), + r#" +iface bond0 inet manual + bond-mode active-backup + bond-miimon 100 + bond-slaves eth0 eth1"# + .trim() + ); + } + + #[test] + fn test_write_network_config_bond_explicit_miimon() { + let mut bond = Interface::new("bond0".to_owned()); + bond.interface_type = NetworkInterfaceType::Bond; + bond.method = Some(NetworkConfigMethod::Manual); + bond.slaves = Some(vec![String::from("eth0"), String::from("eth1")]); + bond.bond_mode = Some(LinuxBondMode::ActiveBackup); + bond.bond_miimon = Some(250); + + let mut eth0 = Interface::new("eth0".to_owned()); + eth0.interface_type = NetworkInterfaceType::Eth; + + let mut eth1 = Interface::new("eth1".to_owned()); + eth1.interface_type = NetworkInterfaceType::Eth; + + let nw_config = NetworkConfig { + interfaces: BTreeMap::from([ + ("eth0".into(), eth0), + ("eth1".into(), eth1), + ("bond0".into(), bond), + ]), + order: vec![NetworkOrderEntry::Iface("bond0".to_owned())], + }; + assert_eq!( + String::try_from(nw_config).unwrap().trim(), + r#" +iface bond0 inet manual + bond-mode active-backup + bond-miimon 250 + bond-slaves eth0 eth1"# + .trim() + ); + } } diff --git a/proxmox-network-api/src/config/parser.rs b/proxmox-network-api/src/config/parser.rs index 71c9ec0f..2b789f64 100644 --- a/proxmox-network-api/src/config/parser.rs +++ b/proxmox-network-api/src/config/parser.rs @@ -378,6 +378,12 @@ impl NetworkParser { interface.bond_xmit_hash_policy = Some(policy); self.eat(Token::Newline)?; } + Token::BondMiimon => { + self.eat(Token::BondMiimon)?; + let miimon = self.next_text()?.parse()?; + interface.bond_miimon = Some(miimon); + self.eat(Token::Newline)?; + } Token::VlanId => { self.eat(Token::VlanId)?; let vlan_id = self.next_text()?.parse()?; @@ -918,4 +924,21 @@ iface individual_name inet static assert_eq!(iface.method, Some(NetworkConfigMethod::Static)); assert_eq!(iface.cidr, Some(String::from("10.0.0.100/16"))); } + + #[test] + fn test_network_config_parser_bond_miimon() { + let input = r#" +iface bond0 inet manual + bond-slaves eth0 eth1 + bond-mode active-backup + bond-miimon 200 +"#; + + let mut parser = NetworkParser::new(input.as_bytes()); + let config = parser.parse_interfaces(None).unwrap(); + + let iface = config.interfaces.get("bond0").unwrap(); + assert_eq!(iface.interface_type, NetworkInterfaceType::Bond); + assert_eq!(iface.bond_miimon, Some(200)); + } } -- 2.55.0