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 0EC6C1FF13B for ; Wed, 25 Mar 2026 10:42:15 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4F602E93C; Wed, 25 Mar 2026 10:42:14 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-ve-rs 1/9] sdn-types: add common route-map helper types Date: Wed, 25 Mar 2026 10:41:14 +0100 Message-ID: <20260325094142.174364-4-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: 1774431663402 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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: ZR22W2NFT54TJMWD26TLTHL3AMU2GLMY X-Message-ID-Hash: ZR22W2NFT54TJMWD26TLTHL3AMU2GLMY 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: The reason for including those types here is that they are used in proxmox-frr for generating the FRR configuratiobn as well as proxmox-ve-config for saving them inside a section config. For some values in route maps FRR supports specifying either an absolute value or a value relative to the existing value when modifying a route via a route map. E.g. 123 would set the value to 123, whereas +/-123 would add/subtract 123 from the existing value. IntegerWithSign can be used to represent such a value in the section config. Metric supports this notation as well as some magical values (e.g. subtracting the round-trip-time). Signed-off-by: Stefan Hanreich --- proxmox-sdn-types/src/bgp.rs | 50 +++++++++++++ proxmox-sdn-types/src/lib.rs | 135 +++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 proxmox-sdn-types/src/bgp.rs diff --git a/proxmox-sdn-types/src/bgp.rs b/proxmox-sdn-types/src/bgp.rs new file mode 100644 index 0000000..168bc1a --- /dev/null +++ b/proxmox-sdn-types/src/bgp.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + +use crate::IntegerWithSign; + +/// Represents a BGP metric value, as used in FRR. +/// +/// A metric can either be a numeric value, or certain 'magic' values. For more information see the +/// respective enum variants. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum SetMetricValue { + /// Set the metric to the round-trip-time. + #[serde(rename = "rtt")] + Rtt, + /// Add the round-trip-time to the metric. + #[serde(rename = "+rtt")] + AddRtt, + /// Subtract the round-trip-time from the metric. + #[serde(rename = "-rtt")] + SubtractRtt, + /// Use the IGP value when importing from another IGP. + #[serde(rename = "igp")] + Igp, + /// Use the accumulated IGP value when importing from another IGP. + #[serde(rename = "aigp")] + Aigp, + /// Set the metric to a fixed numeric value. + #[serde(untagged)] + Numeric(IntegerWithSign), +} + +/// An EVPN route-type, as used in the FRR route maps. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum EvpnRouteType { + Ead, + MacIp, + Multicast, + #[serde(rename = "es")] + EthernetSegment, + Prefix, +} + +/// An tag value, as used in the FRR route maps. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SetTagValue { + Untagged, + #[serde(untagged)] + Numeric(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32), +} diff --git a/proxmox-sdn-types/src/lib.rs b/proxmox-sdn-types/src/lib.rs index 1656f1d..fe4c641 100644 --- a/proxmox-sdn-types/src/lib.rs +++ b/proxmox-sdn-types/src/lib.rs @@ -1,3 +1,138 @@ pub mod area; +pub mod bgp; pub mod net; pub mod openfabric; + +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Serialize}; + +use proxmox_schema::api; + +/// Enum for representing signedness of Integer in [`IntegerWithSign`]. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum Sign { + #[serde(rename = "-")] + Negative, + #[serde(rename = "+")] + Positive, +} + +proxmox_serde::forward_display_to_serialize!(Sign); +proxmox_serde::forward_from_str_to_deserialize!(Sign); + +/// An Integer with an optional [`Sign`]. +/// +/// This is used for representing certain keys in the FRR route maps (e.g. metric). They can be set +/// to either a static value (no sign) or to a value relative to the existing value (with sign). +/// For instance, a value of 50 would set the metric to 50, but a value of +50 would add 50 to the +/// existing metric value. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct IntegerWithSign { + pub(crate) sign: Option, + pub(crate) n: u32, +} + +impl std::fmt::Display for IntegerWithSign { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if let Some(sign) = self.sign.as_ref() { + write!(f, "{}{}", sign, self.n) + } else { + self.n.fmt(f) + } + } +} + +impl std::str::FromStr for IntegerWithSign { + type Err = anyhow::Error; + + fn from_str(s: &str) -> core::result::Result { + if let Some(n) = s.strip_prefix("+") { + return Ok(Self { + sign: Some(Sign::Positive), + n: n.parse()?, + }); + } + + if let Some(n) = s.strip_prefix("-") { + return Ok(Self { + sign: Some(Sign::Negative), + n: n.parse()?, + }); + } + + Ok(Self { + sign: None, + n: s.parse()?, + }) + } +} + +impl<'de> Deserialize<'de> for IntegerWithSign { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = IntegerWithSign; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("An integer with an optional leading sign") + } + + fn visit_i128(self, value: i128) -> Result { + Ok(IntegerWithSign { + sign: None, + n: u32::try_from(value).map_err(E::custom)?, + }) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(IntegerWithSign { + sign: None, + n: u32::try_from(value).map_err(E::custom)?, + }) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(IntegerWithSign { + sign: None, + n: u32::try_from(value).map_err(E::custom)?, + }) + } + + fn visit_u128(self, value: u128) -> Result { + Ok(IntegerWithSign { + sign: None, + n: u32::try_from(value).map_err(E::custom)?, + }) + } + + fn visit_str(self, v: &str) -> Result { + v.parse().map_err(E::custom) + } + } + + deserializer.deserialize_any(V) + } +} + +proxmox_serde::forward_serialize_to_display!(IntegerWithSign); + +#[api( + type: Integer, + minimum: 1, + maximum: 16_777_215, +)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] +/// Represents a VXLAN VNI (24-bit unsigned integer). +pub struct Vni(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32); + +impl Vni { + /// Returns the VNI as u32. + pub fn as_u32(&self) -> u32 { + self.0 + } +} -- 2.47.3