[PATCH proxmox-ve-rs v5 11/46] ve-config: add prefix list section config

Stefan Hanreich
2026-05-05T15:38:49Z

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 for now 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 order
specified in the section config).

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 <s.hanreich@proxmox.com>
---
proxmox-ve-config/src/sdn/mod.rs | 1 +
proxmox-ve-config/src/sdn/prefix_list.rs | 162 +++++++++++++++++++++++
2 files changed, 163 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..33dbbe4
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/prefix_list.rs
@@ -0,0 +1,162 @@
+//! 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
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ #[updater(serde(skip_serializing_if = "Option::is_none"))]
+ pub entries: Vec<PropertyString<PrefixListEntry>>,
+}
+
+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<u32>,
+ /// 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<u32>,
+ /// The sequence number for this prefix list entry.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ seq: Option<u32>,
+}
+
+#[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