On Wed, Jul 02, 2025 at 04:49:58PM +0200, Gabriel Goller wrote: > Add OSPF-specific FRR types. This also reuses the types from > proxmox-network-types. > > The NetworkType FRR option is implemented here, but not exposed to the > interface, as we want to keep it simple for the users. If they do not > set an IP, then the interface is considered to be unnumbered and uses > the Point-to-Point network type. > > Signed-off-by: Gabriel Goller <g.gol...@proxmox.com> > --- > proxmox-frr/src/lib.rs | 25 ++++++ > proxmox-frr/src/ospf.rs | 179 ++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 204 insertions(+) > create mode 100644 proxmox-frr/src/ospf.rs > > diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs > index ba9eedfb4549..0d94aef5a3cd 100644 > --- a/proxmox-frr/src/lib.rs > +++ b/proxmox-frr/src/lib.rs > @@ -1,4 +1,5 @@ > pub mod openfabric; > +pub mod ospf; > use std::{fmt::Display, str::FromStr}; > > use serde::{Deserialize, Serialize}; > @@ -31,6 +32,30 @@ impl Display for InterfaceName { > } > } > > +/// Generic FRR Interface. > +/// > +/// In FRR config it looks like this: > +/// ```text > +/// interface <name> > +/// ! ... > +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, PartialOrd, > Ord)] > +pub enum Interface { > + Openfabric(openfabric::OpenfabricInterface), > + Ospf(ospf::OspfInterface), > +} > + > +impl From<openfabric::OpenfabricInterface> for Interface { > + fn from(value: openfabric::OpenfabricInterface) -> Self { > + Self::Openfabric(value) > + } > +} > + > +impl From<ospf::OspfInterface> for Interface { > + fn from(value: ospf::OspfInterface) -> Self { > + Self::Ospf(value) > + } > +} > + > #[derive(Error, Debug)] > pub enum FrrWordError { > #[error("word is empty")] > diff --git a/proxmox-frr/src/ospf.rs b/proxmox-frr/src/ospf.rs > new file mode 100644 > index 000000000000..f7e63fbe4e55 > --- /dev/null > +++ b/proxmox-frr/src/ospf.rs > @@ -0,0 +1,179 @@ > +use std::fmt::Debug; > +use std::fmt::Display; > +use std::net::Ipv4Addr; > + > +use serde::{Deserialize, Serialize}; > + > +use thiserror::Error; > + > +use crate::{FrrWord, FrrWordError}; > + > +/// The name of the ospf frr router. > +/// > +/// We can only have a single ospf router (ignoring multiple invocations of > the ospfd daemon) > +/// because the router-id needs to be the same between different routers on > a single node. > +/// We can still have multiple fabrics by separating them using areas. > Still, different areas have > +/// the same frr router, so the name of the router is just "ospf" in "router > ospf". > +/// > +/// This serializes roughly to: > +/// ```text > +/// router ospf > +/// !... > +/// ``` > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub struct OspfRouterName; > + > +impl Display for OspfRouterName { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + write!(f, "ospf") > + } > +} > + > +#[derive(Error, Debug)] > +pub enum AreaParsingError { > + #[error("Invalid area idenitifier. Area must be a number or an ipv4 > address.")] > + InvalidArea, > + #[error("Invalid area idenitifier. Missing 'area' prefix.")] > + MissingPrefix, > + #[error("Error parsing to FrrWord")] > + FrrWordError(#[from] FrrWordError), > +} > + > +/// The OSPF Area. > +/// > +/// The OSPF area is a pseud-ipaddress (so it looks like an ip-address but > isn't set on any > +/// interface or even pingable), but can also be specified by a simple > number. So you can use "5" > +/// or "0" as an area, which then gets translated to "0.0.0.5" and "0.0.0.0" > by FRR. We allow both > +/// a number or an ip-address. Note that the area "0" (or "0.0.0.0") is a > special area - it creates > +/// a OSPF "backbone" area. > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub struct Area(FrrWord); > + > +impl TryFrom<FrrWord> for Area { > + type Error = AreaParsingError; > + > + fn try_from(value: FrrWord) -> Result<Self, Self::Error> { > + Area::new(value) > + } > +} > + > +impl Area { > + pub fn new(name: FrrWord) -> Result<Self, AreaParsingError> { > + if name.as_ref().parse::<u32>().is_ok() || > name.as_ref().parse::<Ipv4Addr>().is_ok() { > + Ok(Self(name)) > + } else { > + Err(AreaParsingError::InvalidArea) > + } > + } > +} > + > +impl Display for Area { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + write!(f, "area {}", self.0) > + } > +} > + > +/// The OSPF router properties. > +/// > +/// Currently the only property of a OSPF router is the router_id. The > router_id is used to > +/// differentiate between nodes and every node in the same area must have a > different router_id. > +/// The router_id must also be the same on the different fabrics on the same > node. The OSPFv2 > +/// daemon only supports IPv4. > +/// Note that these properties also serialize with a space prefix (" ") as > they are inside the OSPF > +/// router block. It serializes roughly to: > +/// > +/// ```text > +/// router ospf > +/// router-id <ipv4-address> > +/// ``` > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub struct OspfRouter { > + pub router_id: Ipv4Addr, > +} > + > +impl OspfRouter { > + pub fn new(router_id: Ipv4Addr) -> Self { > + Self { router_id } > + } > + > + pub fn router_id(&self) -> &Ipv4Addr { > + &self.router_id > + } > +} > + > +#[derive(Error, Debug)] > +pub enum OspfInterfaceError { > + #[error("Error parsing area")] > + AreaParsingError(#[from] AreaParsingError), > + #[error("Error parsing frr word")] > + FrrWordParse(#[from] FrrWordError), > +} > + > +/// The NetworkType of the interface. > +/// > +/// The most important options here are Broadcast (which is the default) and > PointToPoint. > +/// When PointToPoint is set, then the interface has to have a /32 address > and will be treated as > +/// unnumbered. > +/// > +/// This roughly serializes to: > +/// ```text > +/// ip ospf network point-to-point > +/// ! or > +/// ip ospf network broadcast > +/// ``` > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub enum NetworkType { > + Broadcast, > + NonBroadcast, > + /// If the interface is unnumbered (i.e. the router-id /32 ip-address is > set on the interface). > + /// > + /// If OSPF is used in an unnumbered way, you don't need to configure > peer-to-peer (e.g. /31) > + /// addresses at every interface, but you just need to set the router-id > at the interface > + /// (/32). You also need to configure the `ip ospf network > point-to-point` FRR option. > + PointToPoint, > + PointToMultipoint,
Will there be larger entries? Could consider `Copy` here maybe. > +} > + > +impl Display for NetworkType { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + match self { > + NetworkType::Broadcast => write!(f, "broadcast"), > + NetworkType::NonBroadcast => write!(f, "non-broadcast"), > + NetworkType::PointToPoint => write!(f, "point-to-point"), (I see a `t` where some people don't use a `t` 🤪 /hj) > + NetworkType::PointToMultipoint => write!(f, > "point-to-multicast"), > + } > + } > +} > + > +/// The OSPF interface properties. > +/// > +/// The interface gets tied to its fabric by the area property and the FRR > `ip ospf area <area>` > +/// command. > +/// > +/// This serializes to: > +/// > +/// ```text > +/// router ospf > +/// ip ospf area <area> > +/// ip ospf passive <value> > +/// ip ospf network <value> > +/// ``` > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub struct OspfInterface { > + // Note: an interface can only be a part of a single area(so no vec > needed here) > + pub area: Area, > + pub passive: Option<bool>, > + pub network_type: Option<NetworkType>, > +} > + > +impl OspfInterface { > + pub fn area(&self) -> &Area { > + &self.area > + } > + pub fn passive(&self) -> &Option<bool> { > + &self.passive > + } > + pub fn network_type(&self) -> &Option<NetworkType> { > + &self.network_type > + } ^ like in the previous patch - pub fields vs getters > +} > -- > 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel