[pve-devel] [PATCH proxmox-ve-rs 01/11] add crate with common network types
Gabriel Goller
g.goller at proxmox.com
Fri Feb 14 14:39:41 CET 2025
The new proxmox-network-types crate holds some common types that are
used by proxmox-frr, proxmox-ve-config and proxmox-perl-rs. These types
are here because we don't want proxmox-frr to be a dependency of
proxmox-ve-config or vice-versa (or at least it should be feature-gated).
They should be independent.
Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
Cargo.toml | 6 +
proxmox-network-types/Cargo.toml | 15 ++
proxmox-network-types/src/lib.rs | 1 +
proxmox-network-types/src/net.rs | 239 +++++++++++++++++++++++++++++++
4 files changed, 261 insertions(+)
create mode 100644 proxmox-network-types/Cargo.toml
create mode 100644 proxmox-network-types/src/lib.rs
create mode 100644 proxmox-network-types/src/net.rs
diff --git a/Cargo.toml b/Cargo.toml
index dc7f312fb8a9..e452c931e78c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
"proxmox-ve-config",
+ "proxmox-network-types",
]
exclude = [
"build",
@@ -15,3 +16,8 @@ homepage = "https://proxmox.com"
exclude = [ "debian" ]
rust-version = "1.82"
+[workspace.dependencies]
+proxmox-section-config = "2.1.1"
+serde = "1"
+serde_with = "3.8.1"
+thiserror = "1.0.59"
diff --git a/proxmox-network-types/Cargo.toml b/proxmox-network-types/Cargo.toml
new file mode 100644
index 000000000000..93f4df87a59f
--- /dev/null
+++ b/proxmox-network-types/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "proxmox-network-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]
+thiserror = { workspace = true }
+anyhow = "1"
+serde = { workspace = true, features = [ "derive" ] }
+serde_with = { workspace = true }
diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs
new file mode 100644
index 000000000000..f9faf2ff6542
--- /dev/null
+++ b/proxmox-network-types/src/lib.rs
@@ -0,0 +1 @@
+pub mod net;
diff --git a/proxmox-network-types/src/net.rs b/proxmox-network-types/src/net.rs
new file mode 100644
index 000000000000..5fdbe3920800
--- /dev/null
+++ b/proxmox-network-types/src/net.rs
@@ -0,0 +1,239 @@
+use std::{fmt::Display, str::FromStr};
+
+use serde::Serialize;
+use serde_with::{DeserializeFromStr, SerializeDisplay};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum NetError {
+ #[error("Some octets are missing")]
+ WrongLength,
+ #[error("The NET selector must be two characters wide and be 00")]
+ InvalidNetSelector,
+ #[error("Invalid AFI (wrong size or position)")]
+ InvalidAFI,
+ #[error("Invalid Area (wrong size or position)")]
+ InvalidArea,
+ #[error("Invalid SystemId (wrong size or position)")]
+ InvalidSystemId,
+}
+
+/// Address Family authority Identifier - 49 The AFI value 49 is what IS-IS (and openfabric) uses
+/// for private addressing.
+#[derive(
+ Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, PartialOrd, Ord,
+)]
+struct NetAFI(String);
+
+impl Default for NetAFI {
+ fn default() -> Self {
+ Self("49".to_owned())
+ }
+}
+
+impl Display for NetAFI {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for NetAFI {
+ type Err = NetError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.len() != 2 {
+ Err(NetError::InvalidAFI)
+ } else {
+ Ok(Self(s.to_owned()))
+ }
+ }
+}
+
+/// 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.
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+struct NetArea(String);
+
+impl Default for NetArea {
+ fn default() -> Self {
+ Self("0001".to_owned())
+ }
+}
+
+impl Display for NetArea {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for NetArea {
+ type Err = NetError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.len() != 4 {
+ Err(NetError::InvalidArea)
+ } else {
+ Ok(Self(s.to_owned()))
+ }
+ }
+}
+
+/// 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.
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+struct NetSystemId(String);
+
+impl Display for NetSystemId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for NetSystemId {
+ type Err = NetError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.split(".").count() != 3 || s.split(".").any(|octet| octet.len() != 4) {
+ Err(NetError::InvalidArea)
+ } else {
+ Ok(Self(s.to_owned()))
+ }
+ }
+}
+
+/// NET selector: 00 Must always be 00. This setting indicates “this system” or “local system.”
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+struct NetSelector(String);
+
+impl Default for NetSelector {
+ fn default() -> Self {
+ Self("00".to_owned())
+ }
+}
+
+impl Display for NetSelector {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for NetSelector {
+ type Err = NetError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.len() != 2 {
+ Err(NetError::InvalidNetSelector)
+ } else {
+ Ok(Self(s.to_owned()))
+ }
+ }
+}
+
+/// The first part (area) of the `net` identifier. The entire OpenFabric fabric has to have the
+/// same area.
+/// f.e.: "49.0001"
+#[derive(
+ Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, PartialOrd, Ord,
+)]
+pub struct Net {
+ afi: NetAFI,
+ area: NetArea,
+ system: NetSystemId,
+ selector: NetSelector,
+}
+
+impl FromStr for Net {
+ type Err = NetError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.split(".").count() != 6 {
+ return Err(NetError::WrongLength);
+ }
+ let mut iter = s.split(".");
+ let afi = iter.next().ok_or(NetError::WrongLength)?;
+ let area = iter.next().ok_or(NetError::WrongLength)?;
+ let system = format!(
+ "{}.{}.{}",
+ iter.next().ok_or(NetError::WrongLength)?,
+ iter.next().ok_or(NetError::WrongLength)?,
+ iter.next().ok_or(NetError::WrongLength)?
+ );
+ let selector = iter.next().ok_or(NetError::WrongLength)?;
+ Ok(Self {
+ afi: afi.parse()?,
+ area: area.parse()?,
+ system: system.parse()?,
+ selector: selector.parse()?,
+ })
+ }
+}
+
+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
+ )
+ }
+}
+
+#[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.0010.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.0010".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";
+ assert!(matches!(
+ input.parse::<Net>(),
+ Err(NetError::InvalidNetSelector)
+ ));
+
+ let input = "49.0001.1921.6800.1002.00.00";
+ assert!(matches!(input.parse::<Net>(), Err(NetError::WrongLength)));
+
+ let input = "49.0001.1921.6800.10002.00";
+ assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidArea)));
+
+ let input = "409.0001.1921.6800.1002.00";
+ assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidAFI)));
+
+ let input = "49.00001.1921.6800.1002.00";
+ assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidArea)));
+ }
+
+ #[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");
+ }
+}
+
--
2.39.5
More information about the pve-devel
mailing list