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 3048D1FF0C0 for ; Wed, 21 Oct 2026 18:14:11 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 56DBD215F8; Wed, 21 Oct 2026 18:14:09 +0200 (CEST) Message-ID: Date: Wed, 21 Oct 2026 18:14:05 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: Stefan Hanreich Subject: Re: [PATCH proxmox-ve-rs 03/15] sdn: generate fabric routing configuration in zone VRFs To: pve-devel@lists.proxmox.com References: <20260821140404.322081-1-g.goller@proxmox.com> <20260821140404.322081-4-g.goller@proxmox.com> Content-Language: en-US In-Reply-To: <20260821140404.322081-4-g.goller@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.348 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: CWFN4GG3M76FENL6EEGHRJDVZJOMJ7BK X-Message-ID-Hash: CWFN4GG3M76FENL6EEGHRJDVZJOMJ7BK 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:03 PM, Gabriel Goller wrote: > 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") could be something like this? vrf_router.entry(vrf_name).or_insert_with(|| vrf_router) > + }; > + 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.