diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1410ec1e..9bba8003 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -58,7 +58,7 @@ jobs: - name: Generate test result and coverage report run: | cargo install cargo2junit grcov; - cargo test $CARGO_OPTIONS -p holo-bfd -p holo-bgp -p holo-ldp -p holo-ospf -p holo-rip -- -Z unstable-options --format json | cargo2junit > results.xml; + cargo test $CARGO_OPTIONS -p holo-bfd -p holo-bgp -i holo-isis -p holo-ldp -p holo-ospf -p holo-rip -- -Z unstable-options --format json | cargo2junit > results.xml; grcov . -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "holo-*/tests/*" -o lcov.info; - name: Upload test results uses: EnricoMi/publish-unit-test-result-action@v1 diff --git a/Cargo.toml b/Cargo.toml index 9c9cc6a0..c3c4b752 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "holo-bgp", "holo-daemon", "holo-interface", + "holo-isis", "holo-keychain", "holo-ldp", "holo-northbound", diff --git a/README.md b/README.md index c1e83392..04aa3755 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ specific needs. ## Compliance -Holo supports the following IETF RFCs and Internet drafts: +Holo supports the following Internet Standards: ##### BFD @@ -174,6 +174,19 @@ Holo supports the following IETF RFCs and Internet drafts: * RFC 8212 - Default External BGP (EBGP) Route Propagation Behavior without Policies * RFC 8642 - Policy Behavior for Well-Known BGP Communities +##### IS-IS + +* ISO/IEC 10589 - Information technology — Telecommunications and information exchange between systems — Intermediate System to Intermediate System intra-domain routeing information exchange protocol for use in conjunction with the protocol for providing the connectionless-mode network service (ISO 8473) +* RFC 1195 - Use of OSI IS-IS for Routing in TCP/IP and Dual Environments +* RFC 3719 - Recommendations for Interoperable Networks using Intermediate System to Intermediate System (IS-IS) +* RFC 3787 - Recommendations for Interoperable IP Networks using Intermediate System to Intermediate System (IS-IS) +* RFC 5301 - Dynamic Hostname Exchange Mechanism for IS-IS +* RFC 5304 - IS-IS Cryptographic Authentication +* RFC 5305 - IS-IS Extensions for Traffic Engineering +* RFC 5308 - Routing IPv6 with IS-IS +* RFC 5310 - IS-IS Generic Cryptographic Authentication +* RFC 8405 - Shortest Path First (SPF) Back-Off Delay Algorithm for Link-State IGPs + ##### MPLS LDP * RFC 5036 - LDP Specification @@ -224,6 +237,7 @@ Holo supports the following IETF RFCs and Internet drafts: | ietf-ip@2018-01-09 | 52.17% | 0.00% | - | - | [40.00%](http://westphal.com.br/holo/ietf-ip.html) | | ietf-ipv4-unicast-routing@2018-03-13 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-ipv4-unicast-routing.html) | | ietf-ipv6-unicast-routing@2018-03-13 | 40.62% | 100.00% | - | - | [45.71%](http://westphal.com.br/holo/ietf-ipv6-unicast-routing.html) | +| ietf-isis@2022-10-19 | 92.66% | 52.21% | 100.00% | 100.00% | [68.81%](http://westphal.com.br/holo/ietf-isis@2022-10-19.coverage.md) | | ietf-key-chain@2017-04-18 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-key-chain.html) | | ietf-mpls-ldp@2022-03-14 | 86.96% | 92.31% | 100.00% | 100.00% | [92.38%](http://westphal.com.br/holo/ietf-mpls-ldp.html) | | ietf-mpls@2020-12-18 | 0.00% | 57.14% | - | - | [35.29%](http://westphal.com.br/holo/ietf-mpls.html) | diff --git a/holo-daemon/Cargo.toml b/holo-daemon/Cargo.toml index ff1e6ca5..16b4c278 100644 --- a/holo-daemon/Cargo.toml +++ b/holo-daemon/Cargo.toml @@ -33,6 +33,7 @@ yang3.workspace = true holo-interface = { path = "../holo-interface", optional = true } holo-bfd = { path = "../holo-bfd", optional = true } holo-bgp = { path = "../holo-bgp", optional = true } +holo-isis = { path = "../holo-isis", optional = true } holo-keychain = { path = "../holo-keychain", optional = true } holo-ldp = { path = "../holo-ldp", optional = true } holo-northbound = { path = "../holo-northbound" } @@ -66,6 +67,7 @@ default = [ # Protocols "bfd", "bgp", + "isis", "ldp", "ospf", "rip", @@ -81,6 +83,7 @@ system = ["dep:holo-system"] # Protocols bfd = ["dep:holo-bfd", "holo-routing/bfd"] bgp = ["dep:holo-bgp", "holo-routing/bgp"] +isis = ["dep:holo-isis", "holo-routing/isis"] ldp = ["dep:holo-ldp", "holo-routing/ldp"] ospf = ["dep:holo-ospf", "holo-routing/ospf"] rip = ["dep:holo-rip", "holo-routing/rip"] diff --git a/holo-daemon/src/northbound/yang.rs b/holo-daemon/src/northbound/yang.rs index 0fc10797..0ed9fa89 100644 --- a/holo-daemon/src/northbound/yang.rs +++ b/holo-daemon/src/northbound/yang.rs @@ -51,6 +51,11 @@ pub(crate) fn create_context() { use holo_bgp::instance::Instance; modules_add::(&mut modules); } + #[cfg(feature = "isis")] + { + use holo_isis::instance::Instance; + modules_add::(&mut modules); + } #[cfg(feature = "ldp")] { use holo_ldp::instance::Instance; diff --git a/holo-isis/Cargo.toml b/holo-isis/Cargo.toml new file mode 100644 index 00000000..7f6ac429 --- /dev/null +++ b/holo-isis/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "holo-isis" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +async-trait.workspace = true +bitflags.workspace = true +bytes.workspace = true +chrono.workspace = true +derive-new.workspace = true +enum-as-inner.workspace = true +fletcher.workspace = true +generational-arena.workspace = true +ipnetwork.workspace = true +itertools.workspace = true +libc.workspace = true +nix.workspace = true +num-derive.workspace = true +num-traits.workspace = true +prefix-trie.workspace = true +rand.workspace = true +regex.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_with.workspace = true +smallvec.workspace = true +socket2.workspace = true +tokio.workspace = true +tracing.workspace = true +yang3.workspace = true + +holo-northbound = { path = "../holo-northbound" } +holo-protocol = { path = "../holo-protocol" } +holo-utils = { path = "../holo-utils" } +holo-yang = { path = "../holo-yang" } + +[dev-dependencies] +criterion.workspace = true + +holo-isis = { path = ".", features = ["testing", "deterministic"] } +holo-protocol = { path = "../holo-protocol", features = ["testing"] } +holo-utils = { path = "../holo-utils", features = ["testing"] } + +[lints] +#workspace = true + +[lints.clippy] +len_without_is_empty = "allow" +too_many_arguments = "allow" + +[features] +default = [] +testing = [] +deterministic = [] diff --git a/holo-isis/LICENSE b/holo-isis/LICENSE new file mode 100644 index 00000000..4481fc10 --- /dev/null +++ b/holo-isis/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 The Holo Core Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/holo-isis/src/adjacency.rs b/holo-isis/src/adjacency.rs new file mode 100644 index 00000000..38a8cd08 --- /dev/null +++ b/holo-isis/src/adjacency.rs @@ -0,0 +1,164 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::collections::BTreeSet; +use std::time::Instant; + +use chrono::Utc; +use holo_utils::task::TimeoutTask; + +use crate::collections::AdjacencyId; +use crate::debug::Debug; +use crate::instance::InstanceUpView; +use crate::interface::{Interface, InterfaceType}; +use crate::northbound::notification; +use crate::packet::{AreaAddr, LanId, LevelType, SystemId}; +use crate::tasks; + +#[derive(Debug)] +pub struct Adjacency { + pub id: AdjacencyId, + pub snpa: [u8; 6], + pub system_id: SystemId, + pub level_capability: LevelType, + pub level_usage: LevelType, + pub state: AdjacencyState, + pub priority: Option, + pub lan_id: Option, + pub area_addrs: BTreeSet, + pub neighbors: BTreeSet<[u8; 6]>, + pub last_uptime: Option, + pub holdtimer: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AdjacencyState { + Down, + Initializing, + Up, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AdjacencyEvent { + HelloOneWayRcvd, + HelloTwoWayRcvd, + HoldtimeExpired, + LinkDown, + Kill, +} + +// ===== impl Adjacency ===== + +impl Adjacency { + // Creates new adjacency. + pub(crate) fn new( + id: AdjacencyId, + snpa: [u8; 6], + system_id: SystemId, + level_capability: LevelType, + level_usage: LevelType, + ) -> Adjacency { + let adj = Adjacency { + id, + snpa, + system_id, + level_capability, + level_usage, + state: AdjacencyState::Down, + priority: None, + lan_id: None, + area_addrs: Default::default(), + neighbors: Default::default(), + last_uptime: None, + holdtimer: None, + }; + Debug::AdjacencyCreate(&adj).log(); + adj + } + + // Transitions the adjacency state if different from the current one. + pub(crate) fn state_change( + &mut self, + iface: &mut Interface, + instance: &mut InstanceUpView<'_>, + event: AdjacencyEvent, + new_state: AdjacencyState, + ) { + if self.state == new_state { + return; + } + + // Log the state transition. + Debug::AdjacencyStateChange(self, new_state, event).log(); + + // Send YANG notification. + notification::adjacency_state_change( + instance, iface, self, new_state, event, + ); + + // Update counters. + if new_state == AdjacencyState::Up { + iface.state.event_counters.adjacency_number += 1; + self.last_uptime = Some(Instant::now()); + } else if self.state == AdjacencyState::Up { + iface.state.event_counters.adjacency_number -= 1; + } + iface.state.event_counters.adjacency_changes += 1; + iface.state.discontinuity_time = Utc::now(); + + // ISO 10589 does not require periodic CSNP transmission on + // point-to-point interfaces. However, sending them helps prevent + // synchronization issues, especially in mesh-group setups. + if iface.config.interface_type == InterfaceType::PointToPoint { + if new_state == AdjacencyState::Up { + // Start CSNP interval task(s). + iface.csnp_interval_start(instance); + } else if self.state == AdjacencyState::Up { + // Stop CSNP interval task(s). + iface.csnp_interval_stop(); + } + } + + // If no adjacencies remain in the Up state, clear SRM and SSN lists. + if iface.state.event_counters.adjacency_number == 0 { + for level in iface.config.levels() { + iface.state.srm_list.get_mut(level).clear(); + iface.state.ssn_list.get_mut(level).clear(); + } + } + + // Effectively transition to the new state. + self.state = new_state; + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(self.level_usage); + } + + // Starts or resets the holdtime timer. + pub(crate) fn holdtimer_reset( + &mut self, + iface: &Interface, + instance: &InstanceUpView<'_>, + holdtime: u16, + ) { + if let Some(holdtimer) = self.holdtimer.as_mut() { + holdtimer.reset(None); + } else { + let task = + tasks::adjacency_holdtimer(self, iface, instance, holdtime); + self.holdtimer = Some(task); + } + } +} + +impl Drop for Adjacency { + fn drop(&mut self) { + Debug::AdjacencyDelete(self).log(); + } +} diff --git a/holo-isis/src/collections.rs b/holo-isis/src/collections.rs new file mode 100644 index 00000000..d65b5375 --- /dev/null +++ b/holo-isis/src/collections.rs @@ -0,0 +1,669 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::collections::{BTreeMap, HashMap}; + +use generational_arena::Index; +use holo_utils::UnboundedSender; +use serde::{Deserialize, Serialize}; + +use crate::adjacency::Adjacency; +use crate::error::Error; +use crate::interface::Interface; +use crate::lsdb::LspEntry; +use crate::packet::pdu::Lsp; +use crate::packet::{LevelNumber, LevelType, LspId, SystemId}; +use crate::tasks::messages::input::LspPurgeMsg; + +pub type ObjectId = u32; + +#[derive(Clone, Debug)] +#[derive(Deserialize, Serialize)] +pub enum ObjectKey { + Id(ObjectId), + Value(T), +} + +pub type InterfaceId = ObjectId; +pub type InterfaceIndex = Index; +pub type InterfaceKey = ObjectKey; +pub type AdjacencyId = ObjectId; +pub type AdjacencyIndex = Index; +pub type AdjacencyKey = ObjectKey; +pub type LspEntryId = ObjectId; +pub type LspEntryIndex = Index; +pub type LspEntryKey = ObjectKey; + +#[derive(Debug)] +pub struct Arena(generational_arena::Arena); + +#[derive(Debug, Default)] +pub struct Interfaces { + arena: Arena, + id_tree: HashMap, + name_tree: BTreeMap, + ifindex_tree: HashMap, + next_id: InterfaceId, +} + +#[derive(Debug, Default)] +pub struct Adjacencies { + id_tree: HashMap, + snpa_tree: BTreeMap<[u8; 6], AdjacencyIndex>, + system_id_tree: HashMap, + next_id: AdjacencyId, +} + +#[derive(Debug, Default)] +pub struct Lsdb { + id_tree: HashMap, + lspid_tree: BTreeMap, + next_id: ObjectId, +} + +// ===== impl ObjectKey ===== + +impl From for ObjectKey { + fn from(id: ObjectId) -> ObjectKey { + ObjectKey::Id(id) + } +} + +// ===== impl Arena ===== + +impl Default for Arena { + fn default() -> Arena { + Arena(Default::default()) + } +} + +impl std::ops::Index for Arena { + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + &self.0[index] + } +} + +impl std::ops::IndexMut for Arena { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + &mut self.0[index] + } +} + +// ===== impl Interfaces ===== + +impl Interfaces { + pub(crate) fn insert( + &mut self, + ifname: &str, + ) -> (InterfaceIndex, &mut Interface) { + // Create and insert interface into the arena. + self.next_id += 1; + let iface = Interface::new(self.next_id, ifname.to_owned()); + let iface_idx = self.arena.0.insert(iface); + + // Link interface to different collections. + let iface = &mut self.arena[iface_idx]; + self.id_tree.insert(iface.id, iface_idx); + if self + .name_tree + .insert(iface.name.clone(), iface_idx) + .is_some() + { + panic!("interface name={} already exists", iface.name); + } + + (iface_idx, iface) + } + + pub(crate) fn delete(&mut self, iface_idx: InterfaceIndex) { + let iface = &mut self.arena[iface_idx]; + + // Unlink interface from different collections. + self.id_tree.remove(&iface.id); + self.name_tree.remove(&iface.name); + if let Some(ifindex) = iface.system.ifindex { + self.ifindex_tree.remove(&ifindex); + } + + // Remove interface from the arena. + self.arena.0.remove(iface_idx); + } + + // Update interface ifindex. + pub(crate) fn update_ifindex( + &mut self, + iface_idx: InterfaceIndex, + ifindex: Option, + ) { + let iface = &mut self.arena[iface_idx]; + if let Some(ifindex) = iface.system.ifindex { + self.ifindex_tree.remove(&ifindex); + } + iface.system.ifindex = ifindex; + if let Some(ifindex) = ifindex { + self.ifindex_tree.insert(ifindex, iface_idx); + } + } + + // Returns a reference to the interface corresponding to the given ID. + pub(crate) fn get_by_id( + &self, + id: InterfaceId, + ) -> Result<(InterfaceIndex, &Interface), Error> { + self.id_tree + .get(&id) + .copied() + .map(|iface_idx| (iface_idx, &self.arena[iface_idx])) + .filter(|(_, iface)| iface.id == id) + .ok_or(Error::InterfaceIdNotFound(id)) + } + + // Returns a mutable reference to the interface corresponding to the given + // ID. + pub(crate) fn get_mut_by_id( + &mut self, + id: InterfaceId, + ) -> Result<(InterfaceIndex, &mut Interface), Error> { + self.id_tree + .get(&id) + .copied() + .map(move |iface_idx| (iface_idx, &mut self.arena[iface_idx])) + .filter(|(_, iface)| iface.id == id) + .ok_or(Error::InterfaceIdNotFound(id)) + } + + // Returns a reference to the interface corresponding to the given name. + pub(crate) fn get_by_name( + &self, + ifname: &str, + ) -> Option<(InterfaceIndex, &Interface)> { + self.name_tree + .get(ifname) + .copied() + .map(|iface_idx| (iface_idx, &self.arena[iface_idx])) + } + + // Returns a mutable reference to the interface corresponding to the given + // name. + pub(crate) fn get_mut_by_name( + &mut self, + ifname: &str, + ) -> Option<(InterfaceIndex, &mut Interface)> { + self.name_tree + .get(ifname) + .copied() + .map(move |iface_idx| (iface_idx, &mut self.arena[iface_idx])) + } + + // Returns a reference to the interface corresponding to the given ifindex. + #[expect(unused)] + pub(crate) fn get_by_ifindex( + &self, + ifindex: u32, + ) -> Option<(InterfaceIndex, &Interface)> { + self.ifindex_tree + .get(&ifindex) + .copied() + .map(|iface_idx| (iface_idx, &self.arena[iface_idx])) + } + + // Returns a mutable reference to the interface corresponding to the given + // ifindex. + #[expect(unused)] + pub(crate) fn get_mut_by_ifindex( + &mut self, + ifindex: u32, + ) -> Option<(InterfaceIndex, &mut Interface)> { + self.ifindex_tree + .get(&ifindex) + .copied() + .map(move |iface_idx| (iface_idx, &mut self.arena[iface_idx])) + } + + // Returns a reference to the interface corresponding to the given object + // key. + #[expect(unused)] + pub(crate) fn get_by_key( + &self, + key: &InterfaceKey, + ) -> Result<(InterfaceIndex, &Interface), Error> { + match key { + InterfaceKey::Id(id) => self.get_by_id(*id), + InterfaceKey::Value(ifname) => { + Ok(self.get_by_name(ifname).unwrap()) + } + } + } + + // Returns a mutable reference to the interface corresponding to the given + // object key. + pub(crate) fn get_mut_by_key( + &mut self, + key: &InterfaceKey, + ) -> Result<(InterfaceIndex, &mut Interface), Error> { + match key { + InterfaceKey::Id(id) => self.get_mut_by_id(*id), + InterfaceKey::Value(ifname) => { + Ok(self.get_mut_by_name(ifname).unwrap()) + } + } + } + + // Returns an iterator visiting all interfaces. + // + // Interfaces are ordered by their names. + pub(crate) fn iter(&self) -> impl Iterator { + self.name_tree + .values() + .map(|iface_idx| &self.arena[*iface_idx]) + } + + // Returns an iterator visiting all interfaces with mutable references. + // + // Order of iteration is not defined. + pub(crate) fn iter_mut( + &mut self, + ) -> impl Iterator { + self.arena.0.iter_mut().map(|(_, iface)| iface) + } + + // Returns an iterator over all interface indexes. + // + // Interfaces are ordered by their names. + #[expect(unused)] + pub(crate) fn indexes(&self) -> impl Iterator + '_ { + self.name_tree.values().copied() + } +} + +impl std::ops::Index for Interfaces { + type Output = Interface; + + fn index(&self, index: InterfaceIndex) -> &Self::Output { + &self.arena[index] + } +} + +impl std::ops::IndexMut for Interfaces { + fn index_mut(&mut self, index: InterfaceIndex) -> &mut Self::Output { + &mut self.arena[index] + } +} + +// ===== impl Adjacencies ===== + +impl Adjacencies { + pub(crate) fn insert<'a>( + &mut self, + arena: &'a mut Arena, + snpa: [u8; 6], + system_id: SystemId, + level_capability: LevelType, + level_usage: LevelType, + ) -> (AdjacencyIndex, &'a mut Adjacency) { + // Create and insert adjacency into the arena. + self.next_id += 1; + let adj = Adjacency::new( + self.next_id, + snpa, + system_id, + level_capability, + level_usage, + ); + let adj_idx = arena.0.insert(adj); + + // Link adjacency to different collections. + let adj = &mut arena[adj_idx]; + self.id_tree.insert(adj.id, adj_idx); + self.snpa_tree.insert(adj.snpa, adj_idx); + self.system_id_tree.insert(adj.system_id, adj_idx); + + (adj_idx, adj) + } + + pub(crate) fn delete( + &mut self, + arena: &mut Arena, + adj_idx: AdjacencyIndex, + ) { + let adj = &mut arena[adj_idx]; + + // Unlink adjacency from different collections. + self.id_tree.remove(&adj.id); + self.snpa_tree.remove(&adj.snpa); + self.system_id_tree.remove(&adj.system_id); + + // Remove adjacency from the arena. + arena.0.remove(adj_idx); + } + + pub(crate) fn clear(&mut self, arena: &mut Arena) { + for adj_idx in self.id_tree.values() { + arena.0.remove(*adj_idx); + } + self.id_tree.clear(); + self.snpa_tree.clear(); + self.system_id_tree.clear(); + } + + pub(crate) fn update_system_id( + &mut self, + adj_idx: AdjacencyIndex, + adj: &mut Adjacency, + system_id: SystemId, + ) { + self.system_id_tree.remove(&adj.system_id); + adj.system_id = system_id; + self.system_id_tree.insert(adj.system_id, adj_idx); + } + + // Returns a reference to the adjacency corresponding to the given ID. + pub(crate) fn get_by_id<'a>( + &self, + arena: &'a Arena, + id: AdjacencyId, + ) -> Result<(AdjacencyIndex, &'a Adjacency), Error> { + self.id_tree + .get(&id) + .copied() + .map(|adj_idx| (adj_idx, &arena[adj_idx])) + .filter(|(_, adj)| adj.id == id) + .ok_or(Error::AdjacencyIdNotFound(id)) + } + + // Returns a mutable reference to the adjacency corresponding to the given + // ID. + pub(crate) fn get_mut_by_id<'a>( + &mut self, + arena: &'a mut Arena, + id: AdjacencyId, + ) -> Result<(AdjacencyIndex, &'a mut Adjacency), Error> { + self.id_tree + .get(&id) + .copied() + .map(move |adj_idx| (adj_idx, &mut arena[adj_idx])) + .filter(|(_, adj)| adj.id == id) + .ok_or(Error::AdjacencyIdNotFound(id)) + } + + // Returns a reference to the adjacency corresponding to the given SNPA. + pub(crate) fn get_by_snpa<'a>( + &self, + arena: &'a Arena, + snpa: [u8; 6], + ) -> Option<(AdjacencyIndex, &'a Adjacency)> { + self.snpa_tree + .get(&snpa) + .copied() + .map(|adj_idx| (adj_idx, &arena[adj_idx])) + } + + // Returns a mutable reference to the adjacency corresponding to the given + // SNPA. + pub(crate) fn get_mut_by_snpa<'a>( + &mut self, + arena: &'a mut Arena, + snpa: [u8; 6], + ) -> Option<(AdjacencyIndex, &'a mut Adjacency)> { + self.snpa_tree + .get(&snpa) + .copied() + .map(move |adj_idx| (adj_idx, &mut arena[adj_idx])) + } + + // Returns a reference to the adjacency corresponding to the given + // System-ID. + pub(crate) fn get_by_system_id<'a>( + &self, + arena: &'a Arena, + system_id: &SystemId, + ) -> Option<(AdjacencyIndex, &'a Adjacency)> { + self.system_id_tree + .get(system_id) + .copied() + .map(|adj_idx| (adj_idx, &arena[adj_idx])) + } + + // Returns a mutable reference to the adjacency corresponding to the given + // System-ID. + pub(crate) fn get_mut_by_system_id<'a>( + &mut self, + arena: &'a mut Arena, + system_id: &SystemId, + ) -> Option<(AdjacencyIndex, &'a mut Adjacency)> { + self.system_id_tree + .get(system_id) + .copied() + .map(move |adj_idx| (adj_idx, &mut arena[adj_idx])) + } + + // Returns a reference to the adjacency corresponding to the given object + // key. + #[expect(unused)] + pub(crate) fn get_by_key<'a>( + &self, + arena: &'a Arena, + key: &AdjacencyKey, + ) -> Result<(AdjacencyIndex, &'a Adjacency), Error> { + match key { + AdjacencyKey::Id(id) => self.get_by_id(arena, *id), + AdjacencyKey::Value(system_id) => { + Ok(self.get_by_system_id(arena, system_id).unwrap()) + } + } + } + + // Returns a mutable reference to the adjacency corresponding to the given + // object key. + pub(crate) fn get_mut_by_key<'a>( + &mut self, + arena: &'a mut Arena, + key: &AdjacencyKey, + ) -> Result<(AdjacencyIndex, &'a mut Adjacency), Error> { + match key { + AdjacencyKey::Id(id) => self.get_mut_by_id(arena, *id), + AdjacencyKey::Value(system_id) => { + Ok(self.get_mut_by_system_id(arena, system_id).unwrap()) + } + } + } + + // Returns an iterator visiting all adjacencies. + // + // Adjacencies are ordered by their System IDs. + pub(crate) fn iter<'a>( + &'a self, + arena: &'a Arena, + ) -> impl Iterator + 'a { + self.system_id_tree.values().map(|adj_idx| &arena[*adj_idx]) + } + + // Returns an iterator over all adjacencies SNPA addresses. + // + // Neighbors are ordered by their SNPA addresses. + pub(crate) fn snpas(&self) -> impl Iterator + '_ { + self.snpa_tree.keys().copied() + } + + // Returns an iterator over all adjacency indexes. + // + // Adjacencies are ordered by their System-IDs. + pub(crate) fn indexes(&self) -> impl Iterator + '_ { + self.system_id_tree.values().copied() + } +} + +// ===== impl Lsdb ===== + +impl Lsdb { + pub(crate) fn insert<'a>( + &mut self, + arena: &'a mut Arena, + level: LevelNumber, + lsp: Lsp, + lsp_purgep: &UnboundedSender, + ) -> (LspEntryIndex, &'a mut LspEntry) { + // Create and insert LSP entry into the arena. + self.next_id += 1; + let lse = LspEntry::new(level, self.next_id, lsp, lsp_purgep); + let lse_idx = arena.0.insert(lse); + + // Link LSP entry to different collections. + let lse = &mut arena[lse_idx]; + self.id_tree.insert(lse.id, lse_idx); + self.lspid_tree.insert(lse.data.lsp_id, lse_idx); + + (lse_idx, lse) + } + + pub(crate) fn delete( + &mut self, + arena: &mut Arena, + lse_idx: LspEntryIndex, + ) -> LspEntry { + let lse = &mut arena[lse_idx]; + + // Unlink LSP entry from different collections. + self.id_tree.remove(&lse.id); + self.lspid_tree.remove(&lse.data.lsp_id); + + // Remove LSP entry from the arena. + arena.0.remove(lse_idx).unwrap() + } + + // Returns a reference to the LSP entry corresponding to the given ID. + pub(crate) fn get_by_id<'a>( + &self, + arena: &'a Arena, + id: ObjectId, + ) -> Result<(LspEntryIndex, &'a LspEntry), Error> { + self.id_tree + .get(&id) + .copied() + .map(|lse_idx| (lse_idx, &arena[lse_idx])) + .filter(|(_, lse)| lse.id == id) + .ok_or(Error::LspEntryIdNotFound(id)) + } + + // Returns a mutable reference to the LSP entry corresponding to the given + // ID. + pub(crate) fn get_mut_by_id<'a>( + &mut self, + arena: &'a mut Arena, + id: ObjectId, + ) -> Result<(LspEntryIndex, &'a mut LspEntry), Error> { + self.id_tree + .get(&id) + .copied() + .map(move |lse_idx| (lse_idx, &mut arena[lse_idx])) + .filter(|(_, lse)| lse.id == id) + .ok_or(Error::LspEntryIdNotFound(id)) + } + + // Returns a reference to the LSP entry corresponding to the given LSP ID. + pub(crate) fn get_by_lspid<'a>( + &self, + arena: &'a Arena, + lsp_id: &LspId, + ) -> Option<(LspEntryIndex, &'a LspEntry)> { + self.lspid_tree + .get(lsp_id) + .copied() + .map(|lse_idx| (lse_idx, &arena[lse_idx])) + } + + // Returns a mutable reference to the LSP entry corresponding to the given + // LSP ID. + pub(crate) fn get_mut_by_lspid<'a>( + &mut self, + arena: &'a mut Arena, + lsp_id: &LspId, + ) -> Option<(LspEntryIndex, &'a mut LspEntry)> { + self.lspid_tree + .get(lsp_id) + .copied() + .map(move |lse_idx| (lse_idx, &mut arena[lse_idx])) + } + + // Returns a reference to the LSP entry corresponding to the given object + // key. + pub(crate) fn get_by_key<'a>( + &self, + arena: &'a Arena, + key: &LspEntryKey, + ) -> Result<(LspEntryIndex, &'a LspEntry), Error> { + match key { + LspEntryKey::Id(id) => self.get_by_id(arena, *id), + LspEntryKey::Value(lsp_id) => { + Ok(self.get_by_lspid(arena, lsp_id).unwrap()) + } + } + } + + // Returns a mutable reference to the LSP entry corresponding to the given + // object key. + pub(crate) fn get_mut_by_key<'a>( + &mut self, + arena: &'a mut Arena, + key: &LspEntryKey, + ) -> Result<(LspEntryIndex, &'a mut LspEntry), Error> { + match key { + LspEntryKey::Id(id) => self.get_mut_by_id(arena, *id), + LspEntryKey::Value(lsp_id) => { + Ok(self.get_mut_by_lspid(arena, lsp_id).unwrap()) + } + } + } + + // Returns an iterator visiting all LSP entries. + // + // LSP are ordered by their LSP IDs. + pub(crate) fn iter<'a>( + &'a self, + arena: &'a Arena, + ) -> impl Iterator + 'a { + self.lspid_tree.values().map(|lse_idx| &arena[*lse_idx]) + } + + // Returns an iterator visiting all LSP entries for the specified System ID. + // + // LSP are ordered by their LSP IDs. + pub(crate) fn iter_for_system_id<'a>( + &'a self, + arena: &'a Arena, + system_id: SystemId, + ) -> impl Iterator + 'a { + let start = LspId::from((system_id, 0, 0)); + let end = LspId::from((system_id, 255, 255)); + self.range(arena, start..=end) + } + + // Returns an iterator over a range of LSP IDs. + // + // LSP are ordered by their LSP IDs. + pub(crate) fn range<'a>( + &'a self, + arena: &'a Arena, + range: impl std::ops::RangeBounds, + ) -> impl Iterator + 'a { + self.lspid_tree + .range(range) + .map(|(_, lse_idx)| &arena[*lse_idx]) + } + + // Returns an iterator over all LSP indexes. + // + // LSPs are ordered by their LSP IDs. + #[expect(unused)] + pub(crate) fn indexes(&self) -> impl Iterator + '_ { + self.lspid_tree.values().copied() + } +} diff --git a/holo-isis/src/debug.rs b/holo-isis/src/debug.rs new file mode 100644 index 00000000..48420e75 --- /dev/null +++ b/holo-isis/src/debug.rs @@ -0,0 +1,294 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use holo_yang::ToYang; +use serde::{Deserialize, Serialize}; +use tracing::{debug, debug_span}; + +use crate::adjacency::{Adjacency, AdjacencyEvent, AdjacencyState}; +use crate::interface::{DisCandidate, Interface}; +use crate::network::MulticastAddr; +use crate::packet::pdu::{Lsp, Pdu}; +use crate::packet::LevelNumber; + +// IS-IS debug messages. +#[derive(Debug)] +pub enum Debug<'a> { + // Instances + InstanceCreate, + InstanceDelete, + InstanceStart, + InstanceStop(InstanceInactiveReason), + // Interfaces + InterfaceCreate(&'a str), + InterfaceDelete(&'a str), + InterfaceStart(&'a str), + InterfaceStop(&'a str, InterfaceInactiveReason), + InterfaceDisChange(&'a str, LevelNumber, &'a Option), + // Adjacencies + AdjacencyCreate(&'a Adjacency), + AdjacencyDelete(&'a Adjacency), + AdjacencyStateChange(&'a Adjacency, AdjacencyState, AdjacencyEvent), + // Network + PduRx(&'a Interface, &'a [u8; 6], &'a Pdu), + PduTx(u32, MulticastAddr, &'a Pdu), + // Flooding + LspDiscard(LevelNumber, &'a Lsp), + // LSDB maintenance + LspInstall(LevelNumber, &'a Lsp), + LspOriginate(LevelNumber, &'a Lsp), + LspPurge(LevelNumber, &'a Lsp, LspPurgeReason), + LspDelete(LevelNumber, &'a Lsp), + LspRefresh(LevelNumber, &'a Lsp), +} + +// Reason why an IS-IS instance is inactive. +#[derive(Debug)] +pub enum InstanceInactiveReason { + AdminDown, + Resetting, +} + +// Reason why IS-IS is inactive on an interface. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum InterfaceInactiveReason { + InstanceDown, + AdminDown, + OperationalDown, + MissingIfindex, + MissingMtu, + MissingMacAddr, + BroadcastUnsupported, + Resetting, +} + +// Reason why an LSP is being purged. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum LspPurgeReason { + Expired, + Removed, + Confusion, +} + +// ===== impl Debug ===== + +impl Debug<'_> { + // Log debug message using the tracing API. + pub(crate) fn log(&self) { + match self { + Debug::InstanceCreate + | Debug::InstanceDelete + | Debug::InstanceStart => { + // Parent span(s): isis-instance + debug!("{}", self); + } + Debug::InstanceStop(reason) => { + // Parent span(s): isis-instance + debug!(%reason, "{}", self); + } + Debug::InterfaceCreate(name) + | Debug::InterfaceDelete(name) + | Debug::InterfaceStart(name) => { + // Parent span(s): isis-instance + debug_span!("interface", %name).in_scope(|| { + debug!("{}", self); + }) + } + Debug::InterfaceStop(name, reason) => { + // Parent span(s): isis-instance + debug_span!("interface", %name).in_scope(|| { + debug!(%reason, "{}", self); + }) + } + Debug::InterfaceDisChange(name, level, dis) => { + // Parent span(s): isis-instance + debug_span!("interface", %name).in_scope(|| { + if let Some(dis) = dis { + debug!(?level, lan_id = ?dis.lan_id, "{}", self); + } else { + debug!(?level, lan_id = "none", "{}", self); + } + }) + } + Debug::AdjacencyCreate(adj) | Debug::AdjacencyDelete(adj) => { + // Parent span(s): isis-instance + debug_span!("adjacency", system_id = ?adj.system_id).in_scope( + || { + debug!("{}", self); + }, + ) + } + Debug::AdjacencyStateChange(adj, new_state, event) => { + // Parent span(s): isis-instance + debug_span!("adjacency", system_id = ?adj.system_id, ?new_state, ?event) + .in_scope(|| { + debug!("{}", self); + }) + } + Debug::PduRx(iface, src, pdu) => { + // Parent span(s): isis-instance + debug_span!("network").in_scope(|| { + debug_span!("input") + .in_scope(|| { + let data = serde_json::to_string(&pdu).unwrap(); + debug!(interface = %iface.name, ?src, %data, "{}", self); + }) + }) + } + Debug::PduTx(ifindex, addr, pdu) => { + // Parent span(s): isis-instance:network:output + let data = serde_json::to_string(&pdu).unwrap(); + debug!(%ifindex, ?addr, %data, "{}", self); + } + Debug::LspDiscard(level, lsp) + | Debug::LspInstall(level, lsp) + | Debug::LspOriginate(level, lsp) + | Debug::LspDelete(level, lsp) + | Debug::LspRefresh(level, lsp) => { + // Parent span(s): isis-instance + debug!(?level, lsp_id = %lsp.lsp_id.to_yang(), seqno = %lsp.seqno, len = %lsp.raw.len(), "{}", self); + } + Debug::LspPurge(level, lsp, reason) => { + // Parent span(s): isis-instance + debug!(?level, lsp_id = %lsp.lsp_id.to_yang(), seqno = %lsp.seqno, len = %lsp.raw.len(), ?reason, "{}", self); + } + } + } +} + +impl std::fmt::Display for Debug<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Debug::InstanceCreate => { + write!(f, "instance created") + } + Debug::InstanceDelete => { + write!(f, "instance deleted") + } + Debug::InstanceStart => { + write!(f, "starting instance") + } + Debug::InstanceStop(..) => { + write!(f, "stopping instance") + } + Debug::InterfaceCreate(..) => { + write!(f, "interface created") + } + Debug::InterfaceDelete(..) => { + write!(f, "interface deleted") + } + Debug::InterfaceStart(..) => { + write!(f, "starting interface") + } + Debug::InterfaceStop(..) => { + write!(f, "stopping interface") + } + Debug::InterfaceDisChange(..) => { + write!(f, "interface DIS change") + } + Debug::AdjacencyCreate(..) => { + write!(f, "adjacency created") + } + Debug::AdjacencyDelete(..) => { + write!(f, "adjacency deleted") + } + Debug::AdjacencyStateChange(..) => { + write!(f, "adjacency state change") + } + Debug::PduRx(..) | Debug::PduTx(..) => { + write!(f, "PDU") + } + Debug::LspDiscard(..) => { + write!(f, "discarding LSP") + } + Debug::LspInstall(..) => { + write!(f, "installing LSP") + } + Debug::LspOriginate(..) => { + write!(f, "originating LSP") + } + Debug::LspPurge(..) => { + write!(f, "purging LSP") + } + Debug::LspDelete(..) => { + write!(f, "deleting LSP") + } + Debug::LspRefresh(..) => { + write!(f, "refreshing LSP") + } + } + } +} + +// ===== impl InstanceInactiveReason ===== + +impl std::fmt::Display for InstanceInactiveReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InstanceInactiveReason::AdminDown => { + write!(f, "administrative status down") + } + InstanceInactiveReason::Resetting => { + write!(f, "resetting") + } + } + } +} + +// ===== impl InterfaceInactiveReason ===== + +impl std::fmt::Display for InterfaceInactiveReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InterfaceInactiveReason::InstanceDown => { + write!(f, "IS-IS instance down") + } + InterfaceInactiveReason::AdminDown => { + write!(f, "administrative status down") + } + InterfaceInactiveReason::OperationalDown => { + write!(f, "operational status down") + } + InterfaceInactiveReason::MissingIfindex => { + write!(f, "missing ifindex") + } + InterfaceInactiveReason::MissingMtu => { + write!(f, "missing MTU") + } + InterfaceInactiveReason::MissingMacAddr => { + write!(f, "missing MAC address") + } + InterfaceInactiveReason::BroadcastUnsupported => { + write!(f, "broadcast mode not supported by interface") + } + InterfaceInactiveReason::Resetting => { + write!(f, "resetting") + } + } + } +} + +// ===== impl LspPurgeReason ===== + +impl std::fmt::Display for LspPurgeReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LspPurgeReason::Expired => { + write!(f, "LSP has expired") + } + LspPurgeReason::Removed => { + write!(f, "LSP no longer exists") + } + LspPurgeReason::Confusion => { + write!(f, "LSP confusion") + } + } + } +} diff --git a/holo-isis/src/error.rs b/holo-isis/src/error.rs new file mode 100644 index 00000000..f08cd6c5 --- /dev/null +++ b/holo-isis/src/error.rs @@ -0,0 +1,264 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use tracing::{error, warn, warn_span}; + +use crate::collections::{ + AdjacencyId, InterfaceId, InterfaceIndex, LspEntryId, +}; +use crate::instance::InstanceArenas; +use crate::network::MulticastAddr; +use crate::packet::error::DecodeError; + +// IS-IS errors. +#[derive(Debug)] +pub enum Error { + // I/O errors + IoError(IoError), + // Inter-task communication + InterfaceIdNotFound(InterfaceId), + AdjacencyIdNotFound(AdjacencyId), + LspEntryIdNotFound(LspEntryId), + // Packet input + PduDecodeError(InterfaceIndex, [u8; 6], DecodeError), + AdjacencyReject(InterfaceIndex, [u8; 6], AdjacencyRejectError), + // Other + CircuitIdAllocationFailed, + InterfaceStartError(String, Box), + InstanceStartError(Box), +} + +// IS-IS I/O errors. +#[derive(Debug)] +pub enum IoError { + SocketError(std::io::Error), + MulticastJoinError(MulticastAddr, std::io::Error), + MulticastLeaveError(MulticastAddr, std::io::Error), + RecvError(std::io::Error), + RecvMissingSourceAddr, + SendError(std::io::Error), +} + +#[derive(Debug)] +pub enum AdjacencyRejectError { + InvalidHelloType, + CircuitTypeMismatch, + MaxAreaAddrsMismatch(u8), + AreaMismatch, + WrongSystem, + DuplicateSystemId, +} + +// ===== impl Error ===== + +impl Error { + pub(crate) fn log(&self, arenas: &InstanceArenas) { + match self { + Error::IoError(error) => { + error.log(); + } + Error::InterfaceIdNotFound(iface_id) => { + warn!(?iface_id, "{}", self); + } + Error::AdjacencyIdNotFound(adj_id) => { + warn!(?adj_id, "{}", self); + } + Error::LspEntryIdNotFound(lse_id) => { + warn!(?lse_id, "{}", self); + } + Error::PduDecodeError(iface_idx, source, error) => { + let iface = &arenas.interfaces[*iface_idx]; + warn_span!("interface", name = %iface.name, ?source).in_scope( + || { + warn!(%error, "{}", self); + }, + ) + } + Error::AdjacencyReject(iface_idx, source, error) => { + let iface = &arenas.interfaces[*iface_idx]; + warn_span!("interface", name = %iface.name, ?source).in_scope( + || { + error.log(); + }, + ) + } + Error::CircuitIdAllocationFailed => { + warn!("{}", self); + } + Error::InterfaceStartError(name, error) => { + error!(%name, error = %with_source(error), "{}", self); + } + Error::InstanceStartError(error) => { + error!(error = %with_source(error), "{}", self); + } + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::IoError(error) => error.fmt(f), + Error::InterfaceIdNotFound(..) => { + write!(f, "interface ID not found") + } + Error::AdjacencyIdNotFound(..) => { + write!(f, "adjacency ID not found") + } + Error::LspEntryIdNotFound(..) => { + write!(f, "LSP entry ID not found") + } + Error::PduDecodeError(..) => { + write!(f, "failed to decode packet") + } + Error::AdjacencyReject(_, _, error) => error.fmt(f), + Error::CircuitIdAllocationFailed => { + write!(f, "failed to allocate Circuit ID") + } + Error::InterfaceStartError(..) => { + write!(f, "failed to start interface") + } + Error::InstanceStartError(..) => { + write!(f, "failed to start instance") + } + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::IoError(error) => Some(error), + Error::InterfaceStartError(_, error) => Some(error), + Error::InstanceStartError(error) => Some(error), + _ => None, + } + } +} + +impl From for Error { + fn from(error: IoError) -> Error { + Error::IoError(error) + } +} + +// ===== impl IoError ===== + +impl IoError { + pub(crate) fn log(&self) { + match self { + IoError::SocketError(error) => { + warn!(error = %with_source(error), "{}", self); + } + IoError::MulticastJoinError(addr, error) + | IoError::MulticastLeaveError(addr, error) => { + warn!(?addr, error = %with_source(error), "{}", self); + } + IoError::RecvError(error) | IoError::SendError(error) => { + warn!(error = %with_source(error), "{}", self); + } + IoError::RecvMissingSourceAddr => { + warn!("{}", self); + } + } + } +} + +impl std::fmt::Display for IoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IoError::SocketError(..) => { + write!(f, "failed to create raw socket") + } + IoError::MulticastJoinError(..) => { + write!(f, "failed to join multicast group") + } + IoError::MulticastLeaveError(..) => { + write!(f, "failed to leave multicast group") + } + IoError::RecvError(..) => { + write!(f, "failed to receive packet") + } + IoError::RecvMissingSourceAddr => { + write!( + f, + "failed to retrieve source address from received packet" + ) + } + IoError::SendError(..) => { + write!(f, "failed to send packet") + } + } + } +} + +impl std::error::Error for IoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + IoError::SocketError(error) + | IoError::MulticastJoinError(_, error) + | IoError::MulticastLeaveError(_, error) + | IoError::RecvError(error) + | IoError::SendError(error) => Some(error), + _ => None, + } + } +} + +// ===== impl AdjacencyRejectError ===== + +impl AdjacencyRejectError { + pub(crate) fn log(&self) { + match self { + AdjacencyRejectError::MaxAreaAddrsMismatch(max_area_addrs) => { + warn!(%max_area_addrs, "{}", self); + } + _ => { + warn!("{}", self); + } + } + } +} + +impl std::fmt::Display for AdjacencyRejectError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AdjacencyRejectError::InvalidHelloType => { + write!(f, "invalid hello type") + } + AdjacencyRejectError::CircuitTypeMismatch => { + write!(f, "level mismatch") + } + AdjacencyRejectError::MaxAreaAddrsMismatch(..) => { + write!(f, "maximumAreaAddresses mismatch") + } + AdjacencyRejectError::AreaMismatch => { + write!(f, "area mismatch") + } + AdjacencyRejectError::WrongSystem => { + write!(f, "wrong system") + } + AdjacencyRejectError::DuplicateSystemId => { + write!(f, "duplicate System-ID") + } + } + } +} + +impl std::error::Error for AdjacencyRejectError {} + +// ===== helper functions ===== + +fn with_source(error: E) -> String { + if let Some(source) = error.source() { + format!("{} ({})", error, with_source(source)) + } else { + error.to_string() + } +} diff --git a/holo-isis/src/events.rs b/holo-isis/src/events.rs new file mode 100644 index 00000000..e606802c --- /dev/null +++ b/holo-isis/src/events.rs @@ -0,0 +1,1170 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::cmp::Ordering; +use std::collections::HashMap; + +use bytes::Bytes; +use chrono::Utc; + +use crate::adjacency::{Adjacency, AdjacencyEvent, AdjacencyState}; +use crate::collections::{ + AdjacencyKey, InterfaceIndex, InterfaceKey, LspEntryKey, +}; +use crate::debug::{Debug, LspPurgeReason}; +use crate::error::{AdjacencyRejectError, Error}; +use crate::instance::{InstanceArenas, InstanceUpView}; +use crate::interface::InterfaceType; +use crate::lsdb::{self, lsp_compare, LspEntryFlags}; +use crate::northbound::notification; +use crate::packet::consts::PduType; +use crate::packet::error::{DecodeError, DecodeResult}; +use crate::packet::pdu::{Hello, HelloVariant, Lsp, Pdu, Snp, SnpTlvs}; +use crate::packet::tlv::LspEntry; +use crate::packet::{LanId, LevelNumber, LevelType, LspId}; +use crate::tasks; +use crate::tasks::messages::input::DisElectionMsg; + +// ===== Network PDU receipt ===== + +pub(crate) fn process_pdu( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, + src: [u8; 6], + bytes: Bytes, + pdu: DecodeResult, +) -> Result<(), Error> { + // Lookup interface. + let (iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Ignore PDUs received on inactive or passive interfaces. + if !iface.state.active || iface.is_passive() { + return Ok(()); + } + + // Check if the PDU was decoded successfully. + let pdu = match pdu { + Ok(pdu) => pdu, + Err(error) => { + match error { + DecodeError::InvalidVersion(version) => { + iface.state.event_counters.version_skew += 1; + iface.state.discontinuity_time = Utc::now(); + notification::version_skew( + instance, iface, version, &bytes, + ); + } + DecodeError::InvalidIdLength(pdu_id_len) => { + iface.state.event_counters.id_len_mismatch += 1; + iface.state.discontinuity_time = Utc::now(); + notification::id_len_mismatch( + instance, iface, pdu_id_len, &bytes, + ); + } + DecodeError::UnknownPduType(_) => { + iface.state.packet_counters.l1.unknown_in += 1; + iface.state.packet_counters.l2.unknown_in += 1; + iface.state.discontinuity_time = Utc::now(); + } + _ => (), + } + return Err(Error::PduDecodeError(iface_idx, src, error)); + } + }; + + // Update packet counters. + match pdu.pdu_type() { + PduType::HelloP2P => { + iface.state.packet_counters.l1.iih_in += 1; + iface.state.packet_counters.l2.iih_in += 1; + } + PduType::HelloLanL1 => { + iface.state.packet_counters.l1.iih_in += 1; + } + PduType::HelloLanL2 => { + iface.state.packet_counters.l2.iih_in += 1; + } + PduType::LspL1 => { + iface.state.packet_counters.l1.lsp_in += 1; + } + PduType::LspL2 => { + iface.state.packet_counters.l2.lsp_in += 1; + } + PduType::CsnpL1 => { + iface.state.packet_counters.l1.csnp_in += 1; + } + PduType::CsnpL2 => { + iface.state.packet_counters.l2.csnp_in += 1; + } + PduType::PsnpL1 => { + iface.state.packet_counters.l1.psnp_in += 1; + } + PduType::PsnpL2 => { + iface.state.packet_counters.l2.psnp_in += 1; + } + } + iface.state.discontinuity_time = Utc::now(); + + // Log received PDU. + Debug::PduRx(iface, &src, &pdu).log(); + + match pdu { + Pdu::Hello(hello) => { + process_pdu_hello(instance, arenas, iface_idx, src, bytes, hello) + } + Pdu::Lsp(lsp) => { + process_pdu_lsp(instance, arenas, iface_idx, src, bytes, lsp) + } + Pdu::Snp(snp) => { + process_pdu_snp(instance, arenas, iface_idx, src, bytes, snp) + } + } +} + +pub(crate) fn process_pdu_hello( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_idx: InterfaceIndex, + src: [u8; 6], + bytes: Bytes, + hello: Hello, +) -> Result<(), Error> { + if let Err(error) = match hello.variant { + // LAN Hello. + HelloVariant::Lan { priority, lan_id } => process_pdu_hello_lan( + instance, arenas, iface_idx, src, hello, priority, lan_id, + ), + // Point-to-Point Hello. + HelloVariant::P2P { .. } => { + process_pdu_hello_p2p(instance, arenas, iface_idx, src, hello) + } + } { + // Error handling. + let iface = &mut arenas.interfaces[iface_idx]; + match error { + AdjacencyRejectError::MaxAreaAddrsMismatch(pdu_max_area_addrs) => { + iface.state.event_counters.max_area_addr_mismatch += 1; + iface.state.discontinuity_time = Utc::now(); + notification::max_area_addresses_mismatch( + instance, + iface, + pdu_max_area_addrs, + &bytes, + ); + } + AdjacencyRejectError::AreaMismatch => { + iface.state.event_counters.area_mismatch += 1; + iface.state.discontinuity_time = Utc::now(); + notification::area_mismatch(instance, iface, &bytes); + } + _ => { + iface.state.event_counters.adjacency_rejects += 1; + iface.state.discontinuity_time = Utc::now(); + notification::rejected_adjacency( + instance, iface, &bytes, &error, + ); + } + } + return Err(Error::AdjacencyReject(iface_idx, src, error)); + } + + Ok(()) +} + +pub(crate) fn process_pdu_hello_lan( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_idx: InterfaceIndex, + src: [u8; 6], + hello: Hello, + priority: u8, + lan_id: LanId, +) -> Result<(), AdjacencyRejectError> { + let iface = &mut arenas.interfaces[iface_idx]; + let mut new_adj = false; + + // Validate PDU type and determine level usage. + let level = match (iface.config.interface_type, hello.hdr.pdu_type) { + (InterfaceType::Broadcast, PduType::HelloLanL1) => LevelNumber::L1, + (InterfaceType::Broadcast, PduType::HelloLanL2) => LevelNumber::L2, + _ => return Err(AdjacencyRejectError::InvalidHelloType), + }; + if !iface.config.level_type.resolved.intersects(level) { + return Err(AdjacencyRejectError::InvalidHelloType); + } + + // Validate the "Circuit Type" field. + if !iface + .config + .level_type + .resolved + .intersects(hello.circuit_type) + { + return Err(AdjacencyRejectError::CircuitTypeMismatch); + } + + if hello.hdr.pdu_type == PduType::HelloLanL1 { + // Validate the "Maximum Area Addresses" field. + if hello.hdr.max_area_addrs != 0 && hello.hdr.max_area_addrs != 3 { + return Err(AdjacencyRejectError::MaxAreaAddrsMismatch( + hello.hdr.max_area_addrs, + )); + } + + // Check for area mismatch. + if !hello + .tlvs + .area_addrs() + .any(|addr| instance.config.area_addrs.contains(addr)) + { + return Err(AdjacencyRejectError::AreaMismatch); + } + } + + // Check for duplicate System-ID. + if hello.source == instance.config.system_id.unwrap() { + return Err(AdjacencyRejectError::DuplicateSystemId); + } + + // Look up or create an adjacency using the source MAC address. + let adjacencies = iface.state.lan_adjacencies.get_mut(level); + let level_usage = level.into(); + let (_, adj) = + match adjacencies.get_mut_by_snpa(&mut arenas.adjacencies, src) { + Some((adj_idx, adj)) => { + if hello.source != adj.system_id { + adjacencies.update_system_id(adj_idx, adj, hello.source); + } + adj.level_capability = hello.circuit_type; + adj.level_usage = level_usage; + (adj_idx, adj) + } + None => { + new_adj = true; + adjacencies.insert( + &mut arenas.adjacencies, + src, + hello.source, + hello.circuit_type, + level_usage, + ) + } + }; + + // Update adjacency with received PDU values. + let old_priority = adj.priority; + let old_state = adj.state; + adj.priority = Some(priority); + adj.lan_id = Some(lan_id); + adj.area_addrs = hello.tlvs.area_addrs().cloned().collect(); + adj.neighbors = hello.tlvs.neighbors().cloned().collect(); + + // Restart hold timer. + adj.holdtimer_reset(iface, instance, hello.holdtime); + + // Check for two-way communication. + let iface_snpa = iface.system.mac_addr.unwrap(); + if adj.neighbors.contains(&iface_snpa) { + adj.state_change( + iface, + instance, + AdjacencyEvent::HelloTwoWayRcvd, + AdjacencyState::Up, + ); + } else { + adj.state_change( + iface, + instance, + AdjacencyEvent::HelloOneWayRcvd, + AdjacencyState::Initializing, + ); + } + + // Restart Hello Tx task if this is a new adjacency (updated list + // of neighbors). + if new_adj { + iface.hello_interval_start(instance, level_usage); + } + + // Trigger DIS election if priority or state changed. + if adj.priority != old_priority || adj.state != old_state { + let msg = DisElectionMsg { + iface_key: iface.id.into(), + level, + }; + let _ = instance.tx.protocol_input.dis_election.send(msg); + } + + Ok(()) +} + +pub(crate) fn process_pdu_hello_p2p( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_idx: InterfaceIndex, + src: [u8; 6], + hello: Hello, +) -> Result<(), AdjacencyRejectError> { + let iface = &mut arenas.interfaces[iface_idx]; + + // Validate PDU type. + if iface.config.interface_type != InterfaceType::PointToPoint { + return Err(AdjacencyRejectError::InvalidHelloType); + } + + // Check for duplicate System-ID. + if hello.source == instance.config.system_id.unwrap() { + return Err(AdjacencyRejectError::DuplicateSystemId); + } + + // Check for an area match. + let area_match = hello + .tlvs + .area_addrs() + .any(|addr| instance.config.area_addrs.contains(addr)); + + // Process existing or new adjacency. + let mut adj = match iface.state.p2p_adjacency.take() { + Some(adj) => { + // Determine if the PDU can be accepted based on area match and + // level usage. + let accept = match (area_match, adj.level_usage) { + (true, LevelType::L1 | LevelType::L2) + | (false, LevelType::L2 | LevelType::All) => { + adj.level_usage.intersects(hello.circuit_type) + } + (true, LevelType::All) => adj.level_usage == hello.circuit_type, + _ => false, + }; + if !accept { + return Err(AdjacencyRejectError::WrongSystem); + } + + // Reject PDU if the System-ID doesn't match (see IS-IS 8.2.5.2.d). + if adj.system_id != hello.source { + return Err(AdjacencyRejectError::WrongSystem); + } + adj + } + None => { + // Determine level usage based on area match and circuit type. + let Some(level_usage) = (match area_match { + true => { + // Area matches: resolve level based on circuit type. + iface + .config + .level_type + .resolved + .intersection(hello.circuit_type) + } + false => { + // Non-matching area: only accept L2 circuit type. + if hello.circuit_type != LevelType::L1 { + Some(LevelType::L2) + } else { + None + } + } + }) else { + return Err(AdjacencyRejectError::WrongSystem); + }; + + // Create a new adjacency. + Adjacency::new( + 0, + src, + hello.source, + hello.circuit_type, + level_usage, + ) + } + }; + + // Update adjacency. + adj.area_addrs = hello.tlvs.area_addrs().cloned().collect(); + + // Restart hold timer. + adj.holdtimer_reset(iface, instance, hello.holdtime); + + // Transition the adjacency to the "Up" state. + adj.state_change( + iface, + instance, + AdjacencyEvent::HelloOneWayRcvd, + AdjacencyState::Up, + ); + iface.state.p2p_adjacency = Some(adj); + + Ok(()) +} + +pub(crate) fn process_pdu_lsp( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_idx: InterfaceIndex, + src: [u8; 6], + bytes: Bytes, + mut lsp: Lsp, +) -> Result<(), Error> { + let iface = &mut arenas.interfaces[iface_idx]; + let system_id = instance.config.system_id.unwrap(); + + // Set the level based on the PDU type, and discard the LSP if the level + // is incompatible with the interface. + let level = if lsp.hdr.pdu_type == PduType::LspL1 { + LevelNumber::L1 + } else { + LevelNumber::L2 + }; + if !iface.config.level_type.resolved.intersects(level) { + return Ok(()); + } + + // Validate the "Maximum Area Addresses" field. + if level == LevelNumber::L1 + && lsp.hdr.max_area_addrs != 0 + && lsp.hdr.max_area_addrs != 3 + { + iface.state.event_counters.max_area_addr_mismatch += 1; + iface.state.discontinuity_time = Utc::now(); + notification::max_area_addresses_mismatch( + instance, + iface, + lsp.hdr.max_area_addrs, + &bytes, + ); + return Ok(()); + } + + // Lookup adjacency. + let Some(adj) = (match iface.config.interface_type { + InterfaceType::Broadcast => iface + .state + .lan_adjacencies + .get(level) + .get_by_snpa(&arenas.adjacencies, src) + .map(|(_, adj)| adj), + InterfaceType::PointToPoint => iface + .state + .p2p_adjacency + .as_ref() + .filter(|adj| adj.level_usage.intersects(level)), + }) else { + // Couldn't find a matching adjacency. Discard the LSP. + return Ok(()); + }; + + // Send YANG notification. + notification::lsp_received(instance, iface, &lsp, &adj.system_id); + + // Check if we're receiving a purge from a self-originated LSP. + if lsp.rem_lifetime == 0 && lsp.lsp_id.system_id == system_id { + // Send YANG notification. + notification::own_lsp_purge(instance, iface, &lsp); + + // Update event counter. + instance.state.counters.get_mut(level).own_lsp_purge += 1; + iface.state.discontinuity_time = Utc::now(); + } + + // Validate LSP checksum. + lsp.raw = bytes; + if lsp.cksum == 0 { + // ISO 10589 - Section 7.3.14.2: + // "A Link State PDU received with a zero checksum shall be treated as + // if the Remaining Lifetime were zero. The age, if not zero, shall be + // overwritten with zero". + lsp.rem_lifetime = 0; + } else if !lsp.is_checksum_valid() { + // Send error notification. + notification::lsp_error_detected(instance, iface, &lsp); + + // Log why the LSP is being discarded. + Debug::LspDiscard(level, &lsp).log(); + + // Discard LSP. + return Ok(()); + } + + // NOTE: Per RFC 3719, LSPs with a Remaining Lifetime greater than MaxAge + // should not be discarded as originally specified. MaxAge is now variable + // and no longer a fixed architectural constant. + + // Lookup LSP in the database. + let lse = instance + .state + .lsdb + .get(level) + .get_by_lspid(&arenas.lsp_entries, &lsp.lsp_id) + .map(|(_, lse)| lse); + + // LSP expiration synchronization (ISO 10589 - Section 7.3.16.4.a). + if lsp.rem_lifetime == 0 && lse.is_none() { + if iface.config.interface_type != InterfaceType::Broadcast { + // Send an acknowledgement. + let pdu = Pdu::Snp(Snp::new( + level, + LanId::from((system_id, iface.state.circuit_id)), + None, + SnpTlvs::new([LspEntry { + rem_lifetime: lsp.rem_lifetime, + lsp_id: lsp.lsp_id, + cksum: lsp.cksum, + seqno: lsp.seqno, + }]), + )); + iface.enqueue_pdu(pdu, level); + } + return Ok(()); + } + + // Check if this is a self-originated LSP. + if lsp.lsp_id.system_id == system_id { + if lse.is_none() { + // Self-originated LSP not found in the LSDB, so it should be purged + // from the network. + lsp.set_rem_lifetime(0); + for iface in arenas.interfaces.iter_mut() { + iface.srm_list_add(level, lsp.clone()); + } + return Ok(()); + } + + // Check if the LSP exists in the LSDB and the received LSP is + // considered more recent. + if let Some(lse) = lse + && lsp_compare(&lse.data, lsp.seqno, lsp.rem_lifetime) + == Ordering::Less + { + // Increase LSP sequence number and regenerate. + let lsp = Lsp::new( + level, + instance.config.lsp_lifetime, + lse.data.lsp_id, + lsp.seqno + 1, + lse.data.flags, + lse.data.tlvs.clone(), + ); + lsdb::lsp_originate(instance, arenas, level, lsp); + } + + return Ok(()); + } + + // Compare the LSP in the database (if it exists) to the incoming LSP. + match lse.map(|lse| lsp_compare(&lse.data, lsp.seqno, lsp.rem_lifetime)) { + None | Some(Ordering::Less) => { + // Store the new LSP, replacing any existing one. + let lse = + lsdb::install(instance, &mut arenas.lsp_entries, level, lsp); + let lsp = &lse.data; + lse.flags.insert(LspEntryFlags::RECEIVED); + + // Update LSP flooding flags for the incoming interface. + iface.srm_list_del(level, &lsp.lsp_id); + if iface.config.interface_type != InterfaceType::Broadcast { + iface.ssn_list_add(level, lsp.as_snp_entry()); + } + + // Update LSP flooding flags for the other interfaces. + let iface_id = iface.id; + for other_iface in arenas + .interfaces + .iter_mut() + .filter(|other_iface| other_iface.id != iface_id) + { + other_iface.srm_list_add(level, lsp.clone()); + other_iface.ssn_list_del(level, &lsp.lsp_id); + } + } + Some(Ordering::Equal) => { + let lse = lse.unwrap(); + + // LSP confusion handling (ISO 10589 - Section 7.3.16.2). + if lse.data.cksum != lsp.cksum { + if lse.flags.contains(LspEntryFlags::RECEIVED) { + // Treat it as if its Remaining Lifetime had expired. + instance.tx.protocol_input.lsp_purge( + level, + lse.id, + LspPurgeReason::Confusion, + ); + } else { + // Increase LSP sequence number and regenerate. + instance.tx.protocol_input.lsp_refresh(level, lse.id); + + // Send YANG notification. + notification::sequence_number_skipped( + instance, iface, &lsp, + ); + + // Update event counter. + instance.state.counters.get_mut(level).seqno_skipped += 1; + iface.state.discontinuity_time = Utc::now(); + } + return Ok(()); + } + + // Update LSP flooding flags for the incoming interface. + iface.srm_list_del(level, &lsp.lsp_id); + if iface.config.interface_type != InterfaceType::Broadcast { + iface.ssn_list_add(level, lsp.as_snp_entry()); + } + } + Some(Ordering::Greater) => { + // Update LSP flooding flags for the incoming interface. + let lsp_id = lsp.lsp_id; + iface.srm_list_add(level, lsp); + iface.ssn_list_del(level, &lsp_id); + } + } + + Ok(()) +} + +pub(crate) fn process_pdu_snp( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_idx: InterfaceIndex, + src: [u8; 6], + bytes: Bytes, + snp: Snp, +) -> Result<(), Error> { + let iface = &mut arenas.interfaces[iface_idx]; + + // Set the level based on the PDU type, and discard the SNP if the level + // is incompatible with the interface. + let level = if matches!(snp.hdr.pdu_type, PduType::CsnpL1 | PduType::PsnpL1) + { + LevelNumber::L1 + } else { + LevelNumber::L2 + }; + if !iface.config.level_type.resolved.intersects(level) { + return Ok(()); + } + + // Validate the "Maximum Area Addresses" field. + if level == LevelNumber::L1 + && snp.hdr.max_area_addrs != 0 + && snp.hdr.max_area_addrs != 3 + { + iface.state.event_counters.max_area_addr_mismatch += 1; + iface.state.discontinuity_time = Utc::now(); + notification::max_area_addresses_mismatch( + instance, + iface, + snp.hdr.max_area_addrs, + &bytes, + ); + return Ok(()); + } + + // Discard PSNP if we're not the DIS for the broadcast interface. + if iface.config.interface_type == InterfaceType::Broadcast + && snp.summary.is_none() + && !iface.is_dis(level) + { + return Ok(()); + } + + // Lookup adjacency. + let Some(_adj) = (match iface.config.interface_type { + InterfaceType::Broadcast => iface + .state + .lan_adjacencies + .get(level) + .get_by_snpa(&arenas.adjacencies, src) + .map(|(_, adj)| adj), + InterfaceType::PointToPoint => iface + .state + .p2p_adjacency + .as_ref() + .filter(|adj| adj.level_usage.intersects(level)), + }) else { + // Couldn't find a matching adjacency. Discard the SNP. + return Ok(()); + }; + + // Iterate over all LSP entries. + let lsp_entries = snp + .tlvs + .lsp_entries() + .map(|entry| (entry.lsp_id, *entry)) + .collect::>(); + for entry in lsp_entries.values() { + // Lookup LSP in the database. + let lse = instance + .state + .lsdb + .get(level) + .get_by_lspid(&arenas.lsp_entries, &entry.lsp_id) + .map(|(_, lse)| lse); + + // Check if the LSP entry in the received SNP is newer than the + // corresponding stored LSP and update the LSP flooding flags + // accordingly. + if let Some(lse) = lse { + match lsp_compare(&lse.data, entry.seqno, entry.rem_lifetime) { + // LSP confusion handling (ISO 10589 - Section 7.3.16.2). + Ordering::Equal if lse.data.cksum != entry.cksum => { + if lse.flags.contains(LspEntryFlags::RECEIVED) { + // Treat it as if its Remaining Lifetime had expired. + instance.tx.protocol_input.lsp_purge( + level, + lse.id, + LspPurgeReason::Confusion, + ); + } else { + // Increase LSP sequence number and regenerate. + instance.tx.protocol_input.lsp_refresh(level, lse.id); + + // Send YANG notification. + notification::sequence_number_skipped( + instance, iface, &lse.data, + ); + + // Update event counter. + instance.state.counters.get_mut(level).seqno_skipped += + 1; + iface.state.discontinuity_time = Utc::now(); + } + } + Ordering::Equal => { + iface.srm_list_del(level, &entry.lsp_id); + } + Ordering::Greater => { + iface.ssn_list_del(level, &entry.lsp_id); + iface.srm_list_add(level, lse.data.clone()); + } + Ordering::Less => { + iface.ssn_list_add(level, *entry); + iface.srm_list_del(level, &entry.lsp_id); + } + } + continue; + } + + // ISO 10589 - Section 7.3.15.2.b.5: + // "If no database entry exists for the LSP, and the reported Remaining + // Lifetime, Checksum and Sequence Number fields of the LSP are all + // non-zero, create an entry with sequence number 0". + if entry.rem_lifetime != 0 && entry.cksum != 0 && entry.seqno != 0 { + let lsp = Lsp::new( + level, + entry.rem_lifetime, + entry.lsp_id, + 0, + Default::default(), + Default::default(), + ); + let lse = + lsdb::install(instance, &mut arenas.lsp_entries, level, lsp); + iface.ssn_list_add(level, lse.data.as_snp_entry()); + } + } + + // Complete Sequence Numbers PDU processing. + // + // Flood LSPs we have that the neighbor doesn't. + if let Some((start, end)) = snp.summary { + for lsp in instance + .state + .lsdb + .get_mut(level) + .range(&arenas.lsp_entries, start..=end) + .map(|lse| &lse.data) + .filter(|lsp| !lsp_entries.contains_key(&lsp.lsp_id)) + // Exclude LSPs with zero Remaining Lifetime. + .filter(|lsp| lsp.rem_lifetime != 0) + // Exclude LSPs with zero sequence number. + .filter(|lsp| lsp.seqno != 0) + { + iface.srm_list_add(level, lsp.clone()); + } + } + + Ok(()) +} + +// ===== Adjacency hold timer expiry ===== + +pub(crate) fn process_lan_adj_holdtimer_expiry( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, + adj_key: AdjacencyKey, + level: LevelNumber, +) -> Result<(), Error> { + // Lookup interface. + let (_iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Lookup adjacency. + let (adj_idx, adj) = iface + .state + .lan_adjacencies + .get_mut(level) + .get_mut_by_key(&mut arenas.adjacencies, &adj_key)?; + + // Delete adjacency. + adj.state_change( + iface, + instance, + AdjacencyEvent::HoldtimeExpired, + AdjacencyState::Down, + ); + iface + .state + .lan_adjacencies + .get_mut(level) + .delete(&mut arenas.adjacencies, adj_idx); + + // Restart Hello Tx task (updated list of neighbors). + iface.hello_interval_start(instance, level); + + Ok(()) +} + +pub(crate) fn process_p2p_adj_holdtimer_expiry( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, +) -> Result<(), Error> { + // Lookup interface. + let (_iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Delete adjacency. + if let Some(mut adj) = iface.state.p2p_adjacency.take() { + adj.state_change( + iface, + instance, + AdjacencyEvent::HoldtimeExpired, + AdjacencyState::Down, + ); + } + + Ok(()) +} + +// ===== DIS election ===== + +pub(crate) fn process_dis_election( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, + level: LevelNumber, +) -> Result<(), Error> { + // Lookup interface. + let (_iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Run DIS election. + let dis = iface.dis_election(instance, &arenas.adjacencies, level); + + // Return if no DIS change. + if *iface.state.dis.get(level) == dis { + return Ok(()); + } + + // Log DIS change. + Debug::InterfaceDisChange(&iface.name, level, &dis).log(); + + // Update DIS. + let old_dis = std::mem::replace(iface.state.dis.get_mut(level), dis); + + // Update event counter. + iface.state.event_counters.lan_dis_changes += 1; + iface.state.discontinuity_time = Utc::now(); + + // Restart Hello Tx task. + iface.hello_interval_start(instance, level); + + // Process DIS changes. + match (old_dis, dis) { + (Some(old), _) if old.myself => { + // We're no longer the DIS. + iface.dis_stop(instance); + } + (_, Some(new)) if new.myself => { + // We're the new DIS. + iface.dis_start(instance); + } + _ => {} + } + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(level); + + Ok(()) +} + +// ===== Request to send PSNP ===== + +pub(crate) fn process_send_psnp( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, + level: LevelNumber, +) -> Result<(), Error> { + // Lookup interface. + let (_iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Do not send PSNP if we're the DIS. + if iface.config.interface_type == InterfaceType::Broadcast + && iface.is_dis(level) + { + return Ok(()); + } + + // Do not send empty PSNP. + if iface.state.ssn_list.get(level).is_empty() { + return Ok(()); + } + + // Add as many LSP entries that will fit in a single PDU. + let mut lsp_entries = vec![]; + let mtu = iface.iso_mtu() as usize; + for _ in 0..SnpTlvs::max_lsp_entries(mtu - Snp::PSNP_HEADER_LEN as usize) { + if let Some((_, lsp_entry)) = + iface.state.ssn_list.get_mut(level).pop_first() + { + lsp_entries.push(lsp_entry); + } else { + break; + } + } + + // Generate PDU. + let pdu = Pdu::Snp(Snp::new( + level, + LanId::from(( + instance.config.system_id.unwrap(), + iface.state.circuit_id, + )), + None, + SnpTlvs::new(lsp_entries), + )); + + // Enqueue PDU for transmission. + iface.enqueue_pdu(pdu, level); + + Ok(()) +} + +// ===== Request to send CSNP ===== + +pub(crate) fn process_send_csnp( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + iface_key: InterfaceKey, + level: LevelNumber, +) -> Result<(), Error> { + // Lookup interface. + let (_iface_idx, iface) = arenas.interfaces.get_mut_by_key(&iface_key)?; + + // Do not send CSNP if we aren't the DIS. + if iface.config.interface_type == InterfaceType::Broadcast + && !iface.is_dis(level) + { + return Ok(()); + } + + // Set CSNP source. + let source = LanId::from(( + instance.config.system_id.unwrap(), + iface.state.circuit_id, + )); + + // Calculate maximum of LSP entries per PDU. + let mtu = iface.iso_mtu() as usize; + let max_lsp_entries = + SnpTlvs::max_lsp_entries(mtu - Snp::CSNP_HEADER_LEN as usize); + + // Closure to generate and send CSNP; + let mut send_csnp = |level, source, start, end, lsp_entries: Vec<_>| { + // Generate PDU. + let pdu = Pdu::Snp(Snp::new( + level, + source, + Some((start, end)), + SnpTlvs::new(lsp_entries), + )); + + // Enqueue PDU for transmission. + iface.enqueue_pdu(pdu, level); + }; + + // Iterate over LSDB and send as many CSNPs as necessary. + let mut start = LspId::from([0; 8]); + let mut lsp_entries = vec![]; + let mut lsdb_iter = instance + .state + .lsdb + .get(level) + .iter(&arenas.lsp_entries) + .map(|lse| &lse.data) + .peekable(); + while let Some(lsp) = lsdb_iter.next() { + // Add current LSP entry. + lsp_entries.push(lsp.as_snp_entry()); + + // Check if this is the last LSP. + let Some(next_lsp) = lsdb_iter.peek() else { + // Send the final CSNP. + let end = LspId::from([0xff; 8]); + (send_csnp)(level, source, start, end, lsp_entries); + break; + }; + + // If max LSP entries reached, send current CSNP. + if lsp_entries.len() == max_lsp_entries { + // Set end LSP ID to current LSP ID. + let end = lsp.lsp_id; + let lsp_entries = std::mem::take(&mut lsp_entries); + (send_csnp)(level, source, start, end, lsp_entries); + + // Update start for the next CSNP. + start = next_lsp.lsp_id; + } + } + + Ok(()) +} + +// ===== LSP origination event ===== + +pub(crate) fn process_lsp_originate( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, +) -> Result<(), Error> { + // Clear LSP origination backoff. + instance.state.lsp_orig_backoff = None; + let Some(level_type) = instance.state.lsp_orig_pending.take() else { + return Ok(()); + }; + + // Originate LSPs for levels with pending requests. + for level in [LevelNumber::L1, LevelNumber::L2] + .into_iter() + .filter(|level| instance.config.level_type.intersects(level)) + .filter(|level| level_type.intersects(level)) + { + lsdb::lsp_originate_all(instance, arenas, level); + } + + Ok(()) +} + +// ===== LSP purge event ===== + +pub(crate) fn process_lsp_purge( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + level: LevelNumber, + lse_key: LspEntryKey, + reason: LspPurgeReason, +) -> Result<(), Error> { + // Lookup LSP entry in the LSDB. + let (_, lse) = instance + .state + .lsdb + .get_mut(level) + .get_mut_by_key(&mut arenas.lsp_entries, &lse_key)?; + let lsp = &mut lse.data; + + // Check if the LSP expired. + let expired = lsp.rem_lifetime() == 0; + + // Log LSP purge. + Debug::LspPurge(level, lsp, reason).log(); + + // Set remaining lifetime to zero if it's not already. + lsp.set_rem_lifetime(0); + + // Stop the LSP's expiration and refresh timers. + lse.expiry_timer = None; + lse.refresh_timer = None; + + // Send purged LSP to all interfaces. + for iface in arenas.interfaces.iter_mut() { + iface.srm_list_add(level, lsp.clone()); + } + + // Mark the LSP as purged and start the delete timer. + lse.flags.insert(LspEntryFlags::PURGED); + let delete_timeout = if expired { + lsdb::LSP_ZERO_AGE_LIFETIME + } else { + instance.config.lsp_lifetime as u64 + }; + let delete_timer = tasks::lsp_delete_timer( + level, + lse.id, + delete_timeout, + &instance.tx.protocol_input.lsp_delete, + ); + lse.delete_timer = Some(delete_timer); + + Ok(()) +} + +// ===== LSP delete event ===== + +pub(crate) fn process_lsp_delete( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + level: LevelNumber, + lse_key: LspEntryKey, +) -> Result<(), Error> { + // Lookup LSP entry in the LSDB. + let lsdb = instance.state.lsdb.get_mut(level); + let (lse_idx, lse) = lsdb.get_by_key(&arenas.lsp_entries, &lse_key)?; + assert!(lse.flags.contains(LspEntryFlags::PURGED)); + + // Log LSP deletion. + Debug::LspDelete(level, &lse.data).log(); + + // Delete the LSP entry from the LSDB. + lsdb.delete(&mut arenas.lsp_entries, lse_idx); + + Ok(()) +} + +// ===== LSP refresh event ===== + +pub(crate) fn process_lsp_refresh( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + level: LevelNumber, + lse_key: LspEntryKey, +) -> Result<(), Error> { + // Lookup LSP entry in the LSDB. + let lsp = instance + .state + .lsdb + .get(level) + .get_by_key(&arenas.lsp_entries, &lse_key) + .map(|(_, lse)| &lse.data)?; + + // Log LSP refresh. + Debug::LspRefresh(level, lsp).log(); + + // Originate new instance of the LSP. + let lsp = Lsp::new( + level, + instance.config.lsp_lifetime, + lsp.lsp_id, + lsp.seqno + 1, + lsp.flags, + lsp.tlvs.clone(), + ); + lsdb::lsp_originate(instance, arenas, level, lsp); + + Ok(()) +} diff --git a/holo-isis/src/instance.rs b/holo-isis/src/instance.rs new file mode 100644 index 00000000..e8f34912 --- /dev/null +++ b/holo-isis/src/instance.rs @@ -0,0 +1,608 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::collections::VecDeque; +use std::net::Ipv4Addr; +use std::time::Instant; + +use async_trait::async_trait; +use chrono::{DateTime, Utc}; +use holo_protocol::{ + InstanceChannelsTx, InstanceShared, MessageReceiver, ProtocolInstance, +}; +use holo_utils::ibus::IbusMsg; +use holo_utils::protocol::Protocol; +use holo_utils::task::TimeoutTask; +use holo_utils::{Receiver, Sender, UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc; + +use crate::adjacency::Adjacency; +use crate::collections::{Arena, Interfaces, Lsdb, LspEntryId}; +use crate::debug::{ + Debug, InstanceInactiveReason, InterfaceInactiveReason, LspPurgeReason, +}; +use crate::error::Error; +use crate::interface::CircuitIdAllocator; +use crate::lsdb::{LspEntry, LspLogEntry}; +use crate::northbound::configuration::InstanceCfg; +use crate::packet::{LevelNumber, LevelType, Levels}; +use crate::tasks::messages::input::{ + AdjHoldTimerMsg, DisElectionMsg, LspDeleteMsg, LspOriginateMsg, + LspPurgeMsg, LspRefreshMsg, NetRxPduMsg, SendCsnpMsg, SendPsnpMsg, +}; +use crate::tasks::messages::{ProtocolInputMsg, ProtocolOutputMsg}; +use crate::{events, lsdb, southbound, tasks}; + +#[derive(Debug)] +pub struct Instance { + // Instance name. + pub name: String, + // Instance system data. + pub system: InstanceSys, + // Instance configuration data. + pub config: InstanceCfg, + // Instance state data. + pub state: Option, + // Instance arenas. + pub arenas: InstanceArenas, + // Instance Tx channels. + pub tx: InstanceChannelsTx, + // Shared data. + pub shared: InstanceShared, +} + +#[derive(Debug, Default)] +pub struct InstanceSys { + // System Router ID. + pub router_id: Option, +} + +#[derive(Debug)] +pub struct InstanceState { + // Circuit ID allocator. + pub circuit_id_allocator: CircuitIdAllocator, + // Link State Database. + pub lsdb: Levels, + // LSP origination data. + pub lsp_orig_last: Option, + pub lsp_orig_backoff: Option, + pub lsp_orig_pending: Option, + // Event counters. + pub counters: Levels, + pub discontinuity_time: DateTime, + // Log of LSP updates. + pub lsp_log: VecDeque, + pub lsp_log_next_id: u32, +} + +#[derive(Debug, Default)] +pub struct InstanceCounters { + pub corrupted_lsps: u32, + pub auth_type_fails: u32, + pub auth_fails: u32, + pub database_overload: u32, + pub own_lsp_purge: u32, + pub manual_addr_drop_from_area: u32, + pub max_sequence: u32, + pub seqno_skipped: u32, + pub id_len_mismatch: u32, + pub partition_changes: u32, + pub lsp_errors: u32, + pub spf_runs: u32, +} + +#[derive(Debug, Default)] +pub struct InstanceArenas { + pub interfaces: Interfaces, + pub adjacencies: Arena, + pub lsp_entries: Arena, +} + +#[derive(Clone, Debug)] +pub struct ProtocolInputChannelsTx { + // PDU Rx event. + pub net_pdu_rx: Sender, + // Adjacency hold timer event. + pub adj_holdtimer: Sender, + // Request to run DIS election. + pub dis_election: UnboundedSender, + // Request to send PSNP(s). + pub send_psnp: UnboundedSender, + // Request to send CSNP(s). + pub send_csnp: UnboundedSender, + // LSP originate event. + pub lsp_originate: UnboundedSender, + // LSP purge event. + pub lsp_purge: UnboundedSender, + // LSP delete event. + pub lsp_delete: UnboundedSender, + // LSP refresh event. + pub lsp_refresh: UnboundedSender, +} + +#[derive(Debug)] +pub struct ProtocolInputChannelsRx { + // PDU Rx event. + pub net_pdu_rx: Receiver, + // Adjacency hold timer event. + pub adj_holdtimer: Receiver, + // Request to run DIS election. + pub dis_election: UnboundedReceiver, + // Request to send PSNP(s). + pub send_psnp: UnboundedReceiver, + // Request to send CSNP(s). + pub send_csnp: UnboundedReceiver, + // LSP originate event. + pub lsp_originate: UnboundedReceiver, + // LSP purge event. + pub lsp_purge: UnboundedReceiver, + // LSP delete event. + pub lsp_delete: UnboundedReceiver, + // LSP refresh event. + pub lsp_refresh: UnboundedReceiver, +} + +pub struct InstanceUpView<'a> { + pub name: &'a str, + pub system: &'a InstanceSys, + pub config: &'a InstanceCfg, + pub state: &'a mut InstanceState, + pub tx: &'a InstanceChannelsTx, + pub shared: &'a InstanceShared, +} + +// ===== impl Instance ===== + +impl Instance { + // Checks if the instance needs to be started or stopped in response to a + // northbound or southbound event. + pub(crate) fn update(&mut self) { + match self.is_ready() { + Ok(()) if !self.is_active() => { + self.start(); + } + Err(reason) if self.is_active() => { + self.stop(reason); + } + _ => (), + } + } + + // Starts the IS-IS instance. + fn start(&mut self) { + Debug::InstanceStart.log(); + + // Create instance initial state. + let state = InstanceState::new(&self.tx); + self.state = Some(state); + + // Start interfaces. + for iface in self.arenas.interfaces.iter() { + iface.query_southbound(&self.tx.ibus); + } + + // Schedule initial LSP origination. + let (mut instance, _) = self.as_up().unwrap(); + instance.schedule_lsp_origination(LevelType::All); + } + + // Stops the IS-IS instance. + fn stop(&mut self, reason: InstanceInactiveReason) { + Debug::InstanceStop(reason).log(); + + // Stop interfaces. + let (mut instance, arenas) = self.as_up().unwrap(); + let reason = InterfaceInactiveReason::InstanceDown; + for iface in arenas + .interfaces + .iter_mut() + .filter(|iface| iface.state.active) + { + iface.stop(&mut instance, &mut arenas.adjacencies, reason); + } + + // Clear instance state. + self.state = None; + } + + // Resets the IS-IS instance. + pub(crate) fn reset(&mut self) { + if self.is_active() { + self.stop(InstanceInactiveReason::Resetting); + self.update(); + } + } + + // Returns whether the IS-IS instance is operational. + pub(crate) fn is_active(&self) -> bool { + self.state.is_some() + } + + // Returns whether the instance is ready for IS-IS operation. + fn is_ready(&self) -> Result<(), InstanceInactiveReason> { + if !self.config.enabled || self.config.system_id.is_none() { + return Err(InstanceInactiveReason::AdminDown); + } + + Ok(()) + } + + // Returns a view struct for the instance if it's operational. + pub(crate) fn as_up( + &mut self, + ) -> Option<(InstanceUpView<'_>, &mut InstanceArenas)> { + if let Some(state) = &mut self.state { + let instance = InstanceUpView { + name: &self.name, + system: &self.system, + config: &self.config, + state, + tx: &self.tx, + shared: &self.shared, + }; + Some((instance, &mut self.arenas)) + } else { + None + } + } +} + +#[async_trait] +impl ProtocolInstance for Instance { + const PROTOCOL: Protocol = Protocol::ISIS; + + type ProtocolInputMsg = ProtocolInputMsg; + type ProtocolOutputMsg = ProtocolOutputMsg; + type ProtocolInputChannelsTx = ProtocolInputChannelsTx; + type ProtocolInputChannelsRx = ProtocolInputChannelsRx; + + async fn new( + name: String, + shared: InstanceShared, + tx: InstanceChannelsTx, + ) -> Instance { + Debug::InstanceCreate.log(); + + Instance { + name, + system: Default::default(), + config: Default::default(), + state: None, + arenas: Default::default(), + tx, + shared, + } + } + + async fn init(&mut self) { + // Request information about the system Router ID. + southbound::tx::router_id_query(&self.tx.ibus); + } + + async fn shutdown(mut self) { + // Ensure instance is disabled before exiting. + self.stop(InstanceInactiveReason::AdminDown); + Debug::InstanceDelete.log(); + } + + async fn process_ibus_msg(&mut self, msg: IbusMsg) { + if let Err(error) = process_ibus_msg(self, msg).await { + error.log(&self.arenas); + } + } + + fn process_protocol_msg(&mut self, msg: ProtocolInputMsg) { + // Ignore event if the instance isn't active. + let Some((mut instance, arenas)) = self.as_up() else { + return; + }; + + if let Err(error) = process_protocol_msg(&mut instance, arenas, msg) { + error.log(arenas); + } + } + + fn protocol_input_channels( + ) -> (ProtocolInputChannelsTx, ProtocolInputChannelsRx) { + let (net_pdu_rxp, net_pdu_rxc) = mpsc::channel(4); + let (adj_holdtimerp, adj_holdtimerc) = mpsc::channel(4); + let (dis_electionp, dis_electionc) = mpsc::unbounded_channel(); + let (send_psnpp, send_psnpc) = mpsc::unbounded_channel(); + let (send_csnpp, send_csnpc) = mpsc::unbounded_channel(); + let (lsp_originatep, lsp_originatec) = mpsc::unbounded_channel(); + let (lsp_purgep, lsp_purgec) = mpsc::unbounded_channel(); + let (lsp_deletep, lsp_deletec) = mpsc::unbounded_channel(); + let (lsp_refreshp, lsp_refreshc) = mpsc::unbounded_channel(); + + let tx = ProtocolInputChannelsTx { + net_pdu_rx: net_pdu_rxp, + adj_holdtimer: adj_holdtimerp, + dis_election: dis_electionp, + send_psnp: send_psnpp, + send_csnp: send_csnpp, + lsp_originate: lsp_originatep, + lsp_purge: lsp_purgep, + lsp_delete: lsp_deletep, + lsp_refresh: lsp_refreshp, + }; + let rx = ProtocolInputChannelsRx { + net_pdu_rx: net_pdu_rxc, + adj_holdtimer: adj_holdtimerc, + dis_election: dis_electionc, + send_psnp: send_psnpc, + send_csnp: send_csnpc, + lsp_originate: lsp_originatec, + lsp_purge: lsp_purgec, + lsp_delete: lsp_deletec, + lsp_refresh: lsp_refreshc, + }; + + (tx, rx) + } + + #[cfg(feature = "testing")] + fn test_dir() -> String { + format!("{}/tests/conformance", env!("CARGO_MANIFEST_DIR"),) + } +} + +// ===== impl InstanceState ===== + +impl InstanceState { + fn new(_instance_tx: &InstanceChannelsTx) -> InstanceState { + InstanceState { + circuit_id_allocator: Default::default(), + lsdb: Default::default(), + lsp_orig_last: None, + lsp_orig_backoff: None, + lsp_orig_pending: None, + counters: Default::default(), + discontinuity_time: Utc::now(), + lsp_log: Default::default(), + lsp_log_next_id: 0, + } + } +} + +// ===== impl ProtocolInputChannelsTx ===== + +impl ProtocolInputChannelsTx { + pub(crate) fn lsp_purge( + &self, + level: LevelNumber, + lse_id: LspEntryId, + reason: LspPurgeReason, + ) { + let msg = LspPurgeMsg { + level, + lse_key: lse_id.into(), + reason, + }; + let _ = self.lsp_purge.send(msg); + } + + pub(crate) fn lsp_refresh(&self, level: LevelNumber, lse_id: LspEntryId) { + let msg = LspRefreshMsg { + level, + lse_key: lse_id.into(), + }; + let _ = self.lsp_refresh.send(msg); + } +} + +// ===== impl ProtocolInputChannelsRx ===== + +#[async_trait] +impl MessageReceiver for ProtocolInputChannelsRx { + async fn recv(&mut self) -> Option { + tokio::select! { + biased; + msg = self.net_pdu_rx.recv() => { + msg.map(ProtocolInputMsg::NetRxPdu) + } + msg = self.adj_holdtimer.recv() => { + msg.map(ProtocolInputMsg::AdjHoldTimer) + } + msg = self.dis_election.recv() => { + msg.map(ProtocolInputMsg::DisElection) + } + msg = self.send_psnp.recv() => { + msg.map(ProtocolInputMsg::SendPsnp) + } + msg = self.send_csnp.recv() => { + msg.map(ProtocolInputMsg::SendCsnp) + } + msg = self.lsp_originate.recv() => { + msg.map(ProtocolInputMsg::LspOriginate) + } + msg = self.lsp_purge.recv() => { + msg.map(ProtocolInputMsg::LspPurge) + } + msg = self.lsp_delete.recv() => { + msg.map(ProtocolInputMsg::LspDelete) + } + msg = self.lsp_refresh.recv() => { + msg.map(ProtocolInputMsg::LspRefresh) + } + } + } +} + +// ===== impl InstanceUpView ===== + +impl InstanceUpView<'_> { + pub(crate) fn schedule_lsp_origination( + &mut self, + level_type: impl Into, + ) { + let level_type = level_type.into(); + + // Update pending LSP origination with the union of the current and + // new level. + self.state.lsp_orig_pending = match self.state.lsp_orig_pending { + Some(pending_level) => Some(level_type.union(pending_level)), + None => Some(level_type), + }; + + #[cfg(not(feature = "deterministic"))] + { + // If LSP origination is currently in backoff, do nothing. + if self.state.lsp_orig_backoff.is_some() { + return; + } + + // If the minimum interval since the last LSP origination hasn't + // passed, initiate a backoff timer and return. + if let Some(last) = self.state.lsp_orig_last + && last.elapsed().as_secs() < lsdb::LSP_MIN_GEN_INTERVAL + { + let task = tasks::lsp_originate_timer( + &self.tx.protocol_input.lsp_originate, + ); + self.state.lsp_orig_backoff = Some(task); + return; + } + } + + // Trigger LSP origination. + let _ = self + .tx + .protocol_input + .lsp_originate + .send(LspOriginateMsg {}); + } +} + +// ===== helper functions ===== + +async fn process_ibus_msg( + instance: &mut Instance, + msg: IbusMsg, +) -> Result<(), Error> { + match msg { + // Router ID update notification. + IbusMsg::RouterIdUpdate(router_id) => { + southbound::rx::process_router_id_update(instance, router_id).await; + } + // Interface update notification. + IbusMsg::InterfaceUpd(msg) => { + southbound::rx::process_iface_update(instance, msg)?; + } + // Interface address addition notification. + IbusMsg::InterfaceAddressAdd(msg) => { + southbound::rx::process_addr_add(instance, msg); + } + // Interface address deletion notification. + IbusMsg::InterfaceAddressDel(msg) => { + southbound::rx::process_addr_del(instance, msg); + } + // Ignore other events. + _ => {} + } + + Ok(()) +} + +fn process_protocol_msg( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + msg: ProtocolInputMsg, +) -> Result<(), Error> { + match msg { + // Received network PDU. + ProtocolInputMsg::NetRxPdu(msg) => { + events::process_pdu( + instance, + arenas, + msg.iface_key, + msg.src, + msg.bytes, + msg.pdu, + )?; + } + // Adjacency hold timer event. + ProtocolInputMsg::AdjHoldTimer(msg) => match msg { + AdjHoldTimerMsg::Broadcast { + iface_key, + adj_key, + level, + } => { + events::process_lan_adj_holdtimer_expiry( + instance, arenas, iface_key, adj_key, level, + )?; + } + AdjHoldTimerMsg::PointToPoint { iface_key } => { + events::process_p2p_adj_holdtimer_expiry( + instance, arenas, iface_key, + )?; + } + }, + // Request to run DIS election. + ProtocolInputMsg::DisElection(msg) => { + events::process_dis_election( + instance, + arenas, + msg.iface_key, + msg.level, + )?; + } + // Request to run send PSNP(s). + ProtocolInputMsg::SendPsnp(msg) => { + events::process_send_psnp( + instance, + arenas, + msg.iface_key, + msg.level, + )?; + } + // Request to run send CSNP(s). + ProtocolInputMsg::SendCsnp(msg) => { + events::process_send_csnp( + instance, + arenas, + msg.iface_key, + msg.level, + )?; + } + // LSP origination event. + ProtocolInputMsg::LspOriginate(_msg) => { + events::process_lsp_originate(instance, arenas)?; + } + // LSP purge event. + ProtocolInputMsg::LspPurge(msg) => { + events::process_lsp_purge( + instance, + arenas, + msg.level, + msg.lse_key, + msg.reason, + )?; + } + // LSP delete event. + ProtocolInputMsg::LspDelete(msg) => { + events::process_lsp_delete( + instance, + arenas, + msg.level, + msg.lse_key, + )?; + } + // LSP refresh event. + ProtocolInputMsg::LspRefresh(msg) => { + events::process_lsp_refresh( + instance, + arenas, + msg.level, + msg.lse_key, + )?; + } + } + + Ok(()) +} diff --git a/holo-isis/src/interface.rs b/holo-isis/src/interface.rs new file mode 100644 index 00000000..b2495660 --- /dev/null +++ b/holo-isis/src/interface.rs @@ -0,0 +1,811 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; + +use chrono::{DateTime, Utc}; +use holo_protocol::InstanceChannelsTx; +use holo_utils::ibus::{IbusMsg, IbusSender}; +use holo_utils::ip::AddressFamily; +use holo_utils::socket::{AsyncFd, Socket, SocketExt}; +use holo_utils::southbound::InterfaceFlags; +use holo_utils::task::{IntervalTask, Task, TimeoutTask}; +use holo_utils::UnboundedSender; +use ipnetwork::{Ipv4Network, Ipv6Network}; +use tokio::sync::mpsc; + +use crate::adjacency::{Adjacency, AdjacencyEvent, AdjacencyState}; +use crate::collections::{Adjacencies, Arena, InterfaceId}; +use crate::debug::{Debug, InterfaceInactiveReason}; +use crate::error::{Error, IoError}; +use crate::instance::{Instance, InstanceUpView}; +use crate::network::{MulticastAddr, LLC_HDR}; +use crate::northbound::configuration::InterfaceCfg; +use crate::northbound::notification; +use crate::packet::consts::PduType; +use crate::packet::pdu::{Hello, HelloTlvs, HelloVariant, Lsp, Pdu}; +use crate::packet::tlv::{LspEntry, Nlpid}; +use crate::packet::{LanId, LevelNumber, LevelType, Levels, LspId}; +use crate::tasks::messages::output::NetTxPduMsg; +use crate::{network, tasks}; + +#[derive(Debug)] +pub struct Interface { + pub id: InterfaceId, + pub name: String, + pub system: InterfaceSys, + pub config: InterfaceCfg, + pub state: InterfaceState, +} + +#[derive(Debug, Default)] +pub struct InterfaceSys { + // Interface flags. + pub flags: InterfaceFlags, + // Interface ifindex. + pub ifindex: Option, + // Interface MTU. + pub mtu: Option, + // Interface MAC address. + pub mac_addr: Option<[u8; 6]>, + // List of IPv4 addresses associated with this interface. + pub ipv4_addr_list: BTreeSet, + // List of IPv4 addresses associated with this interface. + pub ipv6_addr_list: BTreeSet, +} + +#[derive(Debug, Default)] +pub struct InterfaceState { + // Interface status. + pub active: bool, + // Raw socket and Tx/Rx tasks. + pub net: Option, + // Circuit ID. + pub circuit_id: u8, + // Adjacencies attached to this interface. + pub lan_adjacencies: Levels, + pub p2p_adjacency: Option, + // Designated IS for the LAN. + pub dis: Levels>, + // Lists of LSPs used by the IS-IS flooding algorithm. + pub srm_list: Levels>, + pub ssn_list: Levels>, + // Statistics. + pub event_counters: InterfaceEventCounters, + pub packet_counters: Levels, + pub discontinuity_time: DateTime, + // Tasks. + pub tasks: InterfaceTasks, +} + +#[derive(Debug)] +pub struct InterfaceNet { + // Raw socket. + pub socket: Arc>, + // Network Tx/Rx tasks. + _net_tx_task: Task<()>, + _net_rx_task: Task<()>, + // Network Tx output channel. + pub net_tx_pdup: UnboundedSender, +} + +#[derive(Debug, Default)] +pub struct InterfaceTasks { + // Hello Tx interval tasks. + pub hello_interval_p2p: Option, + pub hello_interval_broadcast: Levels>, + // DIS initial election. + pub dis_initial_election: Levels>, + // PSNP interval. + pub psnp_interval: Levels>, + // CSNP interval. + pub csnp_interval: Levels>, +} + +#[derive(Debug, Default)] +pub struct InterfaceEventCounters { + pub adjacency_changes: u32, + pub adjacency_number: u32, + pub init_fails: u32, + pub adjacency_rejects: u32, + pub version_skew: u32, + pub id_len_mismatch: u32, + pub max_area_addr_mismatch: u32, + pub area_mismatch: u32, + pub auth_type_fails: u32, + pub auth_fails: u32, + pub lan_dis_changes: u32, +} + +#[derive(Debug, Default)] +pub struct InterfacePacketCounters { + pub iih_in: u32, + pub iih_out: Arc, + pub lsp_in: u32, + pub lsp_out: u32, + pub psnp_in: u32, + pub psnp_out: u32, + pub csnp_in: u32, + pub csnp_out: u32, + pub unknown_in: u32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum InterfaceType { + Broadcast, + PointToPoint, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DisCandidate { + pub priority: u8, + pub snpa: [u8; 6], + pub lan_id: LanId, + pub myself: bool, +} + +#[derive(Debug)] +pub struct CircuitIdAllocator { + allocated_ids: BTreeSet, + next_id: u8, +} + +// ===== impl Interface ===== + +impl Interface { + pub(crate) fn new(id: InterfaceId, name: String) -> Interface { + Debug::InterfaceCreate(&name).log(); + + Interface { + id, + name, + system: InterfaceSys::default(), + config: InterfaceCfg::default(), + state: InterfaceState::default(), + } + } + + // Checks if the interface needs to be started or stopped in response to a + // northbound or southbound event. + pub(crate) fn update( + &mut self, + instance: &mut InstanceUpView<'_>, + adjacencies: &mut Arena, + ) -> Result<(), Error> { + match self.is_ready() { + Ok(()) if !self.state.active => { + self.start(instance).map_err(|error| { + Error::InterfaceStartError( + self.name.clone(), + Box::new(error), + ) + })? + } + Err(reason) if self.state.active => { + self.stop(instance, adjacencies, reason) + } + _ => (), + } + + Ok(()) + } + + fn start( + &mut self, + instance: &mut InstanceUpView<'_>, + ) -> Result<(), Error> { + Debug::InterfaceStart(&self.name).log(); + notification::if_state_change(instance, self, true); + + if !self.is_passive() { + if self.config.interface_type == InterfaceType::Broadcast { + // Allocate Circuit ID. + // + // For interfaces operating in the point-to-point mode, there's + // no need to allocate a Circuit ID since it's used purely for + // informational purposes. + self.state.circuit_id = + instance.state.circuit_id_allocator.allocate()?; + + // Schedule initial DIS election. + self.dis_initial_election_start(instance); + } + + // Start network Tx/Rx tasks. + let net = + InterfaceNet::new(self, instance.tx).map_err(Error::IoError)?; + self.state.net = Some(net); + + // Start Hello Tx task(s). + self.hello_interval_start(instance, LevelType::All); + + // Start PSNP interval task(s). + self.psnp_interval_start(instance); + } + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(self.config.level_type.resolved); + + self.state.active = true; + + Ok(()) + } + + // Stop interface if it's active. + pub(crate) fn stop( + &mut self, + instance: &mut InstanceUpView<'_>, + arena_adjacencies: &mut Arena, + reason: InterfaceInactiveReason, + ) { + Debug::InterfaceStop(&self.name, reason).log(); + notification::if_state_change(instance, self, false); + + // Resign from being the DIS. + if self.config.interface_type == InterfaceType::Broadcast { + for level in self.config.levels() { + if !self.is_dis(level) { + continue; + } + self.dis_stop(instance); + *self.state.dis.get_mut(level) = None; + } + } + + // Remove all adjacencies. + let event = if reason == InterfaceInactiveReason::OperationalDown { + AdjacencyEvent::LinkDown + } else { + AdjacencyEvent::Kill + }; + let new_state = AdjacencyState::Down; + match self.config.interface_type { + InterfaceType::Broadcast => { + let mut adjacencies = + std::mem::take(&mut self.state.lan_adjacencies); + for level in self.config.levels() { + let adjacencies = adjacencies.get_mut(level); + for adj_idx in adjacencies.indexes() { + let adj = &mut arena_adjacencies[adj_idx]; + adj.state_change(self, instance, event, new_state); + } + adjacencies.clear(arena_adjacencies); + } + self.state.lan_adjacencies = adjacencies; + } + InterfaceType::PointToPoint => { + if let Some(mut adj) = self.state.p2p_adjacency.take() { + adj.state_change(self, instance, event, new_state); + } + } + } + + // Release Circuit ID back to the pool. + if self.config.interface_type == InterfaceType::Broadcast { + instance + .state + .circuit_id_allocator + .release(self.state.circuit_id); + self.state.circuit_id = 0; + } + + // Reset interface state. + self.state.active = false; + self.state.net = None; + self.state.srm_list = Default::default(); + self.state.ssn_list = Default::default(); + self.hello_interval_stop(); + self.dis_initial_election_stop(); + self.psnp_interval_stop(); + self.csnp_interval_stop(); + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(self.config.level_type.resolved); + } + + pub(crate) fn reset( + &mut self, + instance: &mut InstanceUpView<'_>, + adjacencies: &mut Arena, + ) -> Result<(), Error> { + if self.state.active { + self.stop( + instance, + adjacencies, + InterfaceInactiveReason::Resetting, + ); + self.start(instance)?; + } + + Ok(()) + } + + pub(crate) const fn is_passive(&self) -> bool { + self.system.flags.contains(InterfaceFlags::LOOPBACK) + || self.config.passive + } + + fn is_ready(&self) -> Result<(), InterfaceInactiveReason> { + if !self.system.flags.contains(InterfaceFlags::OPERATIVE) { + return Err(InterfaceInactiveReason::OperationalDown); + } + + if self.system.ifindex.is_none() { + return Err(InterfaceInactiveReason::MissingIfindex); + } + + if self.system.mtu.is_none() { + return Err(InterfaceInactiveReason::MissingMtu); + } + + if self.system.flags.contains(InterfaceFlags::BROADCAST) + && self.system.mac_addr.is_none() + { + return Err(InterfaceInactiveReason::MissingMacAddr); + } + + if self.config.interface_type == InterfaceType::Broadcast + && !self.system.flags.contains(InterfaceFlags::BROADCAST) + && !self.system.flags.contains(InterfaceFlags::LOOPBACK) + { + return Err(InterfaceInactiveReason::BroadcastUnsupported); + } + + Ok(()) + } + + pub(crate) fn iso_mtu(&self) -> u32 { + let l2_mtu = self.system.mtu.unwrap(); + match self.config.interface_type { + InterfaceType::Broadcast => l2_mtu - LLC_HDR.len() as u32, + InterfaceType::PointToPoint => l2_mtu, + } + } + + pub(crate) fn dis_election( + &mut self, + instance: &InstanceUpView<'_>, + adjacencies: &Arena, + level: LevelNumber, + ) -> Option { + // Filter adjacencies that are eligible for DIS election (only those + // in the "Up" state). + let mut adjs = self + .state + .lan_adjacencies + .get(level) + .iter(adjacencies) + .filter(|adj| adj.state == AdjacencyState::Up) + .map(|adj| DisCandidate { + priority: adj.priority.unwrap(), + snpa: adj.snpa, + lan_id: adj.lan_id.unwrap(), + myself: false, + }) + .peekable(); + + // No DIS should be elected when there are no eligible adjacencies. + adjs.peek()?; + + // Add ourselves as a DIS candidate. + let system_id = instance.config.system_id.unwrap(); + let myself = DisCandidate { + priority: self.config.priority.get(level), + snpa: self.system.mac_addr.unwrap(), + lan_id: LanId::from((system_id, self.state.circuit_id)), + myself: true, + }; + + // Elect the DIS by comparing priorities, using SNPA as a tie-breaker. + std::iter::once(myself) + .chain(adjs) + .max_by_key(|rtr| (rtr.priority, rtr.snpa)) + } + + pub(crate) fn is_dis(&self, level: LevelNumber) -> bool { + self.state.dis.get(level).map_or(false, |dis| dis.myself) + } + + pub(crate) fn dis_start(&mut self, instance: &mut InstanceUpView<'_>) { + self.csnp_interval_start(instance); + } + + pub(crate) fn dis_stop(&mut self, _instance: &mut InstanceUpView<'_>) { + self.csnp_interval_stop(); + } + + fn generate_hello( + &self, + level: impl Into, + instance: &InstanceUpView<'_>, + ) -> Pdu { + let level = level.into(); + + // Fixed fields. + let circuit_type = self.config.level_type.resolved; + let source = instance.config.system_id.unwrap(); + let holdtime = self.config.hello_holdtime(level); + let variant = match self.config.interface_type { + InterfaceType::Broadcast => HelloVariant::Lan { + priority: self.config.priority.get(level), + lan_id: self + .state + .dis + .get(level) + .map(|dis| dis.lan_id) + // Use the current DIS, or default to ourselves if none is + // elected (see IS-IS 8.4.1.a). + .unwrap_or(LanId::from((source, self.state.circuit_id))), + }, + InterfaceType::PointToPoint => HelloVariant::P2P { + local_circuit_id: self.state.circuit_id, + }, + }; + + // Set area addresses. + let area_addrs = instance.config.area_addrs.clone(); + + // Set LAN neighbors. + let mut neighbors = vec![]; + if self.config.interface_type == InterfaceType::Broadcast { + neighbors.extend(self.state.lan_adjacencies.get(level).snpas()); + } + + // Set IP information. + let mut protocols_supported = vec![]; + let mut ipv4_addrs = vec![]; + if self + .config + .is_af_enabled(AddressFamily::Ipv4, instance.config) + { + protocols_supported.push(Nlpid::Ipv4 as u8); + ipv4_addrs.extend( + self.system.ipv4_addr_list.iter().map(|addr| addr.ip()), + ); + } + + Pdu::Hello(Hello::new( + level, + circuit_type, + source, + holdtime, + variant, + HelloTlvs::new( + protocols_supported, + area_addrs, + neighbors, + ipv4_addrs, + ), + )) + } + + pub(crate) fn hello_interval_start( + &mut self, + instance: &InstanceUpView<'_>, + level_filter: impl Into, + ) { + let level_filter = level_filter.into(); + match self.config.interface_type { + // For broadcast interfaces, send separate Hello PDUs for each level + // (L1 and/or L2), depending on the configuration. + InterfaceType::Broadcast => { + for level in self + .config + .levels() + .into_iter() + .filter(|level| level_filter.intersects(level)) + { + let pdu = self.generate_hello(level, instance); + let task = tasks::hello_interval(self, level, pdu); + *self.state.tasks.hello_interval_broadcast.get_mut(level) = + Some(task); + } + } + // For point-to-point interfaces, send a single Hello PDU, + // regardless of whether IS-IS is enabled for L1, L2, or both. + InterfaceType::PointToPoint => { + let level = LevelType::All; + let pdu = self.generate_hello(level, instance); + let task = tasks::hello_interval(self, level, pdu); + self.state.tasks.hello_interval_p2p = Some(task); + } + } + } + + fn hello_interval_stop(&mut self) { + self.state.tasks.hello_interval_broadcast = Default::default(); + self.state.tasks.hello_interval_p2p = Default::default(); + } + + fn dis_initial_election_start(&mut self, instance: &InstanceUpView<'_>) { + for level in self.config.levels() { + let task = tasks::dis_initial_election(self, level, instance); + *self.state.tasks.dis_initial_election.get_mut(level) = Some(task); + } + } + + fn dis_initial_election_stop(&mut self) { + self.state.tasks.dis_initial_election = Default::default(); + } + + fn psnp_interval_start(&mut self, instance: &InstanceUpView<'_>) { + for level in self.config.levels() { + let task = tasks::psnp_interval(self, level, instance); + *self.state.tasks.psnp_interval.get_mut(level) = Some(task); + } + } + + fn psnp_interval_stop(&mut self) { + self.state.tasks.psnp_interval = Default::default(); + } + + pub(crate) fn csnp_interval_start( + &mut self, + instance: &InstanceUpView<'_>, + ) { + for level in self.config.levels() { + let task = tasks::csnp_interval(self, level, instance); + *self.state.tasks.csnp_interval.get_mut(level) = Some(task); + } + } + + pub(crate) fn csnp_interval_reset( + &mut self, + instance: &InstanceUpView<'_>, + ) { + for level in self.config.levels().into_iter() { + if self.state.tasks.csnp_interval.get(level).is_none() { + continue; + } + let task = tasks::csnp_interval(self, level, instance); + *self.state.tasks.csnp_interval.get_mut(level) = Some(task); + } + } + + pub(crate) fn csnp_interval_stop(&mut self) { + self.state.tasks.csnp_interval = Default::default(); + } + + pub(crate) fn srm_list_add(&mut self, level: LevelNumber, mut lsp: Lsp) { + // Don't add LSPs to the SRM list if there are no active adjacencies + // or if the LSP has a sequence number of zero. + if !self.state.active + || self.state.event_counters.adjacency_number == 0 + || lsp.seqno == 0 + { + return; + } + + // ISO 10589 - Section 7.3.16.3: + // "A system shall decrement the Remaining Lifetime in the PDU being + // transmitted by at least one". + lsp.set_rem_lifetime(lsp.rem_lifetime().saturating_sub(1)); + + // On point-to-point interfaces, all LSPs must be acknowledged. If they + // are not, retransmissions will be triggered until an acknowledgment + // is received. + if self.config.interface_type == InterfaceType::PointToPoint { + let rxmt_interval = self.config.lsp_rxmt_interval; + let dst = self.config.interface_type.multicast_addr(level); + let task = + tasks::lsp_rxmt_interval(self, lsp.clone(), dst, rxmt_interval); + self.state.srm_list.get_mut(level).insert(lsp.lsp_id, task); + } + + // Enqueue LSP for transmission. + // TODO: Implement LSP pacing. + self.enqueue_pdu(Pdu::Lsp(lsp), level); + } + + pub(crate) fn srm_list_del(&mut self, level: LevelNumber, lsp_id: &LspId) { + if self.config.interface_type == InterfaceType::PointToPoint { + self.state.srm_list.get_mut(level).remove(lsp_id); + } + } + + pub(crate) fn ssn_list_add(&mut self, level: LevelNumber, entry: LspEntry) { + self.state + .ssn_list + .get_mut(level) + .insert(entry.lsp_id, entry); + } + + pub(crate) fn ssn_list_del(&mut self, level: LevelNumber, lsp_id: &LspId) { + self.state.ssn_list.get_mut(level).remove(lsp_id); + } + + pub(crate) fn enqueue_pdu(&mut self, pdu: Pdu, level: LevelNumber) { + // Update packet counters. + match pdu.pdu_type() { + PduType::HelloP2P | PduType::HelloLanL1 | PduType::HelloLanL2 => { + // Updated separately on the hello_interval task. + } + PduType::LspL1 => { + self.state.packet_counters.l1.lsp_out += 1; + } + PduType::LspL2 => { + self.state.packet_counters.l2.lsp_out += 1; + } + PduType::CsnpL1 => { + self.state.packet_counters.l1.csnp_out += 1; + } + PduType::CsnpL2 => { + self.state.packet_counters.l2.csnp_out += 1; + } + PduType::PsnpL1 => { + self.state.packet_counters.l1.psnp_out += 1; + } + PduType::PsnpL2 => { + self.state.packet_counters.l2.psnp_out += 1; + } + } + self.state.discontinuity_time = Utc::now(); + + // Enqueue PDU for transmission. + let ifindex = self.system.ifindex.unwrap(); + let dst = self.config.interface_type.multicast_addr(level); + let msg = NetTxPduMsg { pdu, ifindex, dst }; + let _ = self.state.net.as_ref().unwrap().net_tx_pdup.send(msg); + } + + pub(crate) fn query_southbound(&self, ibus_tx: &IbusSender) { + let _ = ibus_tx.send(IbusMsg::InterfaceQuery { + ifname: self.name.clone(), + af: None, + }); + } +} + +impl Drop for Interface { + fn drop(&mut self) { + Debug::InterfaceDelete(&self.name).log(); + } +} + +// ===== impl InterfaceSys ===== + +impl InterfaceSys { + fn join_multicast(&self, socket: &Socket, addr: MulticastAddr) { + #[cfg(not(feature = "testing"))] + { + let ifindex = self.ifindex.unwrap(); + if let Err(error) = + socket.join_packet_multicast(addr.as_bytes(), ifindex) + { + IoError::MulticastJoinError(addr, error).log(); + } + } + } + + #[expect(unused)] + fn leave_multicast(&self, socket: &Socket, addr: MulticastAddr) { + #[cfg(not(feature = "testing"))] + { + let ifindex = self.ifindex.unwrap(); + if let Err(error) = + socket.leave_packet_multicast(addr.as_bytes(), ifindex) + { + IoError::MulticastJoinError(addr, error).log(); + } + } + } +} + +// ===== impl InterfaceNet ===== + +impl InterfaceNet { + fn new( + iface: &Interface, + instance_channels_tx: &InstanceChannelsTx, + ) -> Result { + let broadcast = iface.system.flags.contains(InterfaceFlags::BROADCAST); + + // Create raw socket. + let socket = network::socket(iface.system.ifindex.unwrap()) + .map_err(IoError::SocketError)?; + + // Join IS-IS multicast groups. + if broadcast { + iface.system.join_multicast(&socket, MulticastAddr::AllIss); + iface + .system + .join_multicast(&socket, MulticastAddr::AllL1Iss); + iface + .system + .join_multicast(&socket, MulticastAddr::AllL2Iss); + } + + // Start network Tx/Rx tasks. + let socket = AsyncFd::new(socket).map_err(IoError::SocketError)?; + let socket = Arc::new(socket); + let (net_tx_pdup, net_tx_pduc) = mpsc::unbounded_channel(); + let mut net_tx_task = tasks::net_tx( + socket.clone(), + broadcast, + net_tx_pduc, + #[cfg(feature = "testing")] + &instance_channels_tx.protocol_output, + ); + let net_rx_task = tasks::net_rx( + socket.clone(), + broadcast, + iface, + &instance_channels_tx.protocol_input.net_pdu_rx, + ); + net_tx_task.detach(); + + Ok(InterfaceNet { + socket, + _net_tx_task: net_tx_task, + _net_rx_task: net_rx_task, + net_tx_pdup, + }) + } +} + +// ===== impl InterfaceType ===== + +impl InterfaceType { + // Returns the appropriate multicast address based on the interface type + // and IS-IS level. + pub(crate) fn multicast_addr( + &self, + level_type: impl Into, + ) -> MulticastAddr { + match self { + InterfaceType::Broadcast => match level_type.into() { + LevelNumber::L1 => MulticastAddr::AllL1Iss, + LevelNumber::L2 => MulticastAddr::AllL2Iss, + }, + InterfaceType::PointToPoint => MulticastAddr::AllIss, + } + } +} + +// ===== impl CircuitIdAllocator ===== + +impl CircuitIdAllocator { + // Allocates a new circuit ID from 1 to 255. If all IDs are allocated, + // returns an error. + fn allocate(&mut self) -> Result { + if self.allocated_ids.len() == 255 { + return Err(Error::CircuitIdAllocationFailed); + } + + // Try to allocate the current next_id + let mut id = self.next_id; + + // Find the next available ID + while self.allocated_ids.contains(&id) { + id = if id == 255 { 1 } else { id + 1 }; + } + + // Store the allocated ID and update next_id + self.allocated_ids.insert(id); + self.next_id = if id == 255 { 1 } else { id + 1 }; + + Ok(id) + } + + // Releases a circuit ID back to the pool. + fn release(&mut self, id: u8) { + self.allocated_ids.remove(&id); + } +} + +impl Default for CircuitIdAllocator { + fn default() -> Self { + CircuitIdAllocator { + allocated_ids: BTreeSet::new(), + next_id: 1, + } + } +} diff --git a/holo-isis/src/lib.rs b/holo-isis/src/lib.rs new file mode 100644 index 00000000..c87e7786 --- /dev/null +++ b/holo-isis/src/lib.rs @@ -0,0 +1,28 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +#![cfg_attr( + feature = "testing", + allow(dead_code, unused_variables, unused_imports) +)] +#![feature(let_chains)] + +pub mod adjacency; +pub mod collections; +pub mod debug; +pub mod error; +pub mod events; +pub mod instance; +pub mod interface; +pub mod lsdb; +pub mod network; +pub mod northbound; +pub mod packet; +pub mod southbound; +pub mod tasks; diff --git a/holo-isis/src/lsdb.rs b/holo-isis/src/lsdb.rs new file mode 100644 index 00000000..38d0cd03 --- /dev/null +++ b/holo-isis/src/lsdb.rs @@ -0,0 +1,526 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::cmp::Ordering; +use std::collections::HashSet; +use std::time::Instant; + +use bitflags::bitflags; +use derive_new::new; +use holo_utils::ip::{AddressFamily, Ipv4NetworkExt}; +use holo_utils::task::TimeoutTask; +use holo_utils::UnboundedSender; + +use crate::adjacency::AdjacencyState; +use crate::collections::{Arena, LspEntryId}; +use crate::debug::{Debug, LspPurgeReason}; +use crate::instance::{InstanceArenas, InstanceUpView}; +use crate::interface::{Interface, InterfaceType}; +use crate::northbound::notification; +use crate::packet::consts::LspFlags; +use crate::packet::pdu::{Lsp, LspTlvs}; +use crate::packet::tlv::{ExtIpv4Reach, ExtIsReach, Ipv4Reach, IsReach, Nlpid}; +use crate::packet::{LanId, LevelNumber, LspId}; +use crate::tasks; +use crate::tasks::messages::input::LspPurgeMsg; + +// LSP ZeroAge lifetime. +pub const LSP_ZERO_AGE_LIFETIME: u64 = 60; +// Minimum time interval between generation of LSPs. +pub const LSP_MIN_GEN_INTERVAL: u64 = 5; +// LSP initial sequence number. +const LSP_INIT_SEQNO: u32 = 0x00000001; +// Maximum size of the LSP log record. +const LSP_LOG_MAX_SIZE: usize = 64; + +// LSP database entry. +#[derive(Debug)] +pub struct LspEntry { + // LSP entry ID. + pub id: LspEntryId, + // LSP data. + pub data: Lsp, + // Timer triggered when the LSP's remaining lifetime reaches zero. + pub expiry_timer: Option, + // Timer triggered when the LSP's ZeroAge timeout expires. + pub delete_timer: Option, + // Timer for the periodic LSP refresh interval. + pub refresh_timer: Option, + // LSP entry flags. + pub flags: LspEntryFlags, +} + +bitflags! { + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct LspEntryFlags: u8 { + const RECEIVED = 0x01; + const PURGED = 0x02; + } +} + +#[derive(Debug)] +#[derive(new)] +pub struct LspLogEntry { + pub id: u32, + pub level: LevelNumber, + pub lsp: LspLogId, + pub rcvd_time: Option, + pub reason: LspLogReason, +} + +#[derive(Clone, Debug)] +#[derive(new)] +pub struct LspLogId { + pub lsp_id: LspId, + pub seqno: u32, +} + +#[derive(Debug)] +pub enum LspLogReason { + Refresh, + ContentChange, +} + +// ===== impl LspEntry ===== + +impl LspEntry { + // Creates new LSP database entry. + pub(crate) fn new( + level: LevelNumber, + id: LspEntryId, + data: Lsp, + lsp_purgep: &UnboundedSender, + ) -> LspEntry { + let expiry_timer = (data.rem_lifetime != 0) + .then_some(tasks::lsp_expiry_timer(level, id, &data, lsp_purgep)); + + LspEntry { + id, + data, + expiry_timer, + delete_timer: None, + refresh_timer: None, + flags: Default::default(), + } + } +} + +// ===== helper functions ===== + +fn lsp_build( + instance: &mut InstanceUpView<'_>, + arenas: &InstanceArenas, + level: LevelNumber, +) -> Vec { + let mut lsps = vec![]; + + // LSP flags shared by all fragments. + let lsp_flags = lsp_build_flags(instance); + + // Build main LSP. + let tlvs = lsp_build_tlvs(instance, arenas, level); + let fragments = + lsp_build_fragments(instance, arenas, level, lsp_flags, 0, tlvs); + lsps.extend(fragments); + + // Build pseudonode LSPs. + for iface in arenas + .interfaces + .iter() + .filter(|iface| iface.state.active) + .filter(|iface| iface.config.interface_type == InterfaceType::Broadcast) + .filter(|iface| iface.is_dis(level)) + { + let circuit_id = iface.state.circuit_id; + let tlvs = lsp_build_tlvs_pseudo(instance, arenas, level, iface); + let fragments = lsp_build_fragments( + instance, arenas, level, lsp_flags, circuit_id, tlvs, + ); + lsps.extend(fragments); + } + + lsps +} + +fn lsp_build_flags(instance: &mut InstanceUpView<'_>) -> LspFlags { + let mut lsp_flags = LspFlags::default(); + if instance.config.level_type.intersects(LevelNumber::L1) { + lsp_flags.insert(LspFlags::IS_TYPE1); + } + if instance.config.level_type.intersects(LevelNumber::L2) { + lsp_flags.insert(LspFlags::IS_TYPE2); + } + lsp_flags +} + +fn lsp_build_tlvs( + instance: &mut InstanceUpView<'_>, + arenas: &InstanceArenas, + level: LevelNumber, +) -> LspTlvs { + let metric_type = instance.config.metric_type.get(level); + let mut protocols_supported = vec![]; + let mut is_reach = vec![]; + let mut ext_is_reach = vec![]; + let mut ipv4_addrs = vec![]; + let mut ipv4_internal_reach = vec![]; + let mut ext_ipv4_reach = vec![]; + + // Add supported protocols. + if instance.config.is_af_enabled(AddressFamily::Ipv4) { + protocols_supported.push(Nlpid::Ipv4 as u8); + } + + // Iterate over all active interfaces. + for iface in arenas.interfaces.iter().filter(|iface| iface.state.active) { + let metric = iface.config.metric.get(level); + + // Add IS reachability information. + for adj in iface + .state + .lan_adjacencies + .get(level) + .iter(&arenas.adjacencies) + .chain( + iface + .state + .p2p_adjacency + .as_ref() + .filter(|adj| adj.level_usage.intersects(level)), + ) + .filter(|adj| adj.state == AdjacencyState::Up) + { + let neighbor = match iface.config.interface_type { + InterfaceType::Broadcast => { + let Some(dis) = iface.state.dis.get(level) else { + continue; + }; + dis.lan_id + } + InterfaceType::PointToPoint => LanId::from((adj.system_id, 0)), + }; + if metric_type.is_standard_enabled() { + is_reach.push(IsReach { + metric: metric as u8, + metric_delay: None, + metric_expense: None, + metric_error: None, + neighbor, + }); + } + if metric_type.is_wide_enabled() { + ext_is_reach.push(ExtIsReach { + neighbor, + metric, + sub_tlvs: Default::default(), + }); + } + } + + // Add IPv4 information. + if instance.config.is_af_enabled(AddressFamily::Ipv4) { + for addr in iface.system.ipv4_addr_list.iter() { + ipv4_addrs.push(addr.ip()); + if metric_type.is_standard_enabled() { + ipv4_internal_reach.push(Ipv4Reach { + ie_bit: false, + metric: metric as u8, + metric_delay: None, + metric_expense: None, + metric_error: None, + prefix: addr.apply_mask(), + }); + } + if metric_type.is_wide_enabled() { + ext_ipv4_reach.push(ExtIpv4Reach { + metric, + up_down: false, + prefix: addr.apply_mask(), + sub_tlvs: Default::default(), + }); + } + } + } + } + LspTlvs::new( + protocols_supported, + instance.config.area_addrs.clone(), + is_reach, + ext_is_reach, + ipv4_addrs, + ipv4_internal_reach, + [], + ext_ipv4_reach, + ) +} + +fn lsp_build_tlvs_pseudo( + instance: &mut InstanceUpView<'_>, + arenas: &InstanceArenas, + level: LevelNumber, + iface: &Interface, +) -> LspTlvs { + let system_id = instance.config.system_id.unwrap(); + let metric_type = instance.config.metric_type.get(level); + let mut is_reach = vec![]; + let mut ext_is_reach = vec![]; + + // Add IS reachability information. + for neighbor in iface + .state + .lan_adjacencies + .get(level) + .iter(&arenas.adjacencies) + // Add adjacencies in the Up state. + .filter(|adj| adj.state == AdjacencyState::Up) + .map(|adj| LanId::from((adj.system_id, 0))) + // Add ourselves. + .chain(std::iter::once(LanId::from((system_id, 0)))) + { + if metric_type.is_standard_enabled() { + is_reach.push(IsReach { + metric: 0, + metric_delay: None, + metric_expense: None, + metric_error: None, + neighbor, + }); + } + if metric_type.is_wide_enabled() { + ext_is_reach.push(ExtIsReach { + neighbor, + metric: 0, + sub_tlvs: Default::default(), + }); + } + } + + LspTlvs::new([], [], is_reach, ext_is_reach, [], [], [], []) +} + +fn lsp_build_fragments( + instance: &mut InstanceUpView<'_>, + arenas: &InstanceArenas, + level: LevelNumber, + lsp_flags: LspFlags, + pseudonode_id: u8, + mut tlvs: LspTlvs, +) -> Vec { + let system_id = instance.config.system_id.unwrap(); + let mut fragments = vec![]; + for frag_id in 0..=255 { + let Some(tlvs) = tlvs.next_chunk( + instance.config.lsp_mtu as usize - Lsp::HEADER_LEN as usize, + ) else { + break; + }; + + let lsp_id = LspId::from((system_id, pseudonode_id, frag_id)); + let seqno = instance + .state + .lsdb + .get(level) + .get_by_lspid(&arenas.lsp_entries, &lsp_id) + .map(|(_, lse)| lse.data.seqno + 1) + .unwrap_or(LSP_INIT_SEQNO); + let fragment = Lsp::new( + level, + instance.config.lsp_lifetime, + lsp_id, + seqno, + lsp_flags, + tlvs, + ); + fragments.push(fragment); + } + fragments +} + +// Adds log entry for the newly installed LSP. +fn log_lsp( + instance: &mut InstanceUpView<'_>, + level: LevelNumber, + lsp: LspLogId, + rcvd_time: Option, + reason: LspLogReason, +) { + // Get next log ID. + let log_id = &mut instance.state.lsp_log_next_id; + *log_id += 1; + + // Add new log entry. + let log_entry = LspLogEntry::new(*log_id, level, lsp, rcvd_time, reason); + instance.state.lsp_log.push_front(log_entry); + + // Remove old entries if necessary. + instance.state.lsp_log.truncate(LSP_LOG_MAX_SIZE); +} + +// ===== global functions ===== + +// Compares which LSP is more recent. +pub(crate) fn lsp_compare( + lsp_db: &Lsp, + lsp_rx_seqno: u32, + lsp_rx_rem_lifetime: u16, +) -> Ordering { + let cmp = lsp_db.seqno.cmp(&lsp_rx_seqno); + if cmp != Ordering::Equal { + return cmp; + } + + // ISO 10589 - Section 7.3.16.3: + // If the sequence numbers are the same, prefer the LSP in the database if + // it has expired (Remaining Lifetime = 0) and the received LSP has a + // non-zero Remaining Lifetime. + if lsp_db.rem_lifetime == 0 && lsp_rx_rem_lifetime != 0 { + return Ordering::Greater; + } + + // ISO 10589 - Section 7.3.16.4.b.1: + // If the sequence numbers are the same, prefer the received LSP if it has + // expired (Remaining Lifetime = 0) and the LSP in the database has a + // non-zero Remaining Lifetime. + if lsp_db.rem_lifetime != 0 && lsp_rx_rem_lifetime == 0 { + return Ordering::Less; + } + + Ordering::Equal +} + +// Installs the provided LSP to the LSDB. +pub(crate) fn install<'a>( + instance: &mut InstanceUpView<'_>, + lsp_entries: &'a mut Arena, + level: LevelNumber, + lsp: Lsp, +) -> &'a mut LspEntry { + Debug::LspInstall(level, &lsp).log(); + + // Remove old instance of the LSP. + let lsdb = instance.state.lsdb.get_mut(level); + let mut old_lsp = None; + if let Some((lse_idx, _)) = lsdb.get_by_lspid(lsp_entries, &lsp.lsp_id) { + let old_lse = lsdb.delete(lsp_entries, lse_idx); + old_lsp = Some(old_lse.data); + } + + // Check if the LSP content has changed. + let mut content_change = false; + if let Some(old_lsp) = old_lsp + && (old_lsp.flags != lsp.flags || old_lsp.tlvs != lsp.tlvs) + { + content_change = true; + } + + // Add LSP entry to LSDB. + let (_, lse) = instance.state.lsdb.get_mut(level).insert( + lsp_entries, + level, + lsp, + &instance.tx.protocol_input.lsp_purge, + ); + + // Add entry to LSP log. + let lsp = &lse.data; + let lsp_log_id = LspLogId::new(lsp.lsp_id, lsp.seqno); + let reason = if content_change { + LspLogReason::ContentChange + } else { + LspLogReason::Refresh + }; + log_lsp(instance, level, lsp_log_id.clone(), None, reason); + + lse +} + +pub(crate) fn lsp_originate_all( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + level: LevelNumber, +) { + let system_id = instance.config.system_id.unwrap(); + let before: HashSet<_> = instance + .state + .lsdb + .get(level) + .iter_for_system_id(&arenas.lsp_entries, system_id) + .map(|lse| lse.data.lsp_id) + .collect(); + let mut after = HashSet::new(); + + // Build updated local LSP. + for lsp in lsp_build(instance, arenas, level) { + after.insert(lsp.lsp_id); + + // Get the current instance of this LSP (if any) from the LSDB. + let old_lsp = instance + .state + .lsdb + .get(level) + .get_by_lspid(&arenas.lsp_entries, &lsp.lsp_id) + .map(|(_, lse)| &lse.data); + + // Skip origination if the LSP content hasn't changed. + if let Some(old_lsp) = old_lsp + && old_lsp.flags == lsp.flags + && old_lsp.tlvs == lsp.tlvs + { + continue; + } + + // Log LSP origination. + Debug::LspOriginate(level, &lsp).log(); + + // Send YANG notification. + notification::lsp_generation(instance, &lsp); + + // Originate new LSP version. + lsp_originate(instance, arenas, level, lsp); + } + + // Purge any LSP fragments that are no longer in use. + for (_, lse) in before.difference(&after).filter_map(|lsp_id| { + instance + .state + .lsdb + .get(level) + .get_by_lspid(&arenas.lsp_entries, lsp_id) + }) { + let reason = LspPurgeReason::Removed; + instance.tx.protocol_input.lsp_purge(level, lse.id, reason); + } + + // Update time of last LSP origination. + instance.state.lsp_orig_last = Some(Instant::now()); +} + +pub(crate) fn lsp_originate( + instance: &mut InstanceUpView<'_>, + arenas: &mut InstanceArenas, + level: LevelNumber, + lsp: Lsp, +) { + // Install LSP into the LSDB. + let lse = install(instance, &mut arenas.lsp_entries, level, lsp); + + // Flood LSP over all interfaces. + for iface in arenas.interfaces.iter_mut() { + iface.srm_list_add(level, lse.data.clone()); + } + + // Schedule LSP refreshing. + let refresh_timer = tasks::lsp_refresh_timer( + level, + lse.id, + instance.config.lsp_refresh, + &instance.tx.protocol_input.lsp_refresh, + ); + lse.refresh_timer = Some(refresh_timer); +} diff --git a/holo-isis/src/network.rs b/holo-isis/src/network.rs new file mode 100644 index 00000000..877aa3d7 --- /dev/null +++ b/holo-isis/src/network.rs @@ -0,0 +1,278 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::io::{IoSlice, IoSliceMut}; +use std::ops::Deref; +use std::os::fd::AsRawFd; +use std::sync::Arc; + +use bytes::Bytes; +use holo_utils::socket::{AsyncFd, Socket}; +use holo_utils::{capabilities, Sender, UnboundedReceiver}; +use nix::sys::socket; +use nix::sys::socket::{LinkAddr, SockaddrLike}; +use serde::Serialize; +use tokio::sync::mpsc::error::SendError; + +use crate::collections::InterfaceId; +use crate::debug::Debug; +use crate::error::IoError; +use crate::packet::pdu::Pdu; +use crate::tasks::messages::input::NetRxPduMsg; +use crate::tasks::messages::output::NetTxPduMsg; + +// Ethernet LLC header. +pub const LLC_HDR: [u8; 3] = [0xFE, 0xFE, 0x03]; + +// GRE protocol type for ISO. +pub const GRE_PROTO_TYPE_ISO: u16 = 0x00FE; + +// IS-IS ethernet multicast addresses. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Serialize)] +pub enum MulticastAddr { + AllIss, + AllL1Iss, + AllL2Iss, +} + +// BPF filter that accepts IS-IS over LLC and IS-IS over ethertype 0x00FE +// (e.g. GRE tunnels). Shamelessly copied from FRR! +const ISIS_BPF_FILTER: [libc::sock_filter; 10] = [ + // l0: ldh [0] + bpf_filter_block(0x28, 0, 0, 0x00000000), + // l1: jeq #0xfefe, l2, l4 + bpf_filter_block(0x15, 0, 2, 0x0000fefe), + // l2: ldb [3] + bpf_filter_block(0x30, 0, 0, 0x00000003), + // l3: jmp l7 + bpf_filter_block(0x05, 0, 0, 0x00000003), + // l4: ldh proto + bpf_filter_block(0x28, 0, 0, 0xfffff000), + // l5: jeq #0x00fe, l6, l9 + bpf_filter_block(0x15, 0, 3, 0x000000fe), + // l6: ldb [0] + bpf_filter_block(0x30, 0, 0, 0x00000000), + // l7: jeq #0x83, l8, l9 + bpf_filter_block(0x15, 0, 1, 0x00000083), + // l8: ret #0x40000 + bpf_filter_block(0x06, 0, 0, 0x00040000), + // l9: ret #0 + bpf_filter_block(0x06, 0, 0, 0x00000000), +]; + +// ===== impl MulticastAddr ===== + +impl MulticastAddr { + pub(crate) const fn as_bytes(&self) -> [u8; 6] { + match self { + MulticastAddr::AllIss => [0x09, 0x00, 0x2B, 0x00, 0x00, 0x05], + MulticastAddr::AllL1Iss => [0x01, 0x80, 0xC2, 0x00, 0x00, 0x14], + MulticastAddr::AllL2Iss => [0x01, 0x80, 0xC2, 0x00, 0x00, 0x15], + } + } +} + +// ===== global functions ===== + +pub(crate) fn socket(ifindex: u32) -> Result { + #[cfg(not(feature = "testing"))] + { + use socket2::{Domain, Protocol, Type}; + + // Create raw socket. + let socket = capabilities::raise(|| { + Socket::new( + Domain::PACKET, + Type::DGRAM, + Some(Protocol::from(libc::ETH_P_ALL)), + ) + })?; + socket.set_nonblocking(true)?; + + // Bind to local interface. + let sockaddr = link_addr(libc::ETH_P_ALL as u16, ifindex, None); + socket::bind(socket.as_raw_fd(), &sockaddr)?; + + // Attach BPF filter. + socket.attach_filter(&ISIS_BPF_FILTER)?; + + Ok(socket) + } + #[cfg(feature = "testing")] + { + Ok(Socket {}) + } +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn send_pdu( + socket: &AsyncFd, + broadcast: bool, + ifindex: u32, + dst: MulticastAddr, + pdu: &Pdu, +) -> Result { + Debug::PduTx(ifindex, dst, pdu).log(); + + // Encode PDU. + let buf = pdu.encode(); + + // Send PDU. + socket + .async_io(tokio::io::Interest::WRITABLE, |socket| { + if broadcast { + // Prepend LLC header before IS-IS PDU. + let iov = [IoSlice::new(&LLC_HDR), IoSlice::new(&buf)]; + let sockaddr = link_addr( + (LLC_HDR.len() + buf.len()) as u16, + ifindex, + Some(dst), + ); + socket::sendmsg( + socket.as_raw_fd(), + &iov, + &[], + socket::MsgFlags::empty(), + Some(&sockaddr), + ) + } else { + // For non-broadcast media types, only GRE is supported. + let sockaddr = + link_addr(GRE_PROTO_TYPE_ISO, ifindex, Some(dst)); + socket::sendto( + socket.as_raw_fd(), + &buf, + &sockaddr, + socket::MsgFlags::empty(), + ) + } + .map_err(|errno| errno.into()) + }) + .await + .map_err(IoError::SendError) +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn write_loop( + socket: Arc>, + broadcast: bool, + mut net_tx_packetc: UnboundedReceiver, +) { + while let Some(NetTxPduMsg { pdu, ifindex, dst }) = + net_tx_packetc.recv().await + { + if let Err(error) = + send_pdu(&socket, broadcast, ifindex, dst, &pdu).await + { + error.log(); + } + } +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn read_loop( + socket: Arc>, + broadcast: bool, + iface_id: InterfaceId, + net_packet_rxp: Sender, +) -> Result<(), SendError> { + let mut buf = [0; 16384]; + let mut iov = [IoSliceMut::new(&mut buf)]; + + loop { + // Receive data packet. + match socket + .async_io(tokio::io::Interest::READABLE, |socket| { + match socket::recvmsg::( + socket.as_raw_fd(), + &mut iov, + None, + socket::MsgFlags::empty(), + ) { + Ok(msg) => Ok((msg.address.unwrap(), msg.bytes)), + Err(errno) => Err(errno.into()), + } + }) + .await + { + Ok((src, bytes)) => { + // Filter out non-IS-IS packets by checking the LLC header in + // broadcast interfaces. + if broadcast && iov[0].deref()[0..3] != LLC_HDR { + continue; + } + // For non-broadcast media types, only GRE is supported. + if !broadcast && src.protocol() != GRE_PROTO_TYPE_ISO.to_be() { + continue; + } + + // Extract the source MAC address from the packet metadata. + let Some(src) = src.addr() else { + IoError::RecvMissingSourceAddr.log(); + continue; + }; + + // Decode packet. + let offset = if broadcast { LLC_HDR.len() } else { 0 }; + let bytes = + Bytes::copy_from_slice(&iov[0].deref()[offset..bytes]); + let pdu = Pdu::decode(bytes.clone()); + let msg = NetRxPduMsg { + iface_key: iface_id.into(), + src, + bytes, + pdu, + }; + net_packet_rxp.send(msg).await.unwrap(); + } + Err(error) if error.kind() == std::io::ErrorKind::Interrupted => { + // Retry if the syscall was interrupted (EINTR). + continue; + } + Err(error) => { + IoError::RecvError(error).log(); + } + } + } +} + +// ===== helper functions ===== + +const fn bpf_filter_block( + code: u16, + jt: u8, + jf: u8, + k: u32, +) -> libc::sock_filter { + libc::sock_filter { code, jt, jf, k } +} + +fn link_addr( + protocol: u16, + ifindex: u32, + addr: Option, +) -> LinkAddr { + let mut sll = libc::sockaddr_ll { + sll_family: libc::AF_PACKET as u16, + sll_protocol: protocol.to_be(), + sll_ifindex: ifindex as _, + sll_halen: 0, + sll_hatype: 0, + sll_pkttype: 0, + sll_addr: [0; 8], + }; + if let Some(addr) = addr { + sll.sll_halen = 6; + sll.sll_addr[..6].copy_from_slice(&addr.as_bytes()); + } + let ssl_len = std::mem::size_of_val(&sll) as libc::socklen_t; + unsafe { LinkAddr::from_raw(&sll as *const _ as *const _, Some(ssl_len)) } + .unwrap() +} diff --git a/holo-isis/src/northbound/configuration.rs b/holo-isis/src/northbound/configuration.rs new file mode 100644 index 00000000..cf5eb230 --- /dev/null +++ b/holo-isis/src/northbound/configuration.rs @@ -0,0 +1,1297 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::LazyLock as Lazy; + +use async_trait::async_trait; +use enum_as_inner::EnumAsInner; +use holo_northbound::configuration::{ + Callbacks, CallbacksBuilder, InheritableConfig, Provider, + ValidationCallbacks, ValidationCallbacksBuilder, +}; +use holo_northbound::yang::control_plane_protocol::isis; +use holo_utils::crypto::CryptoAlgo; +use holo_utils::ip::AddressFamily; +use holo_utils::yang::DataNodeRefExt; +use holo_yang::TryFromYang; +use smallvec::SmallVec; + +use crate::collections::InterfaceIndex; +use crate::debug::InterfaceInactiveReason; +use crate::instance::Instance; +use crate::interface::InterfaceType; +use crate::packet::{AreaAddr, LevelNumber, LevelType, SystemId}; +use crate::tasks::messages::input::DisElectionMsg; + +#[derive(Debug, Default)] +#[derive(EnumAsInner)] +pub enum ListEntry { + #[default] + None, + AddressFamily(AddressFamily), + Interface(InterfaceIndex), + InterfaceAddressFamily(InterfaceIndex, AddressFamily), +} + +#[derive(Debug)] +pub enum Resource {} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Event { + InstanceReset, + InstanceUpdate, + InterfaceUpdate(InterfaceIndex), + InstanceLevelTypeUpdate, + InterfaceDelete(InterfaceIndex), + InterfaceReset(InterfaceIndex), + InterfacePriorityChange(InterfaceIndex, LevelNumber), + InterfaceUpdateHelloInterval(InterfaceIndex, LevelNumber), + InterfaceUpdateCsnpInterval(InterfaceIndex), + InterfaceQuerySouthbound(InterfaceIndex), + ReoriginateLsps(LevelNumber), + RefreshLsps, + RerunSpf, +} + +pub static VALIDATION_CALLBACKS: Lazy = + Lazy::new(load_validation_callbacks); +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); + +// ===== configuration structs ===== + +#[derive(Debug)] +pub struct InstanceCfg { + pub enabled: bool, + pub level_type: LevelType, + pub system_id: Option, + pub area_addrs: BTreeSet, + pub lsp_mtu: u16, + pub lsp_lifetime: u16, + pub lsp_refresh: u16, + pub metric_type: LevelsCfg, + pub default_metric: LevelsCfg, + pub auth: LevelsOptCfg, + pub max_paths: u16, + pub afs: BTreeMap, + pub spf_initial_delay: u32, + pub spf_short_delay: u32, + pub spf_long_delay: u32, + pub spf_hold_down: u32, + pub spf_time_to_learn: u32, + pub preference: Preference, + pub overload_status: bool, +} + +#[derive(Debug)] +pub struct AddressFamilyCfg { + pub enabled: bool, +} + +#[derive(Debug)] +pub struct Preference { + pub internal: u8, + pub external: u8, +} + +#[derive(Clone, Copy, Debug)] +pub enum MetricType { + Standard, + Wide, + Both, +} + +#[derive(Debug)] +pub struct InterfaceCfg { + pub enabled: bool, + pub level_type: InheritableConfig, + pub lsp_pacing_interval: u32, + pub lsp_rxmt_interval: u16, + pub passive: bool, + pub csnp_interval: u16, + pub hello_padding: bool, + pub interface_type: InterfaceType, + pub hello_auth: LevelsOptCfg, + pub hello_interval: LevelsCfg, + pub hello_multiplier: LevelsCfg, + pub priority: LevelsCfg, + pub metric: LevelsCfg, + pub afs: BTreeSet, +} + +#[derive(Debug, Default)] +pub struct AuthCfg { + pub keychain: Option, + pub key: Option, + pub algo: Option, +} + +#[derive(Debug)] +pub struct LevelsCfg { + all: T, + l1: Option, + l2: Option, +} + +#[derive(Debug, Default)] +pub struct LevelsOptCfg { + pub all: T, + pub l1: T, + pub l2: T, +} + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() + .path(isis::enabled::PATH) + .modify_apply(|instance, args| { + let enabled = args.dnode.get_bool(); + instance.config.enabled = enabled; + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceUpdate); + }) + .path(isis::level_type::PATH) + .modify_apply(|instance, args| { + let level_type = args.dnode.get_string(); + let level_type = LevelType::try_from_yang(&level_type).unwrap(); + instance.config.level_type = level_type; + + let event_queue = args.event_queue; + // TODO: We can do better than a full reset. + event_queue.insert(Event::InstanceReset); + event_queue.insert(Event::InstanceLevelTypeUpdate); + }) + .path(isis::system_id::PATH) + .modify_apply(|instance, args| { + let system_id = args.dnode.get_string(); + let system_id = SystemId::try_from_yang(&system_id).unwrap(); + instance.config.system_id = Some(system_id); + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceReset); + event_queue.insert(Event::InstanceUpdate); + }) + .delete_apply(|instance, args| { + instance.config.system_id = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceUpdate); + }) + .path(isis::area_address::PATH) + .create_apply(|instance, args| { + let area_addr = args.dnode.get_string(); + let area_addr = AreaAddr::try_from_yang(&area_addr).unwrap(); + instance.config.area_addrs.insert(area_addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let area_addr = args.dnode.get_string(); + let area_addr = AreaAddr::try_from_yang(&area_addr).unwrap(); + instance.config.area_addrs.remove(&area_addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::lsp_mtu::PATH) + .modify_apply(|instance, args| { + let lsp_mtu = args.dnode.get_u16(); + instance.config.lsp_mtu = lsp_mtu; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::lsp_lifetime::PATH) + .modify_apply(|instance, args| { + let lsp_lifetime = args.dnode.get_u16(); + instance.config.lsp_lifetime = lsp_lifetime; + + let event_queue = args.event_queue; + event_queue.insert(Event::RefreshLsps); + }) + .path(isis::lsp_refresh::PATH) + .modify_apply(|instance, args| { + let lsp_refresh = args.dnode.get_u16(); + instance.config.lsp_refresh = lsp_refresh; + + let event_queue = args.event_queue; + event_queue.insert(Event::RefreshLsps); + }) + .path(isis::metric_type::value::PATH) + .modify_apply(|instance, args| { + let metric_type = args.dnode.get_string(); + let metric_type = MetricType::try_from_yang(&metric_type).unwrap(); + instance.config.metric_type.all = metric_type; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::metric_type::level_1::value::PATH) + .modify_apply(|instance, args| { + let metric_type = args.dnode.get_string(); + let metric_type = MetricType::try_from_yang(&metric_type).unwrap(); + instance.config.metric_type.l1 = Some(metric_type); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + }) + .delete_apply(|instance, args| { + instance.config.metric_type.l1 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + }) + .path(isis::metric_type::level_2::value::PATH) + .modify_apply(|instance, args| { + let metric_type = args.dnode.get_string(); + let metric_type = MetricType::try_from_yang(&metric_type).unwrap(); + instance.config.metric_type.l2 = Some(metric_type); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + instance.config.metric_type.l2 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::default_metric::value::PATH) + .modify_apply(|instance, args| { + let metric = args.dnode.get_u32(); + instance.config.default_metric.all = metric; + }) + .path(isis::default_metric::level_1::value::PATH) + .modify_apply(|instance, args| { + let metric = args.dnode.get_u32(); + instance.config.default_metric.l1 = Some(metric); + }) + .delete_apply(|instance, _args| { + instance.config.default_metric.l1 = None; + }) + .path(isis::default_metric::level_2::value::PATH) + .modify_apply(|instance, args| { + let metric = args.dnode.get_u32(); + instance.config.default_metric.l2 = Some(metric); + }) + .delete_apply(|instance, _args| { + instance.config.default_metric.l2 = None; + }) + .path(isis::authentication::key_chain::PATH) + .modify_apply(|instance, args| { + let keychain = args.dnode.get_string(); + instance.config.auth.all.keychain = Some(keychain); + }) + .delete_apply(|instance, _args| { + instance.config.auth.all.keychain = None; + }) + .path(isis::authentication::key::PATH) + .modify_apply(|instance, args| { + let key = args.dnode.get_string(); + instance.config.auth.all.key = Some(key); + }) + .delete_apply(|instance, _args| { + instance.config.auth.all.key = None; + }) + .path(isis::authentication::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + instance.config.auth.all.algo = Some(algo); + }) + .delete_apply(|instance, _args| { + instance.config.auth.all.algo = None; + }) + .path(isis::authentication::level_1::key_chain::PATH) + .modify_apply(|instance, args| { + let keychain = args.dnode.get_string(); + instance.config.auth.l1.keychain = Some(keychain); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l1.keychain = None; + }) + .path(isis::authentication::level_1::key::PATH) + .modify_apply(|instance, args| { + let key = args.dnode.get_string(); + instance.config.auth.l1.key = Some(key); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l1.key = None; + }) + .path(isis::authentication::level_1::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + instance.config.auth.l1.algo = Some(algo); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l1.algo = None; + }) + .path(isis::authentication::level_2::key_chain::PATH) + .modify_apply(|instance, args| { + let keychain = args.dnode.get_string(); + instance.config.auth.l2.keychain = Some(keychain); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l2.keychain = None; + }) + .path(isis::authentication::level_2::key::PATH) + .modify_apply(|instance, args| { + let key = args.dnode.get_string(); + instance.config.auth.l2.key = Some(key); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l2.key = None; + }) + .path(isis::authentication::level_2::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + instance.config.auth.l2.algo = Some(algo); + }) + .delete_apply(|instance, _args| { + instance.config.auth.l2.algo = None; + }) + .path(isis::address_families::address_family_list::PATH) + .create_apply(|instance, args| { + let af = args.dnode.get_string_relative("address-family").unwrap(); + let af = AddressFamily::try_from_yang(&af).unwrap(); + instance.config.afs.insert(af, AddressFamilyCfg::default()); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let af = args.list_entry.into_address_family().unwrap(); + instance.config.afs.remove(&af); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .lookup(|_instance, _list_entry, dnode| { + let af = dnode.get_string_relative("address-family").unwrap(); + let af = AddressFamily::try_from_yang(&af).unwrap(); + ListEntry::AddressFamily(af) + }) + .path(isis::address_families::address_family_list::enabled::PATH) + .modify_apply(|instance, args| { + let af = args.list_entry.into_address_family().unwrap(); + let af_cfg = instance.config.afs.get_mut(&af).unwrap(); + + let enabled = args.dnode.get_bool(); + af_cfg.enabled = enabled; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::spf_control::paths::PATH) + .modify_apply(|instance, args| { + let max_paths = args.dnode.get_u16(); + instance.config.max_paths = max_paths; + + let event_queue = args.event_queue; + event_queue.insert(Event::RerunSpf); + }) + .path(isis::spf_control::ietf_spf_delay::initial_delay::PATH) + .modify_apply(|instance, args| { + let initial_delay = args.dnode.get_u32(); + instance.config.spf_initial_delay = initial_delay; + }) + .path(isis::spf_control::ietf_spf_delay::short_delay::PATH) + .modify_apply(|instance, args| { + let short_delay = args.dnode.get_u32(); + instance.config.spf_short_delay = short_delay; + }) + .path(isis::spf_control::ietf_spf_delay::long_delay::PATH) + .modify_apply(|instance, args| { + let long_delay = args.dnode.get_u32(); + instance.config.spf_long_delay = long_delay; + }) + .path(isis::spf_control::ietf_spf_delay::hold_down::PATH) + .modify_apply(|instance, args| { + let hold_down = args.dnode.get_u32(); + instance.config.spf_hold_down = hold_down; + }) + .path(isis::spf_control::ietf_spf_delay::time_to_learn::PATH) + .modify_apply(|instance, args| { + let time_to_learn = args.dnode.get_u32(); + instance.config.spf_time_to_learn = time_to_learn; + }) + .path(isis::preference::internal::PATH) + .modify_apply(|instance, args| { + let preference = args.dnode.get_u8(); + instance.config.preference.internal = preference; + }) + .delete_apply(|_instance, _args| { + // Nothing to do. + }) + .path(isis::preference::external::PATH) + .modify_apply(|instance, args| { + let preference = args.dnode.get_u8(); + instance.config.preference.external = preference; + }) + .delete_apply(|_instance, _args| { + // Nothing to do. + }) + .path(isis::preference::default::PATH) + .modify_apply(|instance, args| { + let preference = args.dnode.get_u8(); + instance.config.preference.internal = preference; + instance.config.preference.external = preference; + }) + .delete_apply(|_instance, _args| { + // Nothing to do. + }) + .path(isis::overload::status::PATH) + .modify_apply(|instance, args| { + let overload_status = args.dnode.get_bool(); + instance.config.overload_status = overload_status; + }) + .path(isis::interfaces::interface::PATH) + .create_apply(|instance, args| { + let ifname = args.dnode.get_string_relative("name").unwrap(); + + let (iface_idx, _) = instance.arenas.interfaces.insert(&ifname); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdate(iface_idx)); + event_queue.insert(Event::InterfaceQuerySouthbound(iface_idx)); + }) + .delete_apply(|_instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceDelete(iface_idx)); + }) + .lookup(|instance, _list_entry, dnode| { + let ifname = dnode.get_string_relative("./name").unwrap(); + instance.arenas.interfaces.get_by_name(&ifname).map(|(iface_idx, _)| ListEntry::Interface(iface_idx)).expect("could not find IS-IS interface") + }) + .path(isis::interfaces::interface::enabled::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let enabled = args.dnode.get_bool(); + iface.config.enabled = enabled; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdate(iface_idx)); + }) + .path(isis::interfaces::interface::level_type::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let level_type = args.dnode.get_string(); + let level_type = LevelType::try_from_yang(&level_type).unwrap(); + iface.config.level_type.explicit = Some(level_type); + iface.config.level_type.resolved = + iface.config.resolved_level_type(&instance.config); + + let event_queue = args.event_queue; + // TODO: We can do better than a full reset. + event_queue.insert(Event::InterfaceReset(iface_idx)); + }) + .path(isis::interfaces::interface::lsp_pacing_interval::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let lsp_pacing_interval = args.dnode.get_u32(); + iface.config.lsp_pacing_interval = lsp_pacing_interval; + }) + .path(isis::interfaces::interface::lsp_retransmit_interval::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let lsp_rxmt_interval = args.dnode.get_u16(); + iface.config.lsp_rxmt_interval = lsp_rxmt_interval; + }) + .path(isis::interfaces::interface::passive::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let passive = args.dnode.get_bool(); + iface.config.passive = passive; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceReset(iface_idx)); + }) + .path(isis::interfaces::interface::csnp_interval::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let csnp_interval = args.dnode.get_u16(); + iface.config.csnp_interval = csnp_interval; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateCsnpInterval(iface_idx)); + }) + .path(isis::interfaces::interface::hello_padding::enabled::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_padding = args.dnode.get_bool(); + iface.config.hello_padding = hello_padding; + }) + .path(isis::interfaces::interface::interface_type::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let interface_type = args.dnode.get_string(); + let interface_type = InterfaceType::try_from_yang(&interface_type).unwrap(); + iface.config.interface_type = interface_type; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceReset(iface_idx)); + }) + .path(isis::interfaces::interface::hello_authentication::key_chain::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let keychain = args.dnode.get_string(); + iface.config.hello_auth.all.keychain = Some(keychain); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.all.keychain = None; + }) + .path(isis::interfaces::interface::hello_authentication::key::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let key = args.dnode.get_string(); + iface.config.hello_auth.all.key = Some(key); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.all.key = None; + }) + .path(isis::interfaces::interface::hello_authentication::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + iface.config.hello_auth.all.algo = Some(algo); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.all.algo = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_1::key_chain::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let keychain = args.dnode.get_string(); + iface.config.hello_auth.l1.keychain = Some(keychain); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l1.keychain = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_1::key::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let key = args.dnode.get_string(); + iface.config.hello_auth.l1.key = Some(key); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l1.key = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_1::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + iface.config.hello_auth.l1.algo = Some(algo); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l1.algo = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_2::key_chain::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let keychain = args.dnode.get_string(); + iface.config.hello_auth.l2.keychain = Some(keychain); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l2.keychain = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_2::key::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let key = args.dnode.get_string(); + iface.config.hello_auth.l2.key = Some(key); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l2.key = None; + }) + .path(isis::interfaces::interface::hello_authentication::level_2::crypto_algorithm::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let algo = args.dnode.get_string(); + let algo = CryptoAlgo::try_from_yang(&algo).unwrap(); + iface.config.hello_auth.l2.algo = Some(algo); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_auth.l2.algo = None; + }) + .path(isis::interfaces::interface::hello_interval::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_interval = args.dnode.get_u16(); + iface.config.hello_interval.all = hello_interval; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::hello_interval::level_1::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_interval = args.dnode.get_u16(); + iface.config.hello_interval.l1 = Some(hello_interval); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_interval.l1 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .path(isis::interfaces::interface::hello_interval::level_2::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_interval = args.dnode.get_u16(); + iface.config.hello_interval.l2 = Some(hello_interval); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_interval.l2 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::hello_multiplier::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_multiplier = args.dnode.get_u16(); + iface.config.hello_multiplier.all = hello_multiplier; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::hello_multiplier::level_1::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_multiplier = args.dnode.get_u16(); + iface.config.hello_multiplier.l1 = Some(hello_multiplier); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_multiplier.l1 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .path(isis::interfaces::interface::hello_multiplier::level_2::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let hello_multiplier = args.dnode.get_u16(); + iface.config.hello_multiplier.l2 = Some(hello_multiplier); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.hello_multiplier.l2 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::priority::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let priority = args.dnode.get_u8(); + iface.config.priority.all = priority; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L2)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::priority::level_1::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let priority = args.dnode.get_u8(); + iface.config.priority.l1 = Some(priority); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.priority.l1 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L1)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L1)); + }) + .path(isis::interfaces::interface::priority::level_2::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let priority = args.dnode.get_u8(); + iface.config.priority.l2 = Some(priority); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L2)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.priority.l2 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfacePriorityChange(iface_idx, LevelNumber::L2)); + event_queue.insert(Event::InterfaceUpdateHelloInterval(iface_idx, LevelNumber::L2)); + }) + .path(isis::interfaces::interface::metric::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let metric = args.dnode.get_u32(); + iface.config.metric.all = metric; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::interfaces::interface::metric::level_1::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let metric = args.dnode.get_u32(); + iface.config.metric.l1 = Some(metric); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.metric.l1 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + }) + .path(isis::interfaces::interface::metric::level_2::value::PATH) + .modify_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let metric = args.dnode.get_u32(); + iface.config.metric.l2 = Some(metric); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.metric.l2 = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .path(isis::interfaces::interface::address_families::address_family_list::PATH) + .create_apply(|instance, args| { + let iface_idx = args.list_entry.into_interface().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + let af = args.dnode.get_string_relative("address-family").unwrap(); + let af = AddressFamily::try_from_yang(&af).unwrap(); + iface.config.afs.insert(af); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .delete_apply(|instance, args| { + let (iface_idx, af) = args.list_entry.into_interface_address_family().unwrap(); + let iface = &mut instance.arenas.interfaces[iface_idx]; + + iface.config.afs.remove(&af); + + let event_queue = args.event_queue; + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L1)); + event_queue.insert(Event::ReoriginateLsps(LevelNumber::L2)); + }) + .lookup(|_instance, list_entry, dnode| { + let iface_idx = list_entry.into_interface().unwrap(); + let af = dnode.get_string_relative("address-family").unwrap(); + let af = AddressFamily::try_from_yang(&af).unwrap(); + ListEntry::InterfaceAddressFamily(iface_idx, af) + }) + .build() +} + +fn load_validation_callbacks() -> ValidationCallbacks { + ValidationCallbacksBuilder::default().build() +} + +// ===== impl Instance ===== + +#[async_trait] +impl Provider for Instance { + type ListEntry = ListEntry; + type Event = Event; + type Resource = Resource; + + fn validation_callbacks() -> Option<&'static ValidationCallbacks> { + Some(&VALIDATION_CALLBACKS) + } + + fn callbacks() -> Option<&'static Callbacks> { + Some(&CALLBACKS) + } + + async fn process_event(&mut self, event: Event) { + match event { + Event::InstanceReset => self.reset(), + Event::InstanceUpdate => { + self.update(); + } + Event::InstanceLevelTypeUpdate => { + for iface in self.arenas.interfaces.iter_mut() { + iface.config.level_type.resolved = + iface.config.resolved_level_type(&self.config); + } + } + Event::InterfaceUpdate(iface_idx) => { + if let Some((mut instance, arenas)) = self.as_up() { + let iface = &mut arenas.interfaces[iface_idx]; + if let Err(error) = + iface.update(&mut instance, &mut arenas.adjacencies) + { + error.log(arenas); + } + } + } + Event::InterfaceDelete(iface_idx) => { + if let Some((mut instance, arenas)) = self.as_up() { + let iface = &mut arenas.interfaces[iface_idx]; + + // Stop interface if it's active. + let reason = InterfaceInactiveReason::AdminDown; + iface.stop(&mut instance, &mut arenas.adjacencies, reason); + } + + self.arenas.interfaces.delete(iface_idx); + } + Event::InterfaceReset(iface_idx) => { + if let Some((mut instance, arenas)) = self.as_up() { + let iface = &mut arenas.interfaces[iface_idx]; + if let Err(error) = + iface.reset(&mut instance, &mut arenas.adjacencies) + { + error.log(arenas); + } + } + } + Event::InterfacePriorityChange(iface_idx, level) => { + let Some((instance, arenas)) = self.as_up() else { + return; + }; + let iface = &mut arenas.interfaces[iface_idx]; + + if iface.state.active && !iface.is_passive() { + // Schedule new DIS election. + if iface.config.interface_type == InterfaceType::Broadcast { + let msg = DisElectionMsg { + iface_key: iface.id.into(), + level, + }; + let _ = + instance.tx.protocol_input.dis_election.send(msg); + } + } + } + Event::InterfaceUpdateHelloInterval(iface_idx, level) => { + let Some((instance, arenas)) = self.as_up() else { + return; + }; + let iface = &mut arenas.interfaces[iface_idx]; + if iface.state.active && !iface.is_passive() { + iface.hello_interval_start(&instance, level); + } + } + Event::InterfaceUpdateCsnpInterval(iface_idx) => { + let Some((instance, arenas)) = self.as_up() else { + return; + }; + let iface = &mut arenas.interfaces[iface_idx]; + iface.csnp_interval_reset(&instance); + } + Event::InterfaceQuerySouthbound(iface_idx) => { + if self.is_active() { + let iface = &self.arenas.interfaces[iface_idx]; + iface.query_southbound(&self.tx.ibus); + } + } + Event::ReoriginateLsps(level) => { + if let Some((mut instance, _)) = self.as_up() { + instance.schedule_lsp_origination(level); + } + } + Event::RefreshLsps => { + if let Some((instance, arenas)) = self.as_up() { + let system_id = instance.config.system_id.unwrap(); + for level in [LevelNumber::L1, LevelNumber::L2] { + for lse in instance + .state + .lsdb + .get(level) + .iter_for_system_id(&arenas.lsp_entries, system_id) + .filter(|lse| lse.data.rem_lifetime != 0) + { + instance + .tx + .protocol_input + .lsp_refresh(level, lse.id); + } + } + } + } + Event::RerunSpf => {} + } + } +} + +// ===== configuration helpers ===== + +impl InstanceCfg { + // Checks if the specified address family is enabled. + pub(crate) fn is_af_enabled(&self, af: AddressFamily) -> bool { + if let Some(af_cfg) = self.afs.get(&af) { + return af_cfg.enabled; + } + + true + } +} + +impl InterfaceCfg { + // Checks if the specified address family is enabled. + pub(crate) fn is_af_enabled( + &self, + af: AddressFamily, + instance_cfg: &InstanceCfg, + ) -> bool { + if !self.afs.contains(&af) { + return false; + } + + if let Some(af_cfg) = instance_cfg.afs.get(&af) { + return af_cfg.enabled; + } + + true + } + + // Returns the levels supported by the interface. + pub(crate) fn levels(&self) -> SmallVec<[LevelNumber; 2]> { + [LevelNumber::L1, LevelNumber::L2] + .into_iter() + .filter(|level| self.level_type.resolved.intersects(level)) + .collect() + } + + // Calculates the hello hold time for a given level by multiplying the + // hello interval and multiplier. + pub(crate) fn hello_holdtime(&self, level: LevelType) -> u16 { + self.hello_interval.get(level) * self.hello_multiplier.get(level) + } + + // Resolves the level type. + fn resolved_level_type(&self, instance_cfg: &InstanceCfg) -> LevelType { + match instance_cfg.level_type { + LevelType::L1 | LevelType::L2 => instance_cfg.level_type, + LevelType::All => self.level_type.explicit.unwrap(), + } + } +} + +impl LevelsCfg +where + T: Copy, +{ + // Retrieves the configuration value for the specified level. + pub(crate) fn get(&self, level: impl Into) -> T { + let level = level.into(); + match level { + LevelType::L1 => self.l1.unwrap_or(self.all), + LevelType::L2 => self.l2.unwrap_or(self.all), + LevelType::All => self.all, + } + } +} + +impl MetricType { + // Checks if standard metric support is enabled. + pub(crate) const fn is_standard_enabled(&self) -> bool { + matches!(self, MetricType::Standard | MetricType::Both) + } + + // Checks if wide metric support is enabled. + pub(crate) const fn is_wide_enabled(&self) -> bool { + matches!(self, MetricType::Wide | MetricType::Both) + } +} + +// ===== configuration defaults ===== + +impl Default for InstanceCfg { + fn default() -> InstanceCfg { + let enabled = isis::enabled::DFLT; + let level_type = isis::level_type::DFLT; + let level_type = LevelType::try_from_yang(level_type).unwrap(); + let lsp_mtu = isis::lsp_mtu::DFLT; + let lsp_lifetime = isis::lsp_lifetime::DFLT; + let lsp_refresh = isis::lsp_refresh::DFLT; + let metric_type = isis::metric_type::value::DFLT; + let metric_type = LevelsCfg { + all: MetricType::try_from_yang(metric_type).unwrap(), + l1: None, + l2: None, + }; + let default_metric = isis::default_metric::value::DFLT; + let default_metric = LevelsCfg { + all: default_metric, + l1: None, + l2: None, + }; + let max_paths = isis::spf_control::paths::DFLT; + let spf_initial_delay = + isis::spf_control::ietf_spf_delay::initial_delay::DFLT; + let spf_short_delay = + isis::spf_control::ietf_spf_delay::short_delay::DFLT; + let spf_long_delay = + isis::spf_control::ietf_spf_delay::long_delay::DFLT; + let spf_hold_down = isis::spf_control::ietf_spf_delay::hold_down::DFLT; + let spf_time_to_learn = + isis::spf_control::ietf_spf_delay::time_to_learn::DFLT; + let overload_status = isis::overload::status::DFLT; + + InstanceCfg { + enabled, + level_type, + system_id: None, + area_addrs: Default::default(), + lsp_mtu, + lsp_lifetime, + lsp_refresh, + metric_type, + default_metric, + auth: Default::default(), + max_paths, + afs: Default::default(), + spf_initial_delay, + spf_short_delay, + spf_long_delay, + spf_hold_down, + spf_time_to_learn, + preference: Default::default(), + overload_status, + } + } +} + +impl Default for AddressFamilyCfg { + fn default() -> AddressFamilyCfg { + let enabled = + isis::address_families::address_family_list::enabled::DFLT; + + AddressFamilyCfg { enabled } + } +} + +impl Default for Preference { + fn default() -> Preference { + let internal = isis::preference::default::DFLT; + let external = isis::preference::default::DFLT; + + Preference { internal, external } + } +} + +impl Default for InterfaceCfg { + fn default() -> InterfaceCfg { + let enabled = isis::interfaces::interface::enabled::DFLT; + let level_type = isis::interfaces::interface::level_type::DFLT; + let level_type = LevelType::try_from_yang(level_type).unwrap(); + let level_type = InheritableConfig { + explicit: Some(level_type), + resolved: level_type, + }; + let lsp_pacing_interval = + isis::interfaces::interface::lsp_pacing_interval::DFLT; + let lsp_rxmt_interval = + isis::interfaces::interface::lsp_retransmit_interval::DFLT; + let passive = isis::interfaces::interface::passive::DFLT; + let csnp_interval = isis::interfaces::interface::csnp_interval::DFLT; + let hello_padding = + isis::interfaces::interface::hello_padding::enabled::DFLT; + let interface_type = isis::interfaces::interface::interface_type::DFLT; + let interface_type = + InterfaceType::try_from_yang(interface_type).unwrap(); + let hello_interval = + isis::interfaces::interface::hello_interval::value::DFLT; + let hello_interval = LevelsCfg { + all: hello_interval, + l1: None, + l2: None, + }; + let hello_multiplier = + isis::interfaces::interface::hello_multiplier::value::DFLT; + let hello_multiplier = LevelsCfg { + all: hello_multiplier, + l1: None, + l2: None, + }; + let priority = isis::interfaces::interface::priority::value::DFLT; + let priority = LevelsCfg { + all: priority, + l1: None, + l2: None, + }; + let metric = isis::interfaces::interface::metric::value::DFLT; + let metric = LevelsCfg { + all: metric, + l1: None, + l2: None, + }; + InterfaceCfg { + enabled, + level_type, + lsp_pacing_interval, + lsp_rxmt_interval, + passive, + csnp_interval, + hello_padding, + interface_type, + hello_auth: Default::default(), + hello_interval, + hello_multiplier, + priority, + metric, + afs: Default::default(), + } + } +} diff --git a/holo-isis/src/northbound/mod.rs b/holo-isis/src/northbound/mod.rs new file mode 100644 index 00000000..6c939115 --- /dev/null +++ b/holo-isis/src/northbound/mod.rs @@ -0,0 +1,40 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +pub mod configuration; +pub mod notification; +pub mod rpc; +pub mod state; +pub mod yang; + +use holo_northbound::ProviderBase; +use holo_utils::protocol::Protocol; +use holo_yang::ToYang; +use tracing::{debug_span, Span}; + +use crate::instance::Instance; + +// ===== impl Instance ===== + +impl ProviderBase for Instance { + fn yang_modules() -> &'static [&'static str] { + &["ietf-isis", "holo-isis"] + } + + fn top_level_node(&self) -> String { + format!( + "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='{}'][name='main']/ietf-isis:isis", + Protocol::ISIS.to_yang(), + ) + } + + fn debug_span(name: &str) -> Span { + debug_span!("isis-instance", %name) + } +} diff --git a/holo-isis/src/northbound/notification.rs b/holo-isis/src/northbound/notification.rs new file mode 100644 index 00000000..234a95be --- /dev/null +++ b/holo-isis/src/northbound/notification.rs @@ -0,0 +1,399 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::borrow::Cow; + +use bytes::Bytes; +use holo_northbound::{notification, yang}; +use holo_utils::option::OptionExt; +use holo_yang::ToYang; + +use crate::adjacency::{Adjacency, AdjacencyEvent, AdjacencyState}; +use crate::error::AdjacencyRejectError; +use crate::instance::InstanceUpView; +use crate::interface::Interface; +use crate::packet::pdu::Lsp; +use crate::packet::SystemId; + +// ===== global functions ===== + +#[expect(unused)] +pub(crate) fn database_overload( + instance: &InstanceUpView<'_>, + _overload: bool, +) { + use yang::database_overload::{self, DatabaseOverload}; + + let path = database_overload::PATH; + let data = DatabaseOverload { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + overload: None, + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn lsp_too_large(instance: &InstanceUpView<'_>, iface: &Interface) { + use yang::lsp_too_large::{self, LspTooLarge}; + + let path = lsp_too_large::PATH; + let data = LspTooLarge { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + pdu_size: None, + lsp_id: None, + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn if_state_change( + instance: &InstanceUpView<'_>, + iface: &Interface, + up: bool, +) { + use yang::isis_if_state_change::{self, IsisIfStateChange}; + + let path = isis_if_state_change::PATH; + let state = if up { "up" } else { "down" }; + let data = IsisIfStateChange { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + state: Some(state.into()), + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn corrupted_lsp_detected(instance: &InstanceUpView<'_>, lsp: &Lsp) { + use yang::corrupted_lsp_detected::{self, CorruptedLspDetected}; + + let path = corrupted_lsp_detected::PATH; + let data = CorruptedLspDetected { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + lsp_id: Some(lsp.lsp_id.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn attempt_to_exceed_max_sequence( + instance: &InstanceUpView<'_>, + lsp: &Lsp, +) { + use yang::attempt_to_exceed_max_sequence::{ + self, AttemptToExceedMaxSequence, + }; + + let path = attempt_to_exceed_max_sequence::PATH; + let data = AttemptToExceedMaxSequence { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + lsp_id: Some(lsp.lsp_id.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn id_len_mismatch( + instance: &InstanceUpView<'_>, + iface: &Interface, + pdu_id_len: u8, + raw_pdu: &Bytes, +) { + use yang::id_len_mismatch::{self, IdLenMismatch}; + + let path = id_len_mismatch::PATH; + let data = IdLenMismatch { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + pdu_field_len: Some(pdu_id_len), + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn max_area_addresses_mismatch( + instance: &InstanceUpView<'_>, + iface: &Interface, + pdu_max_area_addrs: u8, + raw_pdu: &Bytes, +) { + use yang::max_area_addresses_mismatch::{self, MaxAreaAddressesMismatch}; + + let path = max_area_addresses_mismatch::PATH; + let data = MaxAreaAddressesMismatch { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + max_area_addresses: Some(pdu_max_area_addrs), + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn own_lsp_purge( + instance: &InstanceUpView<'_>, + iface: &Interface, + lsp: &Lsp, +) { + use yang::own_lsp_purge::{self, OwnLspPurge}; + + let path = own_lsp_purge::PATH; + let data = OwnLspPurge { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + lsp_id: Some(lsp.lsp_id.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn sequence_number_skipped( + instance: &InstanceUpView<'_>, + iface: &Interface, + lsp: &Lsp, +) { + use yang::sequence_number_skipped::{self, SequenceNumberSkipped}; + + let path = sequence_number_skipped::PATH; + let data = SequenceNumberSkipped { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + lsp_id: Some(lsp.lsp_id.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn authentication_type_failure( + instance: &InstanceUpView<'_>, + iface: &Interface, + raw_pdu: &Bytes, +) { + use yang::authentication_type_failure::{self, AuthenticationTypeFailure}; + + let path = authentication_type_failure::PATH; + let data = AuthenticationTypeFailure { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn authentication_failure( + instance: &InstanceUpView<'_>, + iface: &Interface, + raw_pdu: &Bytes, +) { + use yang::authentication_failure::{self, AuthenticationFailure}; + + let path = authentication_failure::PATH; + let data = AuthenticationFailure { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn version_skew( + instance: &InstanceUpView<'_>, + iface: &Interface, + version: u8, + raw_pdu: &Bytes, +) { + use yang::version_skew::{self, VersionSkew}; + + let path = version_skew::PATH; + let data = VersionSkew { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + protocol_version: Some(version), + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn area_mismatch( + instance: &InstanceUpView<'_>, + iface: &Interface, + raw_pdu: &Bytes, +) { + use yang::area_mismatch::{self, AreaMismatch}; + + let path = area_mismatch::PATH; + let data = AreaMismatch { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + raw_pdu: Some(raw_pdu.as_ref()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn rejected_adjacency( + instance: &InstanceUpView<'_>, + iface: &Interface, + raw_pdu: &Bytes, + reason: &AdjacencyRejectError, +) { + use yang::rejected_adjacency::{self, RejectedAdjacency}; + + let path = rejected_adjacency::PATH; + let data = RejectedAdjacency { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + raw_pdu: Some(raw_pdu.as_ref()), + reason: Some(reason.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +#[expect(unused)] +pub(crate) fn protocols_supported_mismatch( + instance: &InstanceUpView<'_>, + iface: &Interface, + raw_pdu: &Bytes, +) { + use yang::protocols_supported_mismatch::{ + self, ProtocolsSupportedMismatch, + }; + + let path = protocols_supported_mismatch::PATH; + let data = ProtocolsSupportedMismatch { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + raw_pdu: Some(raw_pdu.as_ref()), + protocols: None, + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn lsp_error_detected( + instance: &InstanceUpView<'_>, + iface: &Interface, + lsp: &Lsp, +) { + use yang::lsp_error_detected::{self, LspErrorDetected}; + + let path = lsp_error_detected::PATH; + let data = LspErrorDetected { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + lsp_id: Some(lsp.lsp_id.to_yang()), + raw_pdu: Some(lsp.raw.as_ref()), + error_offset: None, + tlv_type: None, + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn adjacency_state_change( + instance: &InstanceUpView<'_>, + iface: &Interface, + adj: &Adjacency, + state: AdjacencyState, + event: AdjacencyEvent, +) { + use yang::adjacency_state_change::{self, AdjacencyStateChange}; + + let path = adjacency_state_change::PATH; + let data = AdjacencyStateChange { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + neighbor: None, + neighbor_system_id: Some(adj.system_id.to_yang()), + state: Some(state.to_yang()), + reason: (state == AdjacencyState::Up).then_some(event.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn lsp_received( + instance: &InstanceUpView<'_>, + iface: &Interface, + lsp: &Lsp, + system_id: &SystemId, +) { + use yang::lsp_received::{self, LspReceived}; + + let path = lsp_received::PATH; + let data = LspReceived { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + interface_name: Some(Cow::Borrowed(&iface.name)), + interface_level: Some(iface.config.level_type.resolved.to_yang()), + extended_circuit_id: None, + lsp_id: Some(lsp.lsp_id.to_yang()), + sequence: Some(lsp.seqno), + received_timestamp: lsp + .base_time + .as_ref() + .map(Cow::Borrowed) + .ignore_in_testing(), + neighbor_system_id: Some(system_id.to_yang()), + }; + notification::send(&instance.tx.nb, path, data); +} + +pub(crate) fn lsp_generation(instance: &InstanceUpView<'_>, lsp: &Lsp) { + use yang::lsp_generation::{self, LspGeneration}; + + let path = lsp_generation::PATH; + let data = LspGeneration { + routing_protocol_name: Some(Cow::Borrowed(instance.name)), + isis_level: Some(instance.config.level_type.to_yang()), + lsp_id: Some(lsp.lsp_id.to_yang()), + sequence: Some(lsp.seqno), + send_timestamp: lsp.base_time.as_ref().map(Cow::Borrowed), + }; + notification::send(&instance.tx.nb, path, data); +} diff --git a/holo-isis/src/northbound/rpc.rs b/holo-isis/src/northbound/rpc.rs new file mode 100644 index 00000000..a70db050 --- /dev/null +++ b/holo-isis/src/northbound/rpc.rs @@ -0,0 +1,46 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::sync::LazyLock as Lazy; + +use holo_northbound::rpc::{Callbacks, CallbacksBuilder, Provider}; +use holo_northbound::yang; + +use crate::instance::Instance; + +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() + .path(yang::clear_adjacency::PATH) + .rpc(|_instance, _args| { + Box::pin(async move { + // TODO: implement me! + Ok(()) + }) + }) + .path(yang::isis_clear_database::PATH) + .rpc(|_instance, _args| { + Box::pin(async move { + // TODO: implement me! + Ok(()) + }) + }) + .build() +} + +// ===== impl Instance ===== + +impl Provider for Instance { + fn callbacks() -> Option<&'static Callbacks> { + Some(&CALLBACKS) + } +} diff --git a/holo-isis/src/northbound/state.rs b/holo-isis/src/northbound/state.rs new file mode 100644 index 00000000..1c8be44d --- /dev/null +++ b/holo-isis/src/northbound/state.rs @@ -0,0 +1,734 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +#![allow(unreachable_code)] + +use std::borrow::Cow; +use std::collections::HashSet; +use std::sync::{atomic, LazyLock as Lazy}; + +use enum_as_inner::EnumAsInner; +use holo_northbound::state::{ + Callbacks, CallbacksBuilder, ListEntryKind, Provider, +}; +use holo_northbound::yang::control_plane_protocol::isis; +use holo_utils::option::OptionExt; +use holo_yang::ToYang; + +use crate::adjacency::Adjacency; +use crate::collections::Lsdb; +use crate::instance::Instance; +use crate::interface::Interface; +use crate::lsdb::{LspEntry, LspLogEntry}; +use crate::packet::tlv::{ + ExtIpv4Reach, ExtIsReach, Ipv4Reach, IsReach, UnknownTlv, +}; +use crate::packet::{LanId, LevelNumber}; + +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); + +#[derive(Debug, Default)] +#[derive(EnumAsInner)] +pub enum ListEntry<'a> { + #[default] + None, + LspLog(&'a LspLogEntry), + Lsdb(LevelNumber, &'a Lsdb), + LspEntry(&'a LspEntry), + IsReach(&'a LspEntry, LanId), + IsReachInstance(u32, &'a IsReach), + ExtIsReach(u32, &'a ExtIsReach), + Ipv4Reach(&'a Ipv4Reach), + ExtIpv4Reach(&'a ExtIpv4Reach), + UnknownTlv(&'a UnknownTlv), + SystemCounters(LevelNumber), + Interface(&'a Interface), + InterfacePacketCounters(&'a Interface, LevelNumber), + Adjacency(&'a Adjacency), +} + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() + .path(isis::PATH) + .get_object(|instance, _args| { + use isis::Isis; + Box::new(Isis { + discontinuity_time: instance.state.as_ref().map(|state| &state.discontinuity_time).map(Cow::Borrowed).ignore_in_testing(), + }) + }) + .path(isis::spf_control::ietf_spf_delay::PATH) + .get_object(|_instance, _args| { + use isis::spf_control::ietf_spf_delay::IetfSpfDelay; + Box::new(IetfSpfDelay { + current_state: None, + remaining_time_to_learn: None, + remaining_hold_down: None, + last_event_received: None, + next_spf_time: None, + last_spf_time: None, + }) + }) + .path(isis::spf_log::event::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::spf_log::event::Event; + Box::new(Event { + id: todo!(), + spf_type: None, + level: None, + schedule_timestamp: None, + start_timestamp: None, + end_timestamp: None, + }) + }) + .path(isis::spf_log::event::trigger_lsp::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::spf_log::event::trigger_lsp::TriggerLsp; + Box::new(TriggerLsp { + lsp: todo!(), + sequence: None, + }) + }) + .path(isis::lsp_log::event::PATH) + .get_iterate(|instance, _args| { + let Some(instance_state) = &instance.state else { return None }; + let iter = instance_state.lsp_log.iter().map(ListEntry::LspLog); + Some(Box::new(iter) as _).ignore_in_testing() + }) + .get_object(|_instance, args| { + use isis::lsp_log::event::Event; + let log = args.list_entry.as_lsp_log().unwrap(); + Box::new(Event { + id: log.id, + level: Some(log.level as u8), + received_timestamp: log.rcvd_time.as_ref().map(Cow::Borrowed).ignore_in_testing(), + reason: Some(log.reason.to_yang()), + }) + }) + .path(isis::lsp_log::event::lsp::PATH) + .get_object(|_instance, args| { + use isis::lsp_log::event::lsp::Lsp; + let log = args.list_entry.as_lsp_log().unwrap(); + Box::new(Lsp { + lsp: Some(log.lsp.lsp_id.to_yang()), + sequence: Some(log.lsp.seqno), + }) + }) + .path(isis::hostnames::hostname::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::hostnames::hostname::Hostname; + Box::new(Hostname { + system_id: todo!(), + hostname: None, + }) + }) + .path(isis::database::levels::PATH) + .get_iterate(|instance, _args| { + let Some(instance_state) = &instance.state else { return None }; + let iter = [LevelNumber::L1, LevelNumber::L2].into_iter().map(|level| ListEntry::Lsdb(level, instance_state.lsdb.get(level))); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::Levels; + let (level, _) = args.list_entry.as_lsdb().unwrap(); + Box::new(Levels { + level: *level as u8, + }) + }) + .path(isis::database::levels::lsp::PATH) + .get_iterate(|instance, args| { + let (_, lsdb) = args.parent_list_entry.as_lsdb().unwrap(); + let iter = lsdb.iter(&instance.arenas.lsp_entries).map(ListEntry::LspEntry); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::Lsp; + let lse = args.list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let ipv4_addresses = lsp.tlvs.ipv4_addrs().map(Cow::Borrowed); + let protocol_supported = lsp.tlvs.protocols_supported(); + Box::new(Lsp { + lsp_id: lsp.lsp_id.to_yang(), + decoded_completed: None, + raw_data: Some(lsp.raw.as_ref()).ignore_in_testing(), + checksum: Some(lsp.cksum).ignore_in_testing(), + remaining_lifetime: Some(lsp.rem_lifetime()).ignore_in_testing(), + sequence: Some(lsp.seqno), + ipv4_addresses: Some(Box::new(ipv4_addresses)), + ipv6_addresses: None, + ipv4_te_routerid: None, + ipv6_te_routerid: None, + protocol_supported: Some(Box::new(protocol_supported)), + dynamic_hostname: None, + }) + }) + .path(isis::database::levels::lsp::attributes::PATH) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::attributes::Attributes; + Box::new(Attributes { + lsp_flags: None, + }) + }) + .path(isis::database::levels::lsp::authentication::PATH) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::authentication::Authentication; + Box::new(Authentication { + authentication_type: None, + authentication_key: None, + }) + }) + .path(isis::database::levels::lsp::unknown_tlvs::unknown_tlv::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.unknown.iter().map(ListEntry::UnknownTlv); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::unknown_tlvs::unknown_tlv::UnknownTlv; + let tlv = args.list_entry.as_unknown_tlv().unwrap(); + Box::new(UnknownTlv { + r#type: Some(tlv.tlv_type as u16), + length: Some(tlv.length as u16), + value: Some(tlv.value.as_ref()), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.is_reach().map(|reach| reach.neighbor).collect::>().into_iter().map(|neighbor| ListEntry::IsReach(lse, neighbor)); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::Neighbor; + let (_, neighbor) = args.list_entry.as_is_reach().unwrap(); + Box::new(Neighbor { + neighbor_id: neighbor.to_yang(), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::PATH) + .get_iterate(|_instance, args| { + let (lse, neighbor) = args.parent_list_entry.as_is_reach().unwrap(); + let lsp = &lse.data; + let neighbor = *neighbor; + let iter = lsp.tlvs.is_reach() + .filter(move |reach| reach.neighbor == neighbor) + .enumerate().map(|(id, reach)| ListEntry::IsReachInstance(id as u32, reach)); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::Instance; + let (id, _) = args.list_entry.as_is_reach_instance().unwrap(); + Box::new(Instance { + id: *id, + i_e: Some(false), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::default_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::default_metric::DefaultMetric; + let (_, reach) = args.list_entry.as_is_reach_instance().unwrap(); + Box::new(DefaultMetric { + metric: Some(reach.metric), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::delay_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::delay_metric::DelayMetric; + let (_, reach) = args.list_entry.as_is_reach_instance().unwrap(); + Box::new(DelayMetric { + metric: reach.metric_delay, + supported: Some(reach.metric_delay.is_some()), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::expense_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::expense_metric::ExpenseMetric; + let (_, reach) = args.list_entry.as_is_reach_instance().unwrap(); + Box::new(ExpenseMetric { + metric: reach.metric_expense, + supported: Some(reach.metric_expense.is_some()), + }) + }) + .path(isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::error_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::is_neighbor::neighbor::instances::instance::error_metric::ErrorMetric; + let (_, reach) = args.list_entry.as_is_reach_instance().unwrap(); + Box::new(ErrorMetric { + metric: reach.metric_error, + supported: Some(reach.metric_error.is_some()), + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.ext_is_reach().enumerate().map(|(id, reach)| ListEntry::ExtIsReach(id as u32, reach)); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::Neighbor; + let (_, reach) = args.list_entry.as_ext_is_reach().unwrap(); + Box::new(Neighbor { + neighbor_id: reach.neighbor.to_yang(), + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::PATH) + .get_iterate(|_instance, args| { + let (id, reach) = args.parent_list_entry.as_ext_is_reach().unwrap(); + let iter = std::iter::once(ListEntry::ExtIsReach(*id, reach)); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::Instance; + let (id, reach) = args.list_entry.as_ext_is_reach().unwrap(); + Box::new(Instance { + id: *id, + metric: Some(reach.metric), + admin_group: None, + te_metric: None, + max_bandwidth: None, + max_reservable_bandwidth: None, + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::local_if_ipv4_addrs::PATH) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::local_if_ipv4_addrs::LocalIfIpv4Addrs; + Box::new(LocalIfIpv4Addrs { + local_if_ipv4_addr: None, + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::remote_if_ipv4_addrs::PATH) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::remote_if_ipv4_addrs::RemoteIfIpv4Addrs; + Box::new(RemoteIfIpv4Addrs { + remote_if_ipv4_addr: None, + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::unreserved_bandwidths::unreserved_bandwidth::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::unreserved_bandwidths::unreserved_bandwidth::UnreservedBandwidth; + Box::new(UnreservedBandwidth { + priority: None, + unreserved_bandwidth: None, + }) + }) + .path(isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::unknown_tlvs::unknown_tlv::PATH) + .get_iterate(|_instance, args| { + let (_, reach) = args.parent_list_entry.as_ext_is_reach().unwrap(); + let iter = reach.sub_tlvs.unknown.iter().map(ListEntry::UnknownTlv); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::extended_is_neighbor::neighbor::instances::instance::unknown_tlvs::unknown_tlv::UnknownTlv; + let tlv = args.list_entry.as_unknown_tlv().unwrap(); + Box::new(UnknownTlv { + r#type: Some(tlv.tlv_type as u16), + length: Some(tlv.length as u16), + value: Some(tlv.value.as_ref()), + }) + }) + .path(isis::database::levels::lsp::ipv4_internal_reachability::prefixes::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.ipv4_internal_reach().map(ListEntry::Ipv4Reach); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_internal_reachability::prefixes::Prefixes; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(Prefixes { + ip_prefix: Some(Cow::Owned(reach.prefix.ip())), + prefix_len: Some(reach.prefix.prefix()), + i_e: Some(reach.ie_bit), + }) + }) + .path(isis::database::levels::lsp::ipv4_internal_reachability::prefixes::default_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_internal_reachability::prefixes::default_metric::DefaultMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(DefaultMetric { + metric: Some(reach.metric), + }) + }) + .path(isis::database::levels::lsp::ipv4_internal_reachability::prefixes::delay_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_internal_reachability::prefixes::delay_metric::DelayMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(DelayMetric { + metric: reach.metric_delay, + supported: Some(reach.metric_delay.is_some()), + }) + }) + .path(isis::database::levels::lsp::ipv4_internal_reachability::prefixes::expense_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_internal_reachability::prefixes::expense_metric::ExpenseMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(ExpenseMetric { + metric: reach.metric_expense, + supported: Some(reach.metric_expense.is_some()), + }) + }) + .path(isis::database::levels::lsp::ipv4_internal_reachability::prefixes::error_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_internal_reachability::prefixes::error_metric::ErrorMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(ErrorMetric { + metric: reach.metric_error, + supported: Some(reach.metric_error.is_some()), + }) + }) + .path(isis::database::levels::lsp::ipv4_external_reachability::prefixes::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.ipv4_external_reach().map(ListEntry::Ipv4Reach); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_external_reachability::prefixes::Prefixes; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(Prefixes { + ip_prefix: Some(Cow::Owned(reach.prefix.ip())), + prefix_len: Some(reach.prefix.prefix()), + i_e: Some(reach.ie_bit), + }) + }) + .path(isis::database::levels::lsp::ipv4_external_reachability::prefixes::default_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_external_reachability::prefixes::default_metric::DefaultMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(DefaultMetric { + metric: Some(reach.metric), + }) + }) + .path(isis::database::levels::lsp::ipv4_external_reachability::prefixes::delay_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_external_reachability::prefixes::delay_metric::DelayMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(DelayMetric { + metric: reach.metric_delay, + supported: Some(reach.metric_delay.is_some()), + }) + }) + .path(isis::database::levels::lsp::ipv4_external_reachability::prefixes::expense_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_external_reachability::prefixes::expense_metric::ExpenseMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(ExpenseMetric { + metric: reach.metric_expense, + supported: Some(reach.metric_expense.is_some()), + }) + }) + .path(isis::database::levels::lsp::ipv4_external_reachability::prefixes::error_metric::PATH) + .get_object(|_instance, args| { + use isis::database::levels::lsp::ipv4_external_reachability::prefixes::error_metric::ErrorMetric; + let reach = args.list_entry.as_ipv4_reach().unwrap(); + Box::new(ErrorMetric { + metric: reach.metric_error, + supported: Some(reach.metric_error.is_some()), + }) + }) + .path(isis::database::levels::lsp::extended_ipv4_reachability::prefixes::PATH) + .get_iterate(|_instance, args| { + let lse = args.parent_list_entry.as_lsp_entry().unwrap(); + let lsp = &lse.data; + let iter = lsp.tlvs.ext_ipv4_reach().map(ListEntry::ExtIpv4Reach); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::extended_ipv4_reachability::prefixes::Prefixes; + let reach = args.list_entry.as_ext_ipv4_reach().unwrap(); + Box::new(Prefixes { + up_down: Some(reach.up_down), + ip_prefix: Some(Cow::Owned(reach.prefix.ip())), + prefix_len: Some(reach.prefix.prefix()), + metric: Some(reach.metric), + }) + }) + .path(isis::database::levels::lsp::extended_ipv4_reachability::prefixes::unknown_tlvs::unknown_tlv::PATH) + .get_iterate(|_instance, args| { + let reach = args.parent_list_entry.as_ext_ipv4_reach().unwrap(); + let iter = reach.sub_tlvs.unknown.iter().map(ListEntry::UnknownTlv); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::database::levels::lsp::extended_ipv4_reachability::prefixes::unknown_tlvs::unknown_tlv::UnknownTlv; + let tlv = args.list_entry.as_unknown_tlv().unwrap(); + Box::new(UnknownTlv { + r#type: Some(tlv.tlv_type as u16), + length: Some(tlv.length as u16), + value: Some(tlv.value.as_ref()), + }) + }) + .path(isis::database::levels::lsp::ipv6_reachability::prefixes::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::ipv6_reachability::prefixes::Prefixes; + Box::new(Prefixes { + up_down: None, + ip_prefix: None, + prefix_len: None, + metric: None, + }) + }) + .path(isis::database::levels::lsp::ipv6_reachability::prefixes::unknown_tlvs::unknown_tlv::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::database::levels::lsp::ipv6_reachability::prefixes::unknown_tlvs::unknown_tlv::UnknownTlv; + Box::new(UnknownTlv { + r#type: None, + length: None, + value: None, + }) + }) + .path(isis::local_rib::route::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::local_rib::route::Route; + Box::new(Route { + prefix: todo!(), + metric: None, + level: None, + route_tag: None, + }) + }) + .path(isis::local_rib::route::next_hops::next_hop::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_object(|_instance, _args| { + use isis::local_rib::route::next_hops::next_hop::NextHop; + Box::new(NextHop { + next_hop: todo!(), + outgoing_interface: None, + }) + }) + .path(isis::system_counters::level::PATH) + .get_iterate(|_instance, _args| { + let iter = [LevelNumber::L1, LevelNumber::L2].into_iter().map(ListEntry::SystemCounters); + Some(Box::new(iter) as _).ignore_in_testing() + }) + .get_object(|instance, args| { + use isis::system_counters::level::Level; + let level = args.list_entry.as_system_counters().unwrap(); + let mut corrupted_lsps = None; + let mut authentication_type_fails = None; + let mut authentication_fails = None; + let mut database_overload = None; + let mut own_lsp_purge = None; + let mut manual_address_drop_from_area = None; + let mut max_sequence = None; + let mut sequence_number_skipped = None; + let mut id_len_mismatch = None; + let mut partition_changes = None; + let mut lsp_errors = None; + let mut spf_runs = None; + if let Some(state) = &instance.state { + let counters = state.counters.get(*level); + corrupted_lsps = Some(counters.corrupted_lsps); + authentication_type_fails = Some(counters.auth_type_fails); + authentication_fails = Some(counters.auth_fails); + database_overload = Some(counters.database_overload); + own_lsp_purge = Some(counters.own_lsp_purge); + manual_address_drop_from_area = Some(counters.manual_addr_drop_from_area); + max_sequence = Some(counters.max_sequence); + sequence_number_skipped = Some(counters.seqno_skipped); + id_len_mismatch = Some(counters.id_len_mismatch); + partition_changes = Some(counters.partition_changes); + lsp_errors = Some(counters.lsp_errors); + spf_runs = Some(counters.spf_runs); + } + Box::new(Level { + level: *level as u8, + corrupted_lsps, + authentication_type_fails, + authentication_fails, + database_overload, + own_lsp_purge, + manual_address_drop_from_area, + max_sequence, + sequence_number_skipped, + id_len_mismatch, + partition_changes, + lsp_errors, + spf_runs, + }) + }) + .path(isis::interfaces::interface::PATH) + .get_iterate(|instance, _args| { + let iter = instance.arenas.interfaces.iter().map(ListEntry::Interface); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::interfaces::interface::Interface; + let iface = args.list_entry.as_interface().unwrap(); + let state = if iface.state.active { "up" } else { "down" }; + Box::new(Interface { + name: Cow::Borrowed(&iface.name), + discontinuity_time: Some(Cow::Borrowed(&iface.state.discontinuity_time)).ignore_in_testing(), + state: Some(state.into()), + circuit_id: Some(iface.state.circuit_id), + }) + }) + .path(isis::interfaces::interface::adjacencies::adjacency::PATH) + .get_iterate(|instance, args| { + let iface = args.parent_list_entry.as_interface().unwrap(); + let iter = iface.state.lan_adjacencies.l1.iter(&instance.arenas.adjacencies).chain(iface.state.lan_adjacencies.l2.iter(&instance.arenas.adjacencies)).chain(iface.state.p2p_adjacency.as_ref()).map(ListEntry::Adjacency); + Some(Box::new(iter)) + }) + .get_object(|_instance, args| { + use isis::interfaces::interface::adjacencies::adjacency::Adjacency; + let adj = args.list_entry.as_adjacency().unwrap(); + Box::new(Adjacency { + neighbor_sys_type: Some(adj.level_capability.to_yang()), + neighbor_sysid: Some(adj.system_id.to_yang()), + neighbor_extended_circuit_id: None, + neighbor_snpa: Some(Cow::Owned(format_mac(&adj.snpa))), + usage: Some(adj.level_usage.to_yang()), + hold_timer: adj.holdtimer.as_ref().map(|task| task.remaining()).map(Cow::Owned).ignore_in_testing(), + neighbor_priority: adj.priority, + lastuptime: adj.last_uptime.as_ref().map(Cow::Borrowed).ignore_in_testing(), + state: Some(adj.state.to_yang()), + }) + }) + .path(isis::interfaces::interface::event_counters::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::event_counters::EventCounters; + let iface = args.list_entry.as_interface().unwrap(); + Box::new(EventCounters { + adjacency_changes: Some(iface.state.event_counters.adjacency_changes).ignore_in_testing(), + adjacency_number: Some(iface.state.event_counters.adjacency_number).ignore_in_testing(), + init_fails: Some(iface.state.event_counters.init_fails).ignore_in_testing(), + adjacency_rejects: Some(iface.state.event_counters.adjacency_rejects).ignore_in_testing(), + id_len_mismatch: Some(iface.state.event_counters.id_len_mismatch).ignore_in_testing(), + max_area_addresses_mismatch: Some(iface.state.event_counters.max_area_addr_mismatch).ignore_in_testing(), + authentication_type_fails: Some(iface.state.event_counters.auth_type_fails).ignore_in_testing(), + authentication_fails: Some(iface.state.event_counters.auth_fails).ignore_in_testing(), + lan_dis_changes: Some(iface.state.event_counters.lan_dis_changes).ignore_in_testing(), + }) + }) + .path(isis::interfaces::interface::packet_counters::level::PATH) + .get_iterate(|_instance, args| { + let iface = args.parent_list_entry.as_interface().unwrap(); + let iter = [LevelNumber::L1, LevelNumber::L2].into_iter().map(|level| ListEntry::InterfacePacketCounters(iface, level)); + Some(Box::new(iter) as _).ignore_in_testing() + }) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::Level; + let (_, level) = args.list_entry.as_interface_packet_counters().unwrap(); + Box::new(Level { + level: *level as u8, + }) + }) + .path(isis::interfaces::interface::packet_counters::level::iih::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::iih::Iih; + let (iface, level) = args.list_entry.as_interface_packet_counters().unwrap(); + let packet_counters = iface.state.packet_counters.get(*level); + Box::new(Iih { + r#in: Some(packet_counters.iih_in), + out: Some(packet_counters.iih_out.load(atomic::Ordering::Relaxed)), + }) + }) + .path(isis::interfaces::interface::packet_counters::level::lsp::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::lsp::Lsp; + let (iface, level) = args.list_entry.as_interface_packet_counters().unwrap(); + let packet_counters = iface.state.packet_counters.get(*level); + Box::new(Lsp { + r#in: Some(packet_counters.lsp_in), + out: Some(packet_counters.lsp_out), + }) + }) + .path(isis::interfaces::interface::packet_counters::level::psnp::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::psnp::Psnp; + let (iface, level) = args.list_entry.as_interface_packet_counters().unwrap(); + let packet_counters = iface.state.packet_counters.get(*level); + Box::new(Psnp { + r#in: Some(packet_counters.psnp_in), + out: Some(packet_counters.psnp_out), + }) + }) + .path(isis::interfaces::interface::packet_counters::level::csnp::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::csnp::Csnp; + let (iface, level) = args.list_entry.as_interface_packet_counters().unwrap(); + let packet_counters = iface.state.packet_counters.get(*level); + Box::new(Csnp { + r#in: Some(packet_counters.csnp_in), + out: Some(packet_counters.csnp_out), + }) + }) + .path(isis::interfaces::interface::packet_counters::level::unknown::PATH) + .get_object(|_instance, args| { + use isis::interfaces::interface::packet_counters::level::unknown::Unknown; + let (iface, level) = args.list_entry.as_interface_packet_counters().unwrap(); + let packet_counters = iface.state.packet_counters.get(*level); + Box::new(Unknown { + r#in: Some(packet_counters.unknown_in), + }) + }) + .build() +} + +// ===== impl Instance ===== + +impl Provider for Instance { + const STATE_PATH: &'static str = "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-bgp:bgp'][name='test']/ietf-isis:isis"; + + type ListEntry<'a> = ListEntry<'a>; + + fn callbacks() -> Option<&'static Callbacks> { + Some(&CALLBACKS) + } +} + +// ===== impl ListEntry ===== + +impl ListEntryKind for ListEntry<'_> {} + +// ===== helper functions ===== + +fn format_mac(mac: &[u8; 6]) -> String { + format!( + "{:02x}{:02x}.{:02x}{:02x}.{:02x}{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ) +} diff --git a/holo-isis/src/northbound/yang.rs b/holo-isis/src/northbound/yang.rs new file mode 100644 index 00000000..143d82d1 --- /dev/null +++ b/holo-isis/src/northbound/yang.rs @@ -0,0 +1,198 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::borrow::Cow; + +use holo_yang::{ToYang, TryFromYang}; +use regex::Regex; + +use crate::adjacency::{AdjacencyEvent, AdjacencyState}; +use crate::error::AdjacencyRejectError; +use crate::interface::InterfaceType; +use crate::lsdb::LspLogReason; +use crate::northbound::configuration::MetricType; +use crate::packet::{AreaAddr, LanId, LevelType, LspId, SystemId}; + +// ===== ToYang implementations ===== + +impl ToYang for LevelType { + fn to_yang(&self) -> Cow<'static, str> { + match self { + LevelType::L1 => "level-1".into(), + LevelType::L2 => "level-2".into(), + LevelType::All => "level-all".into(), + } + } +} + +impl ToYang for SystemId { + fn to_yang(&self) -> Cow<'static, str> { + let bytes = self.as_ref(); + Cow::Owned(format!( + "{:02X}{:02X}.{:02X}{:02X}.{:02X}{:02X}", + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5] + )) + } +} + +impl ToYang for LanId { + fn to_yang(&self) -> Cow<'static, str> { + Cow::Owned(format!( + "{}.{:02}", + self.system_id.to_yang(), + self.pseudonode, + )) + } +} + +impl ToYang for LspId { + fn to_yang(&self) -> Cow<'static, str> { + Cow::Owned(format!( + "{}.{:02}-{:02}", + self.system_id.to_yang(), + self.pseudonode, + self.fragment, + )) + } +} + +impl ToYang for AdjacencyState { + fn to_yang(&self) -> Cow<'static, str> { + match self { + AdjacencyState::Down => "down".into(), + AdjacencyState::Initializing => "init".into(), + AdjacencyState::Up => "up".into(), + } + } +} + +impl ToYang for AdjacencyEvent { + // The "reason" leaf in the "adjacency-state-change" notification uses a + // primitive "string" type, allowing us to define error reasons freely. + fn to_yang(&self) -> Cow<'static, str> { + match self { + AdjacencyEvent::HelloOneWayRcvd => "hello-one-way".into(), + AdjacencyEvent::HelloTwoWayRcvd => "hello-two-way".into(), + AdjacencyEvent::HoldtimeExpired => "hold-time-expired".into(), + AdjacencyEvent::LinkDown => "link-down".into(), + AdjacencyEvent::Kill => "kill".into(), + } + } +} + +impl ToYang for AdjacencyRejectError { + // The "reason" leaf in the "rejected-adjacency" notification uses a + // primitive "string" type, allowing us to define error reasons freely. + fn to_yang(&self) -> Cow<'static, str> { + match self { + AdjacencyRejectError::InvalidHelloType => { + "invalid-hello-type".into() + } + AdjacencyRejectError::CircuitTypeMismatch => { + "circuit-type-mismatch".into() + } + AdjacencyRejectError::MaxAreaAddrsMismatch(..) => { + "max-area-addresses-mismatch".into() + } + AdjacencyRejectError::AreaMismatch => "area-mismatch".into(), + AdjacencyRejectError::WrongSystem => "wrong-system".into(), + AdjacencyRejectError::DuplicateSystemId => { + "duplicate-system-id".into() + } + } + } +} + +impl ToYang for LspLogReason { + fn to_yang(&self) -> Cow<'static, str> { + match self { + LspLogReason::Refresh => "refresh".into(), + LspLogReason::ContentChange => "content-change".into(), + } + } +} + +// ===== TryFromYang implementations ===== + +impl TryFromYang for LevelType { + fn try_from_yang(value: &str) -> Option { + match value { + "level-1" => Some(LevelType::L1), + "level-2" => Some(LevelType::L2), + "level-all" => Some(LevelType::All), + _ => None, + } + } +} + +impl TryFromYang for AreaAddr { + fn try_from_yang(value: &str) -> Option { + // Define the regex pattern to match an area address. + let re = regex::Regex::new(r"^[0-9A-Fa-f]{2}(\.[0-9A-Fa-f]{4}){0,6}$") + .ok()?; + if !re.is_match(value) { + return None; + } + + // Remove the dots and convert the hex string into a vector of bytes. + let area_addr = value.replace('.', ""); + let bytes = (0..area_addr.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&area_addr[i..i + 2], 16).unwrap()) + .collect(); + + Some(AreaAddr::new(bytes)) + } +} + +impl TryFromYang for SystemId { + fn try_from_yang(value: &str) -> Option { + // Initialize an array to hold the parsed bytes. + let mut bytes = [0u8; 6]; + + // Define the regex pattern to match a System ID. + let re = Regex::new( + r"^([0-9A-Fa-f]{4})\.([0-9A-Fa-f]{4})\.([0-9A-Fa-f]{4})$", + ) + .ok()?; + + // Apply the regex to the input string. + let caps = re.captures(value)?; + + // Convert each 4-character group to 2 bytes and populate the byte array. + for i in 0..3 { + let group_str = caps.get(i + 1).unwrap().as_str(); + bytes[i * 2] = u8::from_str_radix(&group_str[0..2], 16).ok()?; + bytes[i * 2 + 1] = u8::from_str_radix(&group_str[2..4], 16).ok()?; + } + + Some(SystemId::from(bytes)) + } +} + +impl TryFromYang for InterfaceType { + fn try_from_yang(value: &str) -> Option { + match value { + "broadcast" => Some(InterfaceType::Broadcast), + "point-to-point" => Some(InterfaceType::PointToPoint), + _ => None, + } + } +} + +impl TryFromYang for MetricType { + fn try_from_yang(value: &str) -> Option { + match value { + "wide-only" => Some(MetricType::Wide), + "old-only" => Some(MetricType::Standard), + "both" => Some(MetricType::Both), + _ => None, + } + } +} diff --git a/holo-isis/src/packet/consts.rs b/holo-isis/src/packet/consts.rs new file mode 100644 index 00000000..58b65048 --- /dev/null +++ b/holo-isis/src/packet/consts.rs @@ -0,0 +1,89 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use bitflags::bitflags; +use num_derive::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; + +pub const IDRP_DISCRIMINATOR: u8 = 0x83; +pub const VERSION_PROTO_EXT: u8 = 1; +pub const VERSION: u8 = 1; +pub const SYSTEM_ID_LEN: u8 = 6; + +// IS-IS PDU types. +// +// IANA registry: +// https://www.iana.org/assignments/isis-pdu/isis-pdu.xhtml#pdu +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum PduType { + HelloLanL1 = 15, + HelloLanL2 = 16, + HelloP2P = 17, + LspL1 = 18, + LspL2 = 20, + CsnpL1 = 24, + CsnpL2 = 25, + PsnpL1 = 26, + PsnpL2 = 27, +} + +// IS-IS top-level TLV types. +// +// IANA registry: +// https://www.iana.org/assignments/isis-tlv-codepoints/isis-tlv-codepoints.xhtml#tlv-codepoints +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum TlvType { + AreaAddresses = 1, + IsReach = 2, + Neighbors = 6, + Padding = 8, + LspEntries = 9, + ExtIsReach = 22, + Ipv4InternalReach = 128, + ProtocolsSupported = 129, + Ipv4ExternalReach = 130, + Ipv4Addresses = 132, + ExtIpv4Reach = 135, +} + +// IS-IS Sub-TLVs for TLVs Advertising Neighbor Information. +// +// IANA registry: +// https://www.iana.org/assignments/isis-tlv-codepoints/isis-tlv-codepoints.xhtml#isis-tlv-codepoints-advertising-neighbor-information +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum NeighborSubTlvType {} + +// IS-IS Sub-TLVs for TLVs Advertising Prefix Reachability. +// +// IANA registry: +// https://www.iana.org/assignments/isis-tlv-codepoints/isis-tlv-codepoints.xhtml#isis-tlv-codepoints-advertising-prefix-reachability +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum PrefixSubTlvType {} + +// IS-IS LSP flags field. +bitflags! { + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + #[derive(Deserialize, Serialize)] + #[serde(transparent)] + pub struct LspFlags: u8 { + const P = 0x80; + const ATT = 0x40; + const OL = 0x04; + const IS_TYPE2 = 0x2; + const IS_TYPE1 = 0x1; + } +} diff --git a/holo-isis/src/packet/error.rs b/holo-isis/src/packet/error.rs new file mode 100644 index 00000000..6803cbed --- /dev/null +++ b/holo-isis/src/packet/error.rs @@ -0,0 +1,76 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use serde::{Deserialize, Serialize}; + +// Type aliases. +pub type DecodeResult = Result; + +// IS-IS message decoding errors. +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub enum DecodeError { + IncompletePdu, + InvalidHeaderLength(u8), + InvalidIrdpDiscriminator(u8), + InvalidVersion(u8), + InvalidIdLength(u8), + UnknownPduType(u8), + InvalidPduLength(u16), + InvalidTlvLength(u8), + // Hello + InvalidHelloCircuitType(u8), + InvalidHelloHoldtime(u16), + // TLVs + InvalidAreaAddrLen(u8), +} + +// ===== impl DecodeError ===== + +impl std::fmt::Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DecodeError::IncompletePdu => { + write!(f, "incomplete PDU") + } + DecodeError::InvalidHeaderLength(hdr_len) => { + write!(f, "invalid header length: {}", hdr_len) + } + DecodeError::InvalidIrdpDiscriminator(discriminator) => { + write!(f, "invalid IDRP discriminator: {}", discriminator) + } + DecodeError::InvalidVersion(version) => { + write!(f, "invalid version: {}", version) + } + DecodeError::InvalidIdLength(id_len) => { + write!(f, "invalid ID length: {}", id_len) + } + DecodeError::UnknownPduType(pdu_type) => { + write!(f, "unknown PDU type: {}", pdu_type) + } + DecodeError::InvalidPduLength(pdu_len) => { + write!(f, "invalid PDU length: {}", pdu_len) + } + DecodeError::InvalidTlvLength(tlv_len) => { + write!(f, "invalid TLV length: {}", tlv_len) + } + DecodeError::InvalidHelloCircuitType(circuit_type) => { + write!(f, "invalid hello circuit type: {}", circuit_type) + } + DecodeError::InvalidHelloHoldtime(holdtime) => { + write!(f, "invalid hello holdtime: {}", holdtime) + } + DecodeError::InvalidAreaAddrLen(area_len) => { + write!(f, "invalid area address length: {}", area_len) + } + } + } +} + +impl std::error::Error for DecodeError {} diff --git a/holo-isis/src/packet/mod.rs b/holo-isis/src/packet/mod.rs new file mode 100644 index 00000000..0c2fb850 --- /dev/null +++ b/holo-isis/src/packet/mod.rs @@ -0,0 +1,288 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +pub mod consts; +pub mod error; +pub mod pdu; +pub mod tlv; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; + +// Represent an IS-IS level, or a combination of both of them. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum LevelType { + L1, + L2, + All, +} + +// Represents a single IS-IS level. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum LevelNumber { + L1 = 1, + L2 = 2, +} + +// Container for storing separate values for level 1 and level 2. +#[derive(Clone, Debug, Default)] +pub struct Levels { + pub l1: T, + pub l2: T, +} + +// Represents an IS-IS Area Address. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct AreaAddr(SmallVec<[u8; 13]>); + +// Represents an IS-IS System ID. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct SystemId([u8; 6]); + +// Represents an IS-IS LAN ID. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct LanId { + pub system_id: SystemId, + pub pseudonode: u8, +} + +// Represents an IS-IS LSP ID. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct LspId { + pub system_id: SystemId, + pub pseudonode: u8, + pub fragment: u8, +} + +// ===== impl LevelType ===== + +impl LevelType { + pub(crate) fn intersects(&self, level: impl Into) -> bool { + let level = level.into(); + match self { + LevelType::L1 => matches!(level, LevelType::L1 | LevelType::All), + LevelType::L2 => matches!(level, LevelType::L2 | LevelType::All), + LevelType::All => true, + } + } + + pub(crate) fn intersection( + &self, + level: impl Into, + ) -> Option { + let level = level.into(); + match (self, level) { + (LevelType::L1, LevelType::L1) => Some(LevelType::L1), + (LevelType::L2, LevelType::L2) => Some(LevelType::L2), + (LevelType::All, _) => Some(level), + (_, LevelType::All) => Some(*self), + _ => None, + } + } + + pub(crate) fn union(&self, level: impl Into) -> LevelType { + let level = level.into(); + match (self, level) { + (LevelType::L1, LevelType::L1) => LevelType::L1, + (LevelType::L2, LevelType::L2) => LevelType::L2, + (LevelType::L1, LevelType::L2) | (LevelType::L2, LevelType::L1) => { + LevelType::All + } + (LevelType::All, _) | (_, LevelType::All) => LevelType::All, + } + } +} + +impl From for LevelType { + fn from(level: LevelNumber) -> LevelType { + match level { + LevelNumber::L1 => LevelType::L1, + LevelNumber::L2 => LevelType::L2, + } + } +} + +impl From<&LevelNumber> for LevelType { + fn from(level: &LevelNumber) -> LevelType { + match level { + LevelNumber::L1 => LevelType::L1, + LevelNumber::L2 => LevelType::L2, + } + } +} + +// ===== impl LevelNumber ===== + +impl From for LevelNumber { + fn from(level_type: LevelType) -> LevelNumber { + match level_type { + LevelType::L1 => LevelNumber::L1, + LevelType::L2 => LevelNumber::L2, + LevelType::All => unreachable!(), + } + } +} + +impl From<&LevelType> for LevelNumber { + fn from(level_type: &LevelType) -> LevelNumber { + match level_type { + LevelType::L1 => LevelNumber::L1, + LevelType::L2 => LevelNumber::L2, + LevelType::All => unreachable!(), + } + } +} + +// ===== impl Levels ===== + +impl Levels { + pub(crate) fn get(&self, level: impl Into) -> &T { + let level = level.into(); + match level { + LevelNumber::L1 => &self.l1, + LevelNumber::L2 => &self.l2, + } + } + + pub(crate) fn get_mut(&mut self, level: impl Into) -> &mut T { + let level = level.into(); + match level { + LevelNumber::L1 => &mut self.l1, + LevelNumber::L2 => &mut self.l2, + } + } +} + +// ===== impl AreaAddr ===== + +impl AreaAddr { + pub const MAX_LEN: u8 = 13; + + pub(crate) fn new(bytes: SmallVec<[u8; 13]>) -> Self { + AreaAddr(bytes) + } +} + +impl AsRef<[u8]> for AreaAddr { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<&[u8]> for AreaAddr { + fn from(bytes: &[u8]) -> AreaAddr { + AreaAddr(SmallVec::from_slice(bytes)) + } +} + +// ===== impl SystemId ===== + +impl SystemId { + pub(crate) fn decode(buf: &mut Bytes) -> Self { + let mut system_id = [0; 6]; + buf.copy_to_slice(&mut system_id); + SystemId(system_id) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + buf.put_slice(&self.0); + } +} + +impl AsRef<[u8]> for SystemId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; 6]> for SystemId { + fn from(bytes: [u8; 6]) -> SystemId { + SystemId(bytes) + } +} + +// ===== impl LanId ===== + +impl LanId { + pub(crate) fn decode(buf: &mut Bytes) -> Self { + let mut bytes = [0; 7]; + buf.copy_to_slice(&mut bytes); + Self::from(bytes) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + self.system_id.encode(buf); + buf.put_u8(self.pseudonode); + } +} + +impl From<[u8; 7]> for LanId { + fn from(bytes: [u8; 7]) -> LanId { + LanId { + system_id: SystemId::from([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], + ]), + pseudonode: bytes[6], + } + } +} + +impl From<(SystemId, u8)> for LanId { + fn from(components: (SystemId, u8)) -> LanId { + LanId { + system_id: components.0, + pseudonode: components.1, + } + } +} + +// ===== impl LspId ===== + +impl LspId { + pub(crate) fn decode(buf: &mut Bytes) -> Self { + let mut bytes = [0; 8]; + buf.copy_to_slice(&mut bytes); + Self::from(bytes) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + self.system_id.encode(buf); + buf.put_u8(self.pseudonode); + buf.put_u8(self.fragment); + } +} + +impl From<[u8; 8]> for LspId { + fn from(bytes: [u8; 8]) -> LspId { + LspId { + system_id: SystemId::from([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], + ]), + pseudonode: bytes[6], + fragment: bytes[7], + } + } +} + +impl From<(SystemId, u8, u8)> for LspId { + fn from(components: (SystemId, u8, u8)) -> LspId { + LspId { + system_id: components.0, + pseudonode: components.1, + fragment: components.2, + } + } +} diff --git a/holo-isis/src/packet/pdu.rs b/holo-isis/src/packet/pdu.rs new file mode 100644 index 00000000..4d3c6f5f --- /dev/null +++ b/holo-isis/src/packet/pdu.rs @@ -0,0 +1,1070 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::cell::{RefCell, RefMut}; +use std::net::Ipv4Addr; +use std::time::Instant; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use holo_utils::bytes::TLS_BUF; +use num_traits::FromPrimitive; +use serde::{Deserialize, Serialize}; + +use crate::packet::consts::{ + LspFlags, PduType, TlvType, IDRP_DISCRIMINATOR, SYSTEM_ID_LEN, VERSION, + VERSION_PROTO_EXT, +}; +use crate::packet::error::{DecodeError, DecodeResult}; +use crate::packet::tlv::{ + tlv_entries_split, tlv_take_max, AreaAddressesTlv, ExtIpv4Reach, + ExtIpv4ReachTlv, ExtIsReach, ExtIsReachTlv, Ipv4AddressesTlv, Ipv4Reach, + Ipv4ReachTlv, IsReach, IsReachTlv, LspEntriesTlv, LspEntry, NeighborsTlv, + PaddingTlv, ProtocolsSupportedTlv, Tlv, UnknownTlv, TLV_HDR_SIZE, +}; +use crate::packet::{AreaAddr, LanId, LevelNumber, LevelType, LspId, SystemId}; + +// IS-IS PDU. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum Pdu { + Hello(Hello), + Lsp(Lsp), + Snp(Snp), +} + +// IS-IS PDU common header. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Header { + pub pdu_type: PduType, + pub max_area_addrs: u8, +} + +// IS-IS Hello PDU. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Hello { + pub hdr: Header, + pub circuit_type: LevelType, + pub source: SystemId, + pub holdtime: u16, + pub variant: HelloVariant, + pub tlvs: HelloTlvs, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum HelloVariant { + Lan { priority: u8, lan_id: LanId }, + P2P { local_circuit_id: u8 }, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct HelloTlvs { + pub protocols_supported: Option, + pub area_addrs: Vec, + pub neighbors: Vec, + pub ipv4_addrs: Vec, + pub padding: Vec, + pub unknown: Vec, +} + +// IS-IS Link State PDU. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Lsp { + pub hdr: Header, + pub rem_lifetime: u16, + pub lsp_id: LspId, + pub seqno: u32, + pub cksum: u16, + pub flags: LspFlags, + pub tlvs: LspTlvs, + #[cfg_attr(feature = "testing", serde(default, skip_serializing))] + pub raw: Bytes, + // Time the LSP was created or received. When combined with the Remaining + // Lifetime field, the actual LSP remaining lifetime can be determined. + #[serde(skip)] + pub base_time: Option, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct LspTlvs { + pub protocols_supported: Option, + pub area_addrs: Vec, + pub is_reach: Vec, + pub ext_is_reach: Vec, + pub ipv4_addrs: Vec, + pub ipv4_internal_reach: Vec, + pub ipv4_external_reach: Vec, + pub ext_ipv4_reach: Vec, + pub unknown: Vec, +} + +// IS-IS Sequence Numbers PDU. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Snp { + pub hdr: Header, + pub source: LanId, + pub summary: Option<(LspId, LspId)>, + pub tlvs: SnpTlvs, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct SnpTlvs { + pub lsp_entries: Vec, + pub unknown: Vec, +} + +// ===== impl Pdu ===== + +impl Pdu { + // Decodes IS-IS PDU from a bytes buffer. + pub fn decode(mut buf: Bytes) -> DecodeResult { + let packet_len = buf.len(); + + // Decode PDU common header. + let hdr = Header::decode(&mut buf)?; + + // Decode PDU-specific fields. + let pdu = match hdr.pdu_type { + PduType::HelloLanL1 | PduType::HelloLanL2 | PduType::HelloP2P => { + Pdu::Hello(Hello::decode(hdr, packet_len, &mut buf)?) + } + PduType::LspL1 | PduType::LspL2 => { + Pdu::Lsp(Lsp::decode(hdr, packet_len, &mut buf)?) + } + PduType::CsnpL1 + | PduType::CsnpL2 + | PduType::PsnpL1 + | PduType::PsnpL2 => { + Pdu::Snp(Snp::decode(hdr, packet_len, &mut buf)?) + } + }; + + Ok(pdu) + } + + // Encodes IS-IS PDU into a bytes buffer. + pub fn encode(&self) -> Bytes { + match self { + Pdu::Hello(pdu) => pdu.encode(), + Pdu::Lsp(pdu) => pdu.raw.clone(), + Pdu::Snp(pdu) => pdu.encode(), + } + } + + // Returns the IS-IS PDU type. + pub const fn pdu_type(&self) -> PduType { + match self { + Pdu::Hello(pdu) => pdu.hdr.pdu_type, + Pdu::Lsp(pdu) => pdu.hdr.pdu_type, + Pdu::Snp(pdu) => pdu.hdr.pdu_type, + } + } +} + +// ===== impl Header ===== + +impl Header { + const LEN: u8 = 8; + + pub const fn new(pdu_type: PduType) -> Self { + Header { + pdu_type, + max_area_addrs: 0, + } + } + + // Decodes IS-IS PDU header from a bytes buffer. + pub fn decode(buf: &mut Bytes) -> DecodeResult { + let packet_len = buf.len(); + + // Ensure the packet has enough data for the fixed-length IS-IS header. + if packet_len < Self::LEN as _ { + return Err(DecodeError::IncompletePdu); + } + + // Parse IDRP discriminator. + let idrp_discr = buf.get_u8(); + if idrp_discr != IDRP_DISCRIMINATOR { + return Err(DecodeError::InvalidIrdpDiscriminator(idrp_discr)); + } + + // Parse length of fixed header. + let fixed_header_length = buf.get_u8(); + + // Parse version/protocol ID extension. + let version_proto_ext = buf.get_u8(); + if version_proto_ext != VERSION_PROTO_EXT { + return Err(DecodeError::InvalidVersion(version_proto_ext)); + } + + // Parse ID length. + let id_len = buf.get_u8(); + if id_len != 0 && id_len != SYSTEM_ID_LEN { + return Err(DecodeError::InvalidIdLength(id_len)); + } + + // Parse PDU type. + let pdu_type = buf.get_u8(); + let pdu_type = match PduType::from_u8(pdu_type) { + Some(pdu_type) => pdu_type, + None => return Err(DecodeError::UnknownPduType(pdu_type)), + }; + + // Additional sanity checks. + if fixed_header_length != Self::fixed_header_length(pdu_type) { + return Err(DecodeError::InvalidHeaderLength(fixed_header_length)); + } + if packet_len < fixed_header_length as _ { + return Err(DecodeError::IncompletePdu); + } + + // Parse version. + let version = buf.get_u8(); + if version != VERSION { + return Err(DecodeError::InvalidVersion(version)); + } + + // Parse reserved field. + let _reserved = buf.get_u8(); + + // Parse maximum area addresses. + let max_area_addrs = buf.get_u8(); + + Ok(Header { + pdu_type, + max_area_addrs, + }) + } + + // Encodes IS-IS PDU header into a bytes buffer. + fn encode(&self, buf: &mut BytesMut) { + // Encode IDRP discriminator. + buf.put_u8(IDRP_DISCRIMINATOR); + // Encode length of fixed header. + buf.put_u8(Self::fixed_header_length(self.pdu_type)); + // Encode version/protocol ID extension. + buf.put_u8(VERSION_PROTO_EXT); + // Encode ID length (use default value). + buf.put_u8(0); + // Encode PDU type. + buf.put_u8(self.pdu_type as u8); + // Encode version. + buf.put_u8(VERSION); + // Encode reserved field. + buf.put_u8(0); + // Encode maximum area addresses. + buf.put_u8(self.max_area_addrs); + } + + // Returns the length of the fixed header for a given PDU type. + const fn fixed_header_length(pdu_type: PduType) -> u8 { + match pdu_type { + PduType::HelloLanL1 | PduType::HelloLanL2 => Hello::HEADER_LEN_LAN, + PduType::HelloP2P => Hello::HEADER_LEN_P2P, + PduType::LspL1 | PduType::LspL2 => Lsp::HEADER_LEN, + PduType::CsnpL1 | PduType::CsnpL2 => Snp::CSNP_HEADER_LEN, + PduType::PsnpL1 | PduType::PsnpL2 => Snp::PSNP_HEADER_LEN, + } + } +} + +// ===== impl Hello ===== + +impl Hello { + const HEADER_LEN_LAN: u8 = 27; + const HEADER_LEN_P2P: u8 = 20; + const CIRCUIT_TYPE_MASK: u8 = 0x03; + const PRIORITY_MASK: u8 = 0x7F; + + pub fn new( + level_type: LevelType, + circuit_type: LevelType, + source: SystemId, + holdtime: u16, + variant: HelloVariant, + tlvs: HelloTlvs, + ) -> Self { + let pdu_type = match level_type { + LevelType::L1 => PduType::HelloLanL1, + LevelType::L2 => PduType::HelloLanL2, + LevelType::All => PduType::HelloP2P, + }; + Hello { + hdr: Header::new(pdu_type), + circuit_type, + source, + holdtime, + variant, + tlvs, + } + } + + fn decode( + hdr: Header, + packet_len: usize, + buf: &mut Bytes, + ) -> DecodeResult { + // Parse circuit type. + let circuit_type = buf.get_u8() & Self::CIRCUIT_TYPE_MASK; + let circuit_type = match circuit_type { + 1 if hdr.pdu_type != PduType::HelloLanL2 => LevelType::L1, + 2 if hdr.pdu_type != PduType::HelloLanL1 => LevelType::L2, + 3 => LevelType::All, + _ => { + return Err(DecodeError::InvalidHelloCircuitType(circuit_type)); + } + }; + + // Parse source ID. + let source = SystemId::decode(buf); + + // Parse holding time. + let holdtime = buf.get_u16(); + if holdtime == 0 { + return Err(DecodeError::InvalidHelloHoldtime(holdtime)); + } + + // Parse PDU length. + let pdu_len = buf.get_u16(); + if pdu_len != packet_len as u16 { + return Err(DecodeError::InvalidPduLength(pdu_len)); + } + + // Parse custom fields. + let variant = if hdr.pdu_type == PduType::HelloP2P { + // Parse local circuit ID. + let local_circuit_id = buf.get_u8(); + + HelloVariant::P2P { local_circuit_id } + } else { + // Parse priority. + let priority = buf.get_u8() & Self::PRIORITY_MASK; + // Parse LAN ID. + let lan_id = LanId::decode(buf); + + HelloVariant::Lan { priority, lan_id } + }; + + // Parse top-level TLVs. + let mut tlvs = HelloTlvs::default(); + while buf.remaining() >= TLV_HDR_SIZE { + // Parse TLV type. + let tlv_type = buf.get_u8(); + let tlv_etype = TlvType::from_u8(tlv_type); + + // Parse and validate TLV length. + let tlv_len = buf.get_u8(); + if tlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(tlv_len as usize); + match tlv_etype { + Some(TlvType::AreaAddresses) => { + let tlv = AreaAddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.area_addrs.push(tlv); + } + Some(TlvType::Neighbors) + if hdr.pdu_type != PduType::HelloP2P => + { + let tlv = NeighborsTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.neighbors.push(tlv); + } + Some(TlvType::Padding) => { + let tlv = PaddingTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.padding.push(tlv); + } + Some(TlvType::ProtocolsSupported) => { + if tlvs.protocols_supported.is_some() { + continue; + } + let tlv = + ProtocolsSupportedTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.protocols_supported = Some(tlv); + } + Some(TlvType::Ipv4Addresses) => { + let tlv = Ipv4AddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv4_addrs.push(tlv); + } + _ => { + // Save unknown top-level TLV. + let value = buf_tlv.copy_to_bytes(tlv_len as usize); + tlvs.unknown + .push(UnknownTlv::new(tlv_type, tlv_len, value)); + } + } + } + + Ok(Hello { + hdr, + circuit_type, + source, + holdtime, + variant, + tlvs, + }) + } + + fn encode(&self) -> Bytes { + TLS_BUF.with(|buf| { + let mut buf = pdu_encode_start(buf, &self.hdr); + + let circuit_type = match self.circuit_type { + LevelType::L1 => 1, + LevelType::L2 => 2, + LevelType::All => 3, + }; + buf.put_u8(circuit_type); + self.source.encode(&mut buf); + buf.put_u16(self.holdtime); + + // The PDU length will be initialized later. + let len_pos = buf.len(); + buf.put_u16(0); + + match self.variant { + HelloVariant::Lan { priority, lan_id } => { + buf.put_u8(priority); + lan_id.encode(&mut buf); + } + HelloVariant::P2P { local_circuit_id } => { + buf.put_u8(local_circuit_id); + } + } + + // Encode TLVs. + if let Some(tlv) = &self.tlvs.protocols_supported { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.area_addrs { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.neighbors { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.ipv4_addrs { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.padding { + tlv.encode(&mut buf); + } + + pdu_encode_end(buf, len_pos) + }) + } +} + +impl HelloTlvs { + pub(crate) fn new( + protocols_supported: impl IntoIterator, + area_addrs: impl IntoIterator, + neighbors: impl IntoIterator, + ipv4_addrs: impl IntoIterator, + ) -> Self { + HelloTlvs { + protocols_supported: Some(ProtocolsSupportedTlv::from( + protocols_supported, + )), + area_addrs: tlv_entries_split(area_addrs), + neighbors: tlv_entries_split(neighbors), + ipv4_addrs: tlv_entries_split(ipv4_addrs), + padding: Default::default(), + unknown: Default::default(), + } + } + + // Returns an iterator over all area addresses from TLVs of type 1. + pub(crate) fn area_addrs(&self) -> impl Iterator { + self.area_addrs.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IS neighbors from TLVs of type 6. + pub(crate) fn neighbors(&self) -> impl Iterator { + self.neighbors.iter().flat_map(|tlv| tlv.list.iter()) + } +} + +// ===== impl Lsp ===== + +impl Lsp { + pub const HEADER_LEN: u8 = 27; + + pub fn new( + level: LevelNumber, + rem_lifetime: u16, + lsp_id: LspId, + seqno: u32, + flags: LspFlags, + tlvs: LspTlvs, + ) -> Self { + let pdu_type = match level { + LevelNumber::L1 => PduType::LspL1, + LevelNumber::L2 => PduType::LspL2, + }; + let mut lsp = Lsp { + hdr: Header::new(pdu_type), + rem_lifetime, + lsp_id, + seqno, + cksum: 0, + flags, + tlvs, + raw: Default::default(), + base_time: lsp_base_time(), + }; + lsp.encode(); + lsp + } + + fn decode( + hdr: Header, + packet_len: usize, + buf: &mut Bytes, + ) -> DecodeResult { + // Parse PDU length. + let pdu_len = buf.get_u16(); + if pdu_len != packet_len as u16 { + return Err(DecodeError::InvalidPduLength(pdu_len)); + } + + // Parse remaining lifetime. + let rem_lifetime = buf.get_u16(); + + // Parse LSP ID. + let lsp_id = LspId::decode(buf); + + // Parse sequence number. + let seqno = buf.get_u32(); + + // Parse checksum. + let cksum = buf.get_u16(); + + // Parse flags. + let flags = buf.get_u8(); + let flags = LspFlags::from_bits_truncate(flags); + + // Parse top-level TLVs. + let mut tlvs = LspTlvs::default(); + while buf.remaining() >= TLV_HDR_SIZE { + // Parse TLV type. + let tlv_type = buf.get_u8(); + let tlv_etype = TlvType::from_u8(tlv_type); + + // Parse and validate TLV length. + let tlv_len = buf.get_u8(); + if tlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(tlv_len as usize); + match tlv_etype { + Some(TlvType::AreaAddresses) => { + let tlv = AreaAddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.area_addrs.push(tlv); + } + Some(TlvType::IsReach) => { + let tlv = IsReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.is_reach.push(tlv); + } + Some(TlvType::ExtIsReach) => { + let tlv = ExtIsReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ext_is_reach.push(tlv); + } + Some(TlvType::Ipv4InternalReach) => { + let tlv = Ipv4ReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv4_internal_reach.push(tlv); + } + Some(TlvType::ProtocolsSupported) => { + if tlvs.protocols_supported.is_some() { + continue; + } + let tlv = + ProtocolsSupportedTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.protocols_supported = Some(tlv); + } + Some(TlvType::Ipv4ExternalReach) => { + let tlv = Ipv4ReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv4_external_reach.push(tlv); + } + Some(TlvType::Ipv4Addresses) => { + let tlv = Ipv4AddressesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ipv4_addrs.push(tlv); + } + Some(TlvType::ExtIpv4Reach) => { + let tlv = ExtIpv4ReachTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.ext_ipv4_reach.push(tlv); + } + _ => { + // Save unknown top-level TLV. + let value = buf_tlv.copy_to_bytes(tlv_len as usize); + tlvs.unknown + .push(UnknownTlv::new(tlv_type, tlv_len, value)); + } + } + } + + Ok(Lsp { + hdr, + rem_lifetime, + lsp_id, + seqno, + cksum, + flags, + tlvs, + raw: Default::default(), + base_time: lsp_base_time(), + }) + } + + fn encode(&mut self) -> Bytes { + TLS_BUF.with(|buf| { + let mut buf = pdu_encode_start(buf, &self.hdr); + + // The PDU length will be initialized later. + let len_pos = buf.len(); + buf.put_u16(0); + buf.put_u16(self.rem_lifetime); + self.lsp_id.encode(&mut buf); + buf.put_u32(self.seqno); + // The checksum will be initialized later. + buf.put_u16(0); + buf.put_u8(self.flags.bits()); + + // Encode TLVs. + if let Some(tlv) = &self.tlvs.protocols_supported { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.area_addrs { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.is_reach { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.ext_is_reach { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.ipv4_addrs { + tlv.encode(&mut buf); + } + for tlv in &self.tlvs.ipv4_internal_reach { + tlv.encode(TlvType::Ipv4InternalReach, &mut buf); + } + for tlv in &self.tlvs.ipv4_external_reach { + tlv.encode(TlvType::Ipv4ExternalReach, &mut buf); + } + for tlv in &self.tlvs.ext_ipv4_reach { + tlv.encode(&mut buf); + } + + // Compute LSP checksum. + let cksum = Self::checksum(&buf[12..]); + buf[24..26].copy_from_slice(&cksum); + self.cksum = u16::from_be_bytes(cksum); + + // Store LSP raw data. + let bytes = pdu_encode_end(buf, len_pos); + self.raw = bytes.clone(); + bytes + }) + } + + // Computes the LSP checksum. + fn checksum(data: &[u8]) -> [u8; 2] { + let checksum = fletcher::calc_fletcher16(data); + let mut checkbyte0 = (checksum & 0x00FF) as i32; + let mut checkbyte1 = ((checksum >> 8) & 0x00FF) as i32; + + // Adjust checksum value using scaling factor. + let sop = data.len() as u16 - 13; + let mut x = (sop as i32 * checkbyte0 - checkbyte1) % 255; + if x <= 0 { + x += 255; + } + checkbyte1 = 510 - checkbyte0 - x; + if checkbyte1 > 255 { + checkbyte1 -= 255; + } + checkbyte0 = x; + [checkbyte0 as u8, checkbyte1 as u8] + } + + // Checks if the LSP checksum is valid. + pub(crate) fn is_checksum_valid(&self) -> bool { + // Skip checksum validation in testing mode if the checksum field is set + // to zero. + #[cfg(feature = "testing")] + { + if self.cksum == 0 { + return true; + } + } + + // If both are zero return correct. + if self.raw[24..26] == [0, 0] { + return true; + } + + // If either, but not both are zero return incorrect. + if self.raw[24] == 0 || self.raw[25] == 0 { + return true; + } + + // Skip everything before (and including) the Remaining Lifetime field. + fletcher::calc_fletcher16(&self.raw[12..]) == 0 + } + + // Returns the current LSP remaining lifetime. + pub(crate) fn rem_lifetime(&self) -> u16 { + let mut rem_lifetime = self.rem_lifetime; + + if let Some(base_time) = self.base_time { + let elapsed = u16::try_from(base_time.elapsed().as_secs()) + .unwrap_or(u16::MAX); + rem_lifetime = rem_lifetime.saturating_sub(elapsed); + } + + rem_lifetime + } + + // Updates the LSP remaining lifetime. + pub(crate) fn set_rem_lifetime(&mut self, rem_lifetime: u16) { + // Update Remaining Lifetime field. + self.rem_lifetime = rem_lifetime; + + // Update raw data. + let mut raw = BytesMut::from(self.raw.as_ref()); + raw[10..12].copy_from_slice(&rem_lifetime.to_be_bytes()); + self.raw = raw.freeze(); + + // Update base time. + self.base_time = lsp_base_time(); + } + + // Converts the LSP into an LSP Entry for use in an SNP. + pub(crate) fn as_snp_entry(&self) -> LspEntry { + LspEntry { + rem_lifetime: self.rem_lifetime, + lsp_id: self.lsp_id, + seqno: self.seqno, + cksum: self.cksum, + } + } +} + +impl LspTlvs { + pub(crate) fn new( + protocols_supported: impl IntoIterator, + area_addrs: impl IntoIterator, + is_reach: impl IntoIterator, + ext_is_reach: impl IntoIterator, + ipv4_addrs: impl IntoIterator, + ipv4_internal_reach: impl IntoIterator, + ipv4_external_reach: impl IntoIterator, + ext_ipv4_reach: impl IntoIterator, + ) -> Self { + LspTlvs { + protocols_supported: Some(ProtocolsSupportedTlv::from( + protocols_supported, + )), + area_addrs: tlv_entries_split(area_addrs), + is_reach: tlv_entries_split(is_reach), + ext_is_reach: tlv_entries_split(ext_is_reach), + ipv4_addrs: tlv_entries_split(ipv4_addrs), + ipv4_internal_reach: tlv_entries_split(ipv4_internal_reach), + ipv4_external_reach: tlv_entries_split(ipv4_external_reach), + ext_ipv4_reach: tlv_entries_split(ext_ipv4_reach), + unknown: Default::default(), + } + } + + pub(crate) fn next_chunk(&mut self, max_len: usize) -> Option { + let mut rem_len = max_len; + let protocols_supported = self.protocols_supported.take(); + if let Some(protocols_supported) = &protocols_supported { + rem_len -= protocols_supported.len(); + } + let area_addrs = tlv_take_max(&mut self.area_addrs, &mut rem_len); + let is_reach = tlv_take_max(&mut self.is_reach, &mut rem_len); + let ext_is_reach = tlv_take_max(&mut self.ext_is_reach, &mut rem_len); + let ipv4_addrs = tlv_take_max(&mut self.ipv4_addrs, &mut rem_len); + let ipv4_internal_reach = + tlv_take_max(&mut self.ipv4_internal_reach, &mut rem_len); + let ipv4_external_reach = + tlv_take_max(&mut self.ipv4_external_reach, &mut rem_len); + let ext_ipv4_reach = + tlv_take_max(&mut self.ext_ipv4_reach, &mut rem_len); + if rem_len == max_len { + return None; + } + + Some(LspTlvs { + protocols_supported, + area_addrs, + is_reach, + ext_is_reach, + ipv4_addrs, + ipv4_internal_reach, + ipv4_external_reach, + ext_ipv4_reach, + unknown: Default::default(), + }) + } + + // Returns an iterator over all supported protocols from the TLV of type 129. + pub(crate) fn protocols_supported(&self) -> impl Iterator + '_ { + self.protocols_supported + .iter() + .flat_map(|tlv| tlv.list.iter()) + .copied() + } + + // Returns an iterator over all area addresses from TLVs of type 1. + #[expect(unused)] + pub(crate) fn area_addrs(&self) -> impl Iterator { + self.area_addrs.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IS neighbors from TLVs of type 2. + pub(crate) fn is_reach(&self) -> impl Iterator { + self.is_reach.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IS neighbors from TLVs of type 22. + pub(crate) fn ext_is_reach(&self) -> impl Iterator { + self.ext_is_reach.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IPv4 addresses from TLVs of type 132. + pub(crate) fn ipv4_addrs(&self) -> impl Iterator { + self.ipv4_addrs.iter().flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IPv4 internal reachability entries from TLVs + // of type 128. + pub(crate) fn ipv4_internal_reach( + &self, + ) -> impl Iterator { + self.ipv4_internal_reach + .iter() + .flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IPv4 external reachability entries from TLVs + // of type 130. + pub(crate) fn ipv4_external_reach( + &self, + ) -> impl Iterator { + self.ipv4_external_reach + .iter() + .flat_map(|tlv| tlv.list.iter()) + } + + // Returns an iterator over all IPv4 reachability entries from TLVs of + // type 135. + pub(crate) fn ext_ipv4_reach(&self) -> impl Iterator { + self.ext_ipv4_reach.iter().flat_map(|tlv| tlv.list.iter()) + } +} + +// ===== impl Snp ===== + +impl Snp { + pub const CSNP_HEADER_LEN: u8 = 33; + pub const PSNP_HEADER_LEN: u8 = 17; + + pub fn new( + level: LevelNumber, + source: LanId, + summary: Option<(LspId, LspId)>, + tlvs: SnpTlvs, + ) -> Self { + let pdu_type = match (summary.is_some(), level) { + (false, LevelNumber::L1) => PduType::PsnpL1, + (false, LevelNumber::L2) => PduType::PsnpL2, + (true, LevelNumber::L1) => PduType::CsnpL1, + (true, LevelNumber::L2) => PduType::CsnpL2, + }; + Snp { + hdr: Header::new(pdu_type), + source, + summary, + tlvs, + } + } + + fn decode( + hdr: Header, + packet_len: usize, + buf: &mut Bytes, + ) -> DecodeResult { + // Parse PDU length. + let pdu_len = buf.get_u16(); + if pdu_len != packet_len as u16 { + return Err(DecodeError::InvalidPduLength(pdu_len)); + } + + // Parse source ID. + let source = LanId::decode(buf); + + // Parse start and end LSP IDs. + let mut summary = None; + if matches!(hdr.pdu_type, PduType::CsnpL1 | PduType::CsnpL2) { + let start_lsp_id = LspId::decode(buf); + let end_lsp_id = LspId::decode(buf); + summary = Some((start_lsp_id, end_lsp_id)); + } + + // Parse top-level TLVs. + let mut tlvs = SnpTlvs::default(); + while buf.remaining() >= TLV_HDR_SIZE { + // Parse TLV type. + let tlv_type = buf.get_u8(); + let tlv_etype = TlvType::from_u8(tlv_type); + + // Parse and validate TLV length. + let tlv_len = buf.get_u8(); + if tlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(tlv_len as usize); + match tlv_etype { + Some(TlvType::LspEntries) => { + let tlv = LspEntriesTlv::decode(tlv_len, &mut buf_tlv)?; + tlvs.lsp_entries.push(tlv); + } + _ => { + // Save unknown top-level TLV. + let value = buf_tlv.copy_to_bytes(tlv_len as usize); + tlvs.unknown + .push(UnknownTlv::new(tlv_type, tlv_len, value)); + } + } + } + + Ok(Snp { + hdr, + source, + summary, + tlvs, + }) + } + + fn encode(&self) -> Bytes { + TLS_BUF.with(|buf| { + let mut buf = pdu_encode_start(buf, &self.hdr); + + // The PDU length will be initialized later. + let len_pos = buf.len(); + buf.put_u16(0); + self.source.encode(&mut buf); + + if let Some((start_lsp_id, end_lsp_id)) = &self.summary { + start_lsp_id.encode(&mut buf); + end_lsp_id.encode(&mut buf); + } + + // Encode TLVs. + for tlv in &self.tlvs.lsp_entries { + tlv.encode(&mut buf); + } + + pdu_encode_end(buf, len_pos) + }) + } +} + +impl SnpTlvs { + pub(crate) fn new(lsp_entries: impl IntoIterator) -> Self { + // Fragment TLVs as necessary. + let lsp_entries = lsp_entries + .into_iter() + .collect::>() + .chunks(LspEntriesTlv::MAX_ENTRIES) + .map(|chunk| LspEntriesTlv { + list: chunk.to_vec(), + }) + .collect(); + + SnpTlvs { + lsp_entries, + unknown: Default::default(), + } + } + + // Calculates the maximum number of LSP entries that can fit within the + // given size. + pub(crate) const fn max_lsp_entries(mut size: usize) -> usize { + let mut lsp_entries = 0; + + // Calculate how many full TLVs fit in the available size. + let full_tlvs = size / LspEntriesTlv::MAX_SIZE; + + // Update the remaining size after accounting for all full TLVs. + size %= LspEntriesTlv::MAX_SIZE; + + // Add the number of LSP entries from all full TLVs. + lsp_entries += + full_tlvs * (LspEntriesTlv::MAX_SIZE / LspEntriesTlv::ENTRY_SIZE); + + // Check if the remaining size has enough room for a partial TLV. + if size >= (TLV_HDR_SIZE + LspEntriesTlv::ENTRY_SIZE) { + // Add the number of LSP entries from the remaining partial TLV. + lsp_entries += (size - TLV_HDR_SIZE) / LspEntriesTlv::ENTRY_SIZE; + } + + lsp_entries + } + + // Returns an iterator over all LSP entries from TLVs of type 9. + pub(crate) fn lsp_entries(&self) -> impl Iterator { + self.lsp_entries.iter().flat_map(|tlv| tlv.list.iter()) + } +} + +// ===== helper functions ===== + +fn lsp_base_time() -> Option { + #[cfg(not(feature = "testing"))] + { + Some(Instant::now()) + } + #[cfg(feature = "testing")] + { + None + } +} + +fn pdu_encode_start<'a>( + buf: &'a RefCell, + hdr: &Header, +) -> RefMut<'a, BytesMut> { + let mut buf = buf.borrow_mut(); + buf.clear(); + hdr.encode(&mut buf); + buf +} + +fn pdu_encode_end(mut buf: RefMut<'_, BytesMut>, len_pos: usize) -> Bytes { + // Initialize PDU length. + let pkt_len = buf.len() as u16; + buf[len_pos..len_pos + 2].copy_from_slice(&pkt_len.to_be_bytes()); + + buf.clone().freeze() +} diff --git a/holo-isis/src/packet/tlv.rs b/holo-isis/src/packet/tlv.rs new file mode 100644 index 00000000..d0b7782d --- /dev/null +++ b/holo-isis/src/packet/tlv.rs @@ -0,0 +1,956 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +#![allow(clippy::match_single_binding)] + +use std::net::Ipv4Addr; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use derive_new::new; +use holo_utils::bytes::{BytesExt, BytesMutExt}; +use holo_utils::ip::Ipv4AddrExt; +use ipnetwork::Ipv4Network; +use num_traits::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; + +use crate::packet::consts::{NeighborSubTlvType, PrefixSubTlvType, TlvType}; +use crate::packet::error::{DecodeError, DecodeResult}; +use crate::packet::{AreaAddr, LanId, LspId}; + +// TLV header size. +pub const TLV_HDR_SIZE: usize = 2; +// TLV maximum length. +pub const TLV_MAX_LEN: usize = 255; + +// Network Layer Protocol IDs. +pub enum Nlpid { + Ipv4 = 0xCC, +} + +// Trait for all TLVs. +pub trait Tlv { + // Return the length of TLV. + fn len(&self) -> usize; +} + +// Trait for TLVs that might span across multiple instances. +pub trait MultiTlv: From> { + type Entry; + const FIXED_FIELDS_LEN: usize = 0; + + // Return an iterator over the TLV entries. + fn entries(&self) -> impl Iterator; + + // Return the length of a given entry. + fn entry_len(entry: &Self::Entry) -> usize; + + // Return the length of TLV. + fn len(&self) -> usize { + TLV_HDR_SIZE + + Self::FIXED_FIELDS_LEN + + self.entries().map(Self::entry_len).sum::() + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct AreaAddressesTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct NeighborsTlv { + pub list: Vec<[u8; 6]>, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct PaddingTlv { + pub length: u8, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ProtocolsSupportedTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv4AddressesTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct LspEntriesTlv { + pub list: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct LspEntry { + pub rem_lifetime: u16, + pub lsp_id: LspId, + pub seqno: u32, + pub cksum: u16, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct IsReachTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct IsReach { + pub metric: u8, + pub metric_delay: Option, + pub metric_expense: Option, + pub metric_error: Option, + pub neighbor: LanId, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIsReachTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIsReach { + pub neighbor: LanId, + pub metric: u32, + pub sub_tlvs: ExtIsReachSubTlvs, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIsReachSubTlvs { + pub unknown: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv4ReachTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct Ipv4Reach { + pub ie_bit: bool, + pub metric: u8, + pub metric_delay: Option, + pub metric_expense: Option, + pub metric_error: Option, + pub prefix: Ipv4Network, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIpv4ReachTlv { + pub list: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIpv4Reach { + pub metric: u32, + pub up_down: bool, + pub prefix: Ipv4Network, + pub sub_tlvs: ExtIpv4ReachSubTlvs, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ExtIpv4ReachSubTlvs { + pub unknown: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(new)] +#[derive(Deserialize, Serialize)] +pub struct UnknownTlv { + pub tlv_type: u8, + pub length: u8, + pub value: Bytes, +} + +// ===== impl AreaAddressesTlv ===== + +impl AreaAddressesTlv { + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + while buf.remaining() >= 1 { + // Parse area address length. + let addr_len = buf.get_u8(); + + // Sanity checks. + if addr_len > AreaAddr::MAX_LEN { + return Err(DecodeError::InvalidAreaAddrLen(addr_len)); + } + if addr_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + // Parse area address. + let addr = buf.copy_to_bytes(addr_len as usize); + list.push(AreaAddr::from(addr.as_ref())); + } + + Ok(AreaAddressesTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::AreaAddresses); + for entry in &self.list { + buf.put_u8(entry.as_ref().len() as _); + buf.put_slice(entry.as_ref()); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for AreaAddressesTlv { + type Entry = AreaAddr; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(entry: &AreaAddr) -> usize { + 1 + entry.as_ref().len() + } +} + +impl From for AreaAddressesTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> AreaAddressesTlv { + AreaAddressesTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl NeighborsTlv ===== + +impl NeighborsTlv { + const MAC_ADDR_LEN: usize = 6; + + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if tlv_len as usize % Self::MAC_ADDR_LEN != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + while buf.remaining() >= Self::MAC_ADDR_LEN { + // Parse MAC address. + let mut addr: [u8; Self::MAC_ADDR_LEN] = [0; Self::MAC_ADDR_LEN]; + buf.copy_to_slice(&mut addr); + list.push(addr); + } + + Ok(NeighborsTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::Neighbors); + for entry in &self.list { + buf.put_slice(entry); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for NeighborsTlv { + type Entry = [u8; 6]; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &[u8; 6]) -> usize { + Self::MAC_ADDR_LEN + } +} + +impl From for NeighborsTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> NeighborsTlv { + NeighborsTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl PaddingTlv ===== + +impl PaddingTlv { + const PADDING: [u8; 255] = [0; 255]; + + pub(crate) fn decode(tlv_len: u8, _buf: &mut Bytes) -> DecodeResult { + // Ignore padding data. + Ok(PaddingTlv { length: tlv_len }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::Padding); + buf.put_slice(&Self::PADDING[0..self.length as usize]); + tlv_encode_end(buf, start_pos); + } +} + +// ===== impl ProtocolsSupportedTlv ===== + +impl ProtocolsSupportedTlv { + pub(crate) fn decode(_tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + while buf.remaining() >= 1 { + let proto = buf.get_u8(); + list.push(proto); + } + + Ok(ProtocolsSupportedTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::ProtocolsSupported); + for entry in &self.list { + buf.put_u8(*entry); + } + tlv_encode_end(buf, start_pos); + } +} + +impl Tlv for ProtocolsSupportedTlv { + fn len(&self) -> usize { + TLV_HDR_SIZE + self.list.len() + } +} + +impl From for ProtocolsSupportedTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> ProtocolsSupportedTlv { + ProtocolsSupportedTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl Ipv4AddressesTlv ===== + +impl Ipv4AddressesTlv { + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if tlv_len as usize % Ipv4Addr::LENGTH != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + while buf.remaining() >= Ipv4Addr::LENGTH { + // Parse IPv4 address. + let addr = buf.get_ipv4(); + list.push(addr); + } + + Ok(Ipv4AddressesTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::Ipv4Addresses); + for entry in &self.list { + buf.put_ipv4(entry); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for Ipv4AddressesTlv { + type Entry = Ipv4Addr; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &Ipv4Addr) -> usize { + Ipv4Addr::LENGTH + } +} + +impl From for Ipv4AddressesTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> Ipv4AddressesTlv { + Ipv4AddressesTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl LspEntriesTlv ===== + +impl LspEntriesTlv { + pub const ENTRY_SIZE: usize = 16; + pub const MAX_ENTRIES: usize = TLV_MAX_LEN / Self::ENTRY_SIZE; + pub const MAX_SIZE: usize = TLV_MAX_LEN - Self::MAX_ENTRIES; + + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if tlv_len as usize % Self::ENTRY_SIZE != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + while buf.remaining() >= Self::ENTRY_SIZE { + let rem_lifetime = buf.get_u16(); + let lsp_id = LspId::decode(buf); + let seqno = buf.get_u32(); + let cksum = buf.get_u16(); + + let entry = LspEntry { + rem_lifetime, + lsp_id, + cksum, + seqno, + }; + list.push(entry); + } + + Ok(LspEntriesTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::LspEntries); + for entry in &self.list { + buf.put_u16(entry.rem_lifetime); + entry.lsp_id.encode(buf); + buf.put_u32(entry.seqno); + buf.put_u16(entry.cksum); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for LspEntriesTlv { + type Entry = LspEntry; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &LspEntry) -> usize { + Self::ENTRY_SIZE + } +} + +impl From for LspEntriesTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> LspEntriesTlv { + LspEntriesTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl IsReachTlv ===== + +impl IsReachTlv { + const ENTRY_SIZE: usize = 11; + const METRIC_S_BIT: u8 = 0x80; + const METRIC_MASK: u8 = 0x3F; + + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if (tlv_len - 1) as usize % Self::ENTRY_SIZE != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + let _virtual_flag = buf.get_u8(); + while buf.remaining() >= Self::ENTRY_SIZE { + let metric = buf.get_u8(); + let metric = metric & Self::METRIC_MASK; + let metric_delay = buf.get_u8(); + let metric_delay = (metric_delay & Self::METRIC_S_BIT == 0) + .then_some(metric_delay & Self::METRIC_MASK); + let metric_expense = buf.get_u8(); + let metric_expense = (metric_expense & Self::METRIC_S_BIT == 0) + .then_some(metric_expense & Self::METRIC_MASK); + let metric_error = buf.get_u8(); + let metric_error = (metric_error & Self::METRIC_S_BIT == 0) + .then_some(metric_error & Self::METRIC_MASK); + let neighbor = LanId::decode(buf); + + let entry = IsReach { + metric, + metric_delay, + metric_expense, + metric_error, + neighbor, + }; + list.push(entry); + } + + Ok(IsReachTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::IsReach); + // Virtual Flag - Used by partition repair (unsupported). + buf.put_u8(0); + for entry in &self.list { + buf.put_u8(entry.metric); + buf.put_u8(entry.metric_delay.unwrap_or(Self::METRIC_S_BIT)); + buf.put_u8(entry.metric_expense.unwrap_or(Self::METRIC_S_BIT)); + buf.put_u8(entry.metric_error.unwrap_or(Self::METRIC_S_BIT)); + entry.neighbor.encode(buf); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for IsReachTlv { + type Entry = IsReach; + const FIXED_FIELDS_LEN: usize = 1; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &IsReach) -> usize { + Self::ENTRY_SIZE + } +} + +impl From for IsReachTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> IsReachTlv { + IsReachTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl ExtIsReachTlv ===== + +impl ExtIsReachTlv { + const ENTRY_MIN_SIZE: usize = 11; + + pub(crate) fn decode(_tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + while buf.remaining() >= Self::ENTRY_MIN_SIZE { + let neighbor = LanId::decode(buf); + let metric = buf.get_u24(); + + // Parse Sub-TLVs. + let mut sub_tlvs = ExtIsReachSubTlvs::default(); + let mut sub_tlvs_len = buf.get_u8(); + while sub_tlvs_len >= TLV_HDR_SIZE as u8 { + // Parse TLV type. + let stlv_type = buf.get_u8(); + sub_tlvs_len -= 1; + let stlv_etype = NeighborSubTlvType::from_u8(stlv_type); + + // Parse and validate TLV length. + let stlv_len = buf.get_u8(); + sub_tlvs_len -= 1; + if stlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(stlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(stlv_len as usize); + sub_tlvs_len -= stlv_len; + match stlv_etype { + _ => { + // Save unknown top-level TLV. + let value = buf_tlv.copy_to_bytes(stlv_len as usize); + sub_tlvs + .unknown + .push(UnknownTlv::new(stlv_type, stlv_len, value)); + } + } + } + + list.push(ExtIsReach { + neighbor, + metric, + sub_tlvs, + }); + } + + Ok(ExtIsReachTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::ExtIsReach); + for entry in &self.list { + // Encode neighbor ID. + entry.neighbor.encode(buf); + // Encode metric. + buf.put_u24(entry.metric); + // Encode Sub-TLVs. + buf.put_u8(0); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for ExtIsReachTlv { + type Entry = ExtIsReach; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &ExtIsReach) -> usize { + Self::ENTRY_MIN_SIZE + } +} + +impl From for ExtIsReachTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> ExtIsReachTlv { + ExtIsReachTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl Ipv4ReachTlv ===== + +impl Ipv4ReachTlv { + const ENTRY_SIZE: usize = 12; + const METRIC_S_BIT: u8 = 0x80; + const METRIC_IE_BIT: u8 = 0x80; + const METRIC_MASK: u8 = 0x3F; + + pub(crate) fn decode(tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + // Validate the TLV length. + if tlv_len as usize % Self::ENTRY_SIZE != 0 { + return Err(DecodeError::InvalidTlvLength(tlv_len)); + } + + while buf.remaining() >= Self::ENTRY_SIZE { + let metric = buf.get_u8(); + let ie_bit = metric & Self::METRIC_IE_BIT != 0; + let metric = metric & Self::METRIC_MASK; + let metric_delay = buf.get_u8(); + let metric_delay = (metric_delay & Self::METRIC_S_BIT == 0) + .then_some(metric_delay & Self::METRIC_MASK); + let metric_expense = buf.get_u8(); + let metric_expense = (metric_expense & Self::METRIC_S_BIT == 0) + .then_some(metric_expense & Self::METRIC_MASK); + let metric_error = buf.get_u8(); + let metric_error = (metric_error & Self::METRIC_S_BIT == 0) + .then_some(metric_error & Self::METRIC_MASK); + let addr = buf.get_ipv4(); + let mask = buf.get_ipv4(); + // Ignore prefixes with non-contiguous subnet masks. + let Ok(prefix) = Ipv4Network::with_netmask(addr, mask) else { + continue; + }; + + let entry = Ipv4Reach { + ie_bit, + metric, + metric_delay, + metric_expense, + metric_error, + prefix, + }; + list.push(entry); + } + + Ok(Ipv4ReachTlv { list }) + } + + pub(crate) fn encode(&self, tlv_type: TlvType, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, tlv_type); + for entry in &self.list { + let mut metric = entry.metric; + if entry.ie_bit { + metric |= Self::METRIC_IE_BIT; + } + buf.put_u8(metric); + buf.put_u8(entry.metric_delay.unwrap_or(Self::METRIC_S_BIT)); + buf.put_u8(entry.metric_expense.unwrap_or(Self::METRIC_S_BIT)); + buf.put_u8(entry.metric_error.unwrap_or(Self::METRIC_S_BIT)); + buf.put_ipv4(&entry.prefix.ip()); + buf.put_ipv4(&entry.prefix.mask()); + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for Ipv4ReachTlv { + type Entry = Ipv4Reach; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(_entry: &Ipv4Reach) -> usize { + Self::ENTRY_SIZE + } +} + +impl From for Ipv4ReachTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> Ipv4ReachTlv { + Ipv4ReachTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== impl ExtIpv4ReachTlv ===== + +impl ExtIpv4ReachTlv { + const ENTRY_MIN_SIZE: usize = 5; + const CONTROL_UPDOWN_BIT: u8 = 0x80; + const CONTROL_SUBTLVS: u8 = 0x40; + const CONTROL_PLEN_MASK: u8 = 0x3F; + + pub(crate) fn decode(_tlv_len: u8, buf: &mut Bytes) -> DecodeResult { + let mut list = vec![]; + + while buf.remaining() >= Self::ENTRY_MIN_SIZE { + // Parse metric. + let metric = buf.get_u32(); + + // Parse control field. + let control = buf.get_u8(); + let up_down = (control & Self::CONTROL_UPDOWN_BIT) != 0; + let subtlvs = (control & Self::CONTROL_SUBTLVS) != 0; + let plen = control & Self::CONTROL_PLEN_MASK; + + // Parse prefix (variable length). + let mut prefix_bytes = [0; Ipv4Addr::LENGTH]; + let plen_wire = prefix_wire_len(plen); + buf.copy_to_slice(&mut prefix_bytes[..plen_wire]); + let prefix = Ipv4Addr::from(prefix_bytes); + + // Parse Sub-TLVs. + let mut sub_tlvs = ExtIpv4ReachSubTlvs::default(); + if subtlvs { + let mut sub_tlvs_len = buf.get_u8(); + while sub_tlvs_len >= TLV_HDR_SIZE as u8 { + // Parse TLV type. + let stlv_type = buf.get_u8(); + sub_tlvs_len -= 1; + let stlv_etype = PrefixSubTlvType::from_u8(stlv_type); + + // Parse and validate TLV length. + let stlv_len = buf.get_u8(); + sub_tlvs_len -= 1; + if stlv_len as usize > buf.remaining() { + return Err(DecodeError::InvalidTlvLength(stlv_len)); + } + + // Parse TLV value. + let mut buf_tlv = buf.copy_to_bytes(stlv_len as usize); + sub_tlvs_len -= stlv_len; + match stlv_etype { + _ => { + // Save unknown top-level TLV. + let value = + buf_tlv.copy_to_bytes(stlv_len as usize); + sub_tlvs.unknown.push(UnknownTlv::new( + stlv_type, stlv_len, value, + )); + } + } + } + } + + // Ignore malformed prefixes. + let Ok(prefix) = Ipv4Network::new(prefix, plen) else { + continue; + }; + + list.push(ExtIpv4Reach { + metric, + up_down, + prefix, + sub_tlvs, + }); + } + + Ok(ExtIpv4ReachTlv { list }) + } + + pub(crate) fn encode(&self, buf: &mut BytesMut) { + let start_pos = tlv_encode_start(buf, TlvType::ExtIpv4Reach); + for entry in &self.list { + // Encode metric. + buf.put_u32(entry.metric); + + // Encode control field. + let plen = entry.prefix.prefix(); + let mut control = 0; + if entry.up_down { + control |= Self::CONTROL_UPDOWN_BIT; + } + control |= plen; + buf.put_u8(control); + + // Encode prefix (variable length). + let plen_wire = prefix_wire_len(plen); + buf.put(&entry.prefix.ip().octets()[0..plen_wire]); + + // Encode Sub-TLVs. + } + tlv_encode_end(buf, start_pos); + } +} + +impl MultiTlv for ExtIpv4ReachTlv { + type Entry = ExtIpv4Reach; + + fn entries(&self) -> impl Iterator { + self.list.iter() + } + + fn entry_len(entry: &ExtIpv4Reach) -> usize { + let plen = entry.prefix.prefix(); + Self::ENTRY_MIN_SIZE + prefix_wire_len(plen) + } +} + +impl From for ExtIpv4ReachTlv +where + I: IntoIterator, +{ + fn from(iter: I) -> ExtIpv4ReachTlv { + ExtIpv4ReachTlv { + list: iter.into_iter().collect(), + } + } +} + +// ===== blanket implementations ===== + +impl Tlv for T { + fn len(&self) -> usize { + self.len() + } +} + +// ===== helper functions ===== + +// Calculates the number of bytes required to encode a prefix. +const fn prefix_wire_len(len: u8) -> usize { + (len as usize + 7) / 8 +} + +fn tlv_encode_start(buf: &mut BytesMut, tlv_type: impl ToPrimitive) -> usize { + let start_pos = buf.len(); + buf.put_u8(tlv_type.to_u8().unwrap()); + // The TLV length will be rewritten later. + buf.put_u8(0); + start_pos +} + +fn tlv_encode_end(buf: &mut BytesMut, start_pos: usize) { + // Rewrite TLV length. + buf[start_pos + 1] = (buf.len() - start_pos - TLV_HDR_SIZE) as u8; +} + +// ===== global functions ===== + +// Takes as many TLVs as will fit into the provided PDU remaining length. +pub(crate) fn tlv_take_max( + tlv_list: &mut Vec, + rem_len: &mut usize, +) -> Vec +where + T: Tlv, +{ + let mut tlvs = Vec::new(); + let mut count = 0; + + if *rem_len == 0 { + return tlvs; + } + + for tlv in tlv_list.iter() { + let tlv_len = tlv.len(); + if *rem_len >= tlv_len { + *rem_len -= tlv_len; + count += 1; + } else { + *rem_len = 0; + break; + } + } + + tlvs.extend(tlv_list.drain(0..count)); + tlvs +} + +// Splits a list of TLV entries into as many TLVs as necessary. +pub(crate) fn tlv_entries_split( + entries: impl IntoIterator, +) -> Vec +where + T: MultiTlv, +{ + let mut tlvs = vec![]; + let mut tlv_entries = vec![]; + let mut tlv_len = 0; + + for entry in entries { + let entry_len = T::entry_len(&entry); + if tlv_len + entry_len > (TLV_MAX_LEN - T::FIXED_FIELDS_LEN) { + let tlv = T::from(std::mem::take(&mut tlv_entries)); + tlvs.push(tlv); + tlv_len = 0; + continue; + } + tlv_entries.push(entry); + tlv_len += entry_len; + } + if !tlv_entries.is_empty() { + let tlv = T::from(tlv_entries); + tlvs.push(tlv); + } + + tlvs.shrink_to_fit(); + tlvs +} diff --git a/holo-isis/src/southbound/mod.rs b/holo-isis/src/southbound/mod.rs new file mode 100644 index 00000000..80635437 --- /dev/null +++ b/holo-isis/src/southbound/mod.rs @@ -0,0 +1,11 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +pub mod rx; +pub mod tx; diff --git a/holo-isis/src/southbound/rx.rs b/holo-isis/src/southbound/rx.rs new file mode 100644 index 00000000..e9de2921 --- /dev/null +++ b/holo-isis/src/southbound/rx.rs @@ -0,0 +1,124 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::net::Ipv4Addr; + +use holo_utils::southbound::{AddressMsg, InterfaceUpdateMsg}; +use ipnetwork::IpNetwork; + +use crate::error::Error; +use crate::instance::Instance; +use crate::packet::LevelType; + +// ===== global functions ===== + +pub(crate) async fn process_router_id_update( + instance: &mut Instance, + router_id: Option, +) { + instance.system.router_id = router_id; +} + +pub(crate) fn process_iface_update( + instance: &mut Instance, + msg: InterfaceUpdateMsg, +) -> Result<(), Error> { + let Some((mut instance, arenas)) = instance.as_up() else { + return Ok(()); + }; + + // Lookup interface. + let Some((iface_idx, iface)) = + arenas.interfaces.get_mut_by_name(&msg.ifname) + else { + return Ok(()); + }; + + // Update interface data. + iface.system.flags = msg.flags; + iface.system.mtu = Some(msg.mtu); + iface.system.mac_addr = Some(msg.mac_address); + if iface.system.ifindex != Some(msg.ifindex) { + arenas + .interfaces + .update_ifindex(iface_idx, Some(msg.ifindex)); + } + + // Check if IS-IS needs to be activated or deactivated on this interface. + let iface = &mut arenas.interfaces[iface_idx]; + iface.update(&mut instance, &mut arenas.adjacencies)?; + + Ok(()) +} + +pub(crate) fn process_addr_add(instance: &mut Instance, msg: AddressMsg) { + let Some((mut instance, arenas)) = instance.as_up() else { + return; + }; + + // Lookup interface. + let Some((_iface_idx, iface)) = + arenas.interfaces.get_mut_by_name(&msg.ifname) + else { + return; + }; + + // Add address to interface. + match msg.addr { + IpNetwork::V4(addr) => { + iface.system.ipv4_addr_list.insert(addr); + } + IpNetwork::V6(addr) => { + iface.system.ipv6_addr_list.insert(addr); + } + } + + if iface.state.active { + // Update Hello Tx task(s). + if !iface.is_passive() { + iface.hello_interval_start(&instance, LevelType::All); + } + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(LevelType::All); + } +} + +pub(crate) fn process_addr_del(instance: &mut Instance, msg: AddressMsg) { + let Some((mut instance, arenas)) = instance.as_up() else { + return; + }; + + // Lookup interface. + let Some((_iface_idx, iface)) = + arenas.interfaces.get_mut_by_name(&msg.ifname) + else { + return; + }; + + // Remove address from interface. + match msg.addr { + IpNetwork::V4(addr) => { + iface.system.ipv4_addr_list.remove(&addr); + } + IpNetwork::V6(addr) => { + iface.system.ipv6_addr_list.remove(&addr); + } + } + + if iface.state.active { + // Update Hello Tx task(s). + if !iface.is_passive() { + iface.hello_interval_start(&instance, LevelType::All); + } + + // Schedule LSP reorigination. + instance.schedule_lsp_origination(LevelType::All); + } +} diff --git a/holo-isis/src/southbound/tx.rs b/holo-isis/src/southbound/tx.rs new file mode 100644 index 00000000..11dc1a57 --- /dev/null +++ b/holo-isis/src/southbound/tx.rs @@ -0,0 +1,16 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use holo_utils::ibus::{IbusMsg, IbusSender}; + +// ===== global functions ===== + +pub(crate) fn router_id_query(ibus_tx: &IbusSender) { + let _ = ibus_tx.send(IbusMsg::RouterIdQuery); +} diff --git a/holo-isis/src/tasks.rs b/holo-isis/src/tasks.rs new file mode 100644 index 00000000..1a6f4ad4 --- /dev/null +++ b/holo-isis/src/tasks.rs @@ -0,0 +1,566 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::sync::{atomic, Arc}; +use std::time::Duration; + +use holo_utils::socket::{AsyncFd, Socket}; +use holo_utils::task::{IntervalTask, Task, TimeoutTask}; +use holo_utils::{Sender, UnboundedReceiver, UnboundedSender}; +use tracing::{debug_span, Instrument}; + +use crate::adjacency::Adjacency; +use crate::collections::LspEntryId; +use crate::debug::LspPurgeReason; +use crate::instance::InstanceUpView; +use crate::interface::{Interface, InterfaceType}; +use crate::network::MulticastAddr; +use crate::packet::pdu::{Lsp, Pdu}; +use crate::packet::{LevelNumber, LevelType, Levels}; +use crate::{lsdb, network}; + +// +// IS-IS tasks diagram: +// +--------------+ +// | northbound | +// +--------------+ +// | ^ +// | | +// northbound_rx (1x) V | (1x) northbound_tx +// +--------------+ +// | | +// net_rx (Nx) -> | | -> (Nx) net_tx +// adjacency_holdtimer (Nx) -> | | -> (Nx) hello_interval +// dis_initial_election (Nx) -> | | +// psnp_interval (Nx) -> | | +// csnp_interval (Nx) -> | instance | +// lsp_originate_timer (Nx) -> | | -> (Nx) lsp_rxmt_interval +// lsp_expiry_timer (Nx) -> | | +// lsp_delete_timer (Nx) -> | | +// lsp_refresh_timer (Nx) -> | | +// | | +// +--------------+ +// ibus_tx (1x) | ^ (1x) ibus_rx +// | | +// V | +// +--------------+ +// | ibus | +// +--------------+ +// + +// IS-IS inter-task message types. +pub mod messages { + use bytes::Bytes; + use serde::{Deserialize, Serialize}; + + use crate::collections::{AdjacencyKey, InterfaceKey, LspEntryKey}; + use crate::debug::LspPurgeReason; + use crate::network::MulticastAddr; + use crate::packet::error::DecodeError; + use crate::packet::pdu::Pdu; + use crate::packet::LevelNumber; + + // Type aliases. + pub type ProtocolInputMsg = input::ProtocolMsg; + pub type ProtocolOutputMsg = output::ProtocolMsg; + + // Input messages (child task -> main task). + pub mod input { + use super::*; + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub enum ProtocolMsg { + NetRxPdu(NetRxPduMsg), + AdjHoldTimer(AdjHoldTimerMsg), + DisElection(DisElectionMsg), + SendPsnp(SendPsnpMsg), + SendCsnp(SendCsnpMsg), + LspOriginate(LspOriginateMsg), + LspPurge(LspPurgeMsg), + LspDelete(LspDeleteMsg), + LspRefresh(LspRefreshMsg), + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct NetRxPduMsg { + pub iface_key: InterfaceKey, + pub src: [u8; 6], + pub bytes: Bytes, + pub pdu: Result, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub enum AdjHoldTimerMsg { + Broadcast { + iface_key: InterfaceKey, + adj_key: AdjacencyKey, + level: LevelNumber, + }, + PointToPoint { + iface_key: InterfaceKey, + }, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct DisElectionMsg { + pub iface_key: InterfaceKey, + pub level: LevelNumber, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct SendPsnpMsg { + pub iface_key: InterfaceKey, + pub level: LevelNumber, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct SendCsnpMsg { + pub iface_key: InterfaceKey, + pub level: LevelNumber, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct LspOriginateMsg {} + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct LspPurgeMsg { + pub level: LevelNumber, + pub lse_key: LspEntryKey, + pub reason: LspPurgeReason, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct LspDeleteMsg { + pub level: LevelNumber, + pub lse_key: LspEntryKey, + } + + #[derive(Debug)] + #[derive(Deserialize, Serialize)] + pub struct LspRefreshMsg { + pub level: LevelNumber, + pub lse_key: LspEntryKey, + } + } + + // Output messages (main task -> child task). + pub mod output { + use super::*; + + #[derive(Debug)] + #[derive(Serialize)] + pub enum ProtocolMsg { + NetTxPdu(NetTxPduMsg), + } + + #[derive(Debug)] + #[derive(Serialize)] + pub struct NetTxPduMsg { + pub pdu: Pdu, + pub ifindex: u32, + pub dst: MulticastAddr, + } + } +} + +// ===== IS-IS tasks ===== + +// Network Rx task. +pub(crate) fn net_rx( + socket: Arc>, + broadcast: bool, + iface: &Interface, + net_pdu_rxp: &Sender, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span1 = debug_span!("network"); + let _span1_guard = span1.enter(); + let span2 = debug_span!("input"); + let _span2_guard = span2.enter(); + + let iface_id = iface.id; + let net_pdu_rxp = net_pdu_rxp.clone(); + + let span = tracing::span::Span::current(); + Task::spawn( + async move { + let _span_enter = span.enter(); + let _ = network::read_loop( + socket, + broadcast, + iface_id, + net_pdu_rxp, + ) + .await; + } + .in_current_span(), + ) + } + #[cfg(feature = "testing")] + { + Task::spawn(async move { std::future::pending().await }) + } +} + +// Network Tx task. +#[allow(unused_mut)] +pub(crate) fn net_tx( + socket: Arc>, + broadcast: bool, + mut net_pdu_txc: UnboundedReceiver, + #[cfg(feature = "testing")] proto_output_tx: &Sender< + messages::ProtocolOutputMsg, + >, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span1 = debug_span!("network"); + let _span1_guard = span1.enter(); + let span2 = debug_span!("output"); + let _span2_guard = span2.enter(); + + let span = tracing::span::Span::current(); + Task::spawn( + async move { + let _span_enter = span.enter(); + network::write_loop(socket, broadcast, net_pdu_txc).await; + } + .in_current_span(), + ) + } + #[cfg(feature = "testing")] + { + let proto_output_tx = proto_output_tx.clone(); + Task::spawn(async move { + // Relay message to the test framework. + while let Some(msg) = net_pdu_txc.recv().await { + let msg = messages::ProtocolOutputMsg::NetTxPdu(msg); + let _ = proto_output_tx.send(msg).await; + } + }) + } +} + +// Send periodic IS-IS Hello PDUs. +pub(crate) fn hello_interval( + iface: &Interface, + level_type: impl Into, + pdu: Pdu, +) -> IntervalTask { + #[cfg(not(feature = "testing"))] + { + let level_type = level_type.into(); + let interval = iface.config.hello_interval.get(level_type); + let ifindex = iface.system.ifindex.unwrap(); + let dst = iface.config.interface_type.multicast_addr(level_type); + let packet_counters = &iface.state.packet_counters; + let iih_out_counters = Levels { + l1: packet_counters.get(LevelNumber::L1).iih_out.clone(), + l2: packet_counters.get(LevelNumber::L2).iih_out.clone(), + }; + let net_tx_pdup = iface.state.net.as_ref().unwrap().net_tx_pdup.clone(); + IntervalTask::new( + Duration::from_secs(interval.into()), + true, + move || { + let pdu = pdu.clone(); + let net_tx_pdup = net_tx_pdup.clone(); + + // Update packet counters. + for level in [LevelNumber::L1, LevelNumber::L2] { + if level_type.intersects(level) { + iih_out_counters + .get(level) + .fetch_add(1, atomic::Ordering::Relaxed); + } + } + + async move { + let msg = + messages::output::NetTxPduMsg { pdu, ifindex, dst }; + let _ = net_tx_pdup.send(msg); + } + }, + ) + } + #[cfg(feature = "testing")] + { + IntervalTask {} + } +} + +// Adjacency hold timer. +pub(crate) fn adjacency_holdtimer( + adj: &Adjacency, + iface: &Interface, + instance: &InstanceUpView<'_>, + holdtime: u16, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let timeout = Duration::from_secs(holdtime.into()); + let msg = match iface.config.interface_type { + InterfaceType::Broadcast => { + messages::input::AdjHoldTimerMsg::Broadcast { + iface_key: iface.id.into(), + adj_key: adj.id.into(), + level: adj.level_usage.into(), + } + } + InterfaceType::PointToPoint => { + messages::input::AdjHoldTimerMsg::PointToPoint { + iface_key: iface.id.into(), + } + } + }; + let adj_holdtimerp = instance.tx.protocol_input.adj_holdtimer.clone(); + + TimeoutTask::new(timeout, move || async move { + let _ = adj_holdtimerp.send(msg).await; + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// DIS initial election. +pub(crate) fn dis_initial_election( + iface: &Interface, + level: LevelNumber, + instance: &InstanceUpView<'_>, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let hello_interval = iface.config.hello_interval.get(level); + let timeout = Duration::from_secs(hello_interval as u64 * 2); + let iface_id = iface.id; + let dis_electionp = instance.tx.protocol_input.dis_election.clone(); + + TimeoutTask::new(timeout, move || async move { + let msg = messages::input::DisElectionMsg { + iface_key: iface_id.into(), + level, + }; + let _ = dis_electionp.send(msg); + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// Send periodic IS-IS PSNP PDUs. +pub(crate) fn psnp_interval( + iface: &Interface, + level: LevelNumber, + instance: &InstanceUpView<'_>, +) -> IntervalTask { + #[cfg(not(feature = "testing"))] + { + const PSNP_INTERVAL: u64 = 2; + + let iface_id = iface.id; + let send_psnpp = instance.tx.protocol_input.send_psnp.clone(); + IntervalTask::new(Duration::from_secs(PSNP_INTERVAL), true, move || { + let send_psnpp = send_psnpp.clone(); + + async move { + let msg = messages::input::SendPsnpMsg { + iface_key: iface_id.into(), + level, + }; + let _ = send_psnpp.send(msg); + } + }) + } + #[cfg(feature = "testing")] + { + IntervalTask {} + } +} + +// Send periodic IS-IS CSNP PDUs. +pub(crate) fn csnp_interval( + iface: &Interface, + level: LevelNumber, + instance: &InstanceUpView<'_>, +) -> IntervalTask { + #[cfg(not(feature = "testing"))] + { + let interval = iface.config.csnp_interval; + let iface_id = iface.id; + let send_csnpp = instance.tx.protocol_input.send_csnp.clone(); + IntervalTask::new( + Duration::from_secs(interval.into()), + true, + move || { + let send_csnpp = send_csnpp.clone(); + + async move { + let msg = messages::input::SendCsnpMsg { + iface_key: iface_id.into(), + level, + }; + let _ = send_csnpp.send(msg); + } + }, + ) + } + #[cfg(feature = "testing")] + { + IntervalTask {} + } +} + +// Send periodic IS-IS LSP retransmissions. +pub(crate) fn lsp_rxmt_interval( + iface: &Interface, + lsp: Lsp, + dst: MulticastAddr, + interval: u16, +) -> IntervalTask { + #[cfg(not(feature = "testing"))] + { + // Get interface ifindex. + let ifindex = iface.system.ifindex.unwrap(); + + let pdu = Pdu::Lsp(lsp); + let net_tx_pdup = iface.state.net.as_ref().unwrap().net_tx_pdup.clone(); + IntervalTask::new( + Duration::from_secs(interval.into()), + false, + move || { + let pdu = pdu.clone(); + let net_tx_pdup = net_tx_pdup.clone(); + + async move { + let msg = + messages::output::NetTxPduMsg { pdu, ifindex, dst }; + let _ = net_tx_pdup.send(msg); + } + }, + ) + } + #[cfg(feature = "testing")] + { + IntervalTask {} + } +} + +// LSP originate timer task. +pub(crate) fn lsp_originate_timer( + lsp_originatep: &UnboundedSender, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let timeout = Duration::from_secs(lsdb::LSP_MIN_GEN_INTERVAL); + let lsp_originatep = lsp_originatep.clone(); + + TimeoutTask::new(timeout, move || async move { + let msg = messages::input::LspOriginateMsg {}; + let _ = lsp_originatep.send(msg); + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// LSP expiry timer task. +pub(crate) fn lsp_expiry_timer( + level: LevelNumber, + lse_id: LspEntryId, + lsp: &Lsp, + lsp_purgep: &UnboundedSender, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let timeout = Duration::from_secs(lsp.rem_lifetime.into()); + let lsp_purgep = lsp_purgep.clone(); + + TimeoutTask::new(timeout, move || async move { + let msg = messages::input::LspPurgeMsg { + level, + lse_key: lse_id.into(), + reason: LspPurgeReason::Expired, + }; + let _ = lsp_purgep.send(msg); + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// LSP delete timer task. +pub(crate) fn lsp_delete_timer( + level: LevelNumber, + lse_id: LspEntryId, + timeout: u64, + lsp_deletep: &UnboundedSender, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let timeout = Duration::from_secs(timeout); + let lsp_deletep = lsp_deletep.clone(); + + TimeoutTask::new(timeout, move || async move { + let msg = messages::input::LspDeleteMsg { + level, + lse_key: lse_id.into(), + }; + let _ = lsp_deletep.send(msg); + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// LSP refresh timer task. +pub(crate) fn lsp_refresh_timer( + level: LevelNumber, + lse_id: LspEntryId, + refresh_interval: u16, + lsp_refreshp: &UnboundedSender, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let timeout = Duration::from_secs(refresh_interval.into()); + let lsp_refreshp = lsp_refreshp.clone(); + + TimeoutTask::new(timeout, move || async move { + let msg = messages::input::LspRefreshMsg { + level, + lse_key: lse_id.into(), + }; + let _ = lsp_refreshp.send(msg); + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} diff --git a/holo-isis/tests/conformance/mod.rs b/holo-isis/tests/conformance/mod.rs new file mode 100644 index 00000000..d27e03d9 --- /dev/null +++ b/holo-isis/tests/conformance/mod.rs @@ -0,0 +1,10 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +//mod topologies; diff --git a/holo-isis/tests/mod.rs b/holo-isis/tests/mod.rs new file mode 100644 index 00000000..1871720c --- /dev/null +++ b/holo-isis/tests/mod.rs @@ -0,0 +1,11 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +mod conformance; +mod packet; diff --git a/holo-isis/tests/packet/mod.rs b/holo-isis/tests/packet/mod.rs new file mode 100644 index 00000000..0b88f4cf --- /dev/null +++ b/holo-isis/tests/packet/mod.rs @@ -0,0 +1,749 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// +// Sponsored by NLnet as part of the Next Generation Internet initiative. +// See: https://nlnet.nl/NGI0 +// + +use std::net::Ipv4Addr; +use std::str::FromStr; +use std::sync::LazyLock as Lazy; + +use bytes::Bytes; +use holo_isis::packet::consts::LspFlags; +use holo_isis::packet::pdu::{ + Hello, HelloTlvs, HelloVariant, Lsp, LspTlvs, Pdu, Snp, SnpTlvs, +}; +use holo_isis::packet::tlv::{ + AreaAddressesTlv, ExtIpv4Reach, ExtIpv4ReachTlv, ExtIsReach, ExtIsReachTlv, + Ipv4AddressesTlv, Ipv4Reach, Ipv4ReachTlv, IsReach, IsReachTlv, + LspEntriesTlv, LspEntry, NeighborsTlv, PaddingTlv, ProtocolsSupportedTlv, +}; +use holo_isis::packet::{ + AreaAddr, LanId, LevelNumber, LevelType, LspId, SystemId, +}; +use ipnetwork::Ipv4Network; + +// +// Helper functions. +// + +fn test_encode_pdu(bytes_expected: &[u8], pdu: &Pdu) { + let bytes_actual = pdu.clone().encode(); + let data = format!("{:#04x?}", bytes_actual.as_ref()); + let _ = std::fs::write("/tmp/test-data", data); + assert_eq!(bytes_expected, bytes_actual.as_ref()); +} + +fn test_decode_pdu(bytes: &[u8], pdu_expected: &Pdu) { + let bytes = Bytes::copy_from_slice(bytes); + let mut pdu_actual = Pdu::decode(bytes.clone()).unwrap(); + if let Pdu::Lsp(pdu) = &mut pdu_actual { + pdu.raw = bytes; + } + assert_eq!(*pdu_expected, pdu_actual); +} + +// +// Test packets. +// + +static LAN_HELLO1: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x1b, 0x01, 0x00, 0x0f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x05, 0xd9, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x03, 0x81, 0x02, 0xcc, 0x8e, 0x01, 0x04, + 0x03, 0x49, 0x00, 0x00, 0x06, 0x0c, 0x3e, 0x25, 0x6d, 0x6d, 0x1b, + 0x25, 0x3e, 0xe8, 0x34, 0x2b, 0x1f, 0x96, 0x84, 0x04, 0x0a, 0x00, + 0x01, 0x01, 0x08, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + Pdu::Hello(Hello::new( + LevelType::L1, + LevelType::L1, + SystemId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x01]), + 9, + HelloVariant::Lan { + priority: 64, + lan_id: LanId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03]), + }, + HelloTlvs { + protocols_supported: Some(ProtocolsSupportedTlv { + list: vec![0xcc, 0x8e], + }), + area_addrs: vec![AreaAddressesTlv { + list: vec![AreaAddr::from([0x49, 0x00, 0x00].as_slice())], + }], + neighbors: vec![NeighborsTlv { + list: vec![ + [0x3e, 0x25, 0x6d, 0x6d, 0x1b, 0x25], + [0x3e, 0xe8, 0x34, 0x2b, 0x1f, 0x96], + ], + }], + ipv4_addrs: vec![Ipv4AddressesTlv { + list: vec![Ipv4Addr::from_str("10.0.1.1").unwrap()], + }], + padding: vec![ + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 153 }, + ], + unknown: vec![], + }, + )), + ) +}); + +static P2P_HELLO1: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x14, 0x01, 0x00, 0x11, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0x05, 0xd9, 0x00, 0x81, 0x02, + 0xcc, 0x8e, 0x01, 0x04, 0x03, 0x49, 0x00, 0x00, 0x84, 0x04, 0x0a, + 0x00, 0x07, 0x06, 0x08, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + Pdu::Hello(Hello::new( + LevelType::All, + LevelType::L1, + SystemId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x06]), + 9, + HelloVariant::P2P { + local_circuit_id: 0, + }, + HelloTlvs { + protocols_supported: Some(ProtocolsSupportedTlv { + list: vec![0xcc, 0x8e], + }), + area_addrs: vec![AreaAddressesTlv { + list: vec![AreaAddr::from([0x49, 0x00, 0x00].as_slice())], + }], + neighbors: vec![], + ipv4_addrs: vec![Ipv4AddressesTlv { + list: vec![Ipv4Addr::from_str("10.0.7.6").unwrap()], + }], + padding: vec![ + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 255 }, + PaddingTlv { length: 174 }, + ], + unknown: vec![], + }, + )), + ) +}); + +static CSNP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x21, 0x01, 0x00, 0x18, 0x01, 0x00, 0x00, 0x00, 0x53, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x09, 0x30, 0x04, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x47, 0x04, 0x79, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbc, + 0x41, 0x04, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xc0, 0x3b, + ], + Pdu::Snp(Snp::new( + LevelNumber::L1, + LanId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00]), + Some(( + LspId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + LspId::from([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), + )), + SnpTlvs { + lsp_entries: vec![LspEntriesTlv { + list: vec![ + LspEntry { + rem_lifetime: 1145, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + ]), + cksum: 0xb847, + seqno: 0, + }, + LspEntry { + rem_lifetime: 1145, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + ]), + cksum: 0xbc41, + seqno: 2, + }, + LspEntry { + rem_lifetime: 1162, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + ]), + cksum: 0xc03b, + seqno: 2, + }, + ], + }], + unknown: vec![], + }, + )), + ) +}); + +static PSNP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x11, 0x01, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x00, 0x53, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0x40, 0x04, 0x8e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xb0, 0x53, 0x04, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xd6, 0xe4, 0x04, 0x78, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xb8, + 0x47, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xbc, 0x41, + ], + Pdu::Snp(Snp::new( + LevelNumber::L1, + LanId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00]), + None, + SnpTlvs { + lsp_entries: vec![LspEntriesTlv { + list: vec![ + LspEntry { + rem_lifetime: 1166, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + ]), + cksum: 0xb053, + seqno: 2, + }, + LspEntry { + rem_lifetime: 1185, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, + ]), + cksum: 0xd6e4, + seqno: 1, + }, + LspEntry { + rem_lifetime: 1144, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + ]), + cksum: 0xb847, + seqno: 2, + }, + LspEntry { + rem_lifetime: 1144, + lsp_id: LspId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + ]), + cksum: 0xbc41, + seqno: 2, + }, + ], + }], + unknown: vec![], + }, + )), + ) +}); + +static LSP1: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x1b, 0x01, 0x00, 0x12, 0x01, 0x00, 0x00, 0x00, 0x4a, 0x04, + 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0xd7, 0xd0, 0x01, 0x81, 0x01, 0xcc, 0x01, 0x04, 0x03, + 0x49, 0x00, 0x00, 0x16, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x0a, 0x00, 0x84, 0x04, 0x01, 0x01, 0x01, 0x01, + 0x87, 0x11, 0x00, 0x00, 0x00, 0x0a, 0x18, 0x0a, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0a, 0x20, 0x01, 0x01, 0x01, 0x01, + ], + Pdu::Lsp(Lsp::new( + LevelNumber::L1, + 1170, + LspId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]), + 0x00000004, + LspFlags::IS_TYPE1, + LspTlvs { + protocols_supported: Some(ProtocolsSupportedTlv { + list: vec![0xcc], + }), + area_addrs: vec![AreaAddressesTlv { + list: vec![AreaAddr::from([0x49, 0, 0].as_slice())], + }], + is_reach: vec![], + ext_is_reach: vec![ExtIsReachTlv { + list: vec![ExtIsReach { + neighbor: LanId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, + ]), + metric: 10, + sub_tlvs: Default::default(), + }], + }], + ipv4_addrs: vec![Ipv4AddressesTlv { + list: vec![Ipv4Addr::from_str("1.1.1.1").unwrap()], + }], + ipv4_internal_reach: vec![], + ipv4_external_reach: vec![], + ext_ipv4_reach: vec![ExtIpv4ReachTlv { + list: vec![ + ExtIpv4Reach { + metric: 10, + up_down: false, + prefix: Ipv4Network::from_str("10.0.1.0/24") + .unwrap(), + sub_tlvs: Default::default(), + }, + ExtIpv4Reach { + metric: 10, + up_down: false, + prefix: Ipv4Network::from_str("1.1.1.1/32") + .unwrap(), + sub_tlvs: Default::default(), + }, + ], + }], + unknown: vec![], + }, + )), + ) +}); + +static LSP2: Lazy<(Vec, Pdu)> = Lazy::new(|| { + ( + vec![ + 0x83, 0x1b, 0x01, 0x00, 0x12, 0x01, 0x00, 0x00, 0x00, 0x69, 0x04, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x13, 0x06, 0xe2, 0x01, 0x81, 0x01, 0xcc, 0x01, 0x04, 0x03, + 0x49, 0x00, 0x00, 0x02, 0x17, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x84, 0x04, 0x06, 0x06, 0x06, + 0x06, 0x80, 0x24, 0x0a, 0x80, 0x80, 0x80, 0x0a, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x0a, 0x00, 0x08, + 0x00, 0xff, 0xff, 0xff, 0x00, 0x0a, 0x80, 0x80, 0x80, 0x06, 0x06, + 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, + ], + Pdu::Lsp(Lsp::new( + LevelNumber::L1, + 1187, + LspId::from([0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00]), + 0x00000013, + LspFlags::IS_TYPE1, + LspTlvs { + protocols_supported: Some(ProtocolsSupportedTlv { + list: vec![0xcc], + }), + area_addrs: vec![AreaAddressesTlv { + list: vec![AreaAddr::from([0x49, 0, 0].as_slice())], + }], + is_reach: vec![IsReachTlv { + list: vec![ + IsReach { + metric: 10, + metric_delay: None, + metric_expense: None, + metric_error: None, + neighbor: LanId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + ]), + }, + IsReach { + metric: 10, + metric_delay: None, + metric_expense: None, + metric_error: None, + neighbor: LanId::from([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, + ]), + }, + ], + }], + ext_is_reach: vec![], + ipv4_addrs: vec![Ipv4AddressesTlv { + list: vec![Ipv4Addr::from_str("6.6.6.6").unwrap()], + }], + ipv4_internal_reach: vec![Ipv4ReachTlv { + list: vec![ + Ipv4Reach { + ie_bit: false, + metric: 10, + metric_delay: None, + metric_expense: None, + metric_error: None, + prefix: Ipv4Network::from_str("10.0.7.0/24") + .unwrap(), + }, + Ipv4Reach { + ie_bit: false, + metric: 10, + metric_delay: None, + metric_expense: None, + metric_error: None, + prefix: Ipv4Network::from_str("10.0.8.0/24") + .unwrap(), + }, + Ipv4Reach { + ie_bit: false, + metric: 10, + metric_delay: None, + metric_expense: None, + metric_error: None, + prefix: Ipv4Network::from_str("6.6.6.6/32") + .unwrap(), + }, + ], + }], + ipv4_external_reach: vec![], + ext_ipv4_reach: vec![], + unknown: vec![], + }, + )), + ) +}); + +// +// Tests. +// + +#[test] +fn test_encode_lan_hello1() { + let (ref bytes, ref hello) = *LAN_HELLO1; + test_encode_pdu(bytes, hello); +} + +#[test] +fn test_decode_lan_hello1() { + let (ref bytes, ref hello) = *LAN_HELLO1; + test_decode_pdu(bytes, hello); +} + +#[test] +fn test_encode_p2p_hello1() { + let (ref bytes, ref hello) = *P2P_HELLO1; + test_encode_pdu(bytes, hello); +} + +#[test] +fn test_decode_p2p_hello1() { + let (ref bytes, ref hello) = *P2P_HELLO1; + test_decode_pdu(bytes, hello); +} + +#[test] +fn test_encode_csnp1() { + let (ref bytes, ref csnp) = *CSNP1; + test_encode_pdu(bytes, csnp); +} + +#[test] +fn test_decode_csnp1() { + let (ref bytes, ref csnp) = *CSNP1; + test_decode_pdu(bytes, csnp); +} + +#[test] +fn test_encode_psnp1() { + let (ref bytes, ref psnp) = *PSNP1; + test_encode_pdu(bytes, psnp); +} + +#[test] +fn test_decode_psnp1() { + let (ref bytes, ref psnp) = *PSNP1; + test_decode_pdu(bytes, psnp); +} + +#[test] +fn test_encode_lsp1() { + let (ref bytes, ref lsp) = *LSP1; + test_encode_pdu(bytes, lsp); +} + +#[test] +fn test_decode_lsp1() { + let (ref bytes, ref lsp) = *LSP1; + test_decode_pdu(bytes, lsp); +} + +#[test] +fn test_encode_lsp2() { + let (ref bytes, ref lsp) = *LSP2; + test_encode_pdu(bytes, lsp); +} + +#[test] +fn test_decode_lsp2() { + let (ref bytes, ref lsp) = *LSP2; + test_decode_pdu(bytes, lsp); +} diff --git a/holo-northbound/build.rs b/holo-northbound/build.rs index 8342c8a9..3b53e88b 100644 --- a/holo-northbound/build.rs +++ b/holo-northbound/build.rs @@ -147,9 +147,10 @@ impl<'a> StructBuilder<'a> { let indent5 = " ".repeat((self.level + 5) * 2); let (lifetime, anon_lifetime) = if self.snode.is_within_notification() || self.fields.iter().any(|snode| { - !snode - .leaf_type() - .is_some_and(|leaf_type| leaf_type_is_builtin(&leaf_type)) + snode.kind() == SchemaNodeKind::LeafList + || !snode.leaf_type().is_some_and(|leaf_type| { + leaf_type_is_builtin(&leaf_type) + }) }) { ("<'a>", "<'_>") } else { @@ -349,6 +350,8 @@ fn snode_normalized_name(snode: &SchemaNode<'_>, case: Case) -> String { | "destination-prefix" | "address" | "next-hop-address" + | "clear-database" + | "if-state-change" ) { if snode.module().name() == "ietf-ipv4-unicast-routing" { name.insert_str(0, "ipv4-"); @@ -359,6 +362,16 @@ fn snode_normalized_name(snode: &SchemaNode<'_>, case: Case) -> String { if snode.module().name() == "ietf-mpls" { name.insert_str(0, "mpls-"); } + if snode.module().name() == "ietf-isis" { + name.insert_str(0, "isis-"); + } + } + if let Some(snode_parent) = snode.ancestors().next() + && snode_parent.module().name() == "ietf-routing" + && snode.module().name() == "ietf-isis" + && matches!(snode.name(), "route-type" | "tag" | "metric") + { + name.insert_str(0, "isis-"); } // Case conversion. diff --git a/holo-routing/Cargo.toml b/holo-routing/Cargo.toml index d9220ff0..631e11c3 100644 --- a/holo-routing/Cargo.toml +++ b/holo-routing/Cargo.toml @@ -29,6 +29,7 @@ holo-yang = { path = "../holo-yang" } holo-bfd = { path = "../holo-bfd", optional = true } holo-bgp = { path = "../holo-bgp", optional = true } +holo-isis = { path = "../holo-isis", optional = true } holo-ldp = { path = "../holo-ldp", optional = true } holo-ospf = { path = "../holo-ospf", optional = true } holo-rip = { path = "../holo-rip", optional = true } @@ -39,6 +40,7 @@ workspace = true [features] bfd = ["dep:holo-bfd"] bgp = ["dep:holo-bgp"] +isis = ["dep:holo-isis"] ldp = ["dep:holo-ldp"] ospf = ["dep:holo-ospf"] rip = ["dep:holo-rip"] diff --git a/holo-routing/src/netlink.rs b/holo-routing/src/netlink.rs index 195ada1c..8a55dffb 100644 --- a/holo-routing/src/netlink.rs +++ b/holo-routing/src/netlink.rs @@ -20,6 +20,7 @@ use crate::rib::Route; fn netlink_protocol(protocol: Protocol) -> RouteProtocol { match protocol { Protocol::BGP => RouteProtocol::Bgp, + Protocol::ISIS => RouteProtocol::Isis, Protocol::OSPFV2 | Protocol::OSPFV3 => RouteProtocol::Ospf, Protocol::RIPV2 | Protocol::RIPNG => RouteProtocol::Rip, Protocol::STATIC => RouteProtocol::Static, diff --git a/holo-routing/src/northbound/configuration.rs b/holo-routing/src/northbound/configuration.rs index 60639913..30928bc4 100644 --- a/holo-routing/src/northbound/configuration.rs +++ b/holo-routing/src/northbound/configuration.rs @@ -942,6 +942,8 @@ impl Provider for Master { holo_bfd::northbound::configuration::CALLBACKS.keys(), #[cfg(feature = "bgp")] holo_bgp::northbound::configuration::CALLBACKS.keys(), + #[cfg(feature = "isis")] + holo_isis::northbound::configuration::CALLBACKS.keys(), #[cfg(feature = "ldp")] holo_ldp::northbound::configuration::CALLBACKS.keys(), #[cfg(feature = "ospf")] @@ -1117,6 +1119,18 @@ fn instance_start(master: &mut Master, protocol: Protocol, name: String) { // This protocol type can not be configured. unreachable!() } + #[cfg(feature = "isis")] + Protocol::ISIS => { + use holo_isis::instance::Instance; + + spawn_protocol_task::( + name, + &master.nb_tx, + &master.ibus_tx, + Default::default(), + master.shared.clone(), + ) + } #[cfg(feature = "ldp")] Protocol::LDP => { use holo_ldp::instance::Instance; diff --git a/holo-routing/src/northbound/rpc.rs b/holo-routing/src/northbound/rpc.rs index f39a6ab0..553fa5b8 100644 --- a/holo-routing/src/northbound/rpc.rs +++ b/holo-routing/src/northbound/rpc.rs @@ -18,6 +18,8 @@ impl Provider for Master { let keys: Vec> = vec![ #[cfg(feature = "bgp")] holo_bgp::northbound::rpc::CALLBACKS.keys(), + #[cfg(feature = "isis")] + holo_isis::northbound::rpc::CALLBACKS.keys(), #[cfg(feature = "ldp")] holo_ldp::northbound::rpc::CALLBACKS.keys(), #[cfg(feature = "ospf")] diff --git a/holo-routing/src/northbound/state.rs b/holo-routing/src/northbound/state.rs index 9fee92d7..a1c7eba5 100644 --- a/holo-routing/src/northbound/state.rs +++ b/holo-routing/src/northbound/state.rs @@ -171,6 +171,9 @@ fn load_callbacks() -> Callbacks { metric, tag, route_type, + isis_metric: None, + isis_tag: None, + isis_route_type: None, }) }) .path(ribs::rib::routes::route::next_hop::PATH) @@ -354,6 +357,8 @@ impl Provider for Master { holo_bfd::northbound::state::CALLBACKS.keys(), #[cfg(feature = "bgp")] holo_bgp::northbound::state::CALLBACKS.keys(), + #[cfg(feature = "isis")] + holo_isis::northbound::state::CALLBACKS.keys(), #[cfg(feature = "ldp")] holo_ldp::northbound::state::CALLBACKS.keys(), #[cfg(feature = "ospf")] diff --git a/holo-tools/Cargo.toml b/holo-tools/Cargo.toml index d700a3e4..2d389ff1 100644 --- a/holo-tools/Cargo.toml +++ b/holo-tools/Cargo.toml @@ -18,6 +18,7 @@ yang3.workspace = true holo-bfd = { path = "../holo-bfd", features = ["testing"] } holo-bgp = { path = "../holo-bgp", features = ["testing"] } holo-ldp = { path = "../holo-ldp", features = ["testing"] } +holo-isis = { path = "../holo-isis", features = ["testing"] } holo-rip = { path = "../holo-rip", features = ["testing"] } holo-northbound = { path = "../holo-northbound" } holo-ospf = { path = "../holo-ospf", features = ["testing"] } diff --git a/holo-tools/src/replay.rs b/holo-tools/src/replay.rs index 40a5bcdf..639c9da6 100644 --- a/holo-tools/src/replay.rs +++ b/holo-tools/src/replay.rs @@ -64,6 +64,9 @@ async fn main() { Protocol::BFD => replay::(filename).await, Protocol::BGP => replay::(filename).await, Protocol::LDP => replay::(filename).await, + Protocol::ISIS => { + replay::(filename).await + } Protocol::OSPFV2 => { replay::>(filename).await } diff --git a/holo-tools/yang-coverage.sh b/holo-tools/yang-coverage.sh index e153aaff..f90db802 100755 --- a/holo-tools/yang-coverage.sh +++ b/holo-tools/yang-coverage.sh @@ -19,6 +19,7 @@ cargo run --bin yang_coverage --\ -m ietf-bfd-ip-sh\ -m ietf-bgp\ -m ietf-bgp-policy\ + -m ietf-isis\ -m ietf-mpls-ldp\ -m ietf-ospf\ -m ietf-ospf-sr-mpls\ diff --git a/holo-utils/src/protocol.rs b/holo-utils/src/protocol.rs index 15d48073..134f44ec 100644 --- a/holo-utils/src/protocol.rs +++ b/holo-utils/src/protocol.rs @@ -19,6 +19,7 @@ pub enum Protocol { BFD, BGP, DIRECT, + ISIS, LDP, OSPFV2, OSPFV3, @@ -35,6 +36,7 @@ impl std::fmt::Display for Protocol { Protocol::BFD => write!(f, "bfd"), Protocol::BGP => write!(f, "bgp"), Protocol::DIRECT => write!(f, "direct"), + Protocol::ISIS => write!(f, "isis"), Protocol::LDP => write!(f, "ldp"), Protocol::OSPFV2 => write!(f, "ospfv2"), Protocol::OSPFV3 => write!(f, "ospfv3"), @@ -53,6 +55,7 @@ impl FromStr for Protocol { "bfd" => Ok(Protocol::BFD), "bgp" => Ok(Protocol::BGP), "direct" => Ok(Protocol::DIRECT), + "isis" => Ok(Protocol::ISIS), "ldp" => Ok(Protocol::LDP), "ospfv2" => Ok(Protocol::OSPFV2), "ospfv3" => Ok(Protocol::OSPFV3), @@ -70,6 +73,7 @@ impl ToYang for Protocol { Protocol::BFD => "ietf-bfd-types:bfdv1".into(), Protocol::BGP => "ietf-bgp:bgp".into(), Protocol::DIRECT => "ietf-routing:direct".into(), + Protocol::ISIS => "ietf-isis:isis".into(), Protocol::LDP => "ietf-mpls-ldp:mpls-ldp".into(), Protocol::OSPFV2 => "ietf-ospf:ospfv2".into(), Protocol::OSPFV3 => "ietf-ospf:ospfv3".into(), @@ -86,6 +90,7 @@ impl TryFromYang for Protocol { "ietf-bfd-types:bfdv1" => Some(Protocol::BFD), "ietf-bgp:bgp" => Some(Protocol::BGP), "ietf-routing:direct" => Some(Protocol::DIRECT), + "ietf-isis:isis" => Some(Protocol::ISIS), "ietf-mpls-ldp:mpls-ldp" => Some(Protocol::LDP), "ietf-ospf:ospfv2" => Some(Protocol::OSPFV2), "ietf-ospf:ospfv3" => Some(Protocol::OSPFV3), diff --git a/holo-yang/modules/augmentations/holo-isis.yang b/holo-yang/modules/augmentations/holo-isis.yang new file mode 100644 index 00000000..978b3b6d --- /dev/null +++ b/holo-yang/modules/augmentations/holo-isis.yang @@ -0,0 +1,52 @@ +module holo-isis { + yang-version 1.1; + namespace "http://holo-routing.org/yang/holo-isis"; + prefix holo-isis; + + import ietf-routing { + prefix rt; + } + + import ietf-isis { + prefix isis; + } + + organization + "Holo Routing Stack"; + + description + "This module defines augment statements for the ietf-isis + module."; + + /* + * Augmentations. + */ + + augment "/rt:routing/rt:control-plane-protocols/" + + "rt:control-plane-protocol/isis:isis/" + + "isis:interfaces/isis:interface" { + description + "IS-IS interface augmentations"; + leaf state { + config false; + type enumeration { + enum down { + description + "Interface is in the 'Down' state."; + } + enum up { + description + "Interface is in the 'Up' state."; + } + } + description + "Interface state."; + } + leaf circuit-id { + config false; + type isis:circuit-id; + description + "Interface circuit ID."; + } + } +} diff --git a/holo-yang/modules/deviations/holo-ietf-isis-deviations.yang b/holo-yang/modules/deviations/holo-ietf-isis-deviations.yang new file mode 100644 index 00000000..39c87346 --- /dev/null +++ b/holo-yang/modules/deviations/holo-ietf-isis-deviations.yang @@ -0,0 +1,4372 @@ +module holo-ietf-isis-deviations { + yang-version 1.1; + namespace "http://holo-routing.org/yang/holo-ietf-isis-deviations"; + prefix holo-ietf-isis-deviations; + + import ietf-routing { + prefix rt; + } + + import ietf-isis { + prefix isis; + } + + organization + "Holo Routing Stack"; + + description + "This module defines deviation statements for the ietf-isis + module."; + + /* + * Default values + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-lifetime" { + deviate add { + default "1200"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-refresh" { + deviate add { + default "900"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:address-families/isis:address-family-list/isis:enabled" { + deviate add { + default "true"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:paths" { + deviate add { + default "16"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:lsp-retransmit-interval" { + deviate add { + default "5"; + } + } + + /* + * Type changes + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-refresh" { + deviate replace { + type "uint16" { + range "120..65535"; + } + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity" { + deviate add { + default "coarse"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:coarse/isis:default" { + deviate add { + default "115"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:initial-delay" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:short-delay" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:long-delay" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:hold-down" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:time-to-learn" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:lsp-pacing-interval" { + deviate replace { + type "uint32"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:lsp-retransmit-interval" { + deviate replace { + type "uint16"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:csnp-interval" { + deviate replace { + type "uint16"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:value" { + deviate replace { + type "uint16"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-1/isis:value" { + deviate replace { + type "uint16"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-2/isis:value" { + deviate replace { + type "uint16"; + } + } + + /* + * Workaround + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority" { + deviate delete { + must '../interface-type = "broadcast"'; + } + } + + /* + * Not supported nodes + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:level-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:area-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-mtu" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-lifetime" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-refresh" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:metric-type/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:default-metric/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-1/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:authentication/isis:level-2/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:mpls" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:mpls/isis:ldp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:initial-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:short-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:long-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:hold-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:time-to-learn" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:current-state" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:remaining-time-to-learn" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:remaining-hold-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:last-event-received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:next-spf-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-control/isis:ietf-spf-delay/isis:last-spf-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:detail" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:detail/isis:internal" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:detail/isis:external" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:coarse" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:preference/isis:granularity/isis:coarse/isis:default" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:overload" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:overload/isis:status" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:spf-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:schedule-timestamp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:start-timestamp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:end-timestamp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:trigger-lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:trigger-lsp/isis:lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:spf-log/isis:event/isis:trigger-lsp/isis:sequence" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:lsp/isis:lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:lsp/isis:sequence" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:received-timestamp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:lsp-log/isis:event/isis:reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:hostnames" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:hostnames/isis:hostname" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:hostnames/isis:hostname/isis:system-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:hostnames/isis:hostname/isis:hostname" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:decoded-completed" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:raw-data" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:checksum" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:remaining-lifetime" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:sequence" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:attributes/isis:lsp-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-addresses" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-addresses" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-te-routerid" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-te-routerid" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:protocol-supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:dynamic-hostname" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:authentication" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:authentication/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:authentication/isis:authentication-key" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-entries" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-entries/isis:topology" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-entries/isis:topology/isis:mt-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-entries/isis:topology/isis:attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-entries/isis:topology/isis:attributes/isis:flags" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:flags/isis:router-capability-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:router-capabilities/isis:router-capability/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:neighbor-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:link-local-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:link-remote-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:srlgs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:links-srlgs/isis:links/isis:srlgs/isis:srlg" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:neighbor-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:i-e" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:default-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:default-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:delay-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:delay-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:delay-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:expense-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:expense-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:expense-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:error-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:error-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:error-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:neighbor-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:metric" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:link-local-id" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-local-id" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:protection-capability" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:switching-capability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:encoding" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth/isis:priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth/isis:bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific/isis:minimum-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific/isis:indication" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific/isis:minimum-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific/isis:mtu" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:admin-group" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:local-if-ipv4-addrs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:local-if-ipv4-addrs/isis:local-if-ipv4-addr" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-if-ipv4-addrs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-if-ipv4-addrs/isis:remote-if-ipv4-addr" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:te-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:max-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:max-reservable-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth/isis:priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth/isis:unreserved-bandwidth" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:flags/isis:unidirectional-link-delay-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:flags/isis:min-max-unidirectional-link-delay-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:min-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:max-value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay-variation" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay-variation/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:flags/isis:unidirectional-link-loss-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-residual-bandwidth" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-residual-bandwidth/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-available-bandwidth" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-available-bandwidth/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-utilized-bandwidth" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-utilized-bandwidth/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:link-attributes-flags" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:i-e" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:default-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:default-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:delay-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:delay-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:delay-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:expense-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:expense-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:expense-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:error-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:error-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-internal-reachability/isis:prefixes/isis:error-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:i-e" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:default-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:default-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:delay-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:delay-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:delay-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:expense-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:expense-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:expense-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:error-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:error-metric/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv4-external-reachability/isis:prefixes/isis:error-metric/isis:supported" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:up-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:metric" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:tag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:tag64" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:external-prefix-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:readvertisement-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:node-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:ipv4-source-router-id" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:ipv6-source-router-id" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:mt-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:neighbor-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:link-local-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-local-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:protection-capability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:switching-capability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:encoding" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth/isis:priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:max-lsp-bandwidths/isis:max-lsp-bandwidth/isis:bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific/isis:minimum-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:tdm-specific/isis:indication" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific/isis:minimum-lsp-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:interface-switching-capability/isis:psc-specific/isis:mtu" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:admin-group" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:local-if-ipv4-addrs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:local-if-ipv4-addrs/isis:local-if-ipv4-addr" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-if-ipv4-addrs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:remote-if-ipv4-addrs/isis:remote-if-ipv4-addr" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:te-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:max-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:max-reservable-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth/isis:priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unreserved-bandwidths/isis:unreserved-bandwidth/isis:unreserved-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:flags/isis:unidirectional-link-delay-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:flags/isis:min-max-unidirectional-link-delay-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:min-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:min-max-unidirectional-link-delay/isis:max-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay-variation" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-delay-variation/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:flags/isis:unidirectional-link-loss-subtlv-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-loss/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-residual-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-residual-bandwidth/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-available-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-available-bandwidth/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-utilized-bandwidth" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unidirectional-link-utilized-bandwidth/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:link-attributes-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-is-neighbor/isis:neighbor/isis:instances/isis:instance/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:mt-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:up-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:tag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:tag64" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:external-prefix-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:readvertisement-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:node-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:ipv4-source-router-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:ipv6-source-router-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-extended-ipv4-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:mt-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:up-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:tag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:tag64" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:external-prefix-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:readvertisement-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:node-flag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:ipv4-source-router-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:ipv6-source-router-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:mt-ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:up-down" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:ip-prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:prefix-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:metric" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:tag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:tag64" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:external-prefix-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:readvertisement-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:node-flag" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:ipv4-source-router-id" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:ipv6-source-router-id" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:unknown-tlvs" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:database/isis:levels/isis:lsp/isis:ipv6-reachability/isis:prefixes/isis:unknown-tlvs/isis:unknown-tlv/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:next-hops" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:next-hops/isis:next-hop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:next-hops/isis:next-hop/isis:next-hop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:next-hops/isis:next-hop/isis:outgoing-interface" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:local-rib/isis:route/isis:route-tag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:corrupted-lsps" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:authentication-type-fails" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:authentication-fails" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:database-overload" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:own-lsp-purge" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:manual-address-drop-from-area" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:max-sequence" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:sequence-number-skipped" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:id-len-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:partition-changes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:lsp-errors" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:system-counters/isis:level/isis:spf-runs" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:address-family" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:alternate" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:alternate-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:best" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:non-best-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:protection-available" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:protection-available/isis:protection-types" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:alternate-metric-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:alternate-metric-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protected-routes/isis:address-family-stats/isis:alternate-metric-3" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:unprotected-routes" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:unprotected-routes/isis:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:unprotected-routes/isis:prefixes/isis:address-family" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:unprotected-routes/isis:prefixes/isis:prefix" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:frr-protection-method" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:address-family" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:total-routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:unprotected-routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:protected-routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:link-protected-routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:protection-statistics/isis:address-family-stats/isis:node-protected-routes" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:discontinuity-time" { + deviate add { + config false; + } + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:topologies/isis:topology/isis:default-metric/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:level-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:lsp-pacing-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:lsp-retransmit-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:passive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:csnp-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-padding" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-padding/isis:enabled" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:mesh-group-enabled" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:mesh-group" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:interface-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-1/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type/isis:key-chain/isis:key-chain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type/isis:password" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type/isis:password/isis:key" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-authentication/isis:level-2/isis:authentication-type/isis:password/isis:crypto-algorithm" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-interval/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:hello-multiplier/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:priority/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:metric/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:mpls" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:mpls/isis:ldp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:neighbor-sys-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:neighbor-sysid" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:neighbor-extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:neighbor-snpa" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:usage" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:hold-timer" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:neighbor-priority" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:lastuptime" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:adjacencies/isis:adjacency/isis:state" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:adjacency-changes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:adjacency-number" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:init-fails" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:adjacency-rejects" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:id-len-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:max-area-addresses-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:authentication-type-fails" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:authentication-fails" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:event-counters/isis:lan-dis-changes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:iih" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:iih/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:iih/isis:out" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:ish" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:ish/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:ish/isis:out" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:esh" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:esh/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:esh/isis:out" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:lsp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:lsp/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:lsp/isis:out" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:psnp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:psnp/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:psnp/isis:out" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:csnp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:csnp/isis:in" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:csnp/isis:out" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:unknown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:packet-counters/isis:level/isis:unknown/isis:in" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:discontinuity-time" { + deviate add { + config false; + } + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric/isis:level-1" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric/isis:level-1/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric/isis:level-2" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/isis:isis/isis:interfaces/isis:interface/isis:topologies/isis:topology/isis:metric/isis:level-2/isis:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/isis:metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/isis:tag" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/isis:route-type" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency/isis:input" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency/isis:input/isis:routing-protocol-instance-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency/isis:input/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency/isis:input/isis:interface" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-adjacency/isis:output" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-database" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-database/isis:input" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-database/isis:input/isis:routing-protocol-instance-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-database/isis:input/isis:level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:clear-database/isis:output" { + deviate not-supported; + } + */ + + /* + deviation "/isis:database-overload" { + deviate not-supported; + } + */ + + /* + deviation "/isis:database-overload/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:database-overload/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:database-overload/isis:overload" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:pdu-size" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-too-large/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:if-state-change/isis:state" { + deviate not-supported; + } + */ + + /* + deviation "/isis:corrupted-lsp-detected" { + deviate not-supported; + } + */ + + /* + deviation "/isis:corrupted-lsp-detected/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:corrupted-lsp-detected/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:corrupted-lsp-detected/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:attempt-to-exceed-max-sequence" { + deviate not-supported; + } + */ + + /* + deviation "/isis:attempt-to-exceed-max-sequence/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:attempt-to-exceed-max-sequence/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:attempt-to-exceed-max-sequence/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:pdu-field-len" { + deviate not-supported; + } + */ + + /* + deviation "/isis:id-len-mismatch/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:max-area-addresses" { + deviate not-supported; + } + */ + + /* + deviation "/isis:max-area-addresses-mismatch/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:own-lsp-purge/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:sequence-number-skipped/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-type-failure/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:authentication-failure/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:protocol-version" { + deviate not-supported; + } + */ + + /* + deviation "/isis:version-skew/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:area-mismatch/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:rejected-adjacency/isis:reason" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:protocols-supported-mismatch/isis:protocols" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:raw-pdu" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:error-offset" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-error-detected/isis:tlv-type" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:neighbor-system-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:state" { + deviate not-supported; + } + */ + + /* + deviation "/isis:adjacency-state-change/isis:reason" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:interface-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:interface-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:extended-circuit-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:sequence" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:received-timestamp" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-received/isis:neighbor-system-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation/isis:routing-protocol-name" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation/isis:isis-level" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation/isis:lsp-id" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation/isis:sequence" { + deviate not-supported; + } + */ + + /* + deviation "/isis:lsp-generation/isis:send-timestamp" { + deviate not-supported; + } + */ +} diff --git a/holo-yang/src/lib.rs b/holo-yang/src/lib.rs index cdddc8f9..a773e27d 100644 --- a/holo-yang/src/lib.rs +++ b/holo-yang/src/lib.rs @@ -128,6 +128,8 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { // IETF Holo augmentations EmbeddedModuleKey::new("holo-bgp", None, None, None) => include_str!("../modules/augmentations/holo-bgp.yang"), + EmbeddedModuleKey::new("holo-isis", None, None, None) => + include_str!("../modules/augmentations/holo-isis.yang"), EmbeddedModuleKey::new("holo-ospf", None, None, None) => include_str!("../modules/augmentations/holo-ospf.yang"), EmbeddedModuleKey::new("holo-ospf-dev", None, None, None) => @@ -149,6 +151,8 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { include_str!("../modules/deviations/holo-ietf-interfaces-deviations.yang"), EmbeddedModuleKey::new("holo-ietf-ip-deviations", None, None, None) => include_str!("../modules/deviations/holo-ietf-ip-deviations.yang"), + EmbeddedModuleKey::new("holo-ietf-isis-deviations", None, None, None) => + include_str!("../modules/deviations/holo-ietf-isis-deviations.yang"), EmbeddedModuleKey::new("holo-ietf-mpls-deviations", None, None, None) => include_str!("../modules/deviations/holo-ietf-mpls-deviations.yang"), EmbeddedModuleKey::new("holo-ietf-key-chain-deviations", None, None, None) => @@ -200,6 +204,7 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = "ietf-if-vlan-encapsulation", "ietf-interfaces", "ietf-ip", + "ietf-isis", "ietf-key-chain", "ietf-routing", "ietf-routing-policy", @@ -218,6 +223,7 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = "ietf-tcp", // IETF Holo augmentations "holo-bgp", + "holo-isis", "holo-ospf", "holo-ospf-dev", "holo-routing", @@ -244,6 +250,14 @@ pub static YANG_FEATURES: Lazy>> = "ietf-if-extensions" => vec![ "sub-interfaces", ], + "ietf-isis" => vec![ + "admin-control", + "ietf-spf-delay", + "key-chain", + "lsp-refresh", + "max-ecmp", + "nlpid-control", + ], "ietf-ospf" => vec![ "bfd", "explicit-router-id",