From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id E4A9A1FF141 for ; Tue, 05 May 2026 17:43:51 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 43FFCCC6A; Tue, 5 May 2026 17:43:34 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs v5 19/46] frr: fabrics: apply route_filter setting Date: Tue, 5 May 2026 17:36:47 +0200 Message-ID: <20260505153720.412180-20-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260505153720.412180-1-s.hanreich@proxmox.com> References: <20260505153720.412180-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1777995342141 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.646 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment 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: 7YRYQKPHYCV7Y7EVXUSGCAQESG554M7M X-Message-ID-Hash: 7YRYQKPHYCV7Y7EVXUSGCAQESG554M7M 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: Uses the route_filter property from the OSPF and Openfabric section config to generate the FRR configuration. If the route_filter property is not set, the original behavior persists and a route map entry for the configured IP prefix is generated. Otherwise the specified prefix list is used for generating the protocol route map. If there are multiple fabrics defined for a protocol, then a route map entry for each fabric is generated. This means that if two fabrics in the same protocol have overlapping ranges in the prefix lists, the lexigraphically first fabric "wins", since the route map entry for that fabric will be generated first. Signed-off-by: Stefan Hanreich --- proxmox-frr/src/ser/mod.rs | 2 +- proxmox-ve-config/src/sdn/fabric/frr.rs | 268 ++++++++++-------------- 2 files changed, 115 insertions(+), 155 deletions(-) diff --git a/proxmox-frr/src/ser/mod.rs b/proxmox-frr/src/ser/mod.rs index 7bb4836..74190ec 100644 --- a/proxmox-frr/src/ser/mod.rs +++ b/proxmox-frr/src/ser/mod.rs @@ -178,7 +178,7 @@ pub enum FrrProtocol { Bgp, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default)] pub struct IpProtocolRouteMap { pub v4: Option, pub v6: Option, diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs index b816ef6..c4602b5 100644 --- a/proxmox-ve-config/src/sdn/fabric/frr.rs +++ b/proxmox-ve-config/src/sdn/fabric/frr.rs @@ -4,12 +4,8 @@ use tracing; use proxmox_frr::ser::openfabric::{OpenfabricInterface, OpenfabricRouter, OpenfabricRouterName}; use proxmox_frr::ser::ospf::{self, OspfInterface, OspfRouter}; -use proxmox_frr::ser::route_map::{ - AccessAction, AccessListName, RouteMapEntry, RouteMapMatch, RouteMapName, RouteMapSet, -}; -use proxmox_frr::ser::{ - self, FrrConfig, FrrProtocol, FrrWord, Interface, InterfaceName, IpProtocolRouteMap, -}; +use proxmox_frr::ser::route_map::{AccessListName, RouteMapEntry, RouteMapMatch, RouteMapSet}; +use proxmox_frr::ser::{self, FrrConfig, FrrProtocol, FrrWord, Interface, InterfaceName}; use proxmox_network_types::ip_address::Cidr; use proxmox_sdn_types::net::Net; @@ -104,89 +100,92 @@ pub fn build_fabric( } } - if let Some(ipv4cidr) = fabric.ip_prefix() { - let rule = ser::route_map::AccessListRule { - action: ser::route_map::AccessAction::Permit, - network: Cidr::from(ipv4cidr), - is_ipv6: false, - seq: None, - }; - let access_list_name = - AccessListName::new(format!("pve_openfabric_{}_ips", fabric_id)); - frr_config.access_lists.insert(access_list_name, vec![rule]); - } - if let Some(ipv6cidr) = fabric.ip6_prefix() { - let rule = ser::route_map::AccessListRule { - action: ser::route_map::AccessAction::Permit, - network: Cidr::from(ipv6cidr), - is_ipv6: true, - seq: None, - }; - let access_list_name = - AccessListName::new(format!("pve_openfabric_{}_ip6s", fabric_id)); - frr_config.access_lists.insert(access_list_name, vec![rule]); - } + if let Some(ip) = node.ip() { + let routemap_name = + ser::route_map::RouteMapName::new("pve_openfabric".to_owned()); + let routemap = frr_config + .routemaps + .entry(routemap_name.clone()) + .or_default(); - if let Some(ipv4) = node.ip() { - // create route-map - let (routemap_name, routemap_rule) = - build_openfabric_routemap(fabric_id, IpAddr::V4(ipv4), routemap_seq); + let mut routemap_entry = build_source_routemap(ip.into(), routemap_seq); + routemap_seq += 10; + + if let Some(prefix_list_id) = &fabric.properties().route_filter { + routemap_entry.matches = vec![RouteMapMatch::IpAddressPrefixList( + prefix_list_id.clone().into(), + )]; + } else if let Some(cidr) = fabric.ip_prefix() { + let access_list_name = + AccessListName::new(format!("pve_openfabric_{fabric_id}_ips")); + + let rule = ser::route_map::AccessListRule { + action: ser::route_map::AccessAction::Permit, + network: Cidr::from(cidr), + is_ipv6: false, + seq: None, + }; - if let Some(routemap) = frr_config.routemaps.get_mut(&routemap_name) { - routemap.push(routemap_rule) - } else { frr_config - .routemaps - .insert(routemap_name.clone(), vec![routemap_rule]); + .access_lists + .insert(access_list_name.clone(), vec![rule]); + + routemap_entry.matches = + vec![RouteMapMatch::IpAddressAccessList(access_list_name)]; } - routemap_seq += 10; + routemap.push(routemap_entry); - if let Some(routemap) = frr_config + let protocol_routemap = frr_config .protocol_routemaps - .get_mut(&FrrProtocol::Openfabric) - { - routemap.v4 = Some(routemap_name); - } else { - frr_config.protocol_routemaps.insert( - FrrProtocol::Openfabric, - IpProtocolRouteMap { - v4: Some(routemap_name), - v6: None, - }, - ); - } + .entry(FrrProtocol::Openfabric) + .or_default(); + + protocol_routemap.v4 = Some(routemap_name) } - if let Some(ipv6) = node.ip6() { - // create route-map - let (routemap_name, routemap_rule) = - build_openfabric_routemap(fabric_id, IpAddr::V6(ipv6), routemap_seq); + if let Some(ip) = node.ip6() { + let routemap_name = + ser::route_map::RouteMapName::new("pve_openfabric6".to_owned()); + let routemap = frr_config + .routemaps + .entry(routemap_name.clone()) + .or_default(); + + let mut routemap_entry = build_source_routemap(ip.into(), routemap_seq); + routemap_seq += 10; + + if let Some(prefix_list_id) = &fabric.properties().route_filter { + routemap_entry.matches = vec![RouteMapMatch::Ip6AddressPrefixList( + prefix_list_id.clone().into(), + )]; + } else if let Some(cidr) = fabric.ip6_prefix() { + let access_list_name = + AccessListName::new(format!("pve_openfabric_{fabric_id}_ip6s")); + + let rule = ser::route_map::AccessListRule { + action: ser::route_map::AccessAction::Permit, + network: Cidr::from(cidr), + is_ipv6: true, + seq: None, + }; - if let Some(routemap) = frr_config.routemaps.get_mut(&routemap_name) { - routemap.push(routemap_rule) - } else { frr_config - .routemaps - .insert(routemap_name.clone(), vec![routemap_rule]); + .access_lists + .insert(access_list_name.clone(), vec![rule]); + + routemap_entry.matches = + vec![RouteMapMatch::Ip6AddressAccessList(access_list_name)]; } - routemap_seq += 10; + routemap.push(routemap_entry); - if let Some(routemap) = frr_config + let protocol_routemap = frr_config .protocol_routemaps - .get_mut(&FrrProtocol::Openfabric) - { - routemap.v6 = Some(routemap_name); - } else { - frr_config.protocol_routemaps.insert( - FrrProtocol::Openfabric, - IpProtocolRouteMap { - v4: None, - v6: Some(routemap_name), - }, - ); - } + .entry(FrrProtocol::Openfabric) + .or_default(); + + protocol_routemap.v6 = Some(routemap_name) } } FabricEntry::Ospf(ospf_entry) => { @@ -235,47 +234,49 @@ pub fn build_fabric( } } - let access_list_name = - ser::route_map::AccessListName::new(format!("pve_ospf_{}_ips", fabric_id)); + let routemap_name = ser::route_map::RouteMapName::new("pve_ospf".to_owned()); + let routemap = frr_config + .routemaps + .entry(routemap_name.clone()) + .or_default(); - let rule = ser::route_map::AccessListRule { - action: ser::route_map::AccessAction::Permit, - network: Cidr::from( - fabric.ip_prefix().expect("fabric must have a ipv4 prefix"), - ), - is_ipv6: false, - seq: None, - }; + let source_ip = node + .ip() + .ok_or_else(|| anyhow::anyhow!("node must have an ipv4 address"))?; - frr_config.access_lists.insert(access_list_name, vec![rule]); + let mut routemap_entry = build_source_routemap(source_ip.into(), routemap_seq); + routemap_seq += 10; - let (routemap_name, routemap_rule) = build_ospf_dummy_routemap( - fabric_id, - node.ip().expect("node must have an ipv4 address"), - routemap_seq, - )?; + if let Some(prefix_list_id) = &fabric.properties().route_filter { + routemap_entry.matches = vec![RouteMapMatch::IpAddressPrefixList( + prefix_list_id.clone().into(), + )]; + } else if let Some(ipv4cidr) = fabric.ip_prefix() { + let access_list_name = AccessListName::new(format!("pve_ospf_{fabric_id}_ips")); - routemap_seq += 10; + let rule = ser::route_map::AccessListRule { + action: ser::route_map::AccessAction::Permit, + network: Cidr::from(ipv4cidr), + is_ipv6: false, + seq: None, + }; - if let Some(routemap) = frr_config.routemaps.get_mut(&routemap_name) { - routemap.push(routemap_rule) - } else { frr_config - .routemaps - .insert(routemap_name.clone(), vec![routemap_rule]); - } + .access_lists + .insert(access_list_name.clone(), vec![rule]); - if let Some(routemap) = frr_config.protocol_routemaps.get_mut(&FrrProtocol::Ospf) { - routemap.v4 = Some(routemap_name); - } else { - frr_config.protocol_routemaps.insert( - FrrProtocol::Ospf, - IpProtocolRouteMap { - v4: Some(routemap_name), - v6: None, - }, - ); + routemap_entry.matches = + vec![RouteMapMatch::IpAddressAccessList(access_list_name)]; } + + routemap.push(routemap_entry); + + let protocol_routemap = frr_config + .protocol_routemaps + .entry(FrrProtocol::Ospf) + .or_default(); + + protocol_routemap.v4 = Some(routemap_name); } } } @@ -386,55 +387,14 @@ fn build_openfabric_dummy_interface( } /// Helper that builds a RouteMap for the OpenFabric protocol. -fn build_openfabric_routemap( - fabric_id: &FabricId, - router_ip: IpAddr, - seq: u16, -) -> (RouteMapName, RouteMapEntry) { - let routemap_name = match router_ip { - IpAddr::V4(_) => ser::route_map::RouteMapName::new("pve_openfabric".to_owned()), - IpAddr::V6(_) => ser::route_map::RouteMapName::new("pve_openfabric6".to_owned()), - }; - ( - routemap_name, - RouteMapEntry { - seq, - action: ser::route_map::AccessAction::Permit, - matches: vec![match router_ip { - IpAddr::V4(_) => RouteMapMatch::IpAddressAccessList(AccessListName::new(format!( - "pve_openfabric_{fabric_id}_ips" - ))), - IpAddr::V6(_) => RouteMapMatch::Ip6AddressAccessList(AccessListName::new(format!( - "pve_openfabric_{fabric_id}_ip6s" - ))), - }], - sets: vec![RouteMapSet::Src(router_ip)], - custom_frr_config: Vec::new(), - call: None, - exit_action: None, - }, - ) -} - -/// Helper that builds a RouteMap for the OSPF protocol. -fn build_ospf_dummy_routemap( - fabric_id: &FabricId, - router_ip: Ipv4Addr, - seq: u16, -) -> Result<(RouteMapName, RouteMapEntry), anyhow::Error> { - let routemap_name = ser::route_map::RouteMapName::new("pve_ospf".to_owned()); - // create route-map - let routemap = RouteMapEntry { +fn build_source_routemap(router_ip: IpAddr, seq: u16) -> RouteMapEntry { + RouteMapEntry { seq, - action: AccessAction::Permit, - matches: vec![RouteMapMatch::IpAddressAccessList(AccessListName::new( - format!("pve_ospf_{fabric_id}_ips"), - ))], - sets: vec![RouteMapSet::Src(IpAddr::from(router_ip))], + action: ser::route_map::AccessAction::Permit, + matches: Vec::new(), + sets: vec![RouteMapSet::Src(router_ip)], custom_frr_config: Vec::new(), call: None, exit_action: None, - }; - - Ok((routemap_name, routemap)) + } } -- 2.47.3