* [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics
@ 2026-02-19 15:25 Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 1/3] frr: add fabric properties to ISIS types and rename domain Gabriel Goller
` (10 more replies)
0 siblings, 11 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
As part of our effort to deprecate the existing controllers and transition
toward a fabric-based architecture, we are introducing an IS-IS fabric. To
centralize all routing protocols, we plan to consolidate them under the
"Fabrics" sidebar menu. Additionally, we aim to generate all routing protocol
configurations in a unified location using Rust.
Following the integration of OpenFabric and OSPF, the next logical step is to
migrate the IS-IS controller to the fabrics framework.
This patch series adds an IS-IS Fabric option to the "Fabrics" menu while
retaining the original IS-IS controller for backward compatibility. Since the
two do not interfere, we will include a warning recommending the use of the new
IS-IS Fabrics instead. The primary difference between the IS-IS Controller and
the IS-IS Fabric is that the Fabric does not automatically redistribute
connected routes into the Link State DB. This change is intentional, as we
intend to implement a more advanced redistribution and route-map mechanism in
the future, rather than relying on a simple checkbox. Users who need to
redistribute connected routes with the new IS-IS Fabrics can do so by manually
adding the appropriate FRR statement to the /etc/frr/frr.conf.local file.
This series relies on the template series:
https://lore.proxmox.com/pve-devel/20260203160246.353351-1-g.goller@proxmox.com/
Changelog:
v1:
* rebase ontop of template series
proxmox-ve-rs:
Gabriel Goller (3):
frr: add fabric properties to ISIS types and rename domain
ve-config: add IS-IS fabric config parsing and frr config generation
ve-config: add integration tests for IS-IS fabrics
proxmox-frr-templates/templates/isisd.jinja | 23 +-
proxmox-frr/src/ser/isis.rs | 33 ++-
proxmox-frr/src/ser/mod.rs | 10 +
proxmox-ve-config/src/sdn/fabric/frr.rs | 240 ++++++++++++++++++
proxmox-ve-config/src/sdn/fabric/mod.rs | 130 ++++++++++
.../src/sdn/fabric/section_config/fabric.rs | 22 ++
.../src/sdn/fabric/section_config/mod.rs | 19 ++
.../src/sdn/fabric/section_config/node.rs | 21 ++
.../fabric/section_config/protocol/isis.rs | 151 +++++++++++
.../sdn/fabric/section_config/protocol/mod.rs | 1 +
.../tests/fabric/cfg/isis_default/fabrics.cfg | 18 ++
.../fabric/cfg/isis_dualstack/fabrics.cfg | 22 ++
.../fabric/cfg/isis_ipv6_only/fabrics.cfg | 18 ++
proxmox-ve-config/tests/fabric/main.rs | 63 +++++
.../snapshots/fabric__isis_default_pve.snap | 36 +++
.../snapshots/fabric__isis_default_pve1.snap | 35 +++
.../snapshots/fabric__isis_dualstack_pve.snap | 48 ++++
.../snapshots/fabric__isis_ipv6_only_pve.snap | 37 +++
18 files changed, 921 insertions(+), 6 deletions(-)
create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
proxmox-perl-rs:
Gabriel Goller (2):
pve-rs: fabrics: add IS-IS protocol ifupdown config generation
sdn: add IS-IS fabric status reporting
pve-rs/src/bindings/sdn/fabrics.rs | 143 +++++++++++++++++++++++++++++
pve-rs/src/sdn/status.rs | 15 +++
2 files changed, 158 insertions(+)
pve-network:
Gabriel Goller (2):
fabrics: add IS-IS api types
sdn: controllers: rename isis domain to fabric_id
src/PVE/Network/SDN/Controllers/IsisPlugin.pm | 2 +-
src/PVE/Network/SDN/Fabrics.pm | 47 ++++++++++++++++---
src/PVE/Network/SDN/Frr.pm | 1 +
3 files changed, 43 insertions(+), 7 deletions(-)
pve-manager:
Gabriel Goller (2):
fabrics: add IS-IS panels
sdn: add warning about IS-IS controller deprecation
www/manager6/Makefile | 3 +
www/manager6/sdn/FabricsView.js | 12 ++++
www/manager6/sdn/controllers/IsisEdit.js | 5 ++
www/manager6/sdn/fabrics/NodeEdit.js | 1 +
www/manager6/sdn/fabrics/isis/FabricEdit.js | 69 +++++++++++++++++++
.../sdn/fabrics/isis/InterfacePanel.js | 34 +++++++++
www/manager6/sdn/fabrics/isis/NodeEdit.js | 22 ++++++
7 files changed, 146 insertions(+)
create mode 100644 www/manager6/sdn/fabrics/isis/FabricEdit.js
create mode 100644 www/manager6/sdn/fabrics/isis/InterfacePanel.js
create mode 100644 www/manager6/sdn/fabrics/isis/NodeEdit.js
pve-docs:
Gabriel Goller (1):
sdn: add section about IS-IS fabric
pvesdn.adoc | 111 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 105 insertions(+), 6 deletions(-)
pve-gui-tests:
Gabriel Goller (1):
fabrics: add screenshots for IS-IS fabric and nodes
create_fabrics_screenshots | 43 ++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
Summary over all repositories:
32 files changed, 1416 insertions(+), 19 deletions(-)
--
Generated by git-murpp 0.8.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH proxmox-ve-rs v2 1/3] frr: add fabric properties to ISIS types and rename domain
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 2/3] ve-config: add IS-IS fabric config parsing and frr config generation Gabriel Goller
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
The ISIS types in proxmox-frr are used by pve-network to generate FRR
configuration for ISIS controllers. To share these types between ISIS
fabric and controller implementations, add properties required by fabrics
and rename the "domain" field to "fabric_id" for consistency with fabric
terminology. This rename is also applied in pve-network.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-frr-templates/templates/isisd.jinja | 23 +++++++++++---
proxmox-frr/src/ser/isis.rs | 33 +++++++++++++++++++--
proxmox-frr/src/ser/mod.rs | 10 +++++++
3 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/proxmox-frr-templates/templates/isisd.jinja b/proxmox-frr-templates/templates/isisd.jinja
index 75f066166ad2..22772dd3886d 100644
--- a/proxmox-frr-templates/templates/isisd.jinja
+++ b/proxmox-frr-templates/templates/isisd.jinja
@@ -19,11 +19,26 @@ exit
{% endfor %}
{% for interface_name, interface_config in isis.interfaces|items %}
{% call interface(interface_name, interface_config.addresses) %}
-{% if interface_config.domain and interface_config.is_ipv4 %}
- ip router isis {{ interface_config.domain }}
+{% if interface_config.fabric_id and interface_config.is_ipv4 %}
+ ip router isis {{ interface_config.fabric_id }}
{% endif %}
-{% if interface_config.domain and interface_config.is_ipv6 %}
- ipv6 router isis {{ interface_config.domain }}
+{% if interface_config.fabric_id and interface_config.is_ipv6 %}
+ ipv6 router isis {{ interface_config.fabric_id }}
+{% endif %}
+{% if interface_config.passive %}
+ isis passive
+{% endif %}
+{% if interface_config.hello_interval %}
+ isis hello-interval {{ interface_config.hello_interval }}
+{% endif %}
+{% if interface_config.csnp_interval %}
+ isis csnp-interval {{ interface_config.csnp_interval }}
+{% endif %}
+{% if interface_config.hello_multiplier %}
+ isis hello-multiplier {{ interface_config.hello_multiplier }}
+{% endif %}
+{% if interface_config.point_to_point %}
+ isis network point-to-point
{% endif %}
{% for line in interface_config.custom_frr_config %}
{{ line }}
diff --git a/proxmox-frr/src/ser/isis.rs b/proxmox-frr/src/ser/isis.rs
index 2a38a8310fb5..3a48b7612d73 100644
--- a/proxmox-frr/src/ser/isis.rs
+++ b/proxmox-frr/src/ser/isis.rs
@@ -11,6 +11,18 @@ use crate::ser::FrrWord;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
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)
+ }
+}
+
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum IsisLevel {
#[serde(rename = "level-1")]
@@ -27,7 +39,7 @@ pub struct Redistribute {
ipv6_connected: IsisLevel,
}
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Builder)]
pub struct IsisRouter {
pub net: Net,
#[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
@@ -39,11 +51,28 @@ pub struct IsisRouter {
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Builder)]
pub struct IsisInterface {
- pub domain: IsisRouterName,
+ pub fabric_id: IsisRouterName,
#[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
pub is_ipv4: bool,
#[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
pub is_ipv6: bool,
#[serde(default)]
+ pub passive: Option<bool>,
+ // Note: openfabric is very similar to isis, so we can use the same properties here
+ #[serde(default)]
+ #[builder(required, default = None)]
+ pub hello_interval: Option<proxmox_sdn_types::openfabric::HelloInterval>,
+ #[serde(default)]
+ #[builder(required, default = None)]
+ pub csnp_interval: Option<proxmox_sdn_types::openfabric::CsnpInterval>,
+ #[serde(default)]
+ #[builder(required, default = None)]
+ pub hello_multiplier: Option<proxmox_sdn_types::openfabric::HelloMultiplier>,
+
+ #[serde(default)]
+ pub point_to_point: bool,
+
+ #[serde(default)]
+ #[builder(default)]
pub custom_frr_config: Vec<String>,
}
diff --git a/proxmox-frr/src/ser/mod.rs b/proxmox-frr/src/ser/mod.rs
index f3578ef1323a..dfae70cc01e2 100644
--- a/proxmox-frr/src/ser/mod.rs
+++ b/proxmox-frr/src/ser/mod.rs
@@ -123,6 +123,15 @@ impl From<openfabric::OpenfabricInterface> for Interface<openfabric::OpenfabricI
}
}
+impl From<isis::IsisInterface> for Interface<isis::IsisInterface> {
+ fn from(value: isis::IsisInterface) -> Self {
+ Interface {
+ addresses: Vec::new(),
+ properties: value,
+ }
+ }
+}
+
impl From<ospf::OspfInterface> for Interface<ospf::OspfInterface> {
fn from(value: ospf::OspfInterface) -> Self {
Interface {
@@ -153,6 +162,7 @@ pub struct IpRoute {
pub enum FrrProtocol {
Ospf,
Openfabric,
+ Isis,
Bgp,
}
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH proxmox-ve-rs v2 2/3] ve-config: add IS-IS fabric config parsing and frr config generation
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 1/3] frr: add fabric properties to ISIS types and rename domain Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 3/3] ve-config: add integration tests for IS-IS fabrics Gabriel Goller
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Add the necessary types to parse IS-IS fabrics from the fabrics.cfg
config file and convert that config into the frr config types.
Everything is quite similar to OpenFabric, but it's worth to keep them
separate because they will diverge in the future.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-ve-config/src/sdn/fabric/frr.rs | 240 ++++++++++++++++++
proxmox-ve-config/src/sdn/fabric/mod.rs | 130 ++++++++++
.../src/sdn/fabric/section_config/fabric.rs | 22 ++
.../src/sdn/fabric/section_config/mod.rs | 19 ++
.../src/sdn/fabric/section_config/node.rs | 21 ++
.../fabric/section_config/protocol/isis.rs | 151 +++++++++++
.../sdn/fabric/section_config/protocol/mod.rs | 1 +
7 files changed, 584 insertions(+)
create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs
index ac5e88e905a3..3426375f0435 100644
--- a/proxmox-ve-config/src/sdn/fabric/frr.rs
+++ b/proxmox-ve-config/src/sdn/fabric/frr.rs
@@ -1,5 +1,6 @@
use std::net::{IpAddr, Ipv4Addr};
+use proxmox_frr::ser::isis::{IsisInterface, IsisRouter, IsisRouterName};
use tracing;
use proxmox_frr::ser::openfabric::{OpenfabricInterface, OpenfabricRouter, OpenfabricRouterName};
@@ -16,6 +17,7 @@ use proxmox_sdn_types::net::Net;
use crate::common::valid::Valid;
use crate::sdn::fabric::section_config::protocol::{
+ isis::{IsisInterfaceProperties, IsisProperties},
openfabric::{OpenfabricInterfaceProperties, OpenfabricProperties},
ospf::OspfInterfaceProperties,
};
@@ -190,6 +192,148 @@ pub fn build_fabric(
}
}
}
+ FabricEntry::Isis(isis_entry) => {
+ // Get the current node of this fabric, if it doesn't exist, skip this fabric and
+ // don't generate any FRR config.
+ let Ok(node) = isis_entry.node_section(¤t_node) else {
+ continue;
+ };
+
+ if current_net.is_none() {
+ current_net = match (node.ip(), node.ip6()) {
+ (Some(ip), _) => Some(ip.into()),
+ (_, Some(ip6)) => Some(ip6.into()),
+ (_, _) => None,
+ }
+ }
+
+ let net = current_net
+ .as_ref()
+ .ok_or_else(|| anyhow::anyhow!("no IPv4 or IPv6 set for node"))?;
+ let (router_name, router_item) = build_isis_router(fabric_id, net.clone())?;
+ frr_config.isis.router.insert(router_name, router_item);
+
+ // Create dummy interface for fabric
+ let (interface, interface_name) = build_isis_dummy_interface(
+ fabric_id,
+ node.ip().is_some(),
+ node.ip6().is_some(),
+ )?;
+
+ if frr_config
+ .isis
+ .interfaces
+ .insert(interface_name, interface)
+ .is_some()
+ {
+ tracing::error!(
+ "An interface with the same name as the dummy interface exists"
+ );
+ }
+
+ let fabric = isis_entry.fabric_section();
+
+ for interface in node.properties().interfaces.iter() {
+ let (interface, interface_name) = build_isis_interface(
+ fabric_id,
+ interface,
+ fabric.properties(),
+ node.ip().is_some(),
+ node.ip6().is_some(),
+ )?;
+
+ if frr_config
+ .isis
+ .interfaces
+ .insert(interface_name, interface)
+ .is_some()
+ {
+ tracing::warn!("An interface cannot be in multiple IS-IS fabrics");
+ }
+ }
+
+ if let Some(ipv4cidr) = fabric.ip_prefix() {
+ let rule = ser::route_map::AccessListRule {
+ action: ser::route_map::AccessAction::Permit,
+ network: Cidr::from(ipv4cidr),
+ is_ipv6: false,
+ seq: None,
+ };
+ let access_list_name =
+ ser::route_map::AccessListName::new(format!("pve_isis_{}_ips", fabric_id));
+ frr_config.access_lists.insert(access_list_name, vec![rule]);
+ }
+ if let Some(ipv6cidr) = fabric.ip6_prefix() {
+ let rule = ser::route_map::AccessListRule {
+ action: ser::route_map::AccessAction::Permit,
+ network: Cidr::from(ipv6cidr),
+ is_ipv6: true,
+ seq: None,
+ };
+ let access_list_name =
+ ser::route_map::AccessListName::new(format!("pve_isis_{}_ip6s", fabric_id));
+ frr_config.access_lists.insert(access_list_name, vec![rule]);
+ }
+
+ if let Some(ipv4) = node.ip() {
+ // create route-map
+ let (routemap_name, routemap_rule) =
+ build_isis_routemap(fabric_id, IpAddr::V4(ipv4), routemap_seq);
+
+ if let Some(routemap) = frr_config.routemaps.get_mut(&routemap_name) {
+ routemap.push(routemap_rule)
+ } else {
+ frr_config
+ .routemaps
+ .insert(routemap_name.clone(), vec![routemap_rule]);
+ }
+
+ routemap_seq += 10;
+
+ if let Some(routemap) =
+ frr_config.protocol_routemaps.get_mut(&FrrProtocol::Isis)
+ {
+ routemap.v4 = Some(routemap_name);
+ } else {
+ frr_config.protocol_routemaps.insert(
+ FrrProtocol::Isis,
+ IpProtocolRouteMap {
+ v4: Some(routemap_name),
+ v6: None,
+ },
+ );
+ }
+ }
+ if let Some(ipv6) = node.ip6() {
+ // create route-map
+ let (routemap_name, routemap_rule) =
+ build_isis_routemap(fabric_id, IpAddr::V6(ipv6), routemap_seq);
+
+ if let Some(routemap) = frr_config.routemaps.get_mut(&routemap_name) {
+ routemap.push(routemap_rule)
+ } else {
+ frr_config
+ .routemaps
+ .insert(routemap_name.clone(), vec![routemap_rule]);
+ }
+
+ routemap_seq += 10;
+
+ if let Some(routemap) =
+ frr_config.protocol_routemaps.get_mut(&FrrProtocol::Isis)
+ {
+ routemap.v6 = Some(routemap_name);
+ } else {
+ frr_config.protocol_routemaps.insert(
+ FrrProtocol::Isis,
+ IpProtocolRouteMap {
+ v4: None,
+ v6: Some(routemap_name),
+ },
+ );
+ }
+ }
+ }
FabricEntry::Ospf(ospf_entry) => {
let Ok(node) = ospf_entry.node_section(¤t_node) else {
continue;
@@ -300,6 +444,20 @@ fn build_openfabric_router(
Ok((router_name, router_item))
}
+/// Helper that builds a IS-IS router from a fabric_id and a [`Net`].
+fn build_isis_router(
+ fabric_id: &FabricId,
+ net: Net,
+) -> Result<(IsisRouterName, IsisRouter), anyhow::Error> {
+ let frr_router = IsisRouter::builder()
+ .net(net)
+ .custom_frr_config(vec![])
+ .build();
+ let frr_word_id = ser::FrrWord::new(fabric_id.to_string())?;
+ let router_name = IsisRouterName::new(frr_word_id);
+ Ok((router_name, frr_router))
+}
+
/// Helper that builds a OSPF interface from an [`ospf::Area`] and the [`OspfInterfaceProperties`].
fn build_ospf_interface(
area: ser::ospf::Area,
@@ -415,6 +573,88 @@ fn build_openfabric_routemap(
)
}
+/// Helper that builds the IS-IS interface.
+///
+/// Takes the [`FabricId`], [`IsisInterfaceProperties`], [`IsisProperties`] and flags for
+/// ipv4 and ipv6.
+fn build_isis_interface(
+ fabric_id: &FabricId,
+ interface: &IsisInterfaceProperties,
+ fabric_config: &IsisProperties,
+ is_ipv4: bool,
+ is_ipv6: bool,
+) -> Result<(Interface<IsisInterface>, InterfaceName), anyhow::Error> {
+ let frr_word = ser::FrrWord::new(fabric_id.to_string())?;
+ let mut frr_interface = IsisInterface::builder()
+ .fabric_id(frr_word.into())
+ .hello_interval(fabric_config.hello_interval)
+ .csnp_interval(fabric_config.csnp_interval)
+ .hello_multiplier(interface.hello_multiplier)
+ .is_ipv4(is_ipv4)
+ .is_ipv6(is_ipv6)
+ .point_to_point(interface.ip.is_none())
+ .build();
+
+ // If no specific hello_interval is set, get default one from fabric
+ // config
+ if frr_interface.hello_interval.is_none() {
+ frr_interface.hello_interval = fabric_config.hello_interval;
+ }
+ let interface_name = interface.name.as_str().try_into()?;
+ Ok((frr_interface.into(), interface_name))
+}
+
+/// Helper that builds a IS-IS interface using a [`FabricId`] and ipv4/6 flags.
+fn build_isis_dummy_interface(
+ fabric_id: &FabricId,
+ is_ipv4: bool,
+ is_ipv6: bool,
+) -> Result<(Interface<IsisInterface>, InterfaceName), anyhow::Error> {
+ let frr_word = ser::FrrWord::new(fabric_id.to_string())?;
+ let frr_interface = IsisInterface::builder()
+ .fabric_id(frr_word.into())
+ .passive(true)
+ .is_ipv4(is_ipv4)
+ .is_ipv6(is_ipv6)
+ .point_to_point(false)
+ .build();
+ let interface_name = format!("dummy_{}", fabric_id).try_into()?;
+ Ok((frr_interface.into(), interface_name))
+}
+
+/// Helper that builds a RouteMap for the IS-IS protocol.
+fn build_isis_routemap(
+ fabric_id: &FabricId,
+ router_ip: IpAddr,
+ seq: u32,
+) -> (RouteMapName, RouteMapEntry) {
+ let routemap_name = match router_ip {
+ IpAddr::V4(_) => RouteMapName::new("pve_isis".to_owned()),
+ IpAddr::V6(_) => RouteMapName::new("pve_isis6".to_owned()),
+ };
+ (
+ routemap_name,
+ RouteMapEntry {
+ seq,
+ action: AccessAction::Permit,
+ matches: vec![match router_ip {
+ IpAddr::V4(_) => RouteMapMatch::V4(RouteMapMatchInner::Address(
+ AccessListOrPrefixList::AccessList(AccessListName::new(format!(
+ "pve_isis_{fabric_id}_ips"
+ ))),
+ )),
+ IpAddr::V6(_) => RouteMapMatch::V6(RouteMapMatchInner::Address(
+ AccessListOrPrefixList::AccessList(AccessListName::new(format!(
+ "pve_isis_{fabric_id}_ip6s"
+ ))),
+ )),
+ }],
+ sets: vec![RouteMapSet::Src(router_ip)],
+ custom_frr_config: Vec::new(),
+ },
+ )
+}
+
/// Helper that builds a RouteMap for the OSPF protocol.
fn build_ospf_dummy_routemap(
fabric_id: &FabricId,
diff --git a/proxmox-ve-config/src/sdn/fabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/mod.rs
index 677a30976297..d3ad572a5afd 100644
--- a/proxmox-ve-config/src/sdn/fabric/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/mod.rs
@@ -20,6 +20,10 @@ use crate::sdn::fabric::section_config::node::{
api::{NodeDataUpdater, NodeDeletableProperties, NodeUpdater},
Node, NodeId, NodeSection,
};
+use crate::sdn::fabric::section_config::protocol::isis::{
+ IsisDeletableProperties, IsisNodeDeletableProperties, IsisNodeProperties,
+ IsisNodePropertiesUpdater, IsisProperties, IsisPropertiesUpdater,
+};
use crate::sdn::fabric::section_config::protocol::openfabric::{
OpenfabricDeletableProperties, OpenfabricNodeDeletableProperties, OpenfabricNodeProperties,
OpenfabricNodePropertiesUpdater, OpenfabricProperties, OpenfabricPropertiesUpdater,
@@ -192,6 +196,36 @@ impl Entry<OpenfabricProperties, OpenfabricNodeProperties> {
}
}
+impl Entry<IsisProperties, IsisNodeProperties> {
+ /// Get the IS-IS fabric config.
+ ///
+ /// This method is implemented for [`Entry<IsisProperties, IsisNodeProperties>`],
+ /// so it is guaranteed that a [`FabricSection<IsisProperties>`] is returned.
+ pub fn fabric_section(&self) -> &FabricSection<IsisProperties> {
+ if let Fabric::Isis(section) = &self.fabric {
+ return section;
+ }
+
+ unreachable!();
+ }
+
+ /// Get the IS-IS node config for the given node_id.
+ ///
+ /// This method is implemented for [`Entry<IsisProperties, IsisNodeProperties>`],
+ /// so it is guaranteed that a [`NodeSection<IsisNodeProperties>`] is returned.
+ /// An error is returned if the node is not found.
+ pub fn node_section(
+ &self,
+ id: &NodeId,
+ ) -> Result<&NodeSection<IsisNodeProperties>, FabricConfigError> {
+ if let Node::Isis(section) = self.get_node(id)? {
+ return Ok(section);
+ }
+
+ unreachable!();
+ }
+}
+
impl Entry<OspfProperties, OspfNodeProperties> {
/// Get the OSPF fabric config.
///
@@ -229,6 +263,7 @@ impl Entry<OspfProperties, OspfNodeProperties> {
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
pub enum FabricEntry {
Openfabric(Entry<OpenfabricProperties, OpenfabricNodeProperties>),
+ Isis(Entry<IsisProperties, IsisNodeProperties>),
Ospf(Entry<OspfProperties, OspfNodeProperties>),
}
@@ -240,6 +275,7 @@ impl FabricEntry {
(FabricEntry::Openfabric(entry), Node::Openfabric(node_section)) => {
entry.add_node(node_section)
}
+ (FabricEntry::Isis(entry), Node::Isis(node_section)) => entry.add_node(node_section),
(FabricEntry::Ospf(entry), Node::Ospf(node_section)) => entry.add_node(node_section),
_ => Err(FabricConfigError::ProtocolMismatch),
}
@@ -250,6 +286,7 @@ impl FabricEntry {
pub fn get_node(&self, id: &NodeId) -> Result<&Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.get_node(id),
+ FabricEntry::Isis(entry) => entry.get_node(id),
FabricEntry::Ospf(entry) => entry.get_node(id),
}
}
@@ -259,6 +296,7 @@ impl FabricEntry {
pub fn get_node_mut(&mut self, id: &NodeId) -> Result<&mut Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.get_node_mut(id),
+ FabricEntry::Isis(entry) => entry.get_node_mut(id),
FabricEntry::Ospf(entry) => entry.get_node_mut(id),
}
}
@@ -307,6 +345,38 @@ impl FabricEntry {
Ok(())
}
+ (Node::Isis(node_section), NodeUpdater::Isis(updater)) => {
+ let NodeDataUpdater::<IsisNodePropertiesUpdater, IsisNodeDeletableProperties> {
+ ip,
+ ip6,
+ properties: IsisNodePropertiesUpdater { interfaces },
+ delete,
+ } = updater;
+
+ if let Some(ip) = ip {
+ node_section.ip = Some(ip);
+ }
+
+ if let Some(ip) = ip6 {
+ node_section.ip6 = Some(ip);
+ }
+
+ if let Some(interfaces) = interfaces {
+ node_section.properties.interfaces = interfaces;
+ }
+
+ for property in delete {
+ match property {
+ NodeDeletableProperties::Ip => node_section.ip = None,
+ NodeDeletableProperties::Ip6 => node_section.ip6 = None,
+ NodeDeletableProperties::Protocol(
+ IsisNodeDeletableProperties::Interfaces,
+ ) => node_section.properties.interfaces = Vec::new(),
+ }
+ }
+
+ Ok(())
+ }
(Node::Ospf(node_section), NodeUpdater::Ospf(updater)) => {
let NodeDataUpdater::<OspfNodePropertiesUpdater, OspfNodeDeletableProperties> {
ip,
@@ -347,6 +417,7 @@ impl FabricEntry {
pub fn nodes(&self) -> impl Iterator<Item = (&NodeId, &Node)> + '_ {
match self {
FabricEntry::Openfabric(entry) => entry.nodes.iter(),
+ FabricEntry::Isis(entry) => entry.nodes.iter(),
FabricEntry::Ospf(entry) => entry.nodes.iter(),
}
}
@@ -355,6 +426,7 @@ impl FabricEntry {
pub fn delete_node(&mut self, id: &NodeId) -> Result<Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.delete_node(id),
+ FabricEntry::Isis(entry) => entry.delete_node(id),
FabricEntry::Ospf(entry) => entry.delete_node(id),
}
}
@@ -364,6 +436,7 @@ impl FabricEntry {
pub fn into_section_config(self) -> (Fabric, Vec<Node>) {
match self {
FabricEntry::Openfabric(entry) => entry.into_pair(),
+ FabricEntry::Isis(entry) => entry.into_pair(),
FabricEntry::Ospf(entry) => entry.into_pair(),
}
}
@@ -372,6 +445,7 @@ impl FabricEntry {
pub fn fabric(&self) -> &Fabric {
match self {
FabricEntry::Openfabric(entry) => &entry.fabric,
+ FabricEntry::Isis(entry) => &entry.fabric,
FabricEntry::Ospf(entry) => &entry.fabric,
}
}
@@ -380,6 +454,7 @@ impl FabricEntry {
pub fn fabric_mut(&mut self) -> &mut Fabric {
match self {
FabricEntry::Openfabric(entry) => &mut entry.fabric,
+ FabricEntry::Isis(entry) => &mut entry.fabric,
FabricEntry::Ospf(entry) => &mut entry.fabric,
}
}
@@ -391,6 +466,7 @@ impl From<Fabric> for FabricEntry {
Fabric::Openfabric(fabric_section) => {
FabricEntry::Openfabric(Entry::new(fabric_section))
}
+ Fabric::Isis(fabric_section) => FabricEntry::Isis(Entry::new(fabric_section)),
Fabric::Ospf(fabric_section) => FabricEntry::Ospf(Entry::new(fabric_section)),
}
}
@@ -573,6 +649,13 @@ impl Validatable for FabricConfig {
return Err(FabricConfigError::DuplicateInterface);
}
}
+ Node::Isis(node_section) => {
+ if !node_section.properties().interfaces().all(|interface| {
+ node_interfaces.insert((node_id, interface.name.as_str()))
+ }) {
+ return Err(FabricConfigError::DuplicateInterface);
+ }
+ }
}
}
@@ -694,6 +777,53 @@ impl FabricConfig {
Ok(())
}
+ (Fabric::Isis(fabric_section), FabricUpdater::Isis(updater)) => {
+ let FabricSectionUpdater::<IsisPropertiesUpdater, IsisDeletableProperties> {
+ ip_prefix,
+ ip6_prefix,
+ properties:
+ IsisPropertiesUpdater {
+ hello_interval,
+ csnp_interval,
+ },
+ delete,
+ } = updater;
+
+ if let Some(prefix) = ip_prefix {
+ fabric_section.ip_prefix = Some(prefix);
+ }
+
+ if let Some(prefix) = ip6_prefix {
+ fabric_section.ip6_prefix = Some(prefix);
+ }
+
+ if let Some(hello_interval) = hello_interval {
+ fabric_section.properties.hello_interval = Some(hello_interval);
+ }
+
+ if let Some(csnp_interval) = csnp_interval {
+ fabric_section.properties.csnp_interval = Some(csnp_interval);
+ }
+
+ for property in delete {
+ match property {
+ FabricDeletableProperties::IpPrefix => {
+ fabric_section.ip_prefix = None;
+ }
+ FabricDeletableProperties::Ip6Prefix => {
+ fabric_section.ip6_prefix = None;
+ }
+ FabricDeletableProperties::Protocol(
+ IsisDeletableProperties::CsnpInterval,
+ ) => fabric_section.properties.csnp_interval = None,
+ FabricDeletableProperties::Protocol(
+ IsisDeletableProperties::HelloInterval,
+ ) => fabric_section.properties.hello_interval = None,
+ }
+ }
+
+ Ok(())
+ }
(Fabric::Ospf(fabric_section), FabricUpdater::Ospf(updater)) => {
let FabricSectionUpdater::<OspfPropertiesUpdater, OspfDeletableProperties> {
ip_prefix,
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
index 38911a624740..c8657f2a5dfa 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
@@ -8,6 +8,9 @@ use proxmox_schema::{
};
use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::protocol::isis::{
+ IsisDeletableProperties, IsisProperties, IsisPropertiesUpdater,
+};
use crate::sdn::fabric::section_config::protocol::openfabric::{
OpenfabricDeletableProperties, OpenfabricProperties, OpenfabricPropertiesUpdater,
};
@@ -135,6 +138,10 @@ impl UpdaterType for FabricSection<OpenfabricProperties> {
type Updater = FabricSectionUpdater<OpenfabricPropertiesUpdater, OpenfabricDeletableProperties>;
}
+impl UpdaterType for FabricSection<IsisProperties> {
+ type Updater = FabricSectionUpdater<IsisPropertiesUpdater, IsisDeletableProperties>;
+}
+
impl UpdaterType for FabricSection<OspfProperties> {
type Updater = FabricSectionUpdater<OspfPropertiesUpdater, OspfDeletableProperties>;
}
@@ -158,6 +165,7 @@ impl UpdaterType for FabricSection<OspfProperties> {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Fabric {
Openfabric(FabricSection<OpenfabricProperties>),
+ Isis(FabricSection<IsisProperties>),
Ospf(FabricSection<OspfProperties>),
}
@@ -172,6 +180,7 @@ impl Fabric {
pub fn id(&self) -> &FabricId {
match self {
Self::Openfabric(fabric_section) => fabric_section.id(),
+ Self::Isis(fabric_section) => fabric_section.id(),
Self::Ospf(fabric_section) => fabric_section.id(),
}
}
@@ -182,6 +191,7 @@ impl Fabric {
pub fn ip_prefix(&self) -> Option<Ipv4Cidr> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix(),
+ Fabric::Isis(fabric_section) => fabric_section.ip_prefix(),
Fabric::Ospf(fabric_section) => fabric_section.ip_prefix(),
}
}
@@ -192,6 +202,7 @@ impl Fabric {
pub fn set_ip_prefix(&mut self, ipv4_cidr: Ipv4Cidr) {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
+ Fabric::Isis(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
Fabric::Ospf(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
}
}
@@ -202,6 +213,7 @@ impl Fabric {
pub fn ip6_prefix(&self) -> Option<Ipv6Cidr> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix(),
+ Fabric::Isis(fabric_section) => fabric_section.ip6_prefix(),
Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix(),
}
}
@@ -212,6 +224,7 @@ impl Fabric {
pub fn set_ip6_prefix(&mut self, ipv6_cidr: Ipv6Cidr) {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
+ Fabric::Isis(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
}
}
@@ -224,6 +237,7 @@ impl Validatable for Fabric {
fn validate(&self) -> Result<(), Self::Error> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.validate(),
+ Fabric::Isis(fabric_section) => fabric_section.validate(),
Fabric::Ospf(fabric_section) => fabric_section.validate(),
}
}
@@ -235,6 +249,12 @@ impl From<FabricSection<OpenfabricProperties>> for Fabric {
}
}
+impl From<FabricSection<IsisProperties>> for Fabric {
+ fn from(section: FabricSection<IsisProperties>) -> Self {
+ Fabric::Isis(section)
+ }
+}
+
impl From<FabricSection<OspfProperties>> for Fabric {
fn from(section: FabricSection<OspfProperties>) -> Self {
Fabric::Ospf(section)
@@ -246,6 +266,7 @@ impl From<FabricSection<OspfProperties>> for Fabric {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum FabricUpdater {
Openfabric(<FabricSection<OpenfabricProperties> as UpdaterType>::Updater),
+ Isis(<FabricSection<IsisProperties> as UpdaterType>::Updater),
Ospf(<FabricSection<OspfProperties> as UpdaterType>::Updater),
}
@@ -253,6 +274,7 @@ impl Updater for FabricUpdater {
fn is_empty(&self) -> bool {
match self {
FabricUpdater::Openfabric(updater) => updater.is_empty(),
+ FabricUpdater::Isis(updater) => updater.is_empty(),
FabricUpdater::Ospf(updater) => updater.is_empty(),
}
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
index d02d4ae96437..f4621159dbe5 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
@@ -10,6 +10,7 @@ use crate::sdn::fabric::section_config::{
fabric::{Fabric, FabricSection, FABRIC_ID_REGEX_STR},
node::{Node, NodeSection, NODE_ID_REGEX_STR},
protocol::{
+ isis::{IsisNodeProperties, IsisProperties},
openfabric::{OpenfabricNodeProperties, OpenfabricProperties},
ospf::{OspfNodeProperties, OspfProperties},
},
@@ -30,8 +31,10 @@ impl From<Section> for FabricOrNode<Fabric, Node> {
fn from(section: Section) -> Self {
match section {
Section::OpenfabricFabric(fabric_section) => Self::Fabric(fabric_section.into()),
+ Section::IsisFabric(fabric_section) => Self::Fabric(fabric_section.into()),
Section::OspfFabric(fabric_section) => Self::Fabric(fabric_section.into()),
Section::OpenfabricNode(node_section) => Self::Node(node_section.into()),
+ Section::IsisNode(node_section) => Self::Node(node_section.into()),
Section::OspfNode(node_section) => Self::Node(node_section.into()),
}
}
@@ -61,8 +64,10 @@ pub const SECTION_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SECTION
#[serde(rename_all = "snake_case", tag = "type")]
pub enum Section {
OpenfabricFabric(FabricSection<OpenfabricProperties>),
+ IsisFabric(FabricSection<IsisProperties>),
OspfFabric(FabricSection<OspfProperties>),
OpenfabricNode(NodeSection<OpenfabricNodeProperties>),
+ IsisNode(NodeSection<IsisNodeProperties>),
OspfNode(NodeSection<OspfNodeProperties>),
}
@@ -72,6 +77,12 @@ impl From<FabricSection<OpenfabricProperties>> for Section {
}
}
+impl From<FabricSection<IsisProperties>> for Section {
+ fn from(section: FabricSection<IsisProperties>) -> Self {
+ Self::IsisFabric(section)
+ }
+}
+
impl From<FabricSection<OspfProperties>> for Section {
fn from(section: FabricSection<OspfProperties>) -> Self {
Self::OspfFabric(section)
@@ -84,6 +95,12 @@ impl From<NodeSection<OpenfabricNodeProperties>> for Section {
}
}
+impl From<NodeSection<IsisNodeProperties>> for Section {
+ fn from(section: NodeSection<IsisNodeProperties>) -> Self {
+ Self::IsisNode(section)
+ }
+}
+
impl From<NodeSection<OspfNodeProperties>> for Section {
fn from(section: NodeSection<OspfNodeProperties>) -> Self {
Self::OspfNode(section)
@@ -94,6 +111,7 @@ impl From<Fabric> for Section {
fn from(fabric: Fabric) -> Self {
match fabric {
Fabric::Openfabric(fabric_section) => fabric_section.into(),
+ Fabric::Isis(fabric_section) => fabric_section.into(),
Fabric::Ospf(fabric_section) => fabric_section.into(),
}
}
@@ -103,6 +121,7 @@ impl From<Node> for Section {
fn from(node: Node) -> Self {
match node {
Node::Openfabric(node_section) => node_section.into(),
+ Node::Isis(node_section) => node_section.into(),
Node::Ospf(node_section) => node_section.into(),
}
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
index 17d2f0b8de8a..7410f47835a1 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
@@ -10,6 +10,7 @@ use proxmox_schema::{
};
use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::protocol::isis::IsisNodeProperties;
use crate::sdn::fabric::section_config::{
fabric::{FabricId, FABRIC_ID_REGEX_STR},
protocol::{openfabric::OpenfabricNodeProperties, ospf::OspfNodeProperties},
@@ -185,6 +186,7 @@ impl<T: ApiType> ApiType for NodeSection<T> {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Node {
Openfabric(NodeSection<OpenfabricNodeProperties>),
+ Isis(NodeSection<IsisNodeProperties>),
Ospf(NodeSection<OspfNodeProperties>),
}
@@ -193,6 +195,7 @@ impl Node {
pub fn id(&self) -> &NodeSectionId {
match self {
Node::Openfabric(node_section) => node_section.id(),
+ Node::Isis(node_section) => node_section.id(),
Node::Ospf(node_section) => node_section.id(),
}
}
@@ -201,6 +204,7 @@ impl Node {
pub fn ip(&self) -> Option<std::net::Ipv4Addr> {
match self {
Node::Openfabric(node_section) => node_section.ip(),
+ Node::Isis(node_section) => node_section.ip(),
Node::Ospf(node_section) => node_section.ip(),
}
}
@@ -209,6 +213,7 @@ impl Node {
pub fn ip6(&self) -> Option<std::net::Ipv6Addr> {
match self {
Node::Openfabric(node_section) => node_section.ip6(),
+ Node::Isis(node_section) => node_section.ip6(),
Node::Ospf(node_section) => node_section.ip6(),
}
}
@@ -220,6 +225,7 @@ impl Validatable for Node {
fn validate(&self) -> Result<(), Self::Error> {
match self {
Node::Openfabric(node_section) => node_section.validate(),
+ Node::Isis(node_section) => node_section.validate(),
Node::Ospf(node_section) => node_section.validate(),
}
}
@@ -231,6 +237,12 @@ impl From<NodeSection<OpenfabricNodeProperties>> for Node {
}
}
+impl From<NodeSection<IsisNodeProperties>> for Node {
+ fn from(value: NodeSection<IsisNodeProperties>) -> Self {
+ Self::Isis(value)
+ }
+}
+
impl From<NodeSection<OspfNodeProperties>> for Node {
fn from(value: NodeSection<OspfNodeProperties>) -> Self {
Self::Ospf(value)
@@ -258,6 +270,7 @@ pub mod api {
use proxmox_schema::{Updater, UpdaterType};
use crate::sdn::fabric::section_config::protocol::{
+ isis::{IsisNodeDeletableProperties, IsisNodePropertiesUpdater},
openfabric::{
OpenfabricNodeDeletableProperties, OpenfabricNodeProperties,
OpenfabricNodePropertiesUpdater,
@@ -319,6 +332,7 @@ pub mod api {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Node {
Openfabric(NodeData<OpenfabricNodeProperties>),
+ Isis(NodeData<IsisNodeProperties>),
Ospf(NodeData<OspfNodeProperties>),
}
@@ -326,6 +340,7 @@ pub mod api {
fn from(value: super::Node) -> Self {
match value {
super::Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
+ super::Node::Isis(node_section) => Self::Isis(node_section.into()),
super::Node::Ospf(node_section) => Self::Ospf(node_section.into()),
}
}
@@ -335,6 +350,7 @@ pub mod api {
fn from(value: Node) -> Self {
match value {
Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
+ Node::Isis(node_section) => Self::Isis(node_section.into()),
Node::Ospf(node_section) => Self::Ospf(node_section.into()),
}
}
@@ -345,6 +361,10 @@ pub mod api {
NodeDataUpdater<OpenfabricNodePropertiesUpdater, OpenfabricNodeDeletableProperties>;
}
+ impl UpdaterType for NodeData<IsisNodeProperties> {
+ type Updater = NodeDataUpdater<IsisNodePropertiesUpdater, IsisNodeDeletableProperties>;
+ }
+
impl UpdaterType for NodeData<OspfNodeProperties> {
type Updater = NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>;
}
@@ -383,6 +403,7 @@ pub mod api {
Openfabric(
NodeDataUpdater<OpenfabricNodePropertiesUpdater, OpenfabricNodeDeletableProperties>,
),
+ Isis(NodeDataUpdater<IsisNodePropertiesUpdater, IsisNodeDeletableProperties>),
Ospf(NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>),
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
new file mode 100644
index 000000000000..3d110b06308f
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
@@ -0,0 +1,151 @@
+use std::ops::{Deref, DerefMut};
+
+use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr};
+use serde::{Deserialize, Serialize};
+
+use proxmox_schema::{api, property_string::PropertyString, ApiStringFormat, Updater};
+use proxmox_sdn_types::openfabric::{CsnpInterval, HelloInterval, HelloMultiplier};
+
+use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::fabric::FabricSection;
+use crate::sdn::fabric::section_config::interface::InterfaceName;
+use crate::sdn::fabric::section_config::node::NodeSection;
+use crate::sdn::fabric::FabricConfigError;
+
+/// Protocol-specific options for an IS-IS Fabric.
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisProperties {
+ /// This will be distributed to all interfaces on every node. The Hello Interval for a given
+ /// interface in seconds. The range is 1 to 600. Hello packets are used to establish and
+ /// maintain adjacency between IS-IS neighbors.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) hello_interval: Option<HelloInterval>,
+
+ /// This will be distributed to all interfaces on every node.The Complete Sequence Number
+ /// Packets (CSNP) interval in seconds. The interval range is 1 to 600.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) csnp_interval: Option<CsnpInterval>,
+}
+
+impl Validatable for FabricSection<IsisProperties> {
+ type Error = FabricConfigError;
+
+ /// Validates the [`FabricSection<IsisProperties>`].
+ ///
+ /// Checks if we have either IPv4-prefix or IPv6-prefix. If both are not set, return an error.
+ fn validate(&self) -> Result<(), Self::Error> {
+ if self.ip_prefix().is_none() && self.ip6_prefix().is_none() {
+ return Err(FabricConfigError::FabricNoIpPrefix(self.id().to_string()));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
+#[serde(rename_all = "snake_case")]
+pub enum IsisDeletableProperties {
+ HelloInterval,
+ CsnpInterval,
+}
+
+/// Properties for an IS-IS node
+#[api(
+ properties: {
+ interfaces: {
+ type: Array,
+ optional: true,
+ items: {
+ type: String,
+ description: "IS-IS interface",
+ format: &ApiStringFormat::PropertyString(&IsisInterfaceProperties::API_SCHEMA),
+ }
+ },
+ }
+)]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisNodeProperties {
+ /// Interfaces for this node
+ #[serde(default)]
+ pub(crate) interfaces: Vec<PropertyString<IsisInterfaceProperties>>,
+}
+
+impl IsisNodeProperties {
+ /// Returns an iterator over all the interfaces.
+ pub fn interfaces(&self) -> impl Iterator<Item = &IsisInterfaceProperties> {
+ self.interfaces
+ .iter()
+ .map(|property_string| property_string.deref())
+ }
+
+ /// Returns an iterator over all the interfaces (mutable).
+ pub fn interfaces_mut(&mut self) -> impl Iterator<Item = &mut IsisInterfaceProperties> {
+ self.interfaces
+ .iter_mut()
+ .map(|property_string| property_string.deref_mut())
+ }
+}
+
+impl Validatable for NodeSection<IsisNodeProperties> {
+ type Error = FabricConfigError;
+
+ /// Validates the [`FabricSection<IsisProperties>`].
+ ///
+ /// Checks if we have either an IPv4 or an IPv6 address. If neither is set, return an error.
+ fn validate(&self) -> Result<(), Self::Error> {
+ if self.ip().is_none() && self.ip6().is_none() {
+ return Err(FabricConfigError::NodeNoIp(self.id().to_string()));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum IsisNodeDeletableProperties {
+ Interfaces,
+}
+
+/// Properties for an IS-IS interface
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisInterfaceProperties {
+ pub(crate) name: InterfaceName,
+
+ /// The multiplier for the hello holding time on a given interface. The range is 2 to
+ /// 100.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) hello_multiplier: Option<HelloMultiplier>,
+
+ /// If ip and ip6 are unset, then this is an point-to-point interface
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) ip: Option<Ipv4Cidr>,
+
+ /// If ip6 and ip are unset, then this is an point-to-point interface
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) ip6: Option<Ipv6Cidr>,
+}
+
+impl IsisInterfaceProperties {
+ /// Get the name of the interface.
+ pub fn name(&self) -> &InterfaceName {
+ &self.name
+ }
+
+ /// Set the name of the interface.
+ pub fn set_name(&mut self, name: InterfaceName) {
+ self.name = name
+ }
+
+ /// Get the IPv4 of the interface.
+ pub fn ip(&self) -> Option<Ipv4Cidr> {
+ self.ip
+ }
+
+ /// Get the IPv6 of the interface.
+ pub fn ip6(&self) -> Option<Ipv6Cidr> {
+ self.ip6
+ }
+}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
index c1ec847ffbc3..2edd9f09df67 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
@@ -1,2 +1,3 @@
+pub mod isis;
pub mod openfabric;
pub mod ospf;
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH proxmox-ve-rs v2 3/3] ve-config: add integration tests for IS-IS fabrics
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 1/3] frr: add fabric properties to ISIS types and rename domain Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 2/3] ve-config: add IS-IS fabric config parsing and frr config generation Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 1/2] pve-rs: fabrics: add IS-IS protocol ifupdown config generation Gabriel Goller
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Add three integration tests for the IS-IS fabrics: a simple one, one
with dualstack ipv6 and ipv4, and one with ipv6 only.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
.../tests/fabric/cfg/isis_default/fabrics.cfg | 18 ++++++
.../fabric/cfg/isis_dualstack/fabrics.cfg | 22 +++++++
.../fabric/cfg/isis_ipv6_only/fabrics.cfg | 18 ++++++
proxmox-ve-config/tests/fabric/main.rs | 63 +++++++++++++++++++
.../snapshots/fabric__isis_default_pve.snap | 36 +++++++++++
.../snapshots/fabric__isis_default_pve1.snap | 35 +++++++++++
.../snapshots/fabric__isis_dualstack_pve.snap | 48 ++++++++++++++
.../snapshots/fabric__isis_ipv6_only_pve.snap | 37 +++++++++++
8 files changed, 277 insertions(+)
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
new file mode 100644
index 000000000000..161831977bc6
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
@@ -0,0 +1,18 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip_prefix 192.168.2.0/24
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip 192.168.2.8
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.9
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.10
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
new file mode 100644
index 000000000000..c8a0458d2e88
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
@@ -0,0 +1,22 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip_prefix 192.168.2.0/24
+ ip6_prefix 2001:db8::0/64
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip 192.168.2.8
+ ip6 2001:db8::1
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.9
+ ip6 2001:db8::2
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.10
+ ip6 2001:db8::3
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
new file mode 100644
index 000000000000..584ecbbc4ef9
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
@@ -0,0 +1,18 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip6_prefix a:b::0/75
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip6 a:b::a
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip6 a:b::b
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip6 a:b::c
diff --git a/proxmox-ve-config/tests/fabric/main.rs b/proxmox-ve-config/tests/fabric/main.rs
index 755592ff7482..434227e0c55b 100644
--- a/proxmox-ve-config/tests/fabric/main.rs
+++ b/proxmox-ve-config/tests/fabric/main.rs
@@ -41,6 +41,35 @@ fn openfabric_default() {
insta::assert_snapshot!(helper::reference_name!("pve1"), output);
}
+#[test]
+fn isis_default() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let mut frr_config = FrrConfig::default();
+ build_fabric(
+ NodeId::from_string("pve".to_owned()).expect("invalid nodeid"),
+ config.clone(),
+ &mut frr_config,
+ )
+ .unwrap();
+
+ let mut output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+
+ frr_config = FrrConfig::default();
+ build_fabric(
+ NodeId::from_string("pve1".to_owned()).expect("invalid nodeid"),
+ config.clone(),
+ &mut frr_config,
+ )
+ .unwrap();
+
+ output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve1"), output);
+}
+
#[test]
fn ospf_default() {
let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
@@ -144,6 +173,23 @@ fn openfabric_dualstack() {
insta::assert_snapshot!(helper::reference_name!("pve"), output);
}
+#[test]
+fn isis_dualstack() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let mut frr_config = FrrConfig::default();
+ build_fabric(
+ NodeId::from_string("pve".to_owned()).expect("invalid nodeid"),
+ config,
+ &mut frr_config,
+ )
+ .unwrap();
+
+ let output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+}
+
#[test]
fn openfabric_ipv6_only() {
let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
@@ -160,3 +206,20 @@ fn openfabric_ipv6_only() {
insta::assert_snapshot!(helper::reference_name!("pve"), output);
}
+
+#[test]
+fn isis_ipv6_only() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let mut frr_config = FrrConfig::default();
+ build_fabric(
+ NodeId::from_string("pve".to_owned()).expect("invalid nodeid"),
+ config,
+ &mut frr_config,
+ )
+ .unwrap();
+
+ let output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+}
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
new file mode 100644
index 000000000000..101c005e1725
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
@@ -0,0 +1,36 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+!
+router isis uwu
+ net 49.0001.1921.6800.2008.00
+exit
+!
+interface dummy_uwu
+ ip router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ip router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.8
+exit
+!
+ip protocol isis route-map pve_isis
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
new file mode 100644
index 000000000000..c647a288e47b
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
@@ -0,0 +1,35 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+!
+router isis uwu
+ net 49.0001.1921.6800.2009.00
+exit
+!
+interface dummy_uwu
+ ip router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.9
+exit
+!
+ip protocol isis route-map pve_isis
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
new file mode 100644
index 000000000000..c470f0393ed5
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
@@ -0,0 +1,48 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+!
+router isis uwu
+ net 49.0001.1921.6800.2008.00
+exit
+!
+interface dummy_uwu
+ ip router isis uwu
+ ipv6 router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ip router isis uwu
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ip router isis uwu
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+ipv6 access-list pve_isis_uwu_ip6s permit 2001:db8::/64
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.8
+exit
+!
+route-map pve_isis6 permit 110
+ match ipv6 address pve_isis_uwu_ip6s
+ set src 2001:db8::1
+exit
+!
+ip protocol isis route-map pve_isis
+!
+ipv6 protocol isis route-map pve_isis6
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
new file mode 100644
index 000000000000..802ce7fa137d
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
@@ -0,0 +1,37 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+!
+router isis uwu
+ net 49.0001.0000.0000.000a.00
+exit
+!
+interface dummy_uwu
+ ipv6 router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+ipv6 access-list pve_isis_uwu_ip6s permit a:b::/75
+!
+route-map pve_isis6 permit 100
+ match ipv6 address pve_isis_uwu_ip6s
+ set src a:b::a
+exit
+!
+!
+ipv6 protocol isis route-map pve_isis6
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH proxmox-perl-rs v2 1/2] pve-rs: fabrics: add IS-IS protocol ifupdown config generation
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (2 preceding siblings ...)
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 3/3] ve-config: add integration tests for IS-IS fabrics Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 2/2] sdn: add IS-IS fabric status reporting Gabriel Goller
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Generate the interfaces that have been configured with IS-IS to the
ifupdown config (enable ip-forwarding on all). IS-IS needs to have the
ip-address duplicated from the dummy interface to every interface in
unnumbered mode (like with ospf). This could potentially be fixed in the
future with [0].
[0]: https://github.com/FRRouting/frr/pull/19429
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pve-rs/src/bindings/sdn/fabrics.rs | 44 ++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
index 18848c405201..c4150c830a0a 100644
--- a/pve-rs/src/bindings/sdn/fabrics.rs
+++ b/pve-rs/src/bindings/sdn/fabrics.rs
@@ -354,6 +354,13 @@ pub mod pve_rs_sdn_fabrics {
}
}
}
+ ConfigNode::Isis(node_section) => {
+ for interface in node_section.properties_mut().interfaces_mut() {
+ if let Some(mapped_name) = map_name(&mapping, interface.name())? {
+ interface.set_name(mapped_name);
+ }
+ }
+ }
ConfigNode::Ospf(node_section) => {
for interface in node_section.properties_mut().interfaces_mut() {
if let Some(mapped_name) = map_name(&mapping, interface.name())? {
@@ -453,6 +460,9 @@ pub mod pve_rs_sdn_fabrics {
FabricEntry::Openfabric(_) => {
daemons.insert("fabricd");
}
+ FabricEntry::Isis(_) => {
+ daemons.insert("isisd");
+ }
};
}
@@ -547,6 +557,40 @@ pub mod pve_rs_sdn_fabrics {
}
}
}
+ ConfigNode::Isis(node_section) => {
+ for interface in node_section.properties().interfaces() {
+ if let Some(ip) = interface.ip() {
+ let interface =
+ render_interface(interface.name(), Cidr::from(ip), false)?;
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface}")?;
+ }
+ if let Some(ip) = interface.ip6() {
+ let interface =
+ render_interface(interface.name(), Cidr::from(ip), false)?;
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface}")?;
+ }
+
+ // If no ip is configured, add auto and iface with node ip to bring
+ // interface up ISIS doesn't really need an ip on the interface, but the
+ // problem is that arp can't tell which source address to use in some
+ // cases, so it's better if we set the node address on all the fabric
+ // interfaces.
+ if let (None, None) = (interface.ip(), interface.ip6()) {
+ let cidr = Cidr::from(if let Some(ip) = node.ip() {
+ IpAddr::from(ip)
+ } else if let Some(ip) = node.ip6() {
+ IpAddr::from(ip)
+ } else {
+ anyhow::bail!("there has to be a ipv4 or ipv6 node address");
+ });
+ let interface = render_interface(interface.name(), cidr, false)?;
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface}")?;
+ }
+ }
+ }
ConfigNode::Ospf(node_section) => {
for interface in node_section.properties().interfaces() {
if let Some(ip) = interface.ip() {
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH proxmox-perl-rs v2 2/2] sdn: add IS-IS fabric status reporting
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (3 preceding siblings ...)
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 1/2] pve-rs: fabrics: add IS-IS protocol ifupdown config generation Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-network v2 1/2] fabrics: add IS-IS api types Gabriel Goller
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Extend fabric status reporting to cover the IS-IS fabric alongside the
existing OpenFabric and OSPF support.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pve-rs/src/bindings/sdn/fabrics.rs | 99 ++++++++++++++++++++++++++++++
pve-rs/src/sdn/status.rs | 15 +++++
2 files changed, 114 insertions(+)
diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
index c4150c830a0a..6f4207b24813 100644
--- a/pve-rs/src/bindings/sdn/fabrics.rs
+++ b/pve-rs/src/bindings/sdn/fabrics.rs
@@ -687,6 +687,41 @@ pub mod pve_rs_sdn_fabrics {
proxmox_sys::nodename(),
)
}
+ FabricEntry::Isis(_) => {
+ let isis_ipv4_routes_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show ip route isis json'"])
+ .output()?
+ .stdout,
+ )?;
+
+ let isis_ipv6_routes_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show ipv6 route isis json'"])
+ .output()?
+ .stdout,
+ )?;
+
+ let mut isis_routes: proxmox_frr::de::Routes =
+ if isis_ipv4_routes_string.is_empty() {
+ proxmox_frr::de::Routes::default()
+ } else {
+ serde_json::from_str(&isis_ipv4_routes_string)
+ .with_context(|| "error parsing isis ipv4 routes")?
+ };
+ if !isis_ipv6_routes_string.is_empty() {
+ let isis_ipv6_routes: proxmox_frr::de::Routes =
+ serde_json::from_str(&isis_ipv6_routes_string)
+ .with_context(|| "error parsing isis ipv6 routes")?;
+ isis_routes.0.extend(isis_ipv6_routes.0);
+ }
+ status::get_routes(
+ fabric_id,
+ config,
+ isis_routes,
+ proxmox_sys::nodename(),
+ )
+ }
FabricEntry::Ospf(_) => {
let ospf_routes_string = String::from_utf8(
Command::new("sh")
@@ -738,6 +773,23 @@ pub mod pve_rs_sdn_fabrics {
status::get_neighbors_openfabric(fabric_id, openfabric_neighbors).map(|v| v.into())
}
+ FabricEntry::Isis(_) => {
+ let isis_neighbors_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show isis neighbor detail json'"])
+ .output()?
+ .stdout,
+ )?;
+ let isis_neighbors: proxmox_frr::de::openfabric::Neighbors =
+ if isis_neighbors_string.is_empty() {
+ proxmox_frr::de::openfabric::Neighbors::default()
+ } else {
+ serde_json::from_str(&isis_neighbors_string)
+ .with_context(|| "error parsing isis neighbors")?
+ };
+
+ status::get_neighbors_openfabric(fabric_id, isis_neighbors).map(|v| v.into())
+ }
FabricEntry::Ospf(fabric) => {
let ospf_neighbors_string = String::from_utf8(
Command::new("sh")
@@ -797,6 +849,24 @@ pub mod pve_rs_sdn_fabrics {
status::get_interfaces_openfabric(fabric_id, openfabric_interfaces)
.map(|v| v.into())
}
+ FabricEntry::Isis(_) => {
+ let isis_interface_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show isis interface json'"])
+ .output()?
+ .stdout,
+ )?;
+ let isis_interfaces: proxmox_frr::de::openfabric::Interfaces =
+ if isis_interface_string.is_empty() {
+ proxmox_frr::de::openfabric::Interfaces::default()
+ } else {
+ serde_json::from_str(&isis_interface_string)
+ .with_context(|| "error parsing isis interfaces")?
+ };
+
+ status::get_interfaces_openfabric(fabric_id, isis_interfaces)
+ .map(|v| v.into())
+ }
FabricEntry::Ospf(fabric) => {
let ospf_interfaces_string = String::from_utf8(
Command::new("sh")
@@ -852,6 +922,20 @@ pub mod pve_rs_sdn_fabrics {
.stdout,
)?;
+ let isis_ipv4_routes_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show ip route isis json'"])
+ .output()?
+ .stdout,
+ )?;
+
+ let isis_ipv6_routes_string = String::from_utf8(
+ Command::new("sh")
+ .args(["-c", "vtysh -c 'show ipv6 route isis json'"])
+ .output()?
+ .stdout,
+ )?;
+
let ospf_routes_string = String::from_utf8(
Command::new("sh")
.args(["-c", "vtysh -c 'show ip route ospf json'"])
@@ -873,6 +957,20 @@ pub mod pve_rs_sdn_fabrics {
openfabric_routes.0.extend(openfabric_ipv6_routes.0);
}
+ let mut isis_routes: proxmox_frr::de::Routes =
+ if isis_ipv4_routes_string.is_empty() {
+ proxmox_frr::de::Routes::default()
+ } else {
+ serde_json::from_str(&isis_ipv4_routes_string)
+ .with_context(|| "error parsing isis ipv4 routes")?
+ };
+ if !isis_ipv6_routes_string.is_empty() {
+ let isis_ipv6_routes: proxmox_frr::de::Routes =
+ serde_json::from_str(&isis_ipv6_routes_string)
+ .with_context(|| "error parsing isis ipv6 routes")?;
+ isis_routes.0.extend(isis_ipv6_routes.0);
+ }
+
let ospf_routes: proxmox_frr::de::Routes = if ospf_routes_string.is_empty() {
proxmox_frr::de::Routes::default()
} else {
@@ -883,6 +981,7 @@ pub mod pve_rs_sdn_fabrics {
let route_status = status::RoutesParsed {
openfabric: openfabric_routes,
ospf: ospf_routes,
+ isis: isis_routes,
};
status::get_status(config, route_status, proxmox_sys::nodename())
diff --git a/pve-rs/src/sdn/status.rs b/pve-rs/src/sdn/status.rs
index e1e336297ac9..a11603a8af22 100644
--- a/pve-rs/src/sdn/status.rs
+++ b/pve-rs/src/sdn/status.rs
@@ -135,6 +135,8 @@ pub enum Protocol {
Openfabric,
/// OSPF
Ospf,
+ /// IS-IS
+ Isis,
}
/// The status of a fabric.
@@ -173,6 +175,8 @@ pub struct RoutesParsed {
pub openfabric: de::Routes,
/// All ospf routes in FRR
pub ospf: de::Routes,
+ /// All isis routes in FRR
+ pub isis: de::Routes,
}
/// Config used to parse the fabric part of the running-config
@@ -217,6 +221,11 @@ pub fn get_routes(
.interfaces()
.map(|i| i.name().as_str())
.collect(),
+ ConfigNode::Isis(n) => n
+ .properties()
+ .interfaces()
+ .map(|i| i.name().as_str())
+ .collect(),
};
let dummy_interface = format!("dummy_{}", fabric_id.as_str());
@@ -429,6 +438,7 @@ pub fn get_status(
let (current_protocol, all_routes) = match &node {
ConfigNode::Openfabric(_) => (Protocol::Openfabric, &routes.openfabric.0),
ConfigNode::Ospf(_) => (Protocol::Ospf, &routes.ospf.0),
+ ConfigNode::Isis(_) => (Protocol::Isis, &routes.isis.0),
};
// get interfaces
@@ -443,6 +453,11 @@ pub fn get_status(
.interfaces()
.map(|i| i.name().as_str())
.collect(),
+ ConfigNode::Isis(n) => n
+ .properties()
+ .interfaces()
+ .map(|i| i.name().as_str())
+ .collect(),
};
// determine status by checking if any routes exist for our interfaces
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-network v2 1/2] fabrics: add IS-IS api types
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (4 preceding siblings ...)
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 2/2] sdn: add IS-IS fabric status reporting Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-network v2 2/2] sdn: controllers: rename isis domain to fabric_id Gabriel Goller
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Add IS-IS api-types and add it to the protocol enum. Also make 'isisd' a
frr daemon managed by pve (This means we can enable and disable it).
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/Network/SDN/Fabrics.pm | 47 +++++++++++++++++++++++++++++-----
src/PVE/Network/SDN/Frr.pm | 1 +
2 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/src/PVE/Network/SDN/Fabrics.pm b/src/PVE/Network/SDN/Fabrics.pm
index 3ca362a02660..f218637e0971 100644
--- a/src/PVE/Network/SDN/Fabrics.pm
+++ b/src/PVE/Network/SDN/Fabrics.pm
@@ -46,7 +46,7 @@ PVE::JSONSchema::register_standard_option(
{
description => "Type of configuration entry in an SDN Fabric section config",
type => 'string',
- enum => ['openfabric', 'ospf'],
+ enum => ['openfabric', 'ospf', 'isis'],
},
);
@@ -169,6 +169,41 @@ sub node_properties {
description => 'OpenFabric network interface',
optional => 1,
},
+ {
+ type => 'array',
+ 'instance-types' => ['isis'],
+ items => {
+ type => 'string',
+ format => {
+ name => {
+ type => 'string',
+ format => 'pve-iface',
+ description => 'Name of the network interface',
+ },
+ hello_multiplier => {
+ type => 'integer',
+ description => 'The hello_multiplier property of the interface',
+ optional => 1,
+ minimum => 2,
+ maximum => 100,
+ },
+ ip => {
+ type => 'string',
+ format => 'CIDRv4',
+ description => 'IPv4 address for this node',
+ optional => 1,
+ },
+ ip6 => {
+ type => 'string',
+ format => 'CIDRv6',
+ description => 'IPv6 address for this node',
+ optional => 1,
+ },
+ },
+ },
+ description => 'IS-IS network interface',
+ optional => 1,
+ },
{
type => 'array',
'instance-types' => ['ospf'],
@@ -232,8 +267,8 @@ sub fabric_properties {
hello_interval => {
type => 'number',
'type-property' => 'protocol',
- 'instance-types' => ['openfabric'],
- description => 'The hello_interval property for Openfabric',
+ 'instance-types' => ['openfabric', 'isis'],
+ description => 'The hello_interval property for Openfabric and IS-IS',
optional => 1,
minimum => 1,
maximum => 600,
@@ -241,8 +276,8 @@ sub fabric_properties {
csnp_interval => {
type => 'number',
'type-property' => 'protocol',
- 'instance-types' => ['openfabric'],
- description => 'The csnp_interval property for Openfabric',
+ 'instance-types' => ['openfabric', 'isis'],
+ description => 'The csnp_interval property for Openfabric and IS-IS',
optional => 1,
minimum => 1,
maximum => 600,
@@ -265,7 +300,7 @@ sub fabric_properties {
oneOf => [
{
type => 'array',
- 'instance-types' => ['openfabric'],
+ 'instance-types' => ['openfabric', 'isis'],
items => {
type => 'string',
enum => ['hello_interval', 'csnp_interval'],
diff --git a/src/PVE/Network/SDN/Frr.pm b/src/PVE/Network/SDN/Frr.pm
index 99002c6b65bf..da7ce0381297 100644
--- a/src/PVE/Network/SDN/Frr.pm
+++ b/src/PVE/Network/SDN/Frr.pm
@@ -125,6 +125,7 @@ sub restart {
my $SDN_DAEMONS_DEFAULT = {
ospfd => 0,
fabricd => 0,
+ isisd => 0,
};
=head3 set_daemon_status(\%daemons, $set_default)
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-network v2 2/2] sdn: controllers: rename isis domain to fabric_id
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (5 preceding siblings ...)
2026-02-19 15:25 ` [PATCH pve-network v2 1/2] fabrics: add IS-IS api types Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-manager v2 1/2] fabrics: add IS-IS panels Gabriel Goller
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Rename the "domain" property to "fabric_id" in the ISIS controller to
align with the ISIS fabric naming convention and enable sharing of
proxmox-frr ISIS types between both components.
The ISIS fabric and ISIS controller share basic configuration options,
but previously used different property names ("fabric_id" vs "domain").
Since fabrics are hardcoded to use "fabric_id", standardize the
controller to use the same naming.
This change is backwards compatible as:
1) The template series has not yet been merged and will likely merge
alongside this series
2) It is unlikely that libpve-rs-perl and libpve-network-perl would
become out of sync
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/Network/SDN/Controllers/IsisPlugin.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
index 454bdda6d316..81aef2de7e24 100644
--- a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
@@ -87,7 +87,7 @@ sub generate_frr_config {
for my $iface (sort @ifaces) {
my $iface_name = $altnames->{$iface} // $iface;
$config->{frr}->{isis}->{interfaces}->{$iface_name} //= {};
- $config->{frr}->{isis}->{interfaces}->{$iface_name}->{domain} = $isis_domain;
+ $config->{frr}->{isis}->{interfaces}->{$iface_name}->{fabric_id} = $isis_domain;
$config->{frr}->{isis}->{interfaces}->{$iface_name}->{is_ipv4} = 1;
$config->{frr}->{isis}->{interfaces}->{$iface_name}->{is_ipv6} = 0;
}
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-manager v2 1/2] fabrics: add IS-IS panels
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (6 preceding siblings ...)
2026-02-19 15:25 ` [PATCH pve-network v2 2/2] sdn: controllers: rename isis domain to fabric_id Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-manager v2 2/2] sdn: add warning about IS-IS controller deprecation Gabriel Goller
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
The IS-IS panels have the same properties as OpenFabric both on the
FabricEdit and the NodeEdit.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/Makefile | 3 +
www/manager6/sdn/FabricsView.js | 12 ++++
www/manager6/sdn/fabrics/NodeEdit.js | 1 +
www/manager6/sdn/fabrics/isis/FabricEdit.js | 69 +++++++++++++++++++
.../sdn/fabrics/isis/InterfacePanel.js | 34 +++++++++
www/manager6/sdn/fabrics/isis/NodeEdit.js | 22 ++++++
6 files changed, 141 insertions(+)
create mode 100644 www/manager6/sdn/fabrics/isis/FabricEdit.js
create mode 100644 www/manager6/sdn/fabrics/isis/InterfacePanel.js
create mode 100644 www/manager6/sdn/fabrics/isis/NodeEdit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index da602523b27a..a202a2e822e3 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -331,6 +331,9 @@ JSSRC= \
sdn/fabrics/openfabric/InterfacePanel.js \
sdn/fabrics/openfabric/NodeEdit.js \
sdn/fabrics/openfabric/FabricEdit.js \
+ sdn/fabrics/isis/InterfacePanel.js \
+ sdn/fabrics/isis/NodeEdit.js \
+ sdn/fabrics/isis/FabricEdit.js \
sdn/fabrics/ospf/InterfacePanel.js \
sdn/fabrics/ospf/NodeEdit.js \
sdn/fabrics/ospf/FabricEdit.js \
diff --git a/www/manager6/sdn/FabricsView.js b/www/manager6/sdn/FabricsView.js
index 093a70f35d3c..dcedc214e184 100644
--- a/www/manager6/sdn/FabricsView.js
+++ b/www/manager6/sdn/FabricsView.js
@@ -32,6 +32,7 @@ Ext.define('PVE.sdn.Fabric.View', {
if (rec.data.type === 'fabric') {
const PROTOCOL_DISPLAY_NAMES = {
openfabric: 'OpenFabric',
+ isis: 'IS-IS',
ospf: 'OSPF',
};
const displayValue = PROTOCOL_DISPLAY_NAMES[value];
@@ -190,6 +191,10 @@ Ext.define('PVE.sdn.Fabric.View', {
text: 'OpenFabric',
handler: 'addOpenfabric',
},
+ {
+ text: 'IS-IS',
+ handler: 'addIsis',
+ },
{
text: 'OSPF',
handler: 'addOspf',
@@ -271,6 +276,7 @@ Ext.define('PVE.sdn.Fabric.View', {
getFabricEditPanel: function (protocol) {
const FABRIC_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.Fabric.Edit',
+ isis: 'PVE.sdn.Fabric.Isis.Fabric.Edit',
ospf: 'PVE.sdn.Fabric.Ospf.Fabric.Edit',
};
@@ -280,6 +286,7 @@ Ext.define('PVE.sdn.Fabric.View', {
getNodeEditPanel: function (protocol) {
const NODE_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.Node.Edit',
+ isis: 'PVE.sdn.Fabric.Isis.Node.Edit',
ospf: 'PVE.sdn.Fabric.Ospf.Node.Edit',
};
@@ -291,6 +298,11 @@ Ext.define('PVE.sdn.Fabric.View', {
me.openFabricAddWindow('openfabric');
},
+ addIsis: function () {
+ let me = this;
+ me.openFabricAddWindow('isis');
+ },
+
addOspf: function () {
let me = this;
me.openFabricAddWindow('ospf');
diff --git a/www/manager6/sdn/fabrics/NodeEdit.js b/www/manager6/sdn/fabrics/NodeEdit.js
index 161917ccd1e5..1a8ec9df1b4b 100644
--- a/www/manager6/sdn/fabrics/NodeEdit.js
+++ b/www/manager6/sdn/fabrics/NodeEdit.js
@@ -199,6 +199,7 @@ Ext.define('PVE.sdn.Fabric.Node.Edit', {
getInterfacePanel: function (protocol) {
const INTERFACE_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.InterfacePanel',
+ isis: 'PVE.sdn.Fabric.Isis.InterfacePanel',
ospf: 'PVE.sdn.Fabric.Ospf.InterfacePanel',
};
diff --git a/www/manager6/sdn/fabrics/isis/FabricEdit.js b/www/manager6/sdn/fabrics/isis/FabricEdit.js
new file mode 100644
index 000000000000..38825de0bd0f
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/FabricEdit.js
@@ -0,0 +1,69 @@
+Ext.define('PVE.sdn.Fabric.Isis.Fabric.Edit', {
+ extend: 'PVE.sdn.Fabric.Fabric.Edit',
+
+ subject: 'IS-IS',
+ onlineHelp: 'pvesdn_isis_fabric',
+
+ viewModel: {
+ data: {
+ showIpv6ForwardingHint: false,
+ },
+ },
+
+ extraRequestParams: {
+ protocol: 'isis',
+ },
+
+ additionalItems: [
+ {
+ xtype: 'displayfield',
+ value: 'To make IPv6 fabrics work, enable global IPv6 forwarding on all nodes. Click on the Help button for more details.',
+ bind: {
+ hidden: '{!showIpv6ForwardingHint}',
+ },
+ userCls: 'pmx-hint',
+ },
+ {
+ xtype: 'proxmoxtextfield',
+ fieldLabel: gettext('IPv6 Prefix'),
+ labelWidth: 120,
+ name: 'ip6_prefix',
+ allowBlank: true,
+ skipEmptyText: true,
+ cbind: {
+ disabled: '{!isCreate}',
+ deleteEmpty: '{!isCreate}',
+ },
+ listeners: {
+ change: function (textbox, value) {
+ let vm = textbox.up('window').getViewModel();
+ vm.set('showIpv6ForwardingHint', !!value);
+ },
+ },
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Hello Interval'),
+ labelWidth: 120,
+ name: 'hello_interval',
+ allowBlank: true,
+ emptyText: '3',
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('CSNP Interval'),
+ labelWidth: 120,
+ name: 'csnp_interval',
+ allowBlank: true,
+ emptyText: '10',
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ ],
+});
diff --git a/www/manager6/sdn/fabrics/isis/InterfacePanel.js b/www/manager6/sdn/fabrics/isis/InterfacePanel.js
new file mode 100644
index 000000000000..dc2c5b55eb6a
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/InterfacePanel.js
@@ -0,0 +1,34 @@
+Ext.define('PVE.sdn.Fabric.Isis.InterfacePanel', {
+ extend: 'PVE.sdn.Fabric.InterfacePanel',
+
+ additionalColumns: [
+ {
+ text: gettext('IPv6'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'ip6',
+ flex: 1,
+ widget: {
+ xtype: 'proxmoxtextfield',
+ isFormField: false,
+ bind: {
+ disabled: '{record.isDisabled}',
+ },
+ },
+ },
+ {
+ text: gettext('Hello Multiplier'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'hello_multiplier',
+ flex: 1,
+ hidden: true,
+ widget: {
+ xtype: 'proxmoxintegerfield',
+ isFormField: false,
+ emptyText: '10',
+ bind: {
+ disabled: '{record.isDisabled}',
+ },
+ },
+ },
+ ],
+});
diff --git a/www/manager6/sdn/fabrics/isis/NodeEdit.js b/www/manager6/sdn/fabrics/isis/NodeEdit.js
new file mode 100644
index 000000000000..9878edcccb73
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/NodeEdit.js
@@ -0,0 +1,22 @@
+Ext.define('PVE.sdn.Fabric.Isis.Node.Edit', {
+ extend: 'PVE.sdn.Fabric.Node.Edit',
+ protocol: 'isis',
+
+ extraRequestParams: {
+ protocol: 'isis',
+ },
+
+ additionalItems: [
+ {
+ xtype: 'proxmoxtextfield',
+ fieldLabel: gettext('IPv6'),
+ labelWidth: 120,
+ name: 'ip6',
+ allowBlank: true,
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ ],
+});
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-manager v2 2/2] sdn: add warning about IS-IS controller deprecation
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (7 preceding siblings ...)
2026-02-19 15:25 ` [PATCH pve-manager v2 1/2] fabrics: add IS-IS panels Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-docs v2 1/1] sdn: add section about IS-IS fabric Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-gui-tests v2 1/1] fabrics: add screenshots for IS-IS fabric and nodes Gabriel Goller
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Instead of the IS-IS controller the new IS-IS fabric should be used.
Every option in the IS-IS controller can also be set on the new IS-IS
fabric. Nevertheless they can both be used simultaniously and there is
not yet a clear roadmap of when we are going to deprecate the IS-IS
controller.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/sdn/controllers/IsisEdit.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/www/manager6/sdn/controllers/IsisEdit.js b/www/manager6/sdn/controllers/IsisEdit.js
index edacd1736bba..f075fd7cdc1a 100644
--- a/www/manager6/sdn/controllers/IsisEdit.js
+++ b/www/manager6/sdn/controllers/IsisEdit.js
@@ -20,6 +20,11 @@ Ext.define('PVE.sdn.controllers.IsisInputPanel', {
var me = this;
me.items = [
+ {
+ xtype: 'displayfield',
+ value: 'Consider using the new IS-IS fabrics in the "Fabrics" in the sidebar. The "isis" controller will be deprecated soon.',
+ userCls: 'pmx-hint',
+ },
{
xtype: 'pveNodeSelector',
name: 'node',
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-docs v2 1/1] sdn: add section about IS-IS fabric
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (8 preceding siblings ...)
2026-02-19 15:25 ` [PATCH pve-manager v2 2/2] sdn: add warning about IS-IS controller deprecation Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-gui-tests v2 1/1] fabrics: add screenshots for IS-IS fabric and nodes Gabriel Goller
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Add short section about IS-IS fabrics, nearly the same as the OpenFabric
section. Also add a warning on the IS-IS Controller about the
deprecation and a link to the IS-IS Fabrics. Also mention the
differences between them, which is that the fabrics don't redistribute
connected routes.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pvesdn.adoc | 111 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 105 insertions(+), 6 deletions(-)
diff --git a/pvesdn.adoc b/pvesdn.adoc
index 479468f506de..243d5e312a1d 100644
--- a/pvesdn.adoc
+++ b/pvesdn.adoc
@@ -528,6 +528,10 @@ bgp-multipath-as-path-relax:: Allow ECMP if your peers have different ASN.
ISIS Controller
~~~~~~~~~~~~~~~
+WARNING: Consider using the xref:pvesdn_isis[IS-IS] Fabric, the ISIS Controller
+will be deprecated soon. Note that the IS-IS Fabric does not redistribute
+connected routes into the fabric.
+
The ISIS controller is not used directly by a zone.
You can use it to configure FRR to export EVPN routes to an ISIS domain.
@@ -622,12 +626,12 @@ behavior and proper source address selection throughout the fabric.
Notes on IPv6
^^^^^^^^^^^^^
-IPv6 is currently only usable on OpenFabric fabrics. These IPv6 Fabrics need
-global IPv6 forwarding enabled on all nodes contained in the fabric. Without
-IPv6 forwarding, non-full-mesh fabrics won't work because the transit nodes
-don't forward packets to the outer nodes. Currently there isn't an easy way to
-enable IPv6 forwarding per-interface like with IPv4, so it has to be enabled
-globally. This can be accomplished by appending this line:
+IPv6 is currently only usable on OpenFabric and IS-IS fabrics. These IPv6
+Fabrics need global IPv6 forwarding enabled on all nodes contained in the
+fabric. Without IPv6 forwarding, non-full-mesh fabrics won't work because the
+transit nodes don't forward packets to the outer nodes. Currently there isn't
+an easy way to enable IPv6 forwarding per-interface like with IPv4, so it has
+to be enabled globally. This can be accomplished by appending this line:
----
post-up sysctl -w net.ipv6.conf.all.forwarding=1
@@ -730,6 +734,101 @@ WARNING: When you remove an interface with an entry in `/etc/network/interfaces`
that has `manual` set, then the IP will not get removed on applying the SDN
configuration.
+
+[[pvesdn_isis]]
+IS-IS
+~~~~~
+
+NOTE: Unlike the IS-IS Controller, the IS-IS Fabric does not distribute all the
+connected routes into the fabric. So it does not set
+`redistribute ipv4/6 connected level-1`.
+
+IS-IS is a routing protocol specifically designed for data center fabrics.
+
+[thumbnail="screenshot/gui-datacenter-create-fabric-isis.png"]
+
+Configuration options:
+
+[[pvesdn_isis_fabric]]
+On the Fabric
+^^^^^^^^^^^^^
+
+Name:: This is the name (domain) of the IS-IS fabric and can be at most 8 characters long.
+
+IPv4 Prefix:: IPv4 CIDR network range (e.g., 192.0.2.0/24) used to verify that
+all router-IDs in the fabric are contained within this prefix.
+
+IPv6 Prefix:: IPv6 CIDR network range (e.g., 2001:db8::/64) used to verify that
+all router-IDs in the fabric are contained within this prefix.
+
+WARNING: For IPv6 fabrics to work, global forwarding needs to be enabled on all
+nodes. Check xref:pvesdn_config_fabrics_ipv6[Notes on IPv6] for how to do it and additional info.
+
+Hello Interval:: Controls how frequently (in seconds) hello packets are sent to
+discover and maintain connections with neighboring nodes. Lower values detect
+failures faster but increase network traffic. This option is global on the
+fabric, meaning every interface on every node in this fabric will inherit this
+hello-interval property. The default value is 3 seconds.
+
+CSNP Interval:: Sets how frequently (in seconds) the node synchronizes its
+routing database with neighbors. Lower values keep the network topology information
+more quickly in sync but increase network traffic. This option is global on the
+fabric, meaning every interface on every node in this fabric will inherit this
+property. The default value is 10 seconds.
+
+[[pvesdn_isis_node]]
+On the Node
+^^^^^^^^^^^
+
+[thumbnail="screenshot/gui-datacenter-create-node-isis.png"]
+
+Options that are available on every node that is part of a fabric:
+
+Node:: Select the node which will be added to the fabric. Only nodes that
+currently are in the cluster will be shown.
+
+IPv4:: A unique IPv4 address used to generate the IS-IS
+Network Entity Title (NET). Each node in the same fabric must have a different
+Router-ID, while a single node must use the same NET address across all fabrics
+(If this is not given {pve} will automatically choose one and ensure that the
+configuration is valid).
+
+IPv6:: A unique IPv6 address used to generate the IS-IS
+Network Entity Title (NET). Each node in the same fabric must have a different
+Router-ID, while a single node must use the same NET address across all fabrics.
+If a IPv4 and IPv6 address is configured, the IPv4 one will be used to derive
+the NET.
+
+WARNING: When using IPv6 addresses, the last 3 segments are used to generate
+the NET. Ensure these segments differ between nodes.
+
+Interfaces:: Specify the interfaces used to establish peering connections with
+other IS-IS nodes. Preferably select interfaces without pre-assigned IP
+addresses, then configure addresses in the IPv4/IPv6 column if needed. A dummy
+"loopback" interface with the router-id is automatically created.
+
+On The Interface
+^^^^^^^^^^^^^^^^
+
+The following optional parameters can be configured per interface when enabling
+the additional columns:
+
+IP::: A IPv4 that should get automatically configured on this interface. Must
+include the netmask (e.g. /31). If no address is set, the interface is
+configured as a `point-to-point` interface.
+
+IPv6::: A IPv6 that should get automatically configured on this interface. Must
+include the netmask (e.g. /127). If no address is set, the interface is
+configured as a `point-to-point` interface.
+
+Hello Multiplier::: Defines how many missed hello packets constitute a failed
+connection. Higher values make the connection more resilient to packet loss but
+slow down failure detection. The default value is 10.
+
+WARNING: When you remove an interface with an entry in `/etc/network/interfaces`
+that has `manual` set, then the IP will not get removed on applying the SDN
+configuration.
+
[[pvesdn_ospf]]
OSPF
~~~~
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH pve-gui-tests v2 1/1] fabrics: add screenshots for IS-IS fabric and nodes
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
` (9 preceding siblings ...)
2026-02-19 15:25 ` [PATCH pve-docs v2 1/1] sdn: add section about IS-IS fabric Gabriel Goller
@ 2026-02-19 15:25 ` Gabriel Goller
10 siblings, 0 replies; 12+ messages in thread
From: Gabriel Goller @ 2026-02-19 15:25 UTC (permalink / raw)
To: pve-devel
Add IS-IS fabric and node screenshots.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
create_fabrics_screenshots | 43 ++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/create_fabrics_screenshots b/create_fabrics_screenshots
index a83bb222d44b..c0e67f68063c 100755
--- a/create_fabrics_screenshots
+++ b/create_fabrics_screenshots
@@ -14,6 +14,7 @@ sub prepare_fabrics {
my ($conn) = @_;
eval {
# might not exist, so ignore any errors
+ $conn->delete("/cluster/sdn/fabrics/fabric/test3");
$conn->delete("/cluster/sdn/fabrics/fabric/test2");
$conn->delete("/cluster/sdn/fabrics/fabric/test1");
};
@@ -30,14 +31,24 @@ sub prepare_fabrics {
ip_prefix => "198.51.100.0/24",
protocol => "ospf",
};
+ my $isis = {
+ id => "test3",
+ ip_prefix => "192.0.15.0/24",
+ hello_interval => "1",
+ protocol => "isis",
+ };
$conn->post("/cluster/sdn/fabrics/fabric", $openfabric);
$conn->post("/cluster/sdn/fabrics/fabric", $ospf);
+ $conn->post("/cluster/sdn/fabrics/fabric", $isis);
}
sub prepare_nodes {
my ($conn) = @_;
eval {
# might not exist, so ignore any errors
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve0");
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve1");
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve2");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve0");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve1");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve2");
@@ -53,6 +64,7 @@ sub prepare_nodes {
my $last_octet = $i + 1;
my $ospf_iface = ($i * 2) + 19;
my $openfabric_iface = ($i * 2) + 25;
+ my $isis_iface = ($i * 2) + 30;
my $ospf_node = {
node_id => $node_name,
@@ -72,9 +84,19 @@ sub prepare_nodes {
],
protocol => "openfabric",
};
+ my $isis_node = {
+ node_id => $node_name,
+ ip => "192.0.15.$last_octet",
+ interfaces => [
+ "name=ens$isis_iface",
+ "name=ens" . ($isis_iface + 1)
+ ],
+ protocol => "isis",
+ };
$conn->post("/cluster/sdn/fabrics/node/test1/", $openfabric_node);
$conn->post("/cluster/sdn/fabrics/node/test2/", $ospf_node);
+ $conn->post("/cluster/sdn/fabrics/node/test3/", $isis_node);
}
}
@@ -131,6 +153,18 @@ sub create_fabrics_ui_screenshots {
$gui->element_screenshot("gui-datacenter-create-fabric-openfabric.png", $window);
$gui->window_close($window);
+ $menu = $gui->find_button('Add Fabric', $panel)->click();
+ $gui->find_menu_item('IS-IS')->click();
+
+ $window = $gui->find_dialog("Create: IS-IS");
+ $gui->setValue($window, 'id', 'test3');
+ $gui->setValue($window, 'ip_prefix', '192.0.2.0/24');
+ $gui->setValue($window, 'hello_interval', '1');
+
+ $gui->element_screenshot("gui-datacenter-create-fabric-isis.png", $window);
+ $gui->window_close($window);
+
+
$menu = $gui->find_button('Add Fabric', $panel)->click();
$gui->find_menu_item('OSPF')->click();
@@ -164,6 +198,15 @@ sub create_fabrics_ui_screenshots {
$gui->element_screenshot("gui-datacenter-create-node-ospf.png", $window);
$gui->window_close($window);
+ open_add_node_panel($gui, "isis");
+ sleep_ms(500);
+
+ $window = $gui->find_dialog("Create: Node");
+ $gui->setValue($window, 'ip', '198.51.100.1');
+ select_interfaces($gui, "ens19", "ens20");
+ $gui->element_screenshot("gui-datacenter-create-node-isis.png", $window);
+ $gui->window_close($window);
+
# get fabric overview
prepare_nodes($conn);
sleep_ms(250);
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-02-19 15:26 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-19 15:25 [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs,-perl-rs} v2 00/11] Add IS-IS protocol to fabrics Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 1/3] frr: add fabric properties to ISIS types and rename domain Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 2/3] ve-config: add IS-IS fabric config parsing and frr config generation Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-ve-rs v2 3/3] ve-config: add integration tests for IS-IS fabrics Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 1/2] pve-rs: fabrics: add IS-IS protocol ifupdown config generation Gabriel Goller
2026-02-19 15:25 ` [PATCH proxmox-perl-rs v2 2/2] sdn: add IS-IS fabric status reporting Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-network v2 1/2] fabrics: add IS-IS api types Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-network v2 2/2] sdn: controllers: rename isis domain to fabric_id Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-manager v2 1/2] fabrics: add IS-IS panels Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-manager v2 2/2] sdn: add warning about IS-IS controller deprecation Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-docs v2 1/1] sdn: add section about IS-IS fabric Gabriel Goller
2026-02-19 15:25 ` [PATCH pve-gui-tests v2 1/1] fabrics: add screenshots for IS-IS fabric and nodes Gabriel Goller
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox