From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox-ve-rs v2 13/34] ve-config: add route map section config
Date: Wed, 1 Apr 2026 16:39:22 +0200 [thread overview]
Message-ID: <20260401143957.386809-14-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20260401143957.386809-1-s.hanreich@proxmox.com>
Those types represent FRR route maps inside a section config format.
For an example of the exact format and its FRR representation see the
module-level documentation.
One section config entry maps to one route map entry. A route map
consists of one or more route map entries inside the section config.
The ID of a section encodes the name of the route map as well as the
order # of the entry.
The route map module exports specific types for the API that handle
converting the section config ID, because currently it is only
possible to deserialize section config IDs to Strings. To avoid
having to implement the parsing logic along every step of the stack
(Perl backend, UI), use specific API types in the public API that
handle parsing the section ID into route map name and order.
Contrary to most SDN entities, route maps 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.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-ve-config/src/sdn/mod.rs | 1 +
proxmox-ve-config/src/sdn/route_map.rs | 581 +++++++++++++++++++++++++
2 files changed, 582 insertions(+)
create mode 100644 proxmox-ve-config/src/sdn/route_map.rs
diff --git a/proxmox-ve-config/src/sdn/mod.rs b/proxmox-ve-config/src/sdn/mod.rs
index 344c02c..24069ad 100644
--- a/proxmox-ve-config/src/sdn/mod.rs
+++ b/proxmox-ve-config/src/sdn/mod.rs
@@ -2,6 +2,7 @@ pub mod config;
pub mod fabric;
pub mod ipam;
pub mod prefix_list;
+pub mod route_map;
use std::{error::Error, fmt::Display, str::FromStr};
diff --git a/proxmox-ve-config/src/sdn/route_map.rs b/proxmox-ve-config/src/sdn/route_map.rs
new file mode 100644
index 0000000..61607d7
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/route_map.rs
@@ -0,0 +1,581 @@
+//! Section config types for FRR Route Maps.
+//!
+//! This module contains the API types required for representing FRR Route Maps as section config.
+//! Each entry in the section config maps to a Route Map entry, *not* a route map as a whole, the
+//! order of the entry is encoded in the ID of the Route Map.
+//!
+//! Route maps in FRR consists of at least one entry, which are ordered by their given sequence
+//! number / order. Each entry has a default matching policy, which is applied if the matching
+//! conditions of the entry are met.
+//!
+//! An example for a simple FRR Route Map entry looks like this:
+//!
+//! ```text
+//! route-map test permit 10
+//! match ip next-hop address 192.0.2.1
+//! set local-preference 200
+//! on-match goto 1234
+//!
+//! route-map test permit 20
+//! call some-other-routemap
+//! ```
+//!
+//! The corresponding representation as a section config entry looks like this:
+//!
+//! ```text
+//! route-map-entry: test_10
+//! action permit
+//! match key=ip-next-hop-address,value=192.0.2.1
+//! set key=local-preference,value=200
+//! exit-action key=on-match-goto,value=1234
+//!
+//! route-map-entry: test_20
+//! call some-other-routemap
+//! ```
+//!
+//! Match / Set Actions and Exit Policies are encoded as an array with a property string that has a
+//! key and an optional value paramter, because some options do not require an additional value.
+
+use std::net::IpAddr;
+
+use anyhow::format_err;
+use const_format::concatcp;
+
+use proxmox_network_types::ip_address::api_types::{Ipv4Addr, Ipv6Addr};
+use proxmox_sdn_types::{
+ bgp::{EvpnRouteType, SetMetricValue, SetTagValue},
+ IntegerWithSign, Vni,
+};
+use serde::{Deserialize, Serialize};
+
+use proxmox_schema::{
+ api, api_string_type, const_regex, property_string::PropertyString, ApiStringFormat, ApiType,
+ EnumEntry, ObjectSchema, Schema, StringSchema, Updater, UpdaterType,
+};
+
+use crate::sdn::prefix_list::PrefixListId;
+
+pub const ROUTE_MAP_ID_REGEX_STR: &str =
+ r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-_]){0,30}(?:[a-zA-Z0-9]){0,1})";
+
+pub const ROUTE_MAP_ORDER_REGEX_STR: &str = r"\d+";
+
+const_regex! {
+ pub ROUTE_MAP_ID_REGEX = concatcp!(r"^", ROUTE_MAP_ID_REGEX_STR, r"$");
+ pub ROUTE_MAP_SECTION_ID_REGEX = concatcp!(r"^", ROUTE_MAP_ID_REGEX_STR, r"_", ROUTE_MAP_ORDER_REGEX_STR, r"$");
+}
+
+pub const ROUTE_MAP_SECTION_ID_FORMAT: ApiStringFormat =
+ ApiStringFormat::Pattern(&ROUTE_MAP_SECTION_ID_REGEX);
+
+pub const ROUTE_MAP_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&ROUTE_MAP_ID_REGEX);
+
+api_string_type! {
+ /// ID of a Route Map..
+ #[api(format: &ROUTE_MAP_ID_FORMAT)]
+ #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, UpdaterType)]
+ pub struct RouteMapId(String);
+}
+
+/// The ID of a Route Map entry in the section config (name + order).
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct RouteMapEntryId {
+ /// name of the Route Map
+ route_map_id: RouteMapId,
+ /// seq nr of the Route Map
+ order: u16,
+}
+
+impl RouteMapEntryId {
+ /// Create a new Route Map Entry ID.
+ pub fn new(route_map_id: RouteMapId, order: u16) -> Self {
+ Self {
+ route_map_id,
+ order,
+ }
+ }
+
+ /// Returns the name part of the Route Map section id.
+ pub fn route_map_id(&self) -> &RouteMapId {
+ &self.route_map_id
+ }
+
+ /// Returns the order part of the Route Map section id.
+ pub fn order(&self) -> u16 {
+ self.order
+ }
+}
+
+impl ApiType for RouteMapEntryId {
+ const API_SCHEMA: Schema = StringSchema::new("ID of a SDN node in the section config")
+ .format(&ROUTE_MAP_SECTION_ID_FORMAT)
+ .schema();
+}
+
+impl std::fmt::Display for RouteMapEntryId {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}_{}", self.route_map_id, self.order)
+ }
+}
+
+proxmox_serde::forward_serialize_to_display!(RouteMapEntryId);
+
+impl std::str::FromStr for RouteMapEntryId {
+ type Err = anyhow::Error;
+
+ fn from_str(value: &str) -> Result<Self, Self::Err> {
+ let (name, order) = value
+ .rsplit_once("_")
+ .ok_or_else(|| format_err!("invalid RouteMap section id: {}", value))?;
+
+ Ok(Self {
+ route_map_id: RouteMapId::from_string(name.to_string())?,
+ order: order.parse()?,
+ })
+ }
+}
+
+proxmox_serde::forward_deserialize_to_from_str!(RouteMapEntryId);
+
+#[api(
+ "id-property": "id",
+ "id-schema": {
+ type: String,
+ description: "Route Map Section ID",
+ format: &ROUTE_MAP_SECTION_ID_FORMAT,
+ },
+ "type-key": "type",
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", tag = "type")]
+/// The Route Map section config type.
+pub enum RouteMap {
+ RouteMapEntry(RouteMapEntry),
+}
+
+#[api()]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+/// Matching policy of a Route Map entry.
+pub enum RouteMapAction {
+ /// Permit
+ Permit,
+ /// Deny
+ Deny,
+}
+
+/// The exit action for a route map.
+///
+/// This can be optionally specified to override the default behavior of FRR to terminate
+/// evaluating the route map if an entry matches.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "key", content = "value", rename_all = "kebab-case")]
+pub enum ExitAction {
+ OnMatchNext,
+ OnMatchGoto(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u16")] u16),
+ Continue(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u16")] u16),
+}
+
+impl ApiType for ExitAction {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "Exit action for a FRR route map.",
+ &[
+ (
+ "key",
+ false,
+ &StringSchema::new("The key indicating which value should be set.")
+ .format(&ApiStringFormat::Enum(&[
+ EnumEntry::new(
+ "on-match-next",
+ "Proceed with the next entry in the route map.",
+ ),
+ EnumEntry::new("continue", "Continue with route map entry with order <value>."),
+ EnumEntry::new(
+ "on-match-goto",
+ "Continue processing the route map with the first entry with order >= <value>.",
+ ),
+ ]))
+ .schema(),
+ ),
+ (
+ "value",
+ true,
+ &StringSchema::new("The value that should be set - depends on the given key.")
+ .schema(),
+ ),
+ ],
+ )
+ .schema();
+}
+
+impl UpdaterType for ExitAction {
+ type Updater = Option<ExitAction>;
+}
+
+#[api(
+ properties: {
+ set: {
+ type: Array,
+ description: "A list of Set actions to perform in this entry.",
+ optional: true,
+ items: {
+ type: String,
+ description: "A specific Set action.",
+ format: &ApiStringFormat::PropertyString(&SetAction::API_SCHEMA),
+ }
+ },
+ "match": {
+ type: Array,
+ description: "A list of Match actions to perform in this entry.",
+ optional: true,
+ items: {
+ type: String,
+ description: "A specific match action.",
+ format: &ApiStringFormat::PropertyString(&MatchAction::API_SCHEMA),
+ }
+ },
+ "exit-action": {
+ type: String,
+ description: "Exit action for the route map.",
+ optional: true,
+ format: &ApiStringFormat::PropertyString(&ExitAction::API_SCHEMA),
+ },
+ }
+)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+/// Route Map Entry
+///
+/// Represents one entry in a Route Map. One Route Map is made up of one or more entries, that are
+/// executed in order of their ordering number.
+#[serde(rename_all = "kebab-case")]
+pub struct RouteMapEntry {
+ id: RouteMapEntryId,
+ action: RouteMapAction,
+ #[serde(default, rename = "set")]
+ set_actions: Vec<PropertyString<SetAction>>,
+ #[serde(default, rename = "match")]
+ match_actions: Vec<PropertyString<MatchAction>>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ call: Option<RouteMapId>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ exit_action: Option<PropertyString<ExitAction>>,
+}
+
+impl RouteMapEntry {
+ /// Return the ID of the Route Map.
+ pub fn id(&self) -> &RouteMapEntryId {
+ &self.id
+ }
+
+ /// Sets the action for this entry.
+ pub fn set_action(&mut self, action: RouteMapAction) {
+ self.action = action;
+ }
+
+ /// Set the set actions for this route map entry.
+ pub fn set_set_actions(
+ &mut self,
+ set_actions: impl IntoIterator<Item = PropertyString<SetAction>>,
+ ) {
+ self.set_actions = set_actions.into_iter().collect();
+ }
+
+ /// Set the match actions for this route map entry.
+ pub fn set_match_actions(
+ &mut self,
+ match_actions: impl IntoIterator<Item = PropertyString<MatchAction>>,
+ ) {
+ self.match_actions = match_actions.into_iter().collect();
+ }
+
+ pub fn set_call(&mut self, call: Option<RouteMapId>) {
+ self.call = call;
+ }
+
+ pub fn set_exit_action(&mut self, exit_action: Option<PropertyString<ExitAction>>) {
+ self.exit_action = exit_action;
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", tag = "key", content = "value")]
+/// A Route Map set action
+pub enum SetAction {
+ IpNextHopPeerAddress,
+ IpNextHopUnchanged,
+ IpNextHop(Ipv4Addr),
+ Ip6NextHopPeerAddress,
+ Ip6NextHopPreferGlobal,
+ Ip6NextHop(Ipv6Addr),
+ Weight(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32),
+ Tag(SetTagValue),
+ Metric(SetMetricValue),
+ LocalPreference(IntegerWithSign),
+ Src(IpAddr),
+}
+
+impl ApiType for SetAction {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "FRR set action",
+ &[
+ (
+ "key",
+ false,
+ &StringSchema::new("The key indicating which value should be set.")
+ .format(&ApiStringFormat::Enum(&[
+ EnumEntry::new(
+ "ip-next-hop-peer-address",
+ "Sets the BGP nexthop address to the IPv4 peer address.",
+ ),
+ EnumEntry::new("ip-next-hop-unchanged", "Leaves the nexthop unchanged."),
+ EnumEntry::new(
+ "ip-next-hop",
+ "Sets the nexthop to the given IPv4 address.",
+ ),
+ EnumEntry::new(
+ "ip6-next-hop-peer-address",
+ "Sets the BGP nexthop address to the IPv6 peer address.",
+ ),
+ EnumEntry::new(
+ "ip6-next-hop-prefer-global",
+ "If a LLA and GUA are received, prefer the GUA.",
+ ),
+ EnumEntry::new(
+ "ip6-next-hop",
+ "Sets the nexthop to the given IPv6 address.",
+ ),
+ EnumEntry::new(
+ "local-preference",
+ "Sets the local preference for this route.",
+ ),
+ EnumEntry::new("tag", "Sets a tag for the route."),
+ EnumEntry::new("weight", "Sets the weight for the route."),
+ EnumEntry::new("metric", "Sets the metric for the route."),
+ EnumEntry::new(
+ "src",
+ "The source address to insert into the kernel routing table.",
+ ),
+ ]))
+ .schema(),
+ ),
+ (
+ "value",
+ true,
+ &StringSchema::new("The value that should be set - depends on the given key.")
+ .schema(),
+ ),
+ ],
+ )
+ .schema();
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", tag = "key", content = "value")]
+pub enum MatchAction {
+ RouteType(EvpnRouteType),
+ Vni(Vni),
+ IpAddressPrefixList(PrefixListId),
+ Ip6AddressPrefixList(PrefixListId),
+ IpNextHopPrefixList(PrefixListId),
+ Ip6NextHopPrefixList(PrefixListId),
+ IpNextHopAddress(Ipv4Addr),
+ Ip6NextHopAddress(Ipv6Addr),
+ Tag(SetTagValue),
+ Metric(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32),
+ LocalPreference(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32),
+ Peer(String),
+}
+
+impl ApiType for MatchAction {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "FRR set action",
+ &[
+ (
+ "key",
+ false,
+ &StringSchema::new("The key indicating on which value to match.")
+ .format(&ApiStringFormat::Enum(&[
+ EnumEntry::new("route-type", "Match the EVPN route type."),
+ EnumEntry::new("vni", "Match the VNI of an EVPN route."),
+ EnumEntry::new(
+ "ip-address-prefix-list",
+ "Match the IPv4 CIDR to a prefix-list.",
+ ),
+ EnumEntry::new(
+ "ip6-address-prefix-list",
+ "Match the IPv6 CIDR to a prefix-list",
+ ),
+ EnumEntry::new(
+ "ip-next-hop-prefix-list",
+ "Match the IPv4 next-hop to a prefix-list.",
+ ),
+ EnumEntry::new(
+ "ip6-next-hop-prefix-list",
+ "Match the IPv4 next-hop to a prefix-list.",
+ ),
+ EnumEntry::new(
+ "ip-next-hop-address",
+ "Match the next-hop to an IPv4 address.",
+ ),
+ EnumEntry::new(
+ "ip6-next-hop-address",
+ "Match the next-hop to an IPv6 address.",
+ ),
+ EnumEntry::new("metric", "Match the metric of the route."),
+ EnumEntry::new("local-preference", "Match the local preference."),
+ EnumEntry::new(
+ "peer",
+ "Match the peer IP address, interface name or peer group.",
+ ),
+ ]))
+ .schema(),
+ ),
+ (
+ "value",
+ true,
+ &StringSchema::new("The value that should be matched - depends on the given key.")
+ .schema(),
+ ),
+ ],
+ )
+ .schema();
+}
+
+pub mod api {
+ //! API type for Route Map Entries.
+ //!
+ //! Since Route Map Entries encode information in their ID, these types help converting to /
+ //! from the Section Config types.
+ use super::*;
+
+ #[api(
+ properties: {
+ set: {
+ type: Array,
+ description: "A list of set actions for this Route Map entry",
+ optional: true,
+ items: {
+ type: String,
+ description: "A set action",
+ format: &ApiStringFormat::PropertyString(&SetAction::API_SCHEMA),
+ }
+ },
+ "match": {
+ type: Array,
+ description: "A list of match actions for this Route Map entry",
+ optional: true,
+ items: {
+ type: String,
+ description: "A match action",
+ format: &ApiStringFormat::PropertyString(&MatchAction::API_SCHEMA),
+ }
+ },
+ "exit-action": {
+ type: String,
+ description: "Exit action for the route map.",
+ optional: true,
+ format: &ApiStringFormat::PropertyString(&ExitAction::API_SCHEMA),
+ },
+ }
+ )]
+ #[derive(Debug, Clone, Serialize, Deserialize, Updater)]
+ #[serde(rename_all = "kebab-case")]
+ /// Route Map entry
+ pub struct RouteMapEntry {
+ /// name of the Route Map
+ #[updater(skip)]
+ pub route_map_id: RouteMapId,
+ /// seq nr of the Route Map
+ #[updater(skip)]
+ #[serde(deserialize_with = "proxmox_serde::perl::deserialize_u16")]
+ pub order: u16,
+ pub action: RouteMapAction,
+ #[serde(default, rename = "set")]
+ pub set_actions: Vec<PropertyString<SetAction>>,
+ #[serde(default, rename = "match")]
+ pub match_actions: Vec<PropertyString<MatchAction>>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub call: Option<RouteMapId>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub exit_action: Option<PropertyString<ExitAction>>,
+ }
+
+ impl RouteMapEntry {
+ /// Return the ID of the Route Map this entry belongs to.
+ pub fn route_map_id(&self) -> &RouteMapId {
+ &self.route_map_id
+ }
+
+ /// Return the order for this Route Map entry.
+ pub fn order(&self) -> u16 {
+ self.order
+ }
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize, Hash)]
+ #[serde(rename_all = "kebab-case")]
+ /// Deletable properties for Route Map entries.
+ pub enum RouteMapDeletableProperties {
+ Set,
+ Match,
+ Call,
+ ExitAction,
+ }
+
+ impl From<super::RouteMapEntry> for RouteMapEntry {
+ fn from(value: super::RouteMapEntry) -> RouteMapEntry {
+ RouteMapEntry {
+ route_map_id: value.id.route_map_id,
+ order: value.id.order,
+ action: value.action,
+ set_actions: value.set_actions,
+ match_actions: value.match_actions,
+ call: value.call,
+ exit_action: value.exit_action,
+ }
+ }
+ }
+
+ impl From<RouteMapEntry> for super::RouteMapEntry {
+ fn from(value: RouteMapEntry) -> super::RouteMapEntry {
+ super::RouteMapEntry {
+ id: RouteMapEntryId {
+ route_map_id: value.route_map_id,
+ order: value.order,
+ },
+ action: value.action,
+ set_actions: value.set_actions,
+ match_actions: value.match_actions,
+ call: value.call,
+ exit_action: value.exit_action,
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use proxmox_section_config::typed::ApiSectionDataEntry;
+
+ use super::*;
+
+ #[test]
+ fn test_simple_route_map() -> Result<(), anyhow::Error> {
+ let section_config = r#"
+route-map-entry: test_underscore_123
+ action permit
+ set key=tag,value=23487
+ set key=tag,value=untagged
+ set key=metric,value=+rtt
+ set key=local-preference,value=-12345
+ set key=ip-next-hop,value=192.0.2.0
+ match key=vni,value=23487
+ match key=vni,value=23487
+ call some-other-route-map
+ exit-action key=on-match-goto,value=1234
+"#;
+
+ RouteMap::parse_section_config("route-maps.cfg", section_config)?;
+ Ok(())
+ }
+}
--
2.47.3
next prev parent reply other threads:[~2026-04-01 14:44 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-01 14:39 [PATCH access-control/cluster/network/proxmox{-ve-rs,-perl-rs} v2 00/34] Add support for route maps / prefix lists to SDN Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-cluster v2 01/34] cfs: add 'sdn/route-maps.cfg' to observed files Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-cluster v2 02/34] cfs: add 'sdn/prefix-lists.cfg' " Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-access-control v2 03/34] permissions: add ACL path for prefix-lists and route-maps Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 04/34] frr: add constructor to prefix list name Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 05/34] sdn-types: add common route-map helper types Stefan Hanreich
2026-04-02 13:36 ` Wolfgang Bumiller
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 06/34] frr: change order type to u16 Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 07/34] frr: implement routemap match/set statements via adjacent tagging Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 08/34] frr: implement support for call and exit action Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 09/34] frr-templates: change route maps template to adapt to new frr types Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 10/34] ve-config: fabrics: adapt frr config generation Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 11/34] ve-config: add prefix list section config Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 12/34] ve-config: frr: implement frr config generation for prefix lists Stefan Hanreich
2026-04-01 14:39 ` Stefan Hanreich [this message]
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 14/34] ve-config: frr: implement frr config generation for route maps Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 15/34] ve-config: add prefix lists integration tests Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-ve-rs v2 16/34] ve-config: add route maps " Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-perl-rs v2 17/34] pve-rs: sdn: add route maps module Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-perl-rs v2 18/34] pve-rs: sdn: add prefix lists module Stefan Hanreich
2026-04-01 14:39 ` [PATCH proxmox-perl-rs v2 19/34] sdn: add prefix list / route maps to frr config generation helper Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 20/34] controller: bgp: evpn: adapt to new match / set frr config syntax Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 21/34] sdn: add prefix lists module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 22/34] api2: add prefix list module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 23/34] sdn: add route map module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 24/34] api2: add route maps api module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 25/34] api2: add route map module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 26/34] api2: add route map entry module Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 27/34] evpn controller: add route_map_{in,out} parameter Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 28/34] bgp controller: allow configuring custom route maps Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 29/34] sdn: change detection for route maps / prefix lists Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 30/34] sdn: generate route map / prefix list configuration on sdn apply Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 31/34] tests: add simple route map test case Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 32/34] tests: add bgp evpn route map/prefix list testcase Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 33/34] tests: add route map with prefix " Stefan Hanreich
2026-04-01 14:39 ` [PATCH pve-network v2 34/34] tests: add exit node with custom route map testcase Stefan Hanreich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260401143957.386809-14-s.hanreich@proxmox.com \
--to=s.hanreich@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox