[pve-devel] [PATCH proxmox-ve-rs v4 05/22] frr: add common frr types

Wolfgang Bumiller w.bumiller at proxmox.com
Mon Jul 7 13:18:32 CEST 2025


On Wed, Jul 02, 2025 at 04:49:56PM +0200, Gabriel Goller wrote:
> Add common FRR configuration types such as FrrWord,
> CommonInterfaceName, etc. These are some common types that are used by
> both openfabric and ospf and the generic types that span the two
> protocols. The FrrWord is a simple primitive in FRR, which is a
> ascii-string that doesn't contain whitespaces.
> 
> Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
> ---
>  proxmox-frr/src/lib.rs | 118 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 118 insertions(+)
> 
> diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs
> index e69de29bb2d1..5e0b34602cf4 100644
> --- a/proxmox-frr/src/lib.rs
> +++ b/proxmox-frr/src/lib.rs
> @@ -0,0 +1,118 @@
> +use std::{fmt::Display, str::FromStr};
> +
> +use serde::{Deserialize, Serialize};
> +use serde_with::{DeserializeFromStr, SerializeDisplay};
> +use thiserror::Error;
> +
> +#[derive(Error, Debug)]
> +pub enum RouterNameError {
> +    #[error("invalid name")]
> +    InvalidName,
> +    #[error("invalid frr word")]
> +    FrrWordError(#[from] FrrWordError),
> +}
> +
> +/// The interface name is the same on ospf and openfabric, but it is an enum so we can have two
> +/// different entries in the hashmap. This allows us to have an interface in an ospf and openfabric
> +/// fabric.
> +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash, PartialOrd, Ord)]
> +pub enum InterfaceName {
> +    Openfabric(CommonInterfaceName),
> +    Ospf(CommonInterfaceName),
> +}
> +
> +impl Display for InterfaceName {
> +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> +        match self {
> +            InterfaceName::Openfabric(frr_word) => frr_word.fmt(f),
> +            InterfaceName::Ospf(frr_word) => frr_word.fmt(f),
> +        }
> +    }
> +}
> +
> +#[derive(Error, Debug)]
> +pub enum FrrWordError {
> +    #[error("word is empty")]
> +    IsEmpty,
> +    #[error("word contains invalid character")]
> +    InvalidCharacter,
> +}
> +
> +/// A simple FRR Word.
> +///
> +/// Every string argument or value in FRR is an FrrWord. FrrWords must only contain ascii
> +/// characters and must not have a whitespace.
> +#[derive(
> +    Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr, SerializeDisplay, PartialOrd, Ord,
> +)]
> +pub struct FrrWord(String);
> +
> +impl FrrWord {
> +    pub fn new(name: String) -> Result<Self, FrrWordError> {
> +        if name.is_empty() {
> +            return Err(FrrWordError::IsEmpty);
> +        }
> +
> +        if name
> +            .as_bytes()
> +            .iter()
> +            .any(|c| !c.is_ascii() || c.is_ascii_whitespace())
> +        {
> +            eprintln!("invalid char in: \"{name}\"");
> +            return Err(FrrWordError::InvalidCharacter);
> +        }
> +
> +        Ok(Self(name))
> +    }
> +}
> +
> +impl FromStr for FrrWord {
> +    type Err = FrrWordError;
> +
> +    fn from_str(s: &str) -> Result<Self, Self::Err> {
> +        FrrWord::new(s.to_string())

^ Let's try to avoid allocating before error-checking. We could move the
check out of `new()` into a helper, call that, then just build
`Self(s.to_string())`.

> +    }
> +}
> +
> +impl Display for FrrWord {
> +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> +        self.0.fmt(f)
> +    }
> +}
> +
> +impl AsRef<str> for FrrWord {
> +    fn as_ref(&self) -> &str {
> +        &self.0
> +    }
> +}
> +
> +#[derive(Error, Debug)]
> +pub enum CommonInterfaceNameError {
> +    #[error("interface name too long")]
> +    TooLong,
> +}
> +
> +/// Name of a interface, which is common between all protocols.
> +///
> +/// FRR itself doesn't enforce any limits, but the kernel does. Linux only allows interface names
> +/// to be a maximum of 16 bytes. This is enforced by this struct.
> +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash, PartialOrd, Ord)]
> +pub struct CommonInterfaceName(String);
> +
> +impl FromStr for CommonInterfaceName {

^ `FromStr` is not in the prelude.
Via prelude this only provides `.parse()`, but we don't really "parse
it".
IMO this could also be a `new()`, and  have `TryFrom<String>` and `TryFrom<&str>`.
We can make `fn new<T: AsRef<str> + Into<String>>(T)` to be able to
run the check before any potential allocation...

> +    type Err = CommonInterfaceNameError;
> +
> +    fn from_str(s: &str) -> Result<Self, Self::Err> {
> +        if s.len() <= 15 {
> +            Ok(Self(s.to_owned()))
> +        } else {
> +            Err(CommonInterfaceNameError::TooLong)
> +        }
> +    }
> +}
> +
> +impl Display for CommonInterfaceName {
> +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> +        self.0.fmt(f)
> +    }
> +}
> -- 
> 2.39.5




More information about the pve-devel mailing list