On Wed, Jul 02, 2025 at 04:50:00PM +0200, Gabriel Goller wrote: > Add generic FRR types that contain openfabric and ospf variants. Also > add the FrrConfig, which holds the whole FRR configuration in a single > struct, which will then be serialized to the FRR configuration file. > > Signed-off-by: Gabriel Goller <g.gol...@proxmox.com> > --- > proxmox-frr/src/lib.rs | 106 ++++++++++++++++-- > proxmox-frr/src/serializer.rs | 203 ++++++++++++++++++++++++++++++++++ > 2 files changed, 299 insertions(+), 10 deletions(-) > create mode 100644 proxmox-frr/src/serializer.rs > > diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs > index be9e5c2e142f..4c093e8e9bf4 100644 > --- a/proxmox-frr/src/lib.rs > +++ b/proxmox-frr/src/lib.rs > @@ -1,23 +1,70 @@ > pub mod openfabric; > pub mod ospf; > pub mod route_map; > -use std::{fmt::Display, str::FromStr}; > +pub mod serializer; > > +use std::{ > + collections::{BTreeMap, BTreeSet}, > + fmt::Display, > + str::FromStr, > +}; > + > +use crate::route_map::{AccessList, ProtocolRouteMap, RouteMap}; > 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), > +/// Generic FRR router. > +/// > +/// This generic FRR router contains all the protocols that we implement. > +/// In FRR this is e.g.: > +/// ```text > +/// router openfabric test > +/// !.... > +/// ! or > +/// router ospf > +/// !.... > +/// ``` > +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, > PartialOrd, Ord)] > +pub enum Router { > + Openfabric(openfabric::OpenfabricRouter), > + Ospf(ospf::OspfRouter), > +} > + > +impl From<openfabric::OpenfabricRouter> for Router { > + fn from(value: openfabric::OpenfabricRouter) -> Self { > + Router::Openfabric(value) > + } > +} > + > +/// Generic FRR routername. > +/// > +/// The variants represent different protocols. Some have `router <protocol> > <name>`, others have > +/// `router <protocol> <process-id>`, some only have `router <protocol>`. > +#[derive(Clone, Debug, PartialEq, Eq, Hash, SerializeDisplay, PartialOrd, > Ord)] > +pub enum RouterName { > + Openfabric(openfabric::OpenfabricRouterName), > + Ospf(ospf::OspfRouterName), > +} > + > +impl From<openfabric::OpenfabricRouterName> for RouterName { > + fn from(value: openfabric::OpenfabricRouterName) -> Self { > + Self::Openfabric(value) > + } > } > > -/// 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. > +impl Display for RouterName { > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { > + match self { > + Self::Openfabric(r) => r.fmt(f), > + Self::Ospf(r) => r.fmt(f), > + } > + } > +} > + > +/// The interface name is the same on ospf and openfabric, but it is an enum > so that we can have > +/// two different entries in the btreemap. This allows us to have an > interface in a ospf and > +/// openfabric fabric. > #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash, > PartialOrd, Ord)] > pub enum InterfaceName { > Openfabric(CommonInterfaceName), > @@ -143,3 +190,42 @@ impl Display for CommonInterfaceName { > self.0.fmt(f) > } > } > + > +/// Main FRR config. > +/// > +/// Contains the two main frr building blocks: routers and interfaces. It > also holds other > +/// top-level FRR options, such as access-lists, router-maps and > protocol-routemaps. This struct > +/// gets generated using the `FrrConfigBuilder` in `proxmox-ve-config`. > +#[derive(Clone, Debug, PartialEq, Eq, Default)] > +pub struct FrrConfig { > + pub router: BTreeMap<RouterName, Router>, > + pub interfaces: BTreeMap<InterfaceName, Interface>, > + pub access_lists: Vec<AccessList>, > + pub routemaps: Vec<RouteMap>, > + pub protocol_routemaps: BTreeSet<ProtocolRouteMap>, > +} > + > +impl FrrConfig { > + pub fn new() -> Self { > + Self::default() > + } > + > + pub fn router(&self) -> impl Iterator<Item = (&RouterName, &Router)> + > '_ { > + self.router.iter() > + } > + > + pub fn interfaces(&self) -> impl Iterator<Item = (&InterfaceName, > &Interface)> + '_ { > + self.interfaces.iter() > + } > + > + pub fn access_lists(&self) -> impl Iterator<Item = &AccessList> + '_ { > + self.access_lists.iter() > + } > + pub fn routemaps(&self) -> impl Iterator<Item = &RouteMap> + '_ { > + self.routemaps.iter() > + } > + > + pub fn protocol_routemaps(&self) -> impl Iterator<Item = > &ProtocolRouteMap> + '_ { > + self.protocol_routemaps.iter() > + } > +} > diff --git a/proxmox-frr/src/serializer.rs b/proxmox-frr/src/serializer.rs > new file mode 100644 > index 000000000000..3f8a1fc7619c > --- /dev/null > +++ b/proxmox-frr/src/serializer.rs > @@ -0,0 +1,203 @@ > +use std::fmt::{self, Write}; > + > +use crate::{ > + openfabric::{OpenfabricInterface, OpenfabricRouter}, > + ospf::{OspfInterface, OspfRouter}, > + route_map::{AccessList, AccessListName, ProtocolRouteMap, RouteMap}, > + FrrConfig, Interface, InterfaceName, Router, RouterName, > +}; > + > +pub struct FrrConfigBlob<'a> { > + buf: &'a mut (dyn Write + 'a), > +} > + > +impl Write for FrrConfigBlob<'_> { > + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { > + self.buf.write_str(s) > + } > +} > + > +pub trait FrrSerializer { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result; > +} > + > +pub fn to_raw_config(frr_config: &FrrConfig) -> Result<Vec<String>, > anyhow::Error> { > + let mut out = String::new(); > + let mut blob = FrrConfigBlob { buf: &mut out }; > + frr_config.serialize(&mut blob)?; > + > + Ok(out.as_str().lines().map(String::from).collect()) > +} > + > +pub fn dump(config: &FrrConfig) -> Result<String, anyhow::Error> { > + let mut out = String::new(); > + let mut blob = FrrConfigBlob { buf: &mut out }; > + config.serialize(&mut blob)?; > + Ok(out) > +} > + > +impl FrrSerializer for &FrrConfig {
^ Can drop the &, `serialize` already takes `&self` anyway. Same for lots of other impls below - the tuple impls will still work... > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + self.router().try_for_each(|router| router.serialize(f))?; > + self.interfaces() > + .try_for_each(|interface| interface.serialize(f))?; > + self.access_lists().try_for_each(|list| list.serialize(f))?; > + self.routemaps().try_for_each(|map| map.serialize(f))?; > + self.protocol_routemaps() > + .try_for_each(|pm| pm.serialize(f))?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for (&RouterName, &Router) { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + let router_name = self.0; > + let router = self.1; > + writeln!(f, "router {router_name}")?; > + router.serialize(f)?; > + writeln!(f, "exit")?; > + writeln!(f, "!")?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for (&InterfaceName, &Interface) { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + let interface_name = self.0; > + let interface = self.1; > + writeln!(f, "interface {interface_name}")?; > + interface.serialize(f)?; > + writeln!(f, "exit")?; > + writeln!(f, "!")?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for (&AccessListName, &AccessList) { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + self.1.serialize(f)?; > + writeln!(f, "!") > + } > +} > + > +impl FrrSerializer for Interface { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + match self { > + Interface::Openfabric(openfabric_interface) => > openfabric_interface.serialize(f)?, > + Interface::Ospf(ospf_interface) => ospf_interface.serialize(f)?, > + } > + Ok(()) > + } > +} > + > +impl FrrSerializer for OpenfabricInterface { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + if self.is_ipv6 { > + writeln!(f, " ipv6 router {}", self.fabric_id())?; > + } > + if self.is_ipv4 { > + writeln!(f, " ip router {}", self.fabric_id())?; > + } > + if self.passive() == Some(true) { > + writeln!(f, " openfabric passive")?; > + } > + if let Some(interval) = self.hello_interval() { > + writeln!(f, " openfabric hello-interval {interval}",)?; > + } > + if let Some(multiplier) = self.hello_multiplier() { > + writeln!(f, " openfabric hello-multiplier {multiplier}",)?; > + } > + if let Some(interval) = self.csnp_interval() { > + writeln!(f, " openfabric csnp-interval {interval}",)?; > + } > + Ok(()) > + } > +} > + > +impl FrrSerializer for OspfInterface { > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + writeln!(f, " ip ospf {}", self.area())?; > + if *self.passive() == Some(true) { > + writeln!(f, " ip ospf passive")?; > + } > + if let Some(network_type) = self.network_type() { > + writeln!(f, " ip ospf network {network_type}")?; > + } > + Ok(()) > + } > +} > + > +impl FrrSerializer for &Router { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + match self { > + Router::Openfabric(open_fabric_router) => > open_fabric_router.serialize(f), > + Router::Ospf(ospf_router) => ospf_router.serialize(f), > + } > + } > +} > + > +impl FrrSerializer for &OpenfabricRouter { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + writeln!(f, " net {}", self.net())?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for &OspfRouter { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + writeln!(f, " ospf router-id {}", self.router_id())?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for &AccessList { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + for i in &self.rules { > + if i.network.is_ipv6() { > + write!(f, "ipv6 ")?; > + } > + write!(f, "access-list {} ", self.name)?; > + if let Some(seq) = i.seq { > + write!(f, "seq {seq} ")?; > + } > + write!(f, "{} ", i.action)?; > + writeln!(f, "{}", i.network)?; > + } > + writeln!(f, "!")?; > + Ok(()) > + } > +} > + > +impl FrrSerializer for &RouteMap { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + writeln!(f, "route-map {} {} {}", self.name, self.action, self.seq)?; > + for i in &self.matches { > + writeln!(f, " {}", i)?; > + } > + for i in &self.sets { > + writeln!(f, " {}", i)?; > + } > + writeln!(f, "exit")?; > + writeln!(f, "!") > + } > +} > + > +impl FrrSerializer for &ProtocolRouteMap { ^ Can drop the &. > + fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result { > + if self.is_ipv6 { > + writeln!( > + f, > + "ipv6 protocol {} route-map {}", > + self.protocol, self.routemap_name > + )?; > + } else { > + writeln!( > + f, > + "ip protocol {} route-map {}", > + self.protocol, self.routemap_name > + )?; > + } > + writeln!(f, "!")?; > + Ok(()) > + } > +} > -- > 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel