From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 2D3B61FF13B for ; Wed, 25 Mar 2026 10:42:30 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id CD71AEFCB; Wed, 25 Mar 2026 10:42:16 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs 5/9] ve-config: add prefix list section config Date: Wed, 25 Mar 2026 10:41:18 +0100 Message-ID: <20260325094142.174364-8-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: 1774431663646 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.719 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: CV4GD74SSYXR33ML27BFMHEMNNXQ4CQC X-Message-ID-Hash: CV4GD74SSYXR33ML27BFMHEMNNXQ4CQC 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: Those types represent FRR prefix lists inside a section config format. For an example of the exact format and its FRR representation see the module-level documentation. Contrary to most SDN entities, prefix list IDs can be 32 characters long instead of 8 and support underscores as well as hyphens. This is because the restriction of having to generate network interface names does not apply to FRR entities, so we can be more lenient with IDs here, allowing users to specify more descriptive names. There is support for specifying the sequence number of the prefix list entry, but it will be backend-only. FRR supports auto-generating the sequence numbers, so it is sufficient to write them to the FRR configuration in the desired order. The API types are currently equivalent to the section config types, but are still contained as type aliases in their own module, to make changing that in the future easier, since it requires no changes to call sites as long as the exposed API of the struct is the same. Signed-off-by: Stefan Hanreich --- proxmox-ve-config/src/sdn/mod.rs | 1 + proxmox-ve-config/src/sdn/prefix_list.rs | 160 +++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 proxmox-ve-config/src/sdn/prefix_list.rs diff --git a/proxmox-ve-config/src/sdn/mod.rs b/proxmox-ve-config/src/sdn/mod.rs index 86dcd93..344c02c 100644 --- a/proxmox-ve-config/src/sdn/mod.rs +++ b/proxmox-ve-config/src/sdn/mod.rs @@ -1,6 +1,7 @@ pub mod config; pub mod fabric; pub mod ipam; +pub mod prefix_list; use std::{error::Error, fmt::Display, str::FromStr}; diff --git a/proxmox-ve-config/src/sdn/prefix_list.rs b/proxmox-ve-config/src/sdn/prefix_list.rs new file mode 100644 index 0000000..f4988d9 --- /dev/null +++ b/proxmox-ve-config/src/sdn/prefix_list.rs @@ -0,0 +1,160 @@ +//! Section config types for FRR Prefix Lists. +//! +//! This module contains the API types for representing FRR Prefix Lists as section config. Each +//! entry in the section config represents a Prefix List and its entries. +//! +//! A simple FRR Prefix List looks like this: +//! +//! ```text +//! ip prefix-list example-list permit 192.0.2.0/24 ge 25 le 26 +//! ip prefix-list example-list permit 192.0.2.0/24 le 28 ge 29 +//! ip prefix-list example-list deny 192.0.2.0/24 le 24 +//! ``` +//! +//! The corresponding section config entry looks like this: +//! +//! ```text +//! prefix-list: example_list +//! entries action=permit,prefix=192.0.2.0/24,ge=25,le=26 +//! entries action=permit,prefix=192.0.2.0/24,ge=28,le=29 +//! entries action=deny,prefix=192.0.2.0/24,le=24 +//! ``` + +use const_format::concatcp; +use serde::{Deserialize, Serialize}; + +use proxmox_network_types::Cidr; +use proxmox_schema::{ + api, api_string_type, const_regex, property_string::PropertyString, ApiStringFormat, Updater, + UpdaterType, +}; + +pub const PREFIX_LIST_ID_REGEX_STR: &str = + r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-_]){0,30}(?:[a-zA-Z0-9]){0,1})"; + +const_regex! { + pub PREFIX_LIST_ID_REGEX = concatcp!(r"^", PREFIX_LIST_ID_REGEX_STR, r"$"); +} + +pub const PREFIX_LIST_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PREFIX_LIST_ID_REGEX); + +api_string_type! { + /// ID of a Prefix List. + #[api(format: &PREFIX_LIST_ID_FORMAT)] + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, UpdaterType)] + pub struct PrefixListId(String); +} + +#[api()] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +/// Action for an entry in a Prefix List. +pub enum PrefixListAction { + /// permit + Permit, + /// deny + Deny, +} + +#[api( + properties: { + entries: { + type: Array, + optional: true, + items: { + type: String, + description: "An entry in a prefix list", + format: &ApiStringFormat::PropertyString(&PrefixListEntry::API_SCHEMA), + } + }, + } +)] +#[derive(Debug, Clone, Serialize, Deserialize, Updater)] +/// IP Prefix List +/// +/// Corresponds to the FRR IP Prefix lists, as described in its [documentation](https://docs.frrouting.org/en/latest/filter.html#ip-prefix-list) +pub struct PrefixListSection { + #[updater(skip)] + id: PrefixListId, + /// The entries in this prefix list + pub entries: Vec>, +} + +impl PrefixListSection { + /// Return the ID of the Prefix List. + pub fn id(&self) -> &PrefixListId { + &self.id + } +} + +#[api()] +#[derive(Debug, Clone, Serialize, Deserialize)] +/// IP Prefix List Entry +/// +/// Corresponds to the FRR IP Prefix lists, as described in its [documentation](https://docs.frrouting.org/en/latest/filter.html#ip-prefix-list) +pub struct PrefixListEntry { + action: PrefixListAction, + prefix: Cidr, + /// Prefix length - entry will be applied if the prefix length is less than or equal to this + /// value. + #[serde(skip_serializing_if = "Option::is_none")] + le: Option, + /// Prefix length - entry will be applied if the prefix length is greater than or equal to this + /// value. + #[serde(skip_serializing_if = "Option::is_none")] + ge: Option, + /// The sequence number for this prefix list entry. + #[serde(skip_serializing_if = "Option::is_none")] + seq: Option, +} + +#[api( + "id-property": "id", + "id-schema": { + type: String, + description: "Prefix List Section ID", + format: &PREFIX_LIST_ID_FORMAT, + }, + "type-key": "type", +)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", tag = "type")] +pub enum PrefixList { + PrefixList(PrefixListSection), +} + +pub mod api { + use super::*; + + pub type PrefixList = PrefixListSection; + pub type PrefixListUpdater = PrefixListSectionUpdater; + + #[derive(Debug, Clone, Serialize, Deserialize)] + #[serde(rename_all = "kebab-case")] + /// Deletable properties for [`PrefixList`]. + pub enum PrefixListDeletableProperties { + Entries, + } +} + +#[cfg(test)] +mod tests { + use proxmox_section_config::typed::ApiSectionDataEntry; + + use super::*; + + #[test] + fn test_simple_prefix_list() -> Result<(), anyhow::Error> { + let section_config = r#" +prefix-list: somelist + 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 +"#; + + PrefixList::parse_section_config("prefix-lists.cfg", section_config)?; + Ok(()) + } +} -- 2.47.3