On Wed, Jul 02, 2025 at 04:49:54PM +0200, Gabriel Goller wrote: > From: Stefan Hanreich <s.hanre...@proxmox.com> > > This crate contains SDN specific types, so they can be re-used across > multiple crates (The initial use-case being shared types between > proxmox-frr and proxmox-ve-config). > > This initial commit contains types for the following entities: > * OpenFabric Hello Interval/Multiplier and CSNP Interval > * Network Entity Title (used as Router IDs in IS-IS / OpenFabric) > * OSPF Area > > Co-authored-by: Gabriel Goller <g.gol...@proxmox.com> > Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> > --- > Cargo.toml | 10 + > proxmox-sdn-types/Cargo.toml | 19 ++ > proxmox-sdn-types/debian/changelog | 5 + > proxmox-sdn-types/debian/control | 53 ++++ > proxmox-sdn-types/debian/copyright | 18 ++ > proxmox-sdn-types/debian/debcargo.toml | 7 + > proxmox-sdn-types/src/area.rs | 63 +++++ > proxmox-sdn-types/src/lib.rs | 3 + > proxmox-sdn-types/src/net.rs | 329 +++++++++++++++++++++++++ > proxmox-sdn-types/src/openfabric.rs | 72 ++++++ > proxmox-ve-config/Cargo.toml | 8 +- > 11 files changed, 583 insertions(+), 4 deletions(-) > create mode 100644 proxmox-sdn-types/Cargo.toml > create mode 100644 proxmox-sdn-types/debian/changelog > create mode 100644 proxmox-sdn-types/debian/control > create mode 100644 proxmox-sdn-types/debian/copyright > create mode 100644 proxmox-sdn-types/debian/debcargo.toml > create mode 100644 proxmox-sdn-types/src/area.rs > create mode 100644 proxmox-sdn-types/src/lib.rs > create mode 100644 proxmox-sdn-types/src/net.rs > create mode 100644 proxmox-sdn-types/src/openfabric.rs > > diff --git a/Cargo.toml b/Cargo.toml > index b6e6df77969b..07da9ef70e6d 100644 > --- a/Cargo.toml > +++ b/Cargo.toml > @@ -1,6 +1,7 @@ > [workspace] > members = [ > "proxmox-ve-config", > + "proxmox-sdn-types", > ] > exclude = [ > "build", > @@ -16,4 +17,13 @@ exclude = [ "debian" ] > rust-version = "1.82" > > [workspace.dependencies] > +anyhow = "1" > +const_format = "0.2" > +regex = "1.7" > +serde = { version = "1" } > +serde_with = "3" > +thiserror = "2.0.0" > + > proxmox-network-types = { version = "0.1" } > +proxmox-schema = { version = "4" } > +proxmox-sdn-types = { version = "0.1", path = "proxmox-sdn-types" } > diff --git a/proxmox-sdn-types/Cargo.toml b/proxmox-sdn-types/Cargo.toml > new file mode 100644 > index 000000000000..9a20b071a126 > --- /dev/null > +++ b/proxmox-sdn-types/Cargo.toml > @@ -0,0 +1,19 @@ > +[package] > +name = "proxmox-sdn-types" > +version = "0.1.0" > +authors.workspace = true > +edition.workspace = true > +license.workspace = true > +homepage.workspace = true > +exclude.workspace = true > +rust-version.workspace = true > + > +[dependencies] > +anyhow = { workspace = true } > +const_format = { workspace = true } > +regex = { workspace = true } > +serde = { workspace = true, features = [ "derive" ] } > +serde_with = { workspace = true } > + > +proxmox-schema = { workspace = true, features = [ "api-macro", "api-types" ] > } > +proxmox-serde = { version = "1.0.0", features = [ "perl" ] } > diff --git a/proxmox-sdn-types/debian/changelog > b/proxmox-sdn-types/debian/changelog > new file mode 100644 > index 000000000000..422921c2d1f4 > --- /dev/null > +++ b/proxmox-sdn-types/debian/changelog > @@ -0,0 +1,5 @@ > +rust-proxmox-sdn-types (0.1.0-1) unstable; urgency=medium > + > + * Initial release. > + > + -- Proxmox Support Team <supp...@proxmox.com> Mon, 03 Jun 2024 10:51:11 > +0200 > diff --git a/proxmox-sdn-types/debian/control > b/proxmox-sdn-types/debian/control > new file mode 100644 > index 000000000000..bfdb47eb0b55 > --- /dev/null > +++ b/proxmox-sdn-types/debian/control > @@ -0,0 +1,53 @@ > +Source: rust-proxmox-sdn-types > +Section: rust > +Priority: optional > +Build-Depends: debhelper-compat (= 13), > + dh-sequence-cargo > +Build-Depends-Arch: cargo:native <!nocheck>, > + rustc:native (>= 1.82) <!nocheck>, > + libstd-rust-dev <!nocheck>, > + librust-anyhow-1+default-dev <!nocheck>, > + librust-const-format-0.2+default-dev <!nocheck>, > + librust-proxmox-schema-4+api-macro-dev <!nocheck>, > + librust-proxmox-schema-4+api-types-dev <!nocheck>, > + librust-proxmox-schema-4+default-dev <!nocheck>, > + librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~) <!nocheck>, > + librust-proxmox-serde-0.1+perl-dev (>= 0.1.2-~~) <!nocheck>, > + librust-regex-1+default-dev (>= 1.7-~~) <!nocheck>, > + librust-serde-1+default-dev <!nocheck>, > + librust-serde-1+derive-dev <!nocheck>, > + librust-serde-with-3+default-dev <!nocheck> > +Maintainer: Proxmox Support Team <supp...@proxmox.com> > +Standards-Version: 4.7.0 > +Vcs-Git: git://git.proxmox.com/git/proxmox-ve-rs.git > +Vcs-Browser: https://git.proxmox.com/?p=proxmox-ve-rs.git > +Homepage: https://proxmox.com > +X-Cargo-Crate: proxmox-sdn-types > +Rules-Requires-Root: no > + > +Package: librust-proxmox-sdn-types-dev > +Architecture: any > +Multi-Arch: same > +Depends: > + ${misc:Depends}, > + librust-anyhow-1+default-dev, > + librust-const-format-0.2+default-dev, > + librust-proxmox-schema-4+api-macro-dev, > + librust-proxmox-schema-4+api-types-dev, > + librust-proxmox-schema-4+default-dev, > + librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~), > + librust-proxmox-serde-0.1+perl-dev (>= 0.1.2-~~), > + librust-regex-1+default-dev (>= 1.7-~~), > + librust-serde-1+default-dev, > + librust-serde-1+derive-dev, > + librust-serde-with-3+default-dev > +Provides: > + librust-proxmox-sdn-types+default-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0+default-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0.1-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0.1+default-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0.1.0-dev (= ${binary:Version}), > + librust-proxmox-sdn-types-0.1.0+default-dev (= ${binary:Version}) > +Description: Rust crate "proxmox-sdn-types" - Rust source code > + Source code for Debianized Rust crate "proxmox-sdn-types" > diff --git a/proxmox-sdn-types/debian/copyright > b/proxmox-sdn-types/debian/copyright > new file mode 100644 > index 000000000000..1ea8a56b4f58 > --- /dev/null > +++ b/proxmox-sdn-types/debian/copyright > @@ -0,0 +1,18 @@ > +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ > + > +Files: > + * > +Copyright: 2019 - 2025 Proxmox Server Solutions GmbH <supp...@proxmox.com> > +License: AGPL-3.0-or-later > + This program is free software: you can redistribute it and/or modify it > under > + the terms of the GNU Affero General Public License as published by the Free > + Software Foundation, either version 3 of the License, or (at your option) > any > + later version. > + . > + This program is distributed in the hope that it will be useful, but WITHOUT > + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > FITNESS > + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more > + details. > + . > + You should have received a copy of the GNU Affero General Public License > along > + with this program. If not, see <https://www.gnu.org/licenses/>. > diff --git a/proxmox-sdn-types/debian/debcargo.toml > b/proxmox-sdn-types/debian/debcargo.toml > new file mode 100644 > index 000000000000..87a787e6d03e > --- /dev/null > +++ b/proxmox-sdn-types/debian/debcargo.toml > @@ -0,0 +1,7 @@ > +overlay = "." > +crate_src_path = ".." > +maintainer = "Proxmox Support Team <supp...@proxmox.com>" > + > +[source] > +vcs_git = "git://git.proxmox.com/git/proxmox-ve-rs.git" > +vcs_browser = "https://git.proxmox.com/?p=proxmox-ve-rs.git" > diff --git a/proxmox-sdn-types/src/area.rs b/proxmox-sdn-types/src/area.rs > new file mode 100644 > index 000000000000..71d2d53ba02f > --- /dev/null > +++ b/proxmox-sdn-types/src/area.rs > @@ -0,0 +1,63 @@ > +use std::{fmt::Display, net::Ipv4Addr}; > + > +use anyhow::Error; > +use proxmox_schema::{ApiType, Schema, StringSchema, UpdaterType}; > +use serde_with::{DeserializeFromStr, SerializeDisplay}; > + > +/// An OSPF Area. > +/// > +/// Internally the area is just a 32 bit number and is often represented in > dotted-decimal > +/// notation, like an IPv4. FRR also allows us to specify it as a number or > an IPv4-Address. > +/// To keep a nice user experience we keep whichever format the user entered. > +#[derive( > + Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, > PartialOrd, Ord, > +)] > +pub enum Area { > + Number(u32), > + IpAddress(Ipv4Addr), > +} > + > +impl ApiType for Area { > + const API_SCHEMA: Schema = > + StringSchema::new("The OSPF area, which can be a number or a > ip-address.").schema(); > +} > + > +impl UpdaterType for Area { > + type Updater = Option<Area>; > +} > + > +impl std::str::FromStr for Area { > + type Err = Error; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + if let Ok(ip) = Ipv4Addr::from_str(s) { > + Ok(Self::IpAddress(ip)) > + } else if let Ok(number) = u32::from_str(s) { > + Ok(Self::Number(number)) > + } else { > + anyhow::bail!("Area is not a number, nor an ip address"); > + } > + } > +} > + > +impl Display for Area { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + match self { > + Area::Number(n) => write!(f, "{n}"), > + Area::IpAddress(i) => write!(f, "{i}"), > + } > + } > +} > + > +impl Area { > + /// Get the IPv4 representation of the area. > + /// > + /// If it already is stored as a an IPv4 address, it is returned > directly. > + /// Otherwise, the number is converted to an IPv4 address. > + pub fn get_ipv4_representation(&self) -> Ipv4Addr { > + match self { > + Area::Number(n) => Ipv4Addr::from(*n), > + Area::IpAddress(ip) => *ip, > + } > + } > +} > diff --git a/proxmox-sdn-types/src/lib.rs b/proxmox-sdn-types/src/lib.rs > new file mode 100644 > index 000000000000..1656f1d44b95 > --- /dev/null > +++ b/proxmox-sdn-types/src/lib.rs > @@ -0,0 +1,3 @@ > +pub mod area; > +pub mod net; > +pub mod openfabric; > diff --git a/proxmox-sdn-types/src/net.rs b/proxmox-sdn-types/src/net.rs > new file mode 100644 > index 000000000000..78a47983f0c7 > --- /dev/null > +++ b/proxmox-sdn-types/src/net.rs > @@ -0,0 +1,329 @@ > +use std::{ > + fmt::Display, > + net::{IpAddr, Ipv4Addr, Ipv6Addr}, > +}; > + > +use anyhow::{bail, Error}; > +use const_format::concatcp; > +use serde::{Deserialize, Serialize}; > + > +use proxmox_schema::{api, api_string_type, const_regex, ApiStringFormat, > UpdaterType}; > + > +const NET_AFI_REGEX_STR: &str = r"(?:[a-fA-F0-9]{2})";
Would it make sense to represent the `NetAFI` type as an `u8`? Could then be `Copy` and wouldn't need to allocate a string. > +const NET_AREA_REGEX_STR: &str = r"(?:[a-fA-F0-9]{4})"; > +const NET_SYSTEM_ID_REGEX_STR: &str = > r"(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})"; > +const NET_SELECTOR_REGEX_STR: &str = r"(?:[a-fA-F0-9]{2})"; ^ Same for the selector. > + > +const_regex! { > + NET_AFI_REGEX = concatcp!(r"^", NET_AFI_REGEX_STR, r"$"); > + NET_AREA_REGEX = concatcp!(r"^", NET_AREA_REGEX_STR, r"$"); > + NET_SYSTEM_ID_REGEX = concatcp!(r"^", NET_SYSTEM_ID_REGEX_STR, r"$"); > + NET_SELECTOR_REGEX = concatcp!(r"^", NET_SELECTOR_REGEX_STR, r"$"); ^ Why don't we anchor the consts already instead of concating that here? Or - if they are only used this once we could just inline them here? > +} > + > +const NET_AFI_FORMAT: ApiStringFormat = > ApiStringFormat::Pattern(&NET_AFI_REGEX); > +const NET_AREA_FORMAT: ApiStringFormat = > ApiStringFormat::Pattern(&NET_AREA_REGEX); > +const NET_SYSTEM_ID_FORMAT: ApiStringFormat = > ApiStringFormat::Pattern(&NET_SYSTEM_ID_REGEX); > +const NET_SELECTOR_FORMAT: ApiStringFormat = > ApiStringFormat::Pattern(&NET_SELECTOR_REGEX); > + > +api_string_type! { > + /// Address Family authority Identifier - 49 The AFI value 49 is what > IS-IS (and openfabric) uses > + /// for private addressing. > + #[api(format: &NET_AFI_FORMAT)] > + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, > Deserialize)] > + struct NetAFI(String); > +} > + > +impl Default for NetAFI { > + fn default() -> Self { > + Self("49".to_owned()) > + } > +} > + > +impl UpdaterType for NetAFI { > + type Updater = Option<NetAFI>; > +} > + > +api_string_type! { > + /// Area identifier: 0001 IS-IS area number (numerical area 1) > + /// The second part (system) of the `net` identifier. Every node has to > have a different system > + /// number. > + #[api(format: &NET_AREA_FORMAT)] > + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, > PartialOrd, Ord)] > + struct NetArea(String); > +} > + > +impl Default for NetArea { > + fn default() -> Self { > + Self("0001".to_owned()) > + } > +} > + > +impl UpdaterType for NetArea { > + type Updater = Option<NetArea>; > +} > + > +api_string_type! { > + /// System identifier: 1921.6800.1002 - for system identifiers we > recommend to use IP address or > + /// MAC address of the router itself. The way to construct this is to > keep all of the zeroes of the > + /// router IP address, and then change the periods from being every > three numbers to every four > + /// numbers. The address that is listed here is 192.168.1.2, which if > expanded will turn into > + /// 192.168.001.002. Then all one has to do is move the dots to have > four numbers instead of three. > + /// This gives us 1921.6800.1002. > + #[api(format: &NET_SYSTEM_ID_FORMAT)] > + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, > PartialOrd, Ord)] > + struct NetSystemId(String); > +} > + > +impl UpdaterType for NetSystemId { > + type Updater = Option<NetSystemId>; > +} > + > +/// Convert IP-Address to a NET address with the default afi, area and > selector values. Note that a > +/// valid Ipv4Addr is always a valid SystemId as well. > +impl From<Ipv4Addr> for NetSystemId { > + fn from(value: Ipv4Addr) -> Self { > + let octets = value.octets(); > + > + let system_id_str = format!( > + "{:03}{:01}.{:02}{:02}.{:01}{:03}", > + octets[0], > + octets[1] / 100, > + octets[1] % 100, > + octets[2] / 10, > + octets[2] % 10, > + octets[3] > + ); > + > + Self(system_id_str) > + } > +} > + > +/// Convert IPv6-Address to a NET address with the default afi, area and > selector values. Note that a > +/// valid Ipv6Addr is always a valid SystemId as well. > +impl From<Ipv6Addr> for NetSystemId { > + fn from(value: Ipv6Addr) -> Self { > + let segments = value.segments(); > + > + // Use the last 3 segments (out of 8) of the IPv6 address > + let system_id_str = format!( > + "{:04x}.{:04x}.{:04x}", > + segments[5], segments[6], segments[7] > + ); > + > + Self(system_id_str) > + } > +} > + > +api_string_type! { > + /// NET selector: 00 Must always be 00. This setting indicates “this > system” or “local system.” > + #[api(format: &NET_SELECTOR_FORMAT)] > + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, > PartialOrd, Ord)] > + struct NetSelector(String); > +} > + > +impl UpdaterType for NetSelector { > + type Updater = Option<NetSelector>; > +} > + > +impl Default for NetSelector { > + fn default() -> Self { > + Self("00".to_owned()) > + } > +} > + > +/// The Network Entity Title (NET). > +/// > +/// Every OpenFabric node is identified through the NET. It has a network > and a host > +/// part. > +/// The first part is the network part (also called area). The entire > OpenFabric fabric has to have > +/// the same network part (afi + area). The first number is the [`NetAFI`] > and the second is the > +/// [`NetArea`]. > +/// e.g.: "49.0001" > +/// The second part is the host part, which has to differ on every node in > the fabric, but *not* > +/// between fabrics on the same node. It contains the [`NetSystemId`] and > the [`NetSelector`]. > +/// e.g.: "1921.6800.1002.00" > +#[api] > +#[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, > PartialOrd, Ord)] > +pub struct Net { > + afi: NetAFI, > + area: NetArea, > + system: NetSystemId, > + selector: NetSelector, > +} > + > +impl UpdaterType for Net { > + type Updater = Option<Net>; > +} > + > +impl std::str::FromStr for Net { > + type Err = Error; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + let parts: Vec<&str> = s.split(".").collect(); > + > + if parts.len() != 6 { > + bail!("invalid NET format: {s}") > + } > + > + let system = format!("{}.{}.{}", parts[2], parts[3], parts[4],); > + > + Ok(Self { > + afi: NetAFI::from_string(parts[0].to_string())?, > + area: NetArea::from_string(parts[1].to_string())?, > + system: NetSystemId::from_string(system.to_string())?, > + selector: NetSelector::from_string(parts[5].to_string())?, > + }) > + } > +} > + > +impl Display for Net { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + write!( > + f, > + "{}.{}.{}.{}", > + self.afi, self.area, self.system, self.selector > + ) > + } > +} > + > +/// Default NET address for a given Ipv4Addr. This adds the default afi, > area and selector to the > +/// address. > +impl From<Ipv4Addr> for Net { > + fn from(value: Ipv4Addr) -> Self { > + Self { > + afi: NetAFI::default(), > + area: NetArea::default(), > + system: value.into(), > + selector: NetSelector::default(), > + } > + } > +} > + > +/// Default NET address for a given Ipv6Addr. This adds the default afi, > area and selector to the > +/// address. > +impl From<Ipv6Addr> for Net { > + fn from(value: Ipv6Addr) -> Self { > + Self { > + afi: NetAFI::default(), > + area: NetArea::default(), > + system: value.into(), > + selector: NetSelector::default(), > + } > + } > +} > + > +/// Default NET address for a given IpAddr (can be either Ipv4 or Ipv6). > This adds the default afi, > +/// area and selector to the address. > +impl From<IpAddr> for Net { > + fn from(value: IpAddr) -> Self { > + match value { > + IpAddr::V4(ipv4_addr) => ipv4_addr.into(), > + IpAddr::V6(ipv6_addr) => ipv6_addr.into(), > + } > + } > +} > + > +#[cfg(test)] > +mod tests { > + use super::*; > + > + #[test] > + fn test_net_from_str() { > + let input = "49.0001.1921.6800.1002.00"; > + let net = input.parse::<Net>().expect("this net should parse"); > + assert_eq!(net.afi, NetAFI("49".to_owned())); > + assert_eq!(net.area, NetArea("0001".to_owned())); > + assert_eq!(net.system, NetSystemId("1921.6800.1002".to_owned())); > + assert_eq!(net.selector, NetSelector("00".to_owned())); > + > + let input = "45.0200.0100.1001.ba1f.01"; > + let net = input.parse::<Net>().expect("this net should parse"); > + assert_eq!(net.afi, NetAFI("45".to_owned())); > + assert_eq!(net.area, NetArea("0200".to_owned())); > + assert_eq!(net.system, NetSystemId("0100.1001.ba1f".to_owned())); > + assert_eq!(net.selector, NetSelector("01".to_owned())); > + } > + > + #[test] > + fn test_net_from_str_failed() { > + let input = "49.0001.1921.6800.1002.000"; > + input.parse::<Net>().expect_err("invalid NET selector"); > + > + let input = "49.0001.1921.6800.1002.00.00"; > + input > + .parse::<Net>() > + .expect_err("invalid amount of elements"); > + > + let input = "49.0001.1921.6800.10002.00"; > + input.parse::<Net>().expect_err("invalid system id"); > + > + let input = "49.0001.1921.6800.1z02.00"; > + input.parse::<Net>().expect_err("invalid system id"); > + > + let input = "409.0001.1921.6800.1002.00"; > + input.parse::<Net>().expect_err("invalid AFI"); > + > + let input = "49.00001.1921.6800.1002.00"; > + input.parse::<Net>().expect_err("invalid area"); > + } > + > + #[test] > + fn test_net_display() { > + let net = Net { > + afi: NetAFI("49".to_owned()), > + area: NetArea("0001".to_owned()), > + system: NetSystemId("1921.6800.1002".to_owned()), > + selector: NetSelector("00".to_owned()), > + }; > + assert_eq!(format!("{net}"), "49.0001.1921.6800.1002.00"); > + } > + > + #[test] > + fn test_net_from_ipv4() { > + let ip: Ipv4Addr = "192.168.1.100".parse().unwrap(); > + let net: Net = ip.into(); > + assert_eq!(format!("{net}"), "49.0001.1921.6800.1100.00"); > + > + let ip1: Ipv4Addr = "10.10.2.245".parse().unwrap(); > + let net1: Net = ip1.into(); > + assert_eq!(format!("{net1}"), "49.0001.0100.1000.2245.00"); > + > + let ip2: Ipv4Addr = "1.1.1.1".parse().unwrap(); > + let net2: Net = ip2.into(); > + assert_eq!(format!("{net2}"), "49.0001.0010.0100.1001.00"); > + } > + > + #[test] > + fn test_net_from_ipv6() { > + // 2001:db8::1 -> [2001, 0db8, 0, 0, 0, 0, 0, 1] > + // last 3 segments: [0, 0, 1] > + let ip: Ipv6Addr = "2001:db8::1".parse().unwrap(); > + let net: Net = ip.into(); > + assert_eq!(format!("{net}"), "49.0001.0000.0000.0001.00"); > + > + // fe80::1234:5678:abcd -> [fe80, 0, 0, 0, 0, 1234, 5678, abcd] > + // last 3 segments: [1234, 5678, abcd] > + let ip1: Ipv6Addr = "fe80::1234:5678:abcd".parse().unwrap(); > + let net1: Net = ip1.into(); > + assert_eq!(format!("{net1}"), "49.0001.1234.5678.abcd.00"); > + > + // 2001:0db8:85a3::8a2e:370:7334 -> [2001, 0db8, 85a3, 0, 0, 8a2e, > 0370, 7334] > + // last 3 segments: [8a2e, 0370, 7334] > + let ip2: Ipv6Addr = "2001:0db8:85a3::8a2e:370:7334".parse().unwrap(); > + let net2: Net = ip2.into(); > + assert_eq!(format!("{net2}"), "49.0001.8a2e.0370.7334.00"); > + > + // ::1 -> [0, 0, 0, 0, 0, 0, 0, 1] > + // last 3 segments: [0, 0, 1] > + let ip3: Ipv6Addr = "::1".parse().unwrap(); > + let net3: Net = ip3.into(); > + assert_eq!(format!("{net3}"), "49.0001.0000.0000.0001.00"); > + > + // a:b::0 -> [a, b, 0, 0, 0, 0, 0, 0] > + // last 3 segments: [0, 0, 0] > + let ip4: Ipv6Addr = "a:b::0".parse().unwrap(); > + let net4: Net = ip4.into(); > + assert_eq!(format!("{net4}"), "49.0001.0000.0000.0000.00"); > + } > +} > diff --git a/proxmox-sdn-types/src/openfabric.rs > b/proxmox-sdn-types/src/openfabric.rs > new file mode 100644 > index 000000000000..c79e2d9a2935 > --- /dev/null > +++ b/proxmox-sdn-types/src/openfabric.rs > @@ -0,0 +1,72 @@ > +use serde::{Deserialize, Serialize}; > +use std::fmt::Display; > + > +use proxmox_schema::{api, UpdaterType}; > + > +/// The OpenFabric CSNP Interval. > +/// > +/// The Complete Sequence Number Packets (CSNP) interval in seconds. The > interval range is 1 to > +/// 600. > +#[api( > + type: Integer, > + minimum: 1, > + maximum: 600, > +)] > +#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, > PartialOrd, Ord)] > +#[serde(transparent)] > +pub struct CsnpInterval(#[serde(deserialize_with = > "proxmox_serde::perl::deserialize_u16")] u16); > + > +impl UpdaterType for CsnpInterval { > + type Updater = Option<CsnpInterval>; > +} > + > +impl Display for CsnpInterval { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + self.0.fmt(f) > + } > +} > + > +/// The OpenFabric Hello Interval. > +/// > +/// The Hello Interval for a given interface in seconds. The range is 1 to > 600. Hello packets are > +/// used to establish and maintain adjacency between OpenFabric neighbors. > +#[api( > + type: Integer, > + minimum: 1, > + maximum: 600, > +)] > +#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, > PartialOrd, Ord)] > +#[serde(transparent)] > +pub struct HelloInterval(#[serde(deserialize_with = > "proxmox_serde::perl::deserialize_u16")] u16); > + > +impl UpdaterType for HelloInterval { > + type Updater = Option<HelloInterval>; > +} > + > +impl Display for HelloInterval { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + self.0.fmt(f) > + } > +} > + > +/// The OpenFabric Hello Multiplier. > +/// > +/// This is the multiplier for the hello holding time on a given interface. > The range is 2 to 100. > +#[api( > + type: Integer, > + minimum: 2, > + maximum: 100, > +)] > +#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, > PartialOrd, Ord)] > +#[serde(transparent)] > +pub struct HelloMultiplier(#[serde(deserialize_with = > "proxmox_serde::perl::deserialize_u16")] u16); > + > +impl UpdaterType for HelloMultiplier { > + type Updater = Option<HelloMultiplier>; > +} > + > +impl Display for HelloMultiplier { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + self.0.fmt(f) > + } > +} > diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml > index 5fd700c40b48..19bc793925e6 100644 > --- a/proxmox-ve-config/Cargo.toml > +++ b/proxmox-ve-config/Cargo.toml > @@ -8,14 +8,14 @@ exclude.workspace = true > > [dependencies] > log = "0.4" > -anyhow = "1" > +anyhow = { workspace = true } > nix = "0.29" > -thiserror = "2" > +thiserror = { workspace = true } > > -serde = { version = "1", features = [ "derive" ] } > +serde = { workspace = true, features = [ "derive" ] } > serde_json = "1" > serde_plain = "1" > -serde_with = "3" > +serde_with = { workspace = true } > proxmox-serde = { version = "1.0.0", features = [ "perl" ]} > > proxmox-network-types = { workspace = true } > -- > 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel