From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id D07461FF13C for ; Thu, 19 Feb 2026 16:26:12 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E7FF81C23A; Thu, 19 Feb 2026 16:26:50 +0100 (CET) From: Gabriel Goller To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox-perl-rs v2 2/2] sdn: add IS-IS fabric status reporting Date: Thu, 19 Feb 2026 16:25:25 +0100 Message-ID: <20260219152544.427439-6-g.goller@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260219152544.427439-1-g.goller@proxmox.com> References: <20260219152544.427439-1-g.goller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1771514741384 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.002 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: SJHFOKQPIBERKRJH4HW76HCWDYRBTSP2 X-Message-ID-Hash: SJHFOKQPIBERKRJH4HW76HCWDYRBTSP2 X-MailFrom: g.goller@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Extend fabric status reporting to cover the IS-IS fabric alongside the existing OpenFabric and OSPF support. Signed-off-by: Gabriel Goller --- 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