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 E6A7E1FF13B for ; Wed, 25 Mar 2026 10:41:46 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0D5F6E225; Wed, 25 Mar 2026 10:41:57 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs 2/9] frr: implement routemap match/set statements via adjacent tagging Date: Wed, 25 Mar 2026 10:41:15 +0100 Message-ID: <20260325094142.174364-5-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: 1774431663467 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.721 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: HUBJ7ILMG3TOZAQENHWKIEOLRLAAOGMN X-Message-ID-Hash: HUBJ7ILMG3TOZAQENHWKIEOLRLAAOGMN 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: Previously the types used a mix of adjacent / internal tagging and a nesting of types to represent match and set statements. This has been simplified by utilizing adjacent tagging on the set / match statements and using the exact FRR configuration key as the tag. This way a single enum can be used to represent match / set statements and all variants can be rendered the same by simply printing the keys / values. This commit also adds a lot of new match / set statements that were previously not supported. The crate supports now almost all match / set statements that FRR supports - with only a few having been omitted. Most notably it is not possible to match on community lists, support for those is planned in a future patch series. Signed-off-by: Stefan Hanreich --- proxmox-frr/Cargo.toml | 2 +- proxmox-frr/src/ser/route_map.rs | 101 +++++++++++++++++++------------ 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/proxmox-frr/Cargo.toml b/proxmox-frr/Cargo.toml index 37a112e..1dbbb84 100644 --- a/proxmox-frr/Cargo.toml +++ b/proxmox-frr/Cargo.toml @@ -17,7 +17,7 @@ serde = { workspace = true, features = [ "derive" ] } serde_repr = "0.1" minijinja = { version = "2.5", features = [ "multi_template", "loader" ] } -proxmox-network-types = { workspace = true } +proxmox-network-types = { workspace = true, features = ["api-types"] } proxmox-sdn-types = { workspace = true } proxmox-serde = { workspace = true } proxmox-sortable-macro = "1" diff --git a/proxmox-frr/src/ser/route_map.rs b/proxmox-frr/src/ser/route_map.rs index d12ae05..22807f1 100644 --- a/proxmox-frr/src/ser/route_map.rs +++ b/proxmox-frr/src/ser/route_map.rs @@ -1,6 +1,11 @@ +use core::net::{Ipv4Addr, Ipv6Addr}; use std::net::IpAddr; use proxmox_network_types::ip_address::Cidr; +use proxmox_sdn_types::{ + bgp::{EvpnRouteType, SetMetricValue, SetTagValue}, + IntegerWithSign, Vni, +}; use serde::{Deserialize, Serialize}; /// The action for a [`AccessListRule`]. @@ -45,6 +50,12 @@ impl AccessListName { } } +impl PrefixListName { + pub fn new(name: String) -> PrefixListName { + PrefixListName(name) + } +} + /// A FRR access-list. /// /// Holds a vec of rules. Each rule will get its own line, FRR will collect all the rules with the @@ -83,42 +94,36 @@ pub struct VniMatch { /// execute its actions. If we match on an IP, there are two different syntaxes: `match ip ...` or /// `match ipv6 ...`. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "protocol_type")] +#[serde(tag = "key", content = "value")] pub enum RouteMapMatch { - #[serde(rename = "ip")] - V4(RouteMapMatchInner), - #[serde(rename = "ipv6")] - V6(RouteMapMatchInner), - #[serde(rename = "vni")] - Vni(u32), -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "list_type", content = "list_name", rename_all = "lowercase")] -pub enum AccessListOrPrefixList { - PrefixList(PrefixListName), - AccessList(AccessListName), -} - -/// A route-map match statement generic on the IP-version. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "match_type", content = "value", rename_all = "kebab-case")] -pub enum RouteMapMatchInner { - Address(AccessListOrPrefixList), - NextHop(String), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SetIpNextHopValue { - PeerAddress, - Unchanged, - IpAddr(IpAddr), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SetTagValue { - Untagged, - Numeric(u32), + #[serde(rename = "evpn route-type")] + RouteType(EvpnRouteType), + #[serde(rename = "evpn vni")] + Vni(Vni), + #[serde(rename = "ip address")] + IpAddressAccessList(AccessListName), + #[serde(rename = "ipv6 address")] + Ip6AddressAccessList(AccessListName), + #[serde(rename = "ip address prefix-list")] + IpAddressPrefixList(PrefixListName), + #[serde(rename = "ipv6 address prefix-list")] + Ip6AddressPrefixList(PrefixListName), + #[serde(rename = "ip next-hop prefix-list")] + IpNextHopPrefixList(PrefixListName), + #[serde(rename = "ipv6 next-hop prefix-list")] + Ip6NextHopPrefixList(PrefixListName), + #[serde(rename = "ip next-hop address")] + IpNextHopAddress(Ipv4Addr), + #[serde(rename = "ipv6 next-hop address")] + Ip6NextHopAddress(Ipv6Addr), + #[serde(rename = "metric")] + Metric(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32), + #[serde(rename = "local-preference")] + LocalPreference(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32), + #[serde(rename = "peer")] + Peer(String), + #[serde(rename = "tag")] + Tag(SetTagValue), } /// Defines the Action a route-map takes when it matches on a route. @@ -126,12 +131,30 @@ pub enum SetTagValue { /// If the route matches the [`RouteMapMatch`], then a [`RouteMapSet`] action will be executed. /// We currently only use the IpSrc command which changes the source address of the route. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "set_type", content = "value", rename_all = "kebab-case")] +#[serde(tag = "key", content = "value")] pub enum RouteMapSet { - LocalPreference(u32), + #[serde(rename = "ip next-hop peer-address")] + IpNextHopPeerAddress, + #[serde(rename = "ip next-hop unchanged")] + IpNextHopUnchanged, + #[serde(rename = "ip next-hop")] + IpNextHop(Ipv4Addr), + #[serde(rename = "ipv6 next-hop peer-address")] + Ip6NextHopPeerAddress, + #[serde(rename = "ipv6 next-hop prefer-global")] + Ip6NextHopPreferGlobal, + #[serde(rename = "ipv6 next-hop global")] + Ip6NextHop(Ipv6Addr), + #[serde(rename = "local-preference")] + LocalPreference(IntegerWithSign), + #[serde(rename = "tag")] + Tag(SetTagValue), + #[serde(rename = "weight")] + Weight(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32), + #[serde(rename = "metric")] + Metric(SetMetricValue), + #[serde(rename = "src")] Src(IpAddr), - Metric(u32), - Community(String), } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -- 2.47.3