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 B9E241FF0C0 for ; Wed, 21 Oct 2026 18:14:22 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 7AFEB21641; Wed, 21 Oct 2026 18:14:19 +0200 (CEST) Message-ID: <85270a59-b3f8-4c23-868b-56c4dd5d0302@proxmox.com> Date: Wed, 21 Oct 2026 18:14:14 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: Stefan Hanreich Subject: Re: [PATCH proxmox-perl-rs 06/15] pve-rs: fabrics: assign network interfaces to configured VRFs To: pve-devel@lists.proxmox.com References: <20260821140404.322081-1-g.goller@proxmox.com> <20260821140404.322081-7-g.goller@proxmox.com> Content-Language: en-US In-Reply-To: <20260821140404.322081-7-g.goller@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.344 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: UZGS26Y4O2Q3CZ4OFRNV37EB2J435T3S X-Message-ID-Hash: UZGS26Y4O2Q3CZ4OFRNV37EB2J435T3S X-MailFrom: s.hanreich@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 VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: On 8/21/26 4:04 PM, Gabriel Goller wrote: > Add the VRF stanza to generated interface configuration when a fabric > has a VRF. Apply it to dummy addresses, protocol interfaces, and BGP > unnumbered interfaces so traffic enters the correct routing table. > > Fabrics without a VRF keep their existing configuration. > > Signed-off-by: Gabriel Goller > --- > pve-rs/src/bindings/sdn/fabrics.rs | 69 +++++++++++++++++++++++------- > 1 file changed, 53 insertions(+), 16 deletions(-) > > diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs > index f96b6b1c656f..98470eeaa397 100644 > --- a/pve-rs/src/bindings/sdn/fabrics.rs > +++ b/pve-rs/src/bindings/sdn/fabrics.rs > @@ -582,8 +582,23 @@ pub mod pve_rs_sdn_fabrics { > Ok(interface) > } > > + pub(crate) fn fabric_vrf_name(fabric: &FabricEntry) -> Option { > + let zone = match fabric { > + FabricEntry::Ospf(entry) => entry.fabric_section().properties().zone(), > + FabricEntry::Bgp(entry) => entry.fabric_section().properties().zone(), > + FabricEntry::Openfabric(_) | FabricEntry::WireGuard(_) => None, > + }; > + > + zone.map(|zone| format!("vrf_{zone}")) > + } > + nit: maybe better as a method on the fabric config itself? Although I suppose it could be argued that it's not the decision of the config how the VRF should be named. > /// Helper function to generate the default `/etc/network/interfaces` config for a given CIDR. > - fn render_interface(name: &str, cidr: Cidr, link_type: Option<&str>) -> Result { > + fn render_interface( > + name: &str, > + cidr: Cidr, > + link_type: Option<&str>, > + vrf: Option<&str>, > + ) -> Result { > let mut interface = String::new(); > > writeln!(interface, "auto {name}")?; > @@ -595,6 +610,9 @@ pub mod pve_rs_sdn_fabrics { > if let Some(link_type) = link_type { > writeln!(interface, "\tlink-type {link_type}")?; > } > + if let Some(vrf) = vrf { > + writeln!(interface, "\tvrf {vrf}")?; > + } > writeln!(interface, "\tip-forward 1")?; > > Ok(interface) > @@ -604,12 +622,14 @@ pub mod pve_rs_sdn_fabrics { > interfaces: &mut String, > fabric: &Fabric, > node: &ConfigNode, > + vrf: Option<&str>, > ) -> Result<(), Error> { > if let Some(ip) = node.ip() { > let interface = render_interface( > &format!("dummy_{}", fabric.id()), > Cidr::new_v4(ip, 32)?, > Some("dummy"), > + vrf, > )?; > writeln!(interfaces)?; > write!(interfaces, "{interface}")?; > @@ -620,6 +640,7 @@ pub mod pve_rs_sdn_fabrics { > &format!("dummy_{}", fabric.id()), > Cidr::new_v6(ip6, 128)?, > Some("dummy"), > + vrf, > )?; > writeln!(interfaces)?; > write!(interfaces, "{interface}")?; > @@ -637,28 +658,35 @@ pub mod pve_rs_sdn_fabrics { > let config = this.fabric_config.lock().unwrap(); > let mut interfaces = String::new(); > > - let node_fabrics = config.values().filter_map(|entry| { > - entry > - .get_node(&node_id) > - .map(|node| (entry.fabric(), node)) > - .ok() > - }); > + let node_fabrics = config > + .values() > + .filter_map(|entry| entry.get_node(&node_id).map(|node| (entry, node)).ok()); > > - for (fabric, node) in node_fabrics { > - render_dummy_interfaces(&mut interfaces, fabric, node)?; > + for (entry, node) in node_fabrics { > + let fabric = entry.fabric(); > + let vrf = fabric_vrf_name(entry); > + render_dummy_interfaces(&mut interfaces, fabric, node, vrf.as_deref())?; > > match node { > ConfigNode::Openfabric(node_section) => { > for interface in node_section.properties().interfaces() { > if let Some(ip) = interface.ip() { > - let interface = > - render_interface(interface.name(), Cidr::from(ip), None)?; > + let interface = render_interface( > + interface.name(), > + Cidr::from(ip), > + None, > + vrf.as_deref(), > + )?; > writeln!(interfaces)?; > write!(interfaces, "{interface}")?; > } > if let Some(ip) = interface.ip6() { > - let interface = > - render_interface(interface.name(), Cidr::from(ip), None)?; > + let interface = render_interface( > + interface.name(), > + Cidr::from(ip), > + None, > + vrf.as_deref(), > + )?; > writeln!(interfaces)?; > write!(interfaces, "{interface}")?; > } > @@ -675,7 +703,8 @@ pub mod pve_rs_sdn_fabrics { > } else { > anyhow::bail!("there has to be a ipv4 or ipv6 node address"); > }); > - let interface = render_interface(interface.name(), cidr, None)?; > + let interface = > + render_interface(interface.name(), cidr, None, vrf.as_deref())?; > writeln!(interfaces)?; > write!(interfaces, "{interface}")?; > } > @@ -685,8 +714,12 @@ pub mod pve_rs_sdn_fabrics { > for interface in node_section.properties().interfaces() { > writeln!(interfaces)?; > if let Some(ip) = interface.ip() { > - let interface = > - render_interface(interface.name(), Cidr::from(ip), None)?; > + let interface = render_interface( > + interface.name(), > + Cidr::from(ip), > + None, > + vrf.as_deref(), > + )?; > write!(interfaces, "{interface}")?; > } else { > let interface = render_interface( > @@ -695,6 +728,7 @@ pub mod pve_rs_sdn_fabrics { > anyhow::anyhow!("there has to be a ipv4 address") > })?)), > None, > + vrf.as_deref(), > )?; > write!(interfaces, "{interface}")?; > } > @@ -762,6 +796,9 @@ pub mod pve_rs_sdn_fabrics { > writeln!(interfaces)?; > writeln!(interfaces, "auto {name}")?; > writeln!(interfaces, "iface {name} inet manual")?; > + if let Some(vrf) = &vrf { > + writeln!(interfaces, "\tvrf {vrf}")?; > + } > writeln!(interfaces, "\tip-forward 1")?; > writeln!(interfaces, "\tip6-forward 1")?; > // BGP unnumbered uses RAs to discover peer link-local