[pve-devel] [PATCH proxmox-ve-rs 1/3] frr: add IS-IS frr configuration types

Gabriel Goller g.goller at proxmox.com
Tue Aug 19 15:19:00 CEST 2025


Add types to generate the IS-IS frr configuration.
These are very similar to the OpenFabric configuration, but we want to
keep them separate because they will diverge in the future.

Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
 proxmox-frr/src/isis.rs       | 90 +++++++++++++++++++++++++++++++++++
 proxmox-frr/src/lib.rs        | 13 +++++
 proxmox-frr/src/route_map.rs  |  2 +
 proxmox-frr/src/serializer.rs | 37 ++++++++++++++
 4 files changed, 142 insertions(+)
 create mode 100644 proxmox-frr/src/isis.rs

diff --git a/proxmox-frr/src/isis.rs b/proxmox-frr/src/isis.rs
new file mode 100644
index 000000000000..a407c900ae58
--- /dev/null
+++ b/proxmox-frr/src/isis.rs
@@ -0,0 +1,90 @@
+use std::fmt::Debug;
+use std::fmt::Display;
+
+use proxmox_sdn_types::net::Net;
+
+use thiserror::Error;
+
+use crate::FrrWord;
+use crate::FrrWordError;
+
+/// The name of a IS-IS router. Is an FrrWord.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisRouterName(FrrWord);
+
+impl From<FrrWord> for IsisRouterName {
+    fn from(value: FrrWord) -> Self {
+        Self(value)
+    }
+}
+
+impl IsisRouterName {
+    pub fn new(name: FrrWord) -> Self {
+        Self(name)
+    }
+}
+
+impl Display for IsisRouterName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "isis {}", self.0)
+    }
+}
+
+/// All the properties a IS-IS router can hold.
+///
+/// These can serialized with a " " space prefix as they are in the `router isis` block.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisRouter {
+    /// The NET address
+    pub net: Net,
+}
+
+impl IsisRouter {
+    pub fn new(net: Net) -> Self {
+        Self { net }
+    }
+
+    pub fn net(&self) -> &Net {
+        &self.net
+    }
+}
+
+/// The IS-IS interface properties.
+///
+/// This struct holds all the IS-IS interface properties. The most important one here is the
+/// fabric_id, which ties the interface to a fabric. When serialized these properties all get
+/// prefixed with a space (" ") as they are inside the interface block. They serialize roughly to:
+///
+/// ```text
+/// interface ens20
+///  ip router isis <fabric_id>
+///  ipv6 router isis <fabric_id>
+///  isis hello-interval <value>
+///  isis hello-multiplier <value>
+///  isis csnp-interval <value>
+///  isis passive <value>
+/// ```
+///
+/// The is_ipv4 and is_ipv6 properties decide if we need to add `ip router isis`, `ipv6
+/// router isis`, or both. An interface can only be part of a single fabric.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisInterface {
+    // Note: an interface can only be a part of a single fabric (so no vec needed here)
+    pub fabric_id: IsisRouterName,
+    pub passive: Option<bool>,
+    // Note: openfabric is very similar to isis, so we can use the same properties here
+    pub hello_interval: Option<proxmox_sdn_types::openfabric::HelloInterval>,
+    pub csnp_interval: Option<proxmox_sdn_types::openfabric::CsnpInterval>,
+    pub hello_multiplier: Option<proxmox_sdn_types::openfabric::HelloMultiplier>,
+    pub point_to_point: bool,
+    pub is_ipv4: bool,
+    pub is_ipv6: bool,
+}
+
+#[derive(Error, Debug)]
+pub enum IsisInterfaceError {
+    #[error("Unknown error converting to IsisInterface")]
+    UnknownError,
+    #[error("Error parsing frr word")]
+    FrrWordParse(#[from] FrrWordError),
+}
diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs
index 86101182fafd..daf592e0ad7f 100644
--- a/proxmox-frr/src/lib.rs
+++ b/proxmox-frr/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod isis;
 pub mod openfabric;
 pub mod ospf;
 pub mod route_map;
@@ -25,6 +26,7 @@ use thiserror::Error;
 #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum Router {
     Openfabric(openfabric::OpenfabricRouter),
+    Isis(isis::IsisRouter),
     Ospf(ospf::OspfRouter),
 }
 
@@ -41,6 +43,7 @@ impl From<openfabric::OpenfabricRouter> for Router {
 #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum RouterName {
     Openfabric(openfabric::OpenfabricRouterName),
+    Isis(isis::IsisRouterName),
     Ospf(ospf::OspfRouterName),
 }
 
@@ -54,6 +57,7 @@ impl Display for RouterName {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::Openfabric(r) => r.fmt(f),
+            Self::Isis(r) => r.fmt(f),
             Self::Ospf(r) => r.fmt(f),
         }
     }
@@ -65,6 +69,7 @@ impl Display for RouterName {
 #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum InterfaceName {
     Openfabric(CommonInterfaceName),
+    Isis(CommonInterfaceName),
     Ospf(CommonInterfaceName),
 }
 
@@ -72,6 +77,7 @@ 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::Isis(frr_word) => frr_word.fmt(f),
             InterfaceName::Ospf(frr_word) => frr_word.fmt(f),
         }
     }
@@ -86,6 +92,7 @@ impl Display for InterfaceName {
 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Interface {
     Openfabric(openfabric::OpenfabricInterface),
+    Isis(isis::IsisInterface),
     Ospf(ospf::OspfInterface),
 }
 
@@ -95,6 +102,12 @@ impl From<openfabric::OpenfabricInterface> for Interface {
     }
 }
 
+impl From<isis::IsisInterface> for Interface {
+    fn from(value: isis::IsisInterface) -> Self {
+        Self::Isis(value)
+    }
+}
+
 impl From<ospf::OspfInterface> for Interface {
     fn from(value: ospf::OspfInterface) -> Self {
         Self::Ospf(value)
diff --git a/proxmox-frr/src/route_map.rs b/proxmox-frr/src/route_map.rs
index 0918a3cead14..4e163a912425 100644
--- a/proxmox-frr/src/route_map.rs
+++ b/proxmox-frr/src/route_map.rs
@@ -201,6 +201,7 @@ pub struct RouteMap {
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum ProtocolType {
     Openfabric,
+    Isis,
     Ospf,
 }
 
@@ -208,6 +209,7 @@ impl Display for ProtocolType {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             ProtocolType::Openfabric => write!(f, "openfabric"),
+            ProtocolType::Isis => write!(f, "isis"),
             ProtocolType::Ospf => write!(f, "ospf"),
         }
     }
diff --git a/proxmox-frr/src/serializer.rs b/proxmox-frr/src/serializer.rs
index f8a3c7238d94..794c43db8888 100644
--- a/proxmox-frr/src/serializer.rs
+++ b/proxmox-frr/src/serializer.rs
@@ -1,6 +1,7 @@
 use std::fmt::{self, Write};
 
 use crate::{
+    isis::{IsisInterface, IsisRouter},
     openfabric::{OpenfabricInterface, OpenfabricRouter},
     ospf::{OspfInterface, OspfRouter},
     route_map::{AccessList, AccessListName, ProtocolRouteMap, RouteMap},
@@ -84,6 +85,7 @@ impl FrrSerializer for Interface {
     fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
         match self {
             Interface::Openfabric(openfabric_interface) => openfabric_interface.serialize(f)?,
+            Interface::Isis(isis_interface) => isis_interface.serialize(f)?,
             Interface::Ospf(ospf_interface) => ospf_interface.serialize(f)?,
         }
         Ok(())
@@ -114,6 +116,33 @@ impl FrrSerializer for OpenfabricInterface {
     }
 }
 
+impl FrrSerializer for IsisInterface {
+    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, " isis passive")?;
+        }
+        if let Some(interval) = self.hello_interval {
+            writeln!(f, " isis hello-interval {interval}",)?;
+        }
+        if let Some(multiplier) = self.hello_multiplier {
+            writeln!(f, " isis hello-multiplier {multiplier}",)?;
+        }
+        if let Some(interval) = self.csnp_interval {
+            writeln!(f, " isis csnp-interval {interval}",)?;
+        }
+        if self.point_to_point {
+            writeln!(f, " isis network point-to-point")?;
+        }
+        Ok(())
+    }
+}
+
 impl FrrSerializer for OspfInterface {
     fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
         writeln!(f, " ip ospf {}", self.area)?;
@@ -131,6 +160,7 @@ impl FrrSerializer for Router {
     fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
         match self {
             Router::Openfabric(open_fabric_router) => open_fabric_router.serialize(f),
+            Router::Isis(isis_router) => isis_router.serialize(f),
             Router::Ospf(ospf_router) => ospf_router.serialize(f),
         }
     }
@@ -143,6 +173,13 @@ impl FrrSerializer for OpenfabricRouter {
     }
 }
 
+impl FrrSerializer for IsisRouter {
+    fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
+        writeln!(f, " net {}", self.net())?;
+        Ok(())
+    }
+}
+
 impl FrrSerializer for OspfRouter {
     fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
         writeln!(f, " ospf router-id {}", self.router_id())?;
-- 
2.47.2





More information about the pve-devel mailing list