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 2EF911FF13B for ; Wed, 25 Mar 2026 10:44:05 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2D2F7101CB; Wed, 25 Mar 2026 10:42:45 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs 6/9] ve-config: frr: implement frr config generation for prefix lists Date: Wed, 25 Mar 2026 10:41:19 +0100 Message-ID: <20260325094142.174364-9-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260325094142.174364-1-s.hanreich@proxmox.com> References: <20260325094142.174364-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: 1774431663708 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.720 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: IHZFOIMF3DRJHOEFJPFCR2G2NR5ZKXNH X-Message-ID-Hash: IHZFOIMF3DRJHOEFJPFCR2G2NR5ZKXNH 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: Implements conversion traits for all the section config types, so they can be converted into their respective FRR template counterpart. Also add a helper that adds a list of prefix lists to an existing FRR configuration. This will be used by perl-rs to generate the FRR configuration from the section configuration. The helper will overwrite existing prefix lists in the FRR configuration, allowing users to override pre-defined prefix lists generated by our stack. Signed-off-by: Stefan Hanreich --- proxmox-ve-config/src/sdn/prefix_list.rs | 187 +++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/proxmox-ve-config/src/sdn/prefix_list.rs b/proxmox-ve-config/src/sdn/prefix_list.rs index f4988d9..f371c8d 100644 --- a/proxmox-ve-config/src/sdn/prefix_list.rs +++ b/proxmox-ve-config/src/sdn/prefix_list.rs @@ -123,6 +123,193 @@ pub enum PrefixList { PrefixList(PrefixListSection), } +#[cfg(feature = "frr")] +pub mod frr { + use core::{convert::Into, iter::IntoIterator}; + + use super::*; + + use proxmox_frr::ser::{ + route_map::{ + self, PrefixList as FrrPrefixList, PrefixListName, PrefixListRule as FrrPrefixListRule, + }, + FrrConfig, + }; + + impl Into for PrefixListId { + fn into(self) -> PrefixListName { + PrefixListName::new(self.into_string()) + } + } + + impl Into for PrefixListEntry { + fn into(self) -> FrrPrefixListRule { + FrrPrefixListRule { + action: match self.action { + PrefixListAction::Permit => route_map::AccessAction::Permit, + PrefixListAction::Deny => route_map::AccessAction::Deny, + }, + network: self.prefix, + seq: self.seq, + le: self.le, + ge: self.ge, + is_ipv6: self.prefix.is_ipv6(), + } + } + } + + impl Into for PrefixListSection { + fn into(self) -> FrrPrefixList { + FrrPrefixList { + name: PrefixListName::new(self.id.to_string()), + rules: self + .entries + .into_iter() + .map(|rule| rule.into_inner().into()) + .collect(), + } + } + } + + /// Add a list of Prefix Lists to an [`FrrConfig`]. + /// + /// This will overwrite existing Prefix Lists in the [`FrrConfig`]. Since this will be used for + /// generating the FRR configuration from the SDN stack, this enables users to override Prefix + /// Lists that are predefined by our stack. + pub fn build_frr_prefix_lists( + prefix_lists: impl IntoIterator, + frr_config: &mut FrrConfig, + ) -> Result<(), anyhow::Error> { + for prefix_list in prefix_lists.into_iter() { + let PrefixList::PrefixList(prefix_list) = prefix_list; + let prefix_list_name = PrefixListName::new(prefix_list.id.0); + + frr_config.prefix_lists.insert( + prefix_list_name, + prefix_list + .entries + .into_iter() + .map(|prefix_list| prefix_list.into_inner().into()) + .collect(), + ); + } + + Ok(()) + } + + #[cfg(test)] + mod tests { + use super::*; + + use proxmox_frr::ser::route_map::{AccessAction, PrefixListName}; + use proxmox_frr::ser::serializer::dump; + + use proxmox_section_config::typed::ApiSectionDataEntry; + + #[test] + fn test_build_prefix_list() -> Result<(), anyhow::Error> { + let section_config = r#" +prefix-list: example-1 + entries action=permit,prefix=192.0.2.0/24 + entries action=permit,prefix=192.0.2.0/24,le=32 + entries action=permit,prefix=192.0.2.0/24,le=32,ge=24,seq=123 + entries action=permit,prefix=192.0.2.0/24,ge=24 + entries action=permit,prefix=192.0.2.0/24,ge=24,le=31 + +prefix-list: example-3 + entries action=permit,prefix=192.0.2.0/24,seq=333 + entries action=permit,prefix=198.51.100.0/24,seq=222 + entries action=permit,prefix=203.0.113.0/24,seq=111 + +prefix-list: example-2 + entries action=deny,prefix=192.0.2.0/24,le=25 + entries action=permit,prefix=192.0.2.0/24 +"#; + + let config = PrefixList::parse_section_config("prefix-lists.cfg", section_config)?; + let mut frr_config = FrrConfig::default(); + + build_frr_prefix_lists( + config + .into_iter() + .map(|(_, route_map_entry)| route_map_entry), + &mut frr_config, + )?; + + assert_eq!( + dump(&frr_config)?, + r#"! +ip prefix-list example-1 permit 192.0.2.0/24 +ip prefix-list example-1 permit 192.0.2.0/24 le 32 +ip prefix-list example-1 seq 123 permit 192.0.2.0/24 le 32 ge 24 +ip prefix-list example-1 permit 192.0.2.0/24 ge 24 +ip prefix-list example-1 permit 192.0.2.0/24 le 31 ge 24 +! +ip prefix-list example-2 deny 192.0.2.0/24 le 25 +ip prefix-list example-2 permit 192.0.2.0/24 +! +ip prefix-list example-3 seq 333 permit 192.0.2.0/24 +ip prefix-list example-3 seq 222 permit 198.51.100.0/24 +ip prefix-list example-3 seq 111 permit 203.0.113.0/24 +"# + ); + + Ok(()) + } + + #[test] + fn test_build_prefix_list_overwrite() -> Result<(), anyhow::Error> { + let section_config = r#" +prefix-list: example-1 + entries action=permit,prefix=192.0.2.0/24 +"#; + + let config = PrefixList::parse_section_config("prefix-lists.cfg", section_config)?; + + let example_1_prefix_list = vec![FrrPrefixListRule { + action: AccessAction::Deny, + network: Cidr::new_v4([198, 51, 100, 0], 24).unwrap(), + seq: None, + le: None, + ge: None, + is_ipv6: false, + }]; + + let mut frr_config = FrrConfig::default(); + + frr_config.prefix_lists.insert( + PrefixListName::new("example-1".to_string()), + example_1_prefix_list.clone(), + ); + + build_frr_prefix_lists( + config + .into_iter() + .map(|(_, route_map_entry)| route_map_entry), + &mut frr_config, + )?; + + let new_prefix_list = frr_config + .prefix_lists + .get(&PrefixListName::new("example-1".to_string())) + .expect("'example-1' prefix list exists"); + + assert_ne!(&example_1_prefix_list, new_prefix_list); + + let generated_frr_config = dump(&frr_config)?; + + assert_eq!( + generated_frr_config, + r#"! +ip prefix-list example-1 permit 192.0.2.0/24 +"# + ); + + Ok(()) + } + } +} + pub mod api { use super::*; -- 2.47.3