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 303CD1FF0AA for ; Fri, 21 Aug 2026 16:04:20 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 834452163F; Fri, 21 Aug 2026 16:04:15 +0200 (CEST) From: Gabriel Goller To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs 03/15] sdn: generate fabric routing configuration in zone VRFs Date: Fri, 21 Aug 2026 16:03:47 +0200 Message-ID: <20260821140404.322081-4-g.goller@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260821140404.322081-1-g.goller@proxmox.com> References: <20260821140404.322081-1-g.goller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1787321022774 X-SPAM-LEVEL: Spam detection results: 0 AWL 1.034 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: 6QOHFMCB7WKRXTC7HMCXL3PRN4UGM2RZ X-Message-ID-Hash: 6QOHFMCB7WKRXTC7HMCXL3PRN4UGM2RZ 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 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Map a fabric zone to the corresponding vrf_ FRR instance. Generate OSPF routers and interfaces in that VRF, and store BGP routers under the same VRF name. Keep fabrics without a zone in the default routing table. Collect OSPF redistribution rules on the router for the selected VRF so that several fabrics in one VRF contribute to the same OSPF instance. This keeps protocol configuration and interface configuration in the routing table selected by the zone. Signed-off-by: Gabriel Goller --- proxmox-ve-config/src/sdn/fabric/frr.rs | 82 ++++++++++++++++++------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs index 9203dcd16dae..ca0fbfe6d542 100644 --- a/proxmox-ve-config/src/sdn/fabric/frr.rs +++ b/proxmox-ve-config/src/sdn/fabric/frr.rs @@ -24,10 +24,13 @@ use crate::sdn::fabric::section_config::protocol::{ openfabric::{OpenfabricInterfaceProperties, OpenfabricProperties}, ospf::OspfInterfaceProperties, }; -use crate::sdn::fabric::section_config::{fabric::FabricId, node::NodeId}; +use crate::sdn::fabric::section_config::{ + fabric::{FabricId, ZoneId}, + node::NodeId, +}; use crate::sdn::fabric::{FabricConfig, FabricEntry}; -/// Constructs the FRR config from the the passed [`Valid`]. +/// Constructs the FRR config from the passed [`Valid`]. /// /// Iterates over the [`FabricConfig`] and constructs all the FRR routers, interfaces, route-maps, /// etc. @@ -207,27 +210,50 @@ pub fn build_fabric( .get_or_insert(node.ip().expect("node must have an ipv4 address")); let fabric = ospf_entry.fabric_section(); + let vrf_name = fabric_vrf_name(fabric.properties().zone()); let frr_word_area = ser::FrrWord::new(fabric.properties().area.to_string())?; let frr_area = ser::ospf::Area::new(frr_word_area)?; - if frr_config.ospf.router.is_none() { - let mut ospf_router = build_ospf_router(*router_id)?; - - ospf_router.redistribute = fabric - .properties() - .redistributions() - .into_iter() - .cloned() - .map(OspfRedistribution::from) - .collect(); + let redistributions = fabric + .properties() + .redistributions() + .into_iter() + .cloned() + .map(OspfRedistribution::from); + + let ospf_router = if vrf_name == VrfName::Default { + if frr_config.ospf.router.is_none() { + frr_config.ospf.router = Some(build_ospf_router(*router_id)?); + } + frr_config + .ospf + .router + .as_mut() + .expect("router was inserted") + } else { + if !frr_config.ospf.vrf_router.contains_key(&vrf_name) { + frr_config + .ospf + .vrf_router + .insert(vrf_name.clone(), build_ospf_router(*router_id)?); + } + frr_config + .ospf + .vrf_router + .get_mut(&vrf_name) + .expect("router was inserted") + }; + ospf_router.redistribute.extend(redistributions); - frr_config.ospf.router = Some(ospf_router); - } + let interface_vrf = match &vrf_name { + VrfName::Default => None, + custom => Some(custom.clone()), + }; // Add dummy interface let (interface, interface_name) = - build_ospf_dummy_interface(fabric_id, frr_area.clone())?; + build_ospf_dummy_interface(fabric_id, frr_area.clone(), interface_vrf.clone())?; if frr_config .ospf @@ -242,7 +268,7 @@ pub fn build_fabric( for interface in node.properties().interfaces.iter() { let (interface, interface_name) = - build_ospf_interface(frr_area.clone(), interface)?; + build_ospf_interface(frr_area.clone(), interface, interface_vrf.clone())?; if frr_config .ospf @@ -309,6 +335,7 @@ pub fn build_fabric( }; let fabric = bgp_entry.fabric_section(); + let vrf_name = fabric_vrf_name(fabric.properties().zone()); let local_asn = properties.asn().as_u32(); @@ -323,7 +350,7 @@ pub fn build_fabric( let local_as = frr_config .bgp .vrf_router - .get(&VrfName::Default) + .get(&vrf_name) .filter(|existing| existing.asn != local_asn) .map(|_| LocalAsSettings { asn: local_asn, @@ -562,10 +589,10 @@ pub fn build_fabric( custom_frr_config: Default::default(), }; - if let Some(existing) = frr_config.bgp.vrf_router.get_mut(&VrfName::Default) { + if let Some(existing) = frr_config.bgp.vrf_router.get_mut(&vrf_name) { existing.merge_fabric(router); } else { - frr_config.bgp.vrf_router.insert(VrfName::Default, router); + frr_config.bgp.vrf_router.insert(vrf_name, router); } } } @@ -595,6 +622,13 @@ pub fn build_fabric( Ok(()) } +fn fabric_vrf_name(zone: Option<&ZoneId>) -> VrfName { + match zone { + Some(zone) => VrfName::Custom(format!("vrf_{zone}")), + None => VrfName::Default, + } +} + /// Helper that builds a OSPF router with a the router_id. fn build_ospf_router(router_id: Ipv4Addr) -> Result { Ok(OspfRouter::new(router_id)) @@ -615,6 +649,7 @@ fn build_openfabric_router( fn build_ospf_interface( area: ser::ospf::Area, interface: &OspfInterfaceProperties, + vrf: Option, ) -> Result<(Interface, InterfaceName), anyhow::Error> { let frr_interface = ser::ospf::OspfInterface { area, @@ -633,13 +668,16 @@ fn build_ospf_interface( }; let interface_name = interface.name.as_ref().try_into()?; - Ok((frr_interface.into(), interface_name)) + let mut interface: Interface = frr_interface.into(); + interface.vrf = vrf; + Ok((interface, interface_name)) } /// Helper that builds the OSPF dummy interface using the [`FabricId`] and the [`ospf::Area`]. fn build_ospf_dummy_interface( fabric_id: &FabricId, area: ospf::Area, + vrf: Option, ) -> Result<(Interface, InterfaceName), anyhow::Error> { let frr_interface = ser::ospf::OspfInterface { area, @@ -647,7 +685,9 @@ fn build_ospf_dummy_interface( network_type: None, }; let interface_name = format!("dummy_{}", fabric_id).try_into()?; - Ok((frr_interface.into(), interface_name)) + let mut interface: Interface = frr_interface.into(); + interface.vrf = vrf; + Ok((interface, interface_name)) } /// Helper that builds the OpenFabric interface. -- 2.47.3