[pve-devel] [PATCH proxmox-ve-rs v4 03/22] sdn-types: initial commit

Wolfgang Bumiller w.bumiller at proxmox.com
Fri Jul 4 16:09:25 CEST 2025


On Wed, Jul 02, 2025 at 04:49:54PM +0200, Gabriel Goller wrote:
> From: Stefan Hanreich <s.hanreich at 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.goller at proxmox.com>
> Signed-off-by: Stefan Hanreich <s.hanreich at 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 <support at 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 <support at 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 <support at 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 <support at 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




More information about the pve-devel mailing list