diff --git a/Cargo.toml b/Cargo.toml index b82ae945..01a15879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "holo-bfd", + "holo-bgp", "holo-cli", "holo-daemon", "holo-interface", @@ -35,6 +36,7 @@ check_keyword = "0.2" clap = "2.33" chrono = { version = "0.4", features = ["serde"] } criterion = "0.4" +crossbeam-channel = "0.5" derive-new = "0.5" enum-as-inner = "0.6" futures = "0.3" diff --git a/README.md b/README.md index 745a1169..e8bf33be 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,29 @@ Holo supports the following IETF RFCs and Internet drafts: * RFC 5882 - Generic Application of Bidirectional Forwarding Detection (BFD) * RFC 5883 - Bidirectional Forwarding Detection (BFD) for Multihop Paths +##### BGP + +* RFC 1997 - BGP Communities Attribute +* RFC 2385 - Protection of BGP Sessions via the TCP MD5 Signature Option +* RFC 2545 - Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing +* RFC 2918 - Route Refresh Capability for BGP-4 +* RFC 4271 - A Border Gateway Protocol 4 (BGP-4) +* RFC 4360 - BGP Extended Communities Attribute +* RFC 4486 - Subcodes for BGP Cease Notification Message +* RFC 4760 - Multiprotocol Extensions for BGP-4 +* RFC 5082 - The Generalized TTL Security Mechanism (GTSM) +* RFC 5492 - Capabilities Advertisement with BGP-4 +* RFC 5668 - 4-Octet AS Specific BGP Extended Community +* RFC 5701 - IPv6 Address Specific BGP Extended Community Attribute +* RFC 6286 - Autonomous-System-Wide Unique BGP Identifier for BGP-4 +* RFC 6608 - Subcodes for BGP Finite State Machine Error +* RFC 6793 - BGP Support for Four-Octet Autonomous System (AS) Number Space +* RFC 7606 - Revised Error Handling for BGP UPDATE Messages +* RFC 7607 - Codification of AS 0 Processing +* RFC 8092 - BGP Large Communities Attribute +* RFC 8212 - Default External BGP (EBGP) Route Propagation Behavior without Policies +* RFC 8642 - Policy Behavior for Well-Known BGP Communities + ##### MPLS LDP * RFC 5036 - LDP Specification @@ -192,6 +215,8 @@ Holo supports the following IETF RFCs and Internet drafts: | ietf-bfd-ip-mh@2022-09-22 | 100.00% | 100.00% | - | 100.00% | [100.00%](http://westphal.com.br/holo/ietf-bfd-ip-mh.html) | | ietf-bfd-ip-sh@2022-09-22 | 100.00% | 100.00% | - | 100.00% | [100.00%](http://westphal.com.br/holo/ietf-bfd-ip-sh.html) | | ietf-bfd@2022-09-22 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-bfd.html) | +| ietf-bgp-policy@2023-07-05 | 100.00% | - | - | - | [100.00%](http://westphal.com.br/holo/ietf-bgp-policy.html) | +| ietf-bgp@2023-07-05 | 32.38% | 87.86% | - | - | [61.39%](http://westphal.com.br/holo/ietf-bgp.html) | | ietf-interfaces@2018-01-09 | 100.00% | 0.00% | - | - | [22.22%](http://westphal.com.br/holo/ietf-interfaces.html) | | ietf-ip@2018-01-09 | 17.39% | 0.00% | - | - | [13.33%](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) | diff --git a/holo-bgp/Cargo.toml b/holo-bgp/Cargo.toml new file mode 100644 index 00000000..566c34f7 --- /dev/null +++ b/holo-bgp/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "holo-bgp" +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 +crossbeam-channel.workspace = true +derive-new.workspace = true +enum-as-inner.workspace = true +generational-arena.workspace = true +ipnetwork.workspace = true +itertools.workspace = true +libc.workspace = true +num-derive.workspace = true +num-traits.workspace = true +rand.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tracing.workspace = true +yang2.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-bgp = { path = ".", features = ["testing"] } +holo-protocol = { path = "../holo-protocol", features = ["testing"] } +holo-utils = { path = "../holo-utils", features = ["testing"] } + +[lints] +workspace = true + +[features] +default = [] +testing = [] + +[[bench]] +name = "msg_encoding" +harness = false + +[[bench]] +name = "msg_decoding" +harness = false diff --git a/holo-bgp/LICENSE b/holo-bgp/LICENSE new file mode 100644 index 00000000..4481fc10 --- /dev/null +++ b/holo-bgp/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-bgp/benches/msg_decoding.rs b/holo-bgp/benches/msg_decoding.rs new file mode 100644 index 00000000..3b3b0012 --- /dev/null +++ b/holo-bgp/benches/msg_decoding.rs @@ -0,0 +1,42 @@ +#![feature(lazy_cell)] + +use std::hint::black_box; + +use criterion::{criterion_group, criterion_main, Criterion}; +use holo_bgp::neighbor::PeerType; +use holo_bgp::packet::message::{ + Capability, DecodeCxt, FourOctetAsNumber, Message, +}; + +fn msg_decode(n: u64) { + let cxt = DecodeCxt { + peer_type: PeerType::Internal, + peer_as: n as u32, + capabilities: [Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(n as u32), + }] + .into(), + }; + + let bytes = vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x3d, 0x01, 0x04, 0x00, 0x01, 0x00, 0xb4, + 0x01, 0x01, 0x01, 0x01, 0x20, 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, + 0x01, 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, + 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x01, 0x00, 0x0e, 0x02, 0x02, 0x46, + 0x00, + ]; + + for _ in 0..n { + let _msg = Message::decode(&bytes, &cxt).unwrap(); + } +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("Message decode", |b| { + b.iter(|| msg_decode(black_box(10000))) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/holo-bgp/benches/msg_encoding.rs b/holo-bgp/benches/msg_encoding.rs new file mode 100644 index 00000000..e9545b80 --- /dev/null +++ b/holo-bgp/benches/msg_encoding.rs @@ -0,0 +1,59 @@ +#![feature(lazy_cell)] + +use std::hint::black_box; +use std::net::Ipv4Addr; +use std::str::FromStr; +use std::sync::LazyLock as Lazy; + +use criterion::{criterion_group, criterion_main, Criterion}; +use holo_bgp::packet::consts::{Afi, Safi, BGP_VERSION}; +use holo_bgp::packet::message::{ + Capability, EncodeCxt, FourOctetAsNumber, Message, OpenMsg, +}; + +static MESSAGE: Lazy = Lazy::new(|| { + Message::Open(OpenMsg { + version: BGP_VERSION, + my_as: 1, + holdtime: 180, + identifier: Ipv4Addr::from_str("1.1.1.1").unwrap(), + capabilities: [ + Capability::MultiProtocol { + afi: Afi::Ipv4, + safi: Safi::Unicast, + }, + Capability::MultiProtocol { + afi: Afi::Ipv6, + safi: Safi::Unicast, + }, + Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(65550), + }, + Capability::RouteRefresh, + Capability::EnhancedRouteRefresh, + ] + .into(), + }) +}); + +fn msg_encode(n: u64) { + let cxt = EncodeCxt { + capabilities: [Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(n as u32), + }] + .into(), + }; + + for _ in 0..n { + MESSAGE.encode(&cxt); + } +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("Message encode", |b| { + b.iter(|| msg_encode(black_box(10000))) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/holo-bgp/src/af.rs b/holo-bgp/src/af.rs new file mode 100644 index 00000000..003ab143 --- /dev/null +++ b/holo-bgp/src/af.rs @@ -0,0 +1,310 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use holo_utils::bgp::AfiSafi; +use holo_utils::ip::{IpAddrKind, IpNetworkKind, Ipv4AddrExt, Ipv6AddrExt}; +use ipnetwork::{Ipv4Network, Ipv6Network}; +use itertools::Itertools; + +use crate::neighbor::{ + Neighbor, NeighborUpdateQueue, NeighborUpdateQueues, PeerType, +}; +use crate::packet::attribute::{self, BaseAttrs, ATTR_MIN_LEN_EXT}; +use crate::packet::consts::{Afi, Safi}; +use crate::packet::message::{ + Message, MpReachNlri, MpUnreachNlri, ReachNlri, UnreachNlri, UpdateMsg, +}; +use crate::rib::{RoutingTable, RoutingTables}; + +// BGP address-family specific code. +pub trait AddressFamily: Sized { + // Address Family Identifier. + const AFI: Afi; + // Subsequent Address Family Identifier. + const SAFI: Safi; + // Combined AFI and SAFI. + const AFI_SAFI: AfiSafi; + + // The type of IP address used by this address family. + type IpAddr: IpAddrKind; + // The type of IP network used by this address family. + type IpNetwork: IpNetworkKind; + + // Get the routing table for this address family from the provided + // `RoutingTables`. + fn table(tables: &mut RoutingTables) -> &mut RoutingTable; + + // Get the update queue for this address family from the provided + // `NeighborUpdateQueues`. + fn update_queue( + queues: &mut NeighborUpdateQueues, + ) -> &mut NeighborUpdateQueue; + + // Extract the next hop IP address from the received BGP attributes. + fn nexthop_rx_extract(attrs: &BaseAttrs) -> IpAddr; + + // Modify the next hop(s) for transmission. + fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs); + + // Build BGP UPDATE messages based on the provided update queue. + fn build_updates(queue: &mut NeighborUpdateQueue) -> Vec; +} + +#[derive(Debug)] +pub struct Ipv4Unicast; + +#[derive(Debug)] +pub struct Ipv6Unicast; + +// ===== impl Ipv4Unicast ===== + +impl AddressFamily for Ipv4Unicast { + const AFI: Afi = Afi::Ipv4; + const SAFI: Safi = Safi::Unicast; + const AFI_SAFI: AfiSafi = AfiSafi::Ipv4Unicast; + + type IpAddr = Ipv4Addr; + type IpNetwork = Ipv4Network; + + fn table(tables: &mut RoutingTables) -> &mut RoutingTable { + &mut tables.ipv4_unicast + } + + fn update_queue( + queues: &mut NeighborUpdateQueues, + ) -> &mut NeighborUpdateQueue { + &mut queues.ipv4_unicast + } + + fn nexthop_rx_extract(attrs: &BaseAttrs) -> IpAddr { + attrs.nexthop.unwrap() + } + + fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs) { + match nbr.peer_type { + PeerType::Internal => { + // Next hop isn't modified. + } + PeerType::External => { + if !nbr.shared_subnet { + // Update next hop. + match nbr.conn_info.as_ref().unwrap().local_addr { + IpAddr::V4(src_addr) => { + // BGP over IPv4. + // + // Use source address of the eBGP session. + attrs.nexthop = Some(src_addr.into()) + } + IpAddr::V6(_src_addr) => { + // BGP over IPv6. + // + // TODO: use IPv4 address of the corresponding + // system interface. + attrs.nexthop = None; + } + } + } else { + // Next hop isn't modified (eBGP next hop optimization). + } + } + } + } + + fn build_updates(queue: &mut NeighborUpdateQueue) -> Vec { + let mut msgs = vec![]; + let reach = std::mem::take(&mut queue.reach); + let unreach = std::mem::take(&mut queue.unreach); + + // Reachable prefixes. + for (attrs, prefixes) in reach.into_iter() { + let nexthop = Ipv4Addr::get(attrs.base.nexthop.unwrap()).unwrap(); + let max = (Message::MAX_LEN + - UpdateMsg::MIN_LEN + - attrs.length() + - attribute::nexthop::length()) + / (1 + Ipv4Addr::LENGTH as u16); + + msgs.extend( + prefixes.into_iter().chunks(max as usize).into_iter().map( + |chunk| { + let reach = ReachNlri { + prefixes: chunk.collect(), + nexthop, + }; + Message::Update(UpdateMsg { + reach: Some(reach), + unreach: None, + mp_reach: None, + mp_unreach: None, + attrs: Some(attrs.clone()), + }) + }, + ), + ); + } + + // Unreachable prefixes. + if !unreach.is_empty() { + let max = (Message::MAX_LEN - UpdateMsg::MIN_LEN) + / (1 + Ipv4Addr::LENGTH as u16); + + msgs.extend( + unreach.into_iter().chunks(max as usize).into_iter().map( + |chunk| { + let unreach = UnreachNlri { + prefixes: chunk.collect(), + }; + Message::Update(UpdateMsg { + reach: None, + unreach: Some(unreach), + mp_reach: None, + mp_unreach: None, + attrs: None, + }) + }, + ), + ); + } + + msgs + } +} + +// ===== impl Ipv6Unicast ===== + +impl AddressFamily for Ipv6Unicast { + const AFI: Afi = Afi::Ipv6; + const SAFI: Safi = Safi::Unicast; + const AFI_SAFI: AfiSafi = AfiSafi::Ipv6Unicast; + + type IpAddr = Ipv6Addr; + type IpNetwork = Ipv6Network; + + fn table(tables: &mut RoutingTables) -> &mut RoutingTable { + &mut tables.ipv6_unicast + } + + fn update_queue( + queues: &mut NeighborUpdateQueues, + ) -> &mut NeighborUpdateQueue { + &mut queues.ipv6_unicast + } + + fn nexthop_rx_extract(attrs: &BaseAttrs) -> IpAddr { + attrs + .ll_nexthop + .map(IpAddr::from) + .unwrap_or(attrs.nexthop.unwrap()) + } + + fn nexthop_tx_change(nbr: &Neighbor, attrs: &mut BaseAttrs) { + match nbr.peer_type { + PeerType::Internal => { + // Global next hop isn't modified. + + // TODO: update link-local next hop. + } + PeerType::External => { + if !nbr.shared_subnet { + // Update global next hop. + match nbr.conn_info.as_ref().unwrap().local_addr { + IpAddr::V4(src_addr) => { + // BGP over IPv4. + // + // Use source address of the eBGP session + // (IPv4-mapped IPv6 address). + attrs.nexthop = + Some(src_addr.to_ipv6_mapped().into()) + } + IpAddr::V6(src_addr) => { + // BGP over IPv6. + // + // Use source address of the eBGP session. + attrs.nexthop = Some(src_addr.into()) + } + } + + // Unset link-local next hop. + attrs.ll_nexthop = None; + } else { + // Global next hop isn't modified (eBGP next hop + // optimization). + + // TODO: update link-local next hop. + } + } + } + } + + fn build_updates(queue: &mut NeighborUpdateQueue) -> Vec { + let mut msgs = vec![]; + let reach = std::mem::take(&mut queue.reach); + let unreach = std::mem::take(&mut queue.unreach); + + // Reachable prefixes. + for (attrs, prefixes) in reach.into_iter() { + let nexthop = Ipv6Addr::get(attrs.base.nexthop.unwrap()).unwrap(); + let ll_nexthop = attrs.base.ll_nexthop; + let nexthop_len = if ll_nexthop.is_some() { 32 } else { 16 }; + let max = (Message::MAX_LEN + - UpdateMsg::MIN_LEN + - attrs.length() + - ATTR_MIN_LEN_EXT + - MpReachNlri::MIN_LEN + - nexthop_len) + / (1 + Ipv6Addr::LENGTH as u16); + + msgs.extend( + prefixes.into_iter().chunks(max as usize).into_iter().map( + |chunk| { + let mp_reach = MpReachNlri::Ipv6Unicast { + prefixes: chunk.collect(), + nexthop, + ll_nexthop, + }; + Message::Update(UpdateMsg { + reach: None, + unreach: None, + mp_reach: Some(mp_reach), + mp_unreach: None, + attrs: Some(attrs.clone()), + }) + }, + ), + ); + } + + // Unreachable prefixes. + if !unreach.is_empty() { + let max = (Message::MAX_LEN + - UpdateMsg::MIN_LEN + - ATTR_MIN_LEN_EXT + - MpUnreachNlri::MIN_LEN) + / (1 + Ipv6Addr::LENGTH as u16); + + msgs.extend( + unreach.into_iter().chunks(max as usize).into_iter().map( + |chunk| { + let mp_unreach = MpUnreachNlri::Ipv6Unicast { + prefixes: chunk.collect(), + }; + Message::Update(UpdateMsg { + reach: None, + unreach: None, + mp_reach: None, + mp_unreach: Some(mp_unreach), + attrs: None, + }) + }, + ), + ); + } + + msgs + } +} diff --git a/holo-bgp/src/debug.rs b/holo-bgp/src/debug.rs new file mode 100644 index 00000000..e2183b2b --- /dev/null +++ b/holo-bgp/src/debug.rs @@ -0,0 +1,165 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::IpAddr; + +use ipnetwork::IpNetwork; +use tracing::{debug, debug_span}; + +use crate::neighbor::fsm; +use crate::packet::consts::AttrType; +use crate::packet::error::AttrError; +use crate::packet::message::Message; +use crate::rib::Route; + +// BGP debug messages. +#[derive(Debug)] +pub enum Debug<'a> { + InstanceCreate, + InstanceDelete, + InstanceStart, + InstanceStop(InstanceInactiveReason), + InstanceStatusCheck(&'a str), + NbrFsmEvent(&'a IpAddr, &'a fsm::Event), + NbrFsmTransition(&'a IpAddr, &'a fsm::State, &'a fsm::State), + NbrMsgRx(&'a IpAddr, &'a Message), + NbrMsgTx(&'a IpAddr, &'a Message), + NbrAttrError(AttrType, AttrError), + BestPathFound(IpNetwork, &'a Route), + BestPathNotFound(IpNetwork), +} + +// Reason why an BGP instance is inactive. +#[derive(Debug)] +pub enum InstanceInactiveReason { + AdminDown, + MissingRouterId, +} + +// ===== impl Debug ===== + +impl<'a> Debug<'a> { + // Log debug message using the tracing API. + pub(crate) fn log(&self) { + match self { + Debug::InstanceCreate + | Debug::InstanceDelete + | Debug::InstanceStart => { + // Parent span(s): bgp-instance + debug!("{}", self); + } + Debug::InstanceStop(reason) => { + // Parent span(s): bgp-instance + debug!(%reason, "{}", self); + } + Debug::InstanceStatusCheck(status) => { + // Parent span(s): bgp-instance + debug!(%status, "{}", self); + } + Debug::NbrFsmEvent(nbr_addr, event) => { + // Parent span(s): bgp-instance + debug_span!("neighbor", %nbr_addr).in_scope(|| { + debug_span!("fsm").in_scope(|| { + debug!(?event, "{}", self); + }) + }); + } + Debug::NbrFsmTransition(nbr_addr, old_state, new_state) => { + // Parent span(s): bgp-instance + debug_span!("neighbor", %nbr_addr).in_scope(|| { + debug_span!("fsm").in_scope(|| { + debug!(?old_state, ?new_state, "{}", self); + }) + }); + } + Debug::NbrMsgRx(nbr_addr, msg) => { + // Parent span(s): bgp-instance + debug_span!("neighbor", %nbr_addr).in_scope(|| { + debug_span!("input").in_scope(|| { + let data = serde_json::to_string(&msg).unwrap(); + debug!(%data, "{}", self); + }) + }); + } + Debug::NbrMsgTx(nbr_addr, msg) => { + // Parent span(s): bgp-instance + debug_span!("neighbor", %nbr_addr).in_scope(|| { + debug_span!("output").in_scope(|| { + let data = serde_json::to_string(&msg).unwrap(); + debug!(%data, "{}", self); + }) + }); + } + Debug::NbrAttrError(attr_type, action) => { + // Parent span(s): bgp-instance + debug!(?attr_type, ?action, "{}", self); + } + Debug::BestPathFound(prefix, route) => { + // Parent span(s): bgp-instance + debug!(%prefix, origin = ?route.origin, "{}", self); + } + Debug::BestPathNotFound(prefix) => { + // Parent span(s): bgp-instance + debug!(%prefix, "{}", self); + } + } + } +} + +impl<'a> std::fmt::Display for Debug<'a> { + 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::InstanceStatusCheck(..) => { + write!(f, "checking instance status") + } + Debug::NbrFsmEvent(..) => { + write!(f, "event") + } + Debug::NbrFsmTransition(..) => { + write!(f, "state transition") + } + Debug::NbrMsgRx(..) | Debug::NbrMsgTx(..) => { + write!(f, "message") + } + Debug::NbrAttrError(..) => { + write!(f, "malformed attribute") + } + Debug::BestPathFound(..) => { + write!(f, "best path found") + } + Debug::BestPathNotFound(..) => { + write!(f, "best path not found") + } + } + } +} + +// ===== 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::MissingRouterId => { + write!(f, "missing router-id") + } + } + } +} diff --git a/holo-bgp/src/error.rs b/holo-bgp/src/error.rs new file mode 100644 index 00000000..110a6de3 --- /dev/null +++ b/holo-bgp/src/error.rs @@ -0,0 +1,220 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::{IpAddr, Ipv4Addr}; + +use serde::{Deserialize, Serialize}; +use tracing::{error, warn, warn_span}; + +use crate::packet::error::DecodeError; + +// BGP errors. +#[derive(Debug)] +pub enum Error { + // I/O errors + IoError(IoError), + // Network input + NbrRxError(NbrRxError), + // Message processing + NbrBadAs(IpAddr, u32, u32), + NbrBadIdentifier(IpAddr, Ipv4Addr), + // Other + InstanceStartError(Box), +} + +// BGP I/O errors. +#[derive(Debug)] +pub enum IoError { + TcpSocketError(std::io::Error), + TcpAcceptError(std::io::Error), + TcpConnectError(std::io::Error), + TcpInfoError(std::io::Error), + TcpAuthError(std::io::Error), + TcpRecvError(std::io::Error), + TcpSendError(std::io::Error), +} + +// Neighbor Rx errors. +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub enum NbrRxError { + TcpConnClosed(IpAddr), + MsgDecodeError(IpAddr, DecodeError), +} + +// ===== impl Error ===== + +impl Error { + pub(crate) fn log(&self) { + match self { + Error::IoError(error) => { + error.log(); + } + Error::NbrRxError(error) => { + error.log(); + } + Error::NbrBadAs(addr, received, expected) => { + warn_span!("neighbor", %addr).in_scope(|| { + warn!(%received, %expected, "{}", self); + }); + } + Error::NbrBadIdentifier(addr, identifier) => { + warn_span!("neighbor", %addr).in_scope(|| { + warn!(%identifier, "{}", 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::NbrRxError(error) => error.fmt(f), + Error::NbrBadAs(..) => { + write!(f, "bad peer AS") + } + Error::NbrBadIdentifier(..) => { + write!(f, "BGP identifier conflict") + } + 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::NbrRxError(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::TcpSocketError(error) + | IoError::TcpAcceptError(error) + | IoError::TcpConnectError(error) + | IoError::TcpAuthError(error) + | IoError::TcpInfoError(error) + | IoError::TcpRecvError(error) + | IoError::TcpSendError(error) => { + warn!(error = %with_source(error), "{}", self); + } + } + } +} + +impl std::fmt::Display for IoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IoError::TcpSocketError(..) => { + write!(f, "failed to create TCP socket") + } + IoError::TcpAcceptError(..) => { + write!(f, "failed to accept connection request") + } + IoError::TcpConnectError(..) => { + write!(f, "failed to establish TCP connection") + } + IoError::TcpAuthError(..) => { + write!(f, "failed to set TCP authentication option") + } + IoError::TcpInfoError(..) => { + write!(f, "failed to fetch address and port information from the socket") + } + IoError::TcpRecvError(..) => { + write!(f, "failed to read TCP data") + } + IoError::TcpSendError(..) => { + write!(f, "failed to send TCP data") + } + } + } +} + +impl std::error::Error for IoError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + IoError::TcpSocketError(error) + | IoError::TcpAcceptError(error) + | IoError::TcpConnectError(error) + | IoError::TcpAuthError(error) + | IoError::TcpInfoError(error) + | IoError::TcpRecvError(error) + | IoError::TcpSendError(error) => Some(error), + } + } +} + +// ===== impl NbrRxError ===== + +impl NbrRxError { + pub(crate) fn log(&self) { + match self { + NbrRxError::TcpConnClosed(addr) => { + warn_span!("neighbor", %addr).in_scope(|| { + warn!("{}", self); + }); + } + NbrRxError::MsgDecodeError(addr, error) => { + warn_span!("neighbor", %addr).in_scope(|| { + warn!(error = %with_source(error), "{}", self); + }); + } + } + } +} + +impl std::fmt::Display for NbrRxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NbrRxError::TcpConnClosed(..) => { + write!(f, "connection closed by remote end") + } + NbrRxError::MsgDecodeError(..) => { + write!(f, "failed to decode BGP message") + } + } + } +} + +impl std::error::Error for NbrRxError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + NbrRxError::MsgDecodeError(_, error) => Some(error), + _ => None, + } + } +} + +// ===== global 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-bgp/src/events.rs b/holo-bgp/src/events.rs new file mode 100644 index 00000000..13763357 --- /dev/null +++ b/holo-bgp/src/events.rs @@ -0,0 +1,725 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::btree_map; +use std::net::IpAddr; + +use chrono::Utc; +use holo_protocol::InstanceShared; +use holo_utils::bgp::RouteType; +use holo_utils::ip::{IpAddrKind, IpNetworkKind}; +use holo_utils::policy::{PolicyResult, PolicyType}; +use holo_utils::socket::{TcpConnInfo, TcpStream}; +use ipnetwork::IpNetwork; +use num_traits::FromPrimitive; + +use crate::af::{AddressFamily, Ipv4Unicast, Ipv6Unicast}; +use crate::debug::Debug; +use crate::error::{Error, IoError, NbrRxError}; +use crate::instance::{InstanceUpView, PolicyApplyTasks}; +use crate::neighbor::{fsm, Neighbor, Neighbors, PeerType}; +use crate::packet::attribute::Attrs; +use crate::packet::consts::{Afi, Safi}; +use crate::packet::message::{ + Capability, Message, MpReachNlri, MpUnreachNlri, RouteRefreshMsg, UpdateMsg, +}; +use crate::policy::RoutePolicyInfo; +use crate::rib::{Rib, Route, RouteOrigin, RoutingTable}; +use crate::tasks::messages::output::PolicyApplyMsg; +use crate::{network, rib}; + +// ===== TCP connection request ===== + +pub(crate) fn process_tcp_accept( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + stream: TcpStream, + conn_info: TcpConnInfo, +) -> Result<(), Error> { + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&conn_info.remote_addr) else { + return Ok(()); + }; + + // Workaround to prevent connection collision until collision resolution + // is implemented. + if nbr.conn_info.is_some() { + return Ok(()); + } + + // Initialize the accepted stream. + network::accepted_stream_init( + &stream, + nbr.remote_addr.address_family(), + nbr.tx_ttl(), + nbr.config.transport.ttl_security, + nbr.config.transport.tcp_mss, + ) + .map_err(IoError::TcpSocketError)?; + + // Invoke FSM event. + nbr.fsm_event(instance, fsm::Event::Connected(stream, conn_info)); + + Ok(()) +} + +// ===== TCP connection established ===== + +pub(crate) fn process_tcp_connect( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + stream: TcpStream, + conn_info: TcpConnInfo, +) -> Result<(), Error> { + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&conn_info.remote_addr) else { + return Ok(()); + }; + nbr.tasks.connect = None; + + // Workaround to prevent connection collision until collision resolution + // is implemented. + if nbr.conn_info.is_some() { + return Ok(()); + } + + // Invoke FSM event. + nbr.fsm_event(instance, fsm::Event::Connected(stream, conn_info)); + + Ok(()) +} + +// ===== neighbor message receipt ===== + +pub(crate) fn process_nbr_msg( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + nbr_addr: IpAddr, + msg: Result, +) -> Result<(), Error> { + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&nbr_addr) else { + return Ok(()); + }; + + // Process received message. + match msg { + Ok(msg) => { + Debug::NbrMsgRx(&nbr.remote_addr, &msg).log(); + + // Update statistics. + nbr.statistics.msgs_rcvd.update(&msg); + + match msg { + Message::Open(msg) => { + nbr.fsm_event(instance, fsm::Event::RcvdOpen(msg)); + } + Message::Update(msg) => { + nbr.fsm_event(instance, fsm::Event::RcvdUpdate); + process_nbr_update(instance, nbr, msg)?; + } + Message::Notification(msg) => { + nbr.fsm_event(instance, fsm::Event::RcvdNotif(msg.clone())); + // Keep track of the last received notification. + nbr.notification_rcvd = Some((Utc::now(), msg)); + } + Message::Keepalive(_) => { + nbr.fsm_event(instance, fsm::Event::RcvdKalive); + } + Message::RouteRefresh(msg) => { + process_nbr_route_refresh(instance, nbr, msg)?; + } + } + } + Err(error) => match error { + NbrRxError::TcpConnClosed(_) => { + nbr.fsm_event(instance, fsm::Event::ConnFail); + } + NbrRxError::MsgDecodeError(_, error) => { + nbr.fsm_event(instance, fsm::Event::RcvdError(error)); + } + }, + } + + Ok(()) +} + +fn process_nbr_update( + instance: &mut InstanceUpView<'_>, + nbr: &mut Neighbor, + msg: UpdateMsg, +) -> Result<(), Error> { + let rib = &mut instance.state.rib; + + // Process IPv4 reachable NLRIs. + // + // Use nexthop from the NEXTHOP attribute. + if let Some(reach) = msg.reach { + if let Some(attrs) = &msg.attrs { + let mut attrs = attrs.clone(); + attrs.base.nexthop = Some(reach.nexthop.into()); + process_nbr_reach_prefixes::( + nbr, + rib, + reach.prefixes.clone(), + attrs, + &instance.shared, + &instance.state.policy_apply_tasks, + ); + } else { + // Treat as withdraw. + process_nbr_unreach_prefixes::( + nbr, + rib, + reach.prefixes, + ); + } + } + + // Process multiprotocol reachable NLRIs. + // + // Use nexthop(s) from the MP_REACH_NLRI attribute. + if let Some(mp_reach) = msg.mp_reach { + if let Some(mut attrs) = msg.attrs { + match mp_reach { + MpReachNlri::Ipv4Unicast { prefixes, nexthop } => { + attrs.base.nexthop = Some(nexthop.into()); + process_nbr_reach_prefixes::( + nbr, + rib, + prefixes, + attrs, + &instance.shared, + &instance.state.policy_apply_tasks, + ); + } + MpReachNlri::Ipv6Unicast { + prefixes, + nexthop, + ll_nexthop, + } => { + attrs.base.nexthop = Some(nexthop.into()); + attrs.base.ll_nexthop = ll_nexthop; + process_nbr_reach_prefixes::( + nbr, + rib, + prefixes, + attrs, + &instance.shared, + &instance.state.policy_apply_tasks, + ); + } + } + } else { + // Treat as withdraw. + match mp_reach { + MpReachNlri::Ipv4Unicast { prefixes, .. } => { + process_nbr_unreach_prefixes::( + nbr, rib, prefixes, + ); + } + MpReachNlri::Ipv6Unicast { prefixes, .. } => { + process_nbr_unreach_prefixes::( + nbr, rib, prefixes, + ); + } + } + } + } + + // Process IPv4 unreachable NLRIs. + if let Some(unreach) = msg.unreach { + process_nbr_unreach_prefixes::(nbr, rib, unreach.prefixes); + } + + // Process multiprotocol unreachable NLRIs. + if let Some(mp_unreach) = msg.mp_unreach { + match mp_unreach { + MpUnreachNlri::Ipv4Unicast { prefixes } => { + process_nbr_unreach_prefixes::(nbr, rib, prefixes); + } + MpUnreachNlri::Ipv6Unicast { prefixes } => { + process_nbr_unreach_prefixes::(nbr, rib, prefixes); + } + } + } + + // Schedule the BGP Decision Process. + instance.state.schedule_decision_process(&instance.tx); + + Ok(()) +} + +fn process_nbr_reach_prefixes( + nbr: &Neighbor, + rib: &mut Rib, + nlri_prefixes: Vec, + attrs: Attrs, + shared: &InstanceShared, + policy_apply_tasks: &PolicyApplyTasks, +) where + A: AddressFamily, +{ + // Check if the address-family is enabled for this session. + if !nbr.is_af_enabled(A::AFI, A::SAFI) { + return; + } + + // Initialize route origin and type. + let origin = RouteOrigin::Neighbor { + identifier: nbr.identifier.unwrap(), + remote_addr: nbr.remote_addr, + }; + let route_type = match nbr.peer_type { + PeerType::Internal => RouteType::Internal, + PeerType::External => RouteType::External, + }; + + // Update pre-policy Adj-RIB-In routes. + let table = A::table(&mut rib.tables); + let route_attrs = rib.attr_sets.get_route_attr_sets(attrs.clone()); + for prefix in &nlri_prefixes { + let dest = table.prefixes.entry(*prefix).or_default(); + let route = Route::new(origin, route_attrs.clone(), route_type); + dest.adj_in_pre.insert(nbr.remote_addr, route); + } + + // Get policy configuration for the address family. + let apply_policy_cfg = &nbr + .config + .afi_safi + .get(&A::AFI_SAFI) + .map(|afi_safi| &afi_safi.apply_policy) + .unwrap_or(&nbr.config.apply_policy); + + // Enqueue import policy application. + let rpinfo = RoutePolicyInfo::new(origin, attrs, route_type); + let msg = PolicyApplyMsg::Neighbor { + policy_type: PolicyType::Import, + nbr_addr: nbr.remote_addr, + afi_safi: A::AFI_SAFI, + routes: nlri_prefixes + .into_iter() + .map(|prefix| (prefix.into(), rpinfo.clone())) + .collect(), + policies: apply_policy_cfg + .import_policy + .iter() + .map(|policy| shared.policies.get(policy).unwrap().clone()) + .collect(), + match_sets: shared.policy_match_sets.clone(), + default_policy: apply_policy_cfg.default_import_policy, + }; + policy_apply_tasks.enqueue(msg); +} + +fn process_nbr_unreach_prefixes( + nbr: &Neighbor, + rib: &mut Rib, + nlri_prefixes: Vec, +) where + A: AddressFamily, +{ + // Check if the address-family is enabled for this session. + if !nbr.is_af_enabled(A::AFI, A::SAFI) { + return; + } + + // Remove routes from Adj-RIB-In. + let table = A::table(&mut rib.tables); + for prefix in nlri_prefixes { + let dest = table.prefixes.entry(prefix).or_default(); + dest.adj_in_pre.remove(&nbr.remote_addr); + dest.adj_in_post.remove(&nbr.remote_addr); + + // Enqueue prefix for the BGP Decision Process. + table.queued_prefixes.insert(prefix); + } +} + +fn process_nbr_route_refresh( + instance: &mut InstanceUpView<'_>, + nbr: &mut Neighbor, + msg: RouteRefreshMsg, +) -> Result<(), Error> { + let Some(afi) = Afi::from_u16(msg.afi) else { + // Ignore unknown AFI. + return Ok(()); + }; + let Some(safi) = Safi::from_u8(msg.safi) else { + // Ignore unknown SAFI. + return Ok(()); + }; + + // RFC 2918 - Section 4: + // If a BGP speaker receives from its peer a ROUTE-REFRESH message with + // the that the speaker didn't advertise to the peer at the + // session establishment time via capability advertisement, the speaker + // shall ignore such a message. + let cap = Capability::MultiProtocol { afi, safi }; + if nbr.capabilities_adv.get(&cap).is_none() { + return Ok(()); + } + + match (afi, safi) { + (Afi::Ipv4, Safi::Unicast) => { + process_nbr_route_refresh_af::(instance, nbr) + } + (Afi::Ipv6, Safi::Unicast) => { + process_nbr_route_refresh_af::(instance, nbr) + } + _ => { + // Ignore unsupported AFI/SAFI combination. + return Ok(()); + } + } + + // Send UPDATE message(s) to the neighbor. + let msg_list = nbr.update_queues.build_updates(); + nbr.message_list_send(msg_list); + + Ok(()) +} + +fn process_nbr_route_refresh_af( + instance: &mut InstanceUpView<'_>, + nbr: &mut Neighbor, +) where + A: AddressFamily, +{ + let table = A::table(&mut instance.state.rib.tables); + let update_queue = A::update_queue(&mut nbr.update_queues); + for (prefix, dest) in &table.prefixes { + let (route, _) = dest.local.as_ref().unwrap(); + let attrs = route.attrs.get(); + update_queue.reach.entry(attrs).or_default().insert(*prefix); + } +} + +// ===== neighbor expired timeout ===== + +pub(crate) fn process_nbr_timer( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + nbr_addr: IpAddr, + timer: fsm::Timer, +) -> Result<(), Error> { + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&nbr_addr) else { + return Ok(()); + }; + + // Invoke FSM event. + nbr.fsm_event(instance, fsm::Event::Timer(timer)); + + Ok(()) +} + +// ===== neighbor policy import result ===== + +pub(crate) fn process_nbr_policy_import( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + nbr_addr: IpAddr, + prefixes: Vec<(IpNetwork, PolicyResult)>, +) -> Result<(), Error> +where + A: AddressFamily, +{ + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&nbr_addr) else { + return Ok(()); + }; + if nbr.state < fsm::State::Established { + return Ok(()); + } + + let rib = &mut instance.state.rib; + let table = A::table(&mut rib.tables); + for (prefix, result) in prefixes { + // Get RIB destination. + let prefix = A::IpNetwork::get(prefix).unwrap(); + let dest = table.prefixes.entry(prefix).or_default(); + + // Update post-policy Adj-RIB-In routes. + match result { + PolicyResult::Accept(rpinfo) => { + let route = Route::new( + rpinfo.origin, + rib.attr_sets.get_route_attr_sets(rpinfo.attrs), + rpinfo.route_type, + ); + dest.adj_in_post.insert(nbr.remote_addr, route); + } + PolicyResult::Reject => { + dest.adj_in_post.remove(&nbr.remote_addr); + } + } + + // Enqueue prefix for the BGP Decision Process. + table.queued_prefixes.insert(prefix); + } + + // Schedule the BGP Decision Process. + instance.state.schedule_decision_process(&instance.tx); + + Ok(()) +} + +// ===== neighbor policy export result ===== + +pub(crate) fn process_nbr_policy_export( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + nbr_addr: IpAddr, + prefixes: Vec<(IpNetwork, PolicyResult)>, +) -> Result<(), Error> +where + A: AddressFamily, +{ + // Lookup neighbor. + let Some(nbr) = neighbors.get_mut(&nbr_addr) else { + return Ok(()); + }; + if nbr.state < fsm::State::Established { + return Ok(()); + } + + let rib = &mut instance.state.rib; + let table = A::table(&mut rib.tables); + for (prefix, result) in prefixes { + // Get RIB destination. + let prefix = A::IpNetwork::get(prefix).unwrap(); + let dest = table.prefixes.entry(prefix).or_default(); + + // Update post-policy Adj-RIB-Out routes. + match result { + PolicyResult::Accept(rpinfo) => { + let route = Route::new( + rpinfo.origin, + rib.attr_sets.get_route_attr_sets(rpinfo.attrs.clone()), + rpinfo.route_type, + ); + + let mut update = false; + match dest.adj_out_post.entry(nbr.remote_addr) { + btree_map::Entry::Occupied(mut entry) => { + if entry.get_mut().attrs != route.attrs { + entry.insert(route); + update = true; + } + } + btree_map::Entry::Vacant(entry) => { + entry.insert(route); + update = true; + } + } + + if update { + // Prepend local AS number when advertising to eBGP + // neighbors. + let mut attrs = rpinfo.attrs; + if nbr.peer_type == PeerType::External { + attrs.base.as_path.prepend(instance.config.asn); + } + + // Update the next-hop attribute if necessary. + A::nexthop_tx_change(nbr, &mut attrs.base); + + // Update neighbor's Tx queue. + let update_queue = A::update_queue(&mut nbr.update_queues); + update_queue.reach.entry(attrs).or_default().insert(prefix); + } + } + PolicyResult::Reject => { + if dest.adj_out_post.remove(&nbr.remote_addr).is_some() { + // Update neighbor's Tx queue. + let update_queue = A::update_queue(&mut nbr.update_queues); + update_queue.unreach.insert(prefix); + } + } + } + } + + // Send UPDATE message(s) to the neighbor. + let msg_list = nbr.update_queues.build_updates(); + nbr.message_list_send(msg_list); + + Ok(()) +} + +// ===== BGP decision process ===== + +pub(crate) fn decision_process( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, +) -> Result<(), Error> +where + A: AddressFamily, +{ + // Get route selection configuration for the address family. + let selection_cfg = &instance + .config + .afi_safi + .get(&A::AFI_SAFI) + .map(|afi_safi| &afi_safi.route_selection) + .unwrap_or(&instance.config.route_selection); + + // Get multipath configuration for the address family. + let mpath_cfg = &instance + .config + .afi_safi + .get(&A::AFI_SAFI) + .map(|afi_safi| &afi_safi.multipath) + .unwrap_or(&instance.config.multipath); + + // Phase 2: Route Selection. + // + // Process each queued destination in the RIB. + let table = A::table(&mut instance.state.rib.tables); + let queued_prefixes = std::mem::take(&mut table.queued_prefixes); + let mut reach = vec![]; + let mut unreach = vec![]; + for prefix in queued_prefixes { + let Some(dest) = table.prefixes.get_mut(&prefix) else { + continue; + }; + + // Perform best-path selection for the destination. + let best_route = + rib::best_path(dest, instance.config.asn, selection_cfg); + + // Update the Loc-RIB with the best path. + rib::loc_rib_update::( + prefix, + dest, + best_route.clone(), + selection_cfg, + mpath_cfg, + &instance.config.distance, + &instance.tx.ibus, + ); + + // Group best routes and unfeasible routes separately. + match best_route { + Some(best_route) => reach.push((prefix, best_route)), + None => unreach.push(prefix), + } + } + + // Phase 3: Route Dissemination. + for nbr in neighbors + .values_mut() + .filter(|nbr| nbr.state == fsm::State::Established) + { + // Skip neighbors that haven't this address-family enabled. + if !nbr.is_af_enabled(A::AFI, A::SAFI) { + continue; + } + + // Withdraw unfeasible routes immediately. + if !unreach.is_empty() { + withdraw_routes::(nbr, table, &unreach); + } + + // Advertise best routes. + if !reach.is_empty() { + advertise_routes::( + nbr, + table, + &reach, + &instance.shared, + &instance.state.policy_apply_tasks, + ); + } + } + + Ok(()) +} + +fn withdraw_routes( + nbr: &mut Neighbor, + table: &mut RoutingTable, + routes: &[A::IpNetwork], +) where + A: AddressFamily, +{ + // Update Adj-RIB-Out. + for prefix in routes { + let dest = table.prefixes.get_mut(&prefix).unwrap(); + dest.adj_out_pre.remove(&nbr.remote_addr); + if dest.adj_out_post.remove(&nbr.remote_addr).is_some() { + let update_queue = A::update_queue(&mut nbr.update_queues); + update_queue.unreach.insert(*prefix); + } + } + + // Send UPDATE message(s) to the neighbor. + let msg_list = nbr.update_queues.build_updates(); + nbr.message_list_send(msg_list); +} + +pub(crate) fn advertise_routes( + nbr: &mut Neighbor, + table: &mut RoutingTable, + routes: &[(A::IpNetwork, Route)], + shared: &InstanceShared, + policy_apply_tasks: &PolicyApplyTasks, +) where + A: AddressFamily, +{ + // Create an iterator over the best routes, filtering out routes that + // should not be redistributed to this neighbor. + let routes = routes.iter().filter(|(_, route)| { + // RFC 4271 - Section 9.2: + // "When a BGP speaker receives an UPDATE message from an internal + // peer, the receiving BGP speaker SHALL NOT re-distribute the + // routing information contained in that UPDATE message to other + // internal peers". + if route.route_type == RouteType::Internal { + if let RouteOrigin::Neighbor { remote_addr, .. } = &route.origin + && *remote_addr == nbr.remote_addr + { + return false; + } + } + + true + }); + + // Update pre-policy Adj-RIB-Out routes. + for (prefix, route) in routes.clone() { + let dest = table.prefixes.get_mut(&prefix).unwrap(); + dest.adj_out_pre.insert(nbr.remote_addr, route.clone()); + } + + // Get policy configuration for the address family. + let apply_policy_cfg = &nbr + .config + .afi_safi + .get(&A::AFI_SAFI) + .map(|afi_safi| &afi_safi.apply_policy) + .unwrap_or(&nbr.config.apply_policy); + + // Enqueue export policy application. + let msg = PolicyApplyMsg::Neighbor { + policy_type: PolicyType::Export, + nbr_addr: nbr.remote_addr, + afi_safi: A::AFI_SAFI, + routes: routes + .into_iter() + .map(|(prefix, route)| ((*prefix).into(), route.policy_info())) + .collect(), + policies: apply_policy_cfg + .export_policy + .iter() + .map(|policy| shared.policies.get(policy).unwrap().clone()) + .collect(), + match_sets: shared.policy_match_sets.clone(), + default_policy: apply_policy_cfg.default_export_policy, + }; + policy_apply_tasks.enqueue(msg); +} diff --git a/holo-bgp/src/instance.rs b/holo-bgp/src/instance.rs new file mode 100644 index 00000000..217d53a3 --- /dev/null +++ b/holo-bgp/src/instance.rs @@ -0,0 +1,544 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::Ipv4Addr; +use std::sync::Arc; + +use async_trait::async_trait; +use holo_protocol::{ + InstanceChannelsTx, InstanceShared, MessageReceiver, ProtocolInstance, +}; +use holo_utils::bgp::AfiSafi; +use holo_utils::ibus::IbusMsg; +use holo_utils::ip::AddressFamily; +use holo_utils::policy::PolicyType; +use holo_utils::protocol::Protocol; +use holo_utils::socket::TcpListener; +use holo_utils::task::{Task, TimeoutTask}; +use holo_utils::{Receiver, Sender, UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc; + +use crate::af::{Ipv4Unicast, Ipv6Unicast}; +use crate::debug::{Debug, InstanceInactiveReason}; +use crate::error::{Error, IoError}; +use crate::neighbor::{fsm, Neighbors}; +use crate::northbound::configuration::InstanceCfg; +use crate::packet::consts::{CeaseSubcode, ErrorCode}; +use crate::packet::message::NotificationMsg; +use crate::rib::Rib; +use crate::tasks::messages::input::{ + NbrRxMsg, NbrTimerMsg, PolicyResultMsg, TcpAcceptMsg, TcpConnectMsg, +}; +use crate::tasks::messages::output::PolicyApplyMsg; +use crate::tasks::messages::{ProtocolInputMsg, ProtocolOutputMsg}; +use crate::{events, network, 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 neighbors. + pub neighbors: Neighbors, + // 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 { + // Instance Router ID. + pub router_id: Ipv4Addr, + // TCP listening sockets. + pub listening_sockets: Vec, + // Policy tasks. + pub policy_apply_tasks: PolicyApplyTasks, + // Timeout to trigger the decision process. + pub decision_process_task: Option, + // BGP RIB. + pub rib: Rib, +} + +#[derive(Debug)] +pub struct TcpListenerTask { + pub af: AddressFamily, + pub socket: Arc, + _task: Task<()>, +} + +#[derive(Debug)] +pub struct PolicyApplyTasks { + pub tx: crossbeam_channel::Sender, + _tasks: Vec>, +} + +#[derive(Clone, Debug)] +pub struct ProtocolInputChannelsTx { + // TCP accept event. + pub tcp_accept: Sender, + // TCP connect event. + pub tcp_connect: Sender, + // TCP neighbor message. + pub nbr_msg_rx: Sender, + // Neighbor timeout event. + pub nbr_timer: Sender, + // Policy result message. + pub policy_result: UnboundedSender, + // Decision Process triggering message. + pub decision_process: Sender<()>, +} + +#[derive(Debug)] +pub struct ProtocolInputChannelsRx { + // TCP accept event. + pub tcp_accept: Receiver, + // TCP connect event. + pub tcp_connect: Receiver, + // TCP neighbor message. + pub nbr_msg_rx: Receiver, + // Neighbor timeout event. + pub nbr_timer: Receiver, + // Policy result message. + pub policy_result: UnboundedReceiver, + // Decision Process triggering message. + pub decision_process: Receiver<()>, +} + +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. + // + // Note: Router ID updates are ignored if the instance is already active. + pub(crate) async fn update(&mut self) { + let router_id = self.get_router_id(); + + match self.is_ready(router_id) { + Ok(()) if !self.is_active() => { + self.start(router_id.unwrap()).await; + } + Err(reason) if self.is_active() => { + self.stop(reason); + } + _ => (), + } + } + + // Starts the BGP instance. + async fn start(&mut self, router_id: Ipv4Addr) { + Debug::InstanceStart.log(); + + match InstanceState::new(router_id, &self.tx).await { + Ok(state) => { + // Store instance initial state. + self.state = Some(state); + } + Err(error) => { + Error::InstanceStartError(Box::new(error)).log(); + } + } + } + + // Stops the BGP instance. + fn stop(&mut self, reason: InstanceInactiveReason) { + let Some((mut instance, neighbors)) = self.as_up() else { + return; + }; + + Debug::InstanceStop(reason).log(); + + // Stop neighbors. + let error_code = ErrorCode::Cease; + let error_subcode = CeaseSubcode::AdministrativeShutdown; + for nbr in neighbors.values_mut() { + let msg = NotificationMsg::new(error_code, error_subcode); + nbr.fsm_event(&mut instance, fsm::Event::Stop(Some(msg))); + } + + // Clear instance state. + self.state = None; + } + + // Returns whether the BGP instance is operational. + fn is_active(&self) -> bool { + self.state.is_some() + } + + // Returns whether the instance is ready for BGP operation. + fn is_ready( + &self, + router_id: Option, + ) -> Result<(), InstanceInactiveReason> { + if router_id.is_none() { + return Err(InstanceInactiveReason::MissingRouterId); + } + + Ok(()) + } + + // Retrieves the Router ID from configuration or system information. + // Prioritizes the configured Router ID, using the system's Router ID as a + // fallback. + fn get_router_id(&self) -> Option { + self.config.identifier.or(self.system.router_id) + } + + // Returns a view struct for the instance if it is operational. + pub(crate) fn as_up( + &mut self, + ) -> Option<(InstanceUpView<'_>, &mut Neighbors)> { + 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.neighbors)) + } else { + None + } + } +} + +#[async_trait] +impl ProtocolInstance for Instance { + const PROTOCOL: Protocol = Protocol::BGP; + + 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, + neighbors: 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(); + } + } + + fn process_protocol_msg(&mut self, msg: ProtocolInputMsg) { + // Ignore event if the instance isn't active. + if let Some((mut instance, neighbors)) = self.as_up() { + if let Err(error) = + process_protocol_msg(&mut instance, neighbors, msg) + { + error.log(); + } + } + } + + fn protocol_input_channels( + ) -> (ProtocolInputChannelsTx, ProtocolInputChannelsRx) { + let (tcp_acceptp, tcp_acceptc) = mpsc::channel(4); + let (tcp_connectp, tcp_connectc) = mpsc::channel(4); + let (nbr_msg_rxp, nbr_msg_rxc) = mpsc::channel(4); + let (nbr_timerp, nbr_timerc) = mpsc::channel(4); + let (policy_resultp, policy_resultc) = mpsc::unbounded_channel(); + let (decision_processp, decision_processc) = mpsc::channel(1); + + let tx = ProtocolInputChannelsTx { + tcp_accept: tcp_acceptp, + tcp_connect: tcp_connectp, + nbr_msg_rx: nbr_msg_rxp, + nbr_timer: nbr_timerp, + policy_result: policy_resultp, + decision_process: decision_processp, + }; + let rx = ProtocolInputChannelsRx { + tcp_accept: tcp_acceptc, + tcp_connect: tcp_connectc, + nbr_msg_rx: nbr_msg_rxc, + nbr_timer: nbr_timerc, + policy_result: policy_resultc, + decision_process: decision_processc, + }; + + (tx, rx) + } + + #[cfg(feature = "testing")] + fn test_dir() -> String { + format!("{}/tests/conformance", env!("CARGO_MANIFEST_DIR"),) + } +} + +// ===== impl InstanceState ===== + +impl InstanceState { + async fn new( + router_id: Ipv4Addr, + instance_tx: &InstanceChannelsTx, + ) -> Result { + let mut listening_sockets = Vec::new(); + + // Create TCP listeners. + for af in [AddressFamily::Ipv4, AddressFamily::Ipv6] { + let socket = network::listen_socket(af) + .map(Arc::new) + .map_err(IoError::TcpSocketError)?; + let task = tasks::tcp_listener( + &socket, + &instance_tx.protocol_input.tcp_accept, + ); + listening_sockets.push(TcpListenerTask { + af, + socket, + _task: task, + }); + } + + // Create routing policy tasks. + let (policy_tx, policy_rx) = crossbeam_channel::unbounded(); + let num_cpus = std::thread::available_parallelism().unwrap().get(); + let tasks = (0..num_cpus) + .map(|_| { + tasks::policy_apply( + policy_rx.clone(), + &instance_tx.protocol_input.policy_result, + #[cfg(feature = "testing")] + &instance_tx.protocol_output, + ) + }) + .collect(); + let policy_apply_tasks = PolicyApplyTasks { + tx: policy_tx, + _tasks: tasks, + }; + + Ok(InstanceState { + router_id, + listening_sockets, + policy_apply_tasks, + decision_process_task: None, + rib: Default::default(), + }) + } + + // Schedules the BGP Decision Process to happen 100 milliseconds + // from now, renewing the timeout if called before expiry. + pub(crate) fn schedule_decision_process( + &mut self, + instance_tx: &InstanceChannelsTx, + ) { + let task = tasks::schedule_decision_process( + &instance_tx.protocol_input.decision_process, + ); + self.decision_process_task = Some(task); + } +} + +// ===== impl ProtocolInputChannelsTx ===== + +impl ProtocolInputChannelsTx { + // Triggers the BGP Decision Process. + pub(crate) fn trigger_decision_process(&self) { + let _ = self.decision_process.try_send(()); + } +} + +// ===== impl ProtocolInputChannelsRx ===== + +#[async_trait] +impl MessageReceiver for ProtocolInputChannelsRx { + async fn recv(&mut self) -> Option { + tokio::select! { + msg = self.tcp_accept.recv() => { + msg.map(ProtocolInputMsg::TcpAccept) + } + msg = self.tcp_connect.recv() => { + msg.map(ProtocolInputMsg::TcpConnect) + } + msg = self.nbr_msg_rx.recv() => { + msg.map(ProtocolInputMsg::NbrRx) + } + msg = self.nbr_timer.recv() => { + msg.map(ProtocolInputMsg::NbrTimer) + } + msg = self.policy_result.recv() => { + msg.map(ProtocolInputMsg::PolicyResult) + } + msg = self.decision_process.recv() => { + msg.map(ProtocolInputMsg::TriggerDecisionProcess) + } + } + } +} + +// ===== impl PolicyApplyTasks, ===== + +impl PolicyApplyTasks { + pub(crate) fn enqueue(&self, msg: PolicyApplyMsg) { + let _ = self.tx.send(msg); + } +} + +// ===== helper functions ===== + +async fn process_ibus_msg( + instance: &mut Instance, + msg: IbusMsg, +) -> Result<(), Error> { + match msg { + IbusMsg::RouterIdUpdate(router_id) => { + // Router ID update notification. + southbound::rx::process_router_id_update(instance, router_id).await; + } + IbusMsg::PolicyMatchSetsUpd(match_sets) => { + // Update the local copy of the policy match sets. + instance.shared.policy_match_sets = match_sets; + } + IbusMsg::PolicyUpd(policy) => { + // Update the local copy of the policy definition. + instance + .shared + .policies + .insert(policy.name.clone(), policy.clone()); + } + IbusMsg::PolicyDel(policy_name) => { + // Remove the local copy of the policy definition. + instance.shared.policies.remove(&policy_name); + } + // Ignore other events. + _ => {} + } + + Ok(()) +} + +fn process_protocol_msg( + instance: &mut InstanceUpView<'_>, + neighbors: &mut Neighbors, + msg: ProtocolInputMsg, +) -> Result<(), Error> { + match msg { + // Accepted TCP connection request. + ProtocolInputMsg::TcpAccept(mut msg) => { + events::process_tcp_accept( + instance, + neighbors, + msg.stream(), + msg.conn_info, + )?; + } + // Established TCP connection. + ProtocolInputMsg::TcpConnect(mut msg) => { + events::process_tcp_connect( + instance, + neighbors, + msg.stream(), + msg.conn_info, + )?; + } + // Received message from neighbor. + ProtocolInputMsg::NbrRx(msg) => { + events::process_nbr_msg( + instance, + neighbors, + msg.nbr_addr, + msg.msg, + )?; + } + // Neighbor's timeout has expired. + ProtocolInputMsg::NbrTimer(msg) => { + events::process_nbr_timer( + instance, + neighbors, + msg.nbr_addr, + msg.timer, + )?; + } + // Policy result. + ProtocolInputMsg::PolicyResult(msg) => match msg { + PolicyResultMsg::Neighbor { + policy_type, + afi_safi, + nbr_addr, + routes, + } => match (policy_type, afi_safi) { + (PolicyType::Import, AfiSafi::Ipv4Unicast) => { + events::process_nbr_policy_import::( + instance, neighbors, nbr_addr, routes, + )? + } + (PolicyType::Import, AfiSafi::Ipv6Unicast) => { + events::process_nbr_policy_import::( + instance, neighbors, nbr_addr, routes, + )? + } + (PolicyType::Export, AfiSafi::Ipv4Unicast) => { + events::process_nbr_policy_export::( + instance, neighbors, nbr_addr, routes, + )? + } + (PolicyType::Export, AfiSafi::Ipv6Unicast) => { + events::process_nbr_policy_export::( + instance, neighbors, nbr_addr, routes, + )? + } + }, + }, + // Decision process. + ProtocolInputMsg::TriggerDecisionProcess(_) => { + events::decision_process::(instance, neighbors)?; + events::decision_process::(instance, neighbors)?; + } + } + + Ok(()) +} diff --git a/holo-bgp/src/lib.rs b/holo-bgp/src/lib.rs new file mode 100644 index 00000000..ce61b314 --- /dev/null +++ b/holo-bgp/src/lib.rs @@ -0,0 +1,25 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +#![cfg_attr( + feature = "testing", + allow(dead_code, unused_variables, unused_imports) +)] +#![feature(let_chains, lazy_cell)] + +pub mod af; +pub mod debug; +pub mod error; +pub mod events; +pub mod instance; +pub mod neighbor; +pub mod network; +pub mod northbound; +pub mod packet; +pub mod policy; +pub mod rib; +pub mod southbound; +pub mod tasks; diff --git a/holo-bgp/src/neighbor.rs b/holo-bgp/src/neighbor.rs new file mode 100644 index 00000000..2876630f --- /dev/null +++ b/holo-bgp/src/neighbor.rs @@ -0,0 +1,991 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::{BTreeMap, BTreeSet}; +use std::net::{IpAddr, Ipv4Addr}; +use std::sync::atomic::{self, AtomicU32}; +use std::sync::Arc; +use std::time::Duration; + +use chrono::{DateTime, Utc}; +use holo_protocol::InstanceChannelsTx; +use holo_utils::bgp::AfiSafi; +use holo_utils::socket::{TcpConnInfo, TcpStream, TTL_MAX}; +use holo_utils::task::{IntervalTask, Task, TimeoutTask}; +use holo_utils::{Sender, UnboundedSender}; +use tokio::sync::mpsc; + +use crate::af::{AddressFamily, Ipv4Unicast, Ipv6Unicast}; +use crate::debug::Debug; +use crate::error::Error; +use crate::instance::{Instance, InstanceUpView}; +use crate::northbound::configuration::{InstanceCfg, NeighborCfg}; +use crate::packet::attribute::Attrs; +use crate::packet::consts::{ + Afi, ErrorCode, FsmErrorSubcode, Safi, AS_TRANS, BGP_VERSION, +}; +use crate::packet::message::{ + Capability, DecodeCxt, EncodeCxt, FourOctetAsNumber, KeepaliveMsg, Message, + NotificationMsg, OpenMsg, +}; +use crate::rib::Rib; +use crate::tasks::messages::input::{NbrRxMsg, NbrTimerMsg, TcpConnectMsg}; +use crate::tasks::messages::output::NbrTxMsg; +#[cfg(feature = "testing")] +use crate::tasks::messages::ProtocolOutputMsg; +use crate::{events, tasks}; + +// Large hold-time used during session initialization. +const LARGE_HOLDTIME: u16 = 240; + +// BGP neighbor. +#[derive(Debug)] +pub struct Neighbor { + pub remote_addr: IpAddr, + pub config: NeighborCfg, + pub state: fsm::State, + pub peer_type: PeerType, + pub conn_info: Option, + pub shared_subnet: bool, + pub identifier: Option, + pub holdtime_nego: Option, + pub capabilities_adv: BTreeSet, + pub capabilities_rcvd: BTreeSet, + pub capabilities_nego: BTreeSet, + pub notification_sent: Option<(DateTime, NotificationMsg)>, + pub notification_rcvd: Option<(DateTime, NotificationMsg)>, + pub last_established: Option>, + pub statistics: NeighborStatistics, + pub tasks: NeighborTasks, + pub update_queues: NeighborUpdateQueues, + pub msg_txp: Option>, +} + +// BGP peer type. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PeerType { + Internal, + External, +} + +// Neighbor statistics. +#[derive(Debug, Default)] +pub struct NeighborStatistics { + pub established_transitions: u32, + pub msgs_rcvd: MessageStatistics, + pub msgs_sent: MessageStatistics, + pub erroneous_updates_withdrawn: u32, + pub erroneous_updates_attribute_discarded: u32, + pub in_update_elapsed_time: Duration, +} + +// Inbound and outbound message counters. +#[derive(Debug, Default)] +pub struct MessageStatistics { + pub total: Arc, + pub updates: u32, + pub notifications: u32, + pub route_refreshes: u32, +} + +// Neighbor tasks. +#[derive(Debug, Default)] +pub struct NeighborTasks { + pub autostart: Option, + pub connect: Option>, + pub connect_retry: Option, + pub tcp_rx: Option>, + pub keepalive: Option, + pub holdtime: Option, +} + +// Neighbor Tx update queues. +#[derive(Debug, Default)] +pub struct NeighborUpdateQueues { + pub ipv4_unicast: NeighborUpdateQueue, + pub ipv6_unicast: NeighborUpdateQueue, +} + +// Neighbor Tx update queue. +#[derive(Debug)] +pub struct NeighborUpdateQueue { + pub reach: BTreeMap>, + pub unreach: BTreeSet, +} + +// Type aliases. +pub type Neighbors = BTreeMap; + +// Finite State Machine. +pub mod fsm { + use holo_utils::socket::{TcpConnInfo, TcpStream}; + use serde::{Deserialize, Serialize}; + + use crate::packet::error::DecodeError; + use crate::packet::message::{NotificationMsg, OpenMsg}; + + // FSM states. + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] + pub enum State { + Idle, + Connect, + Active, + OpenSent, + OpenConfirm, + Established, + } + + // FSM events. + // + // The original RFC FSM events are listed above each event for clarity. + #[derive(Debug)] + pub enum Event { + // ManualStart + // ManualStart_with_PassiveTcpEstablishment + Start, + // ManualStop + Stop(Option), + // Tcp_CR_Acked + // TcpConnectionConfirmed + Connected(TcpStream, TcpConnInfo), + // TcpConnectionFails + ConnFail, + // BGPHeaderErr + // BGPOpenMsgErr + // UpdateMsgErr + RcvdError(DecodeError), + // BGPOpen + RcvdOpen(OpenMsg), + // NotifMsg + RcvdNotif(NotificationMsg), + // KeepAliveMsg + RcvdKalive, + // UpdateMsg + RcvdUpdate, + // ConnectRetryTimer_Expires + // HoldTimer_Expires + // AutomaticStart + // AutomaticStart_with_PassiveTcpEstablishment + Timer(Timer), + } + + // BGP timers. + // + // Note: KEEPALIVE messages are sent independently, separate from the FSM. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Deserialize, Serialize)] + pub enum Timer { + ConnectRetry, + Hold, + AutoStart, + } +} + +// ===== impl Neighbor ===== + +impl Neighbor { + // Creates a new neighbor in the Idle state with default configuration. + pub(crate) fn new(remote_addr: IpAddr, peer_type: PeerType) -> Neighbor { + Neighbor { + remote_addr, + config: Default::default(), + state: fsm::State::Idle, + peer_type, + conn_info: None, + shared_subnet: false, + identifier: None, + holdtime_nego: None, + capabilities_adv: Default::default(), + capabilities_rcvd: Default::default(), + capabilities_nego: Default::default(), + notification_sent: None, + notification_rcvd: None, + last_established: None, + statistics: Default::default(), + tasks: Default::default(), + update_queues: Default::default(), + msg_txp: None, + } + } + + // Injects an event into the neighbor's FSM. + pub(crate) fn fsm_event( + &mut self, + instance: &mut InstanceUpView<'_>, + event: fsm::Event, + ) { + Debug::NbrFsmEvent(&self.remote_addr, &event).log(); + + // Process FSM event. + let rib = &mut instance.state.rib; + let next_state = match self.state { + // Idle state + fsm::State::Idle => match event { + fsm::Event::Start + | fsm::Event::Timer(fsm::Timer::AutoStart) => { + self.connect_retry_start( + &instance.tx.protocol_input.nbr_timer, + ); + if self.config.transport.passive_mode { + Some(fsm::State::Active) + } else { + self.connect(&instance.tx.protocol_input.tcp_connect); + Some(fsm::State::Connect) + } + } + _ => None, + }, + // Connect state + fsm::State::Connect => match event { + fsm::Event::Start => None, + fsm::Event::Stop(_) => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::Connected(stream, conn_info) => { + self.connect_retry_stop(); + self.connection_setup( + stream, + conn_info, + &instance.tx.protocol_input.nbr_msg_rx, + #[cfg(feature = "testing")] + &instance.tx.protocol_output, + ); + self.open_send(instance.config, instance.state.router_id); + self.holdtime_start( + LARGE_HOLDTIME, + &instance.tx.protocol_input.nbr_timer, + ); + Some(fsm::State::OpenSent) + } + fsm::Event::ConnFail => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdError(error) => { + let msg = NotificationMsg::from(error); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + fsm::Event::Timer(fsm::Timer::ConnectRetry) => { + self.connect(&instance.tx.protocol_input.tcp_connect); + self.connect_retry_start( + &instance.tx.protocol_input.nbr_timer, + ); + None + } + _ => { + // FSM error. + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + }, + // Active state + fsm::State::Active => match event { + fsm::Event::Start => None, + fsm::Event::Stop(_) => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::Connected(stream, conn_info) => { + self.connect_retry_stop(); + self.connection_setup( + stream, + conn_info, + &instance.tx.protocol_input.nbr_msg_rx, + #[cfg(feature = "testing")] + &instance.tx.protocol_output, + ); + self.open_send(instance.config, instance.state.router_id); + self.holdtime_start( + LARGE_HOLDTIME, + &instance.tx.protocol_input.nbr_timer, + ); + Some(fsm::State::OpenSent) + } + fsm::Event::ConnFail => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdError(error) => { + let msg = NotificationMsg::from(error); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + fsm::Event::Timer(fsm::Timer::ConnectRetry) => { + self.connect(&instance.tx.protocol_input.tcp_connect); + self.connect_retry_start( + &instance.tx.protocol_input.nbr_timer, + ); + Some(fsm::State::Connect) + } + _ => { + // FSM error. + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + }, + // OpenSent state + fsm::State::OpenSent => match event { + fsm::Event::Start => None, + fsm::Event::Stop(msg) => { + self.session_close(rib, &instance.tx, msg); + Some(fsm::State::Idle) + } + fsm::Event::ConnFail => { + self.session_close(rib, &instance.tx, None); + self.connect_retry_start( + &instance.tx.protocol_input.nbr_timer, + ); + Some(fsm::State::Active) + } + fsm::Event::RcvdError(error) => { + let msg = NotificationMsg::from(error); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + fsm::Event::RcvdOpen(msg) => { + let next_state = self.open_process(instance, msg); + Some(next_state) + } + fsm::Event::Timer(fsm::Timer::Hold) => { + let error_code = ErrorCode::HoldTimerExpired; + let error_subcode = 0; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + _ => { + // FSM error. + let error_code = ErrorCode::FiniteStateMachineError; + let error_subcode = + FsmErrorSubcode::UnexpectedMessageInOpenSent; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + }, + // OpenConfirm state + fsm::State::OpenConfirm => match event { + fsm::Event::Start => None, + fsm::Event::Stop(msg) => { + self.session_close(rib, &instance.tx, msg); + Some(fsm::State::Idle) + } + fsm::Event::ConnFail => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdError(error) => { + let msg = NotificationMsg::from(error); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + fsm::Event::RcvdOpen(_msg) => { + // TODO: collision detection + Some(fsm::State::Idle) + } + fsm::Event::RcvdNotif(_) => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdKalive => { + self.holdtime_restart(); + Some(fsm::State::Established) + } + fsm::Event::Timer(fsm::Timer::Hold) => { + let error_code = ErrorCode::HoldTimerExpired; + let error_subcode = 0; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + _ => { + // FSM error. + let error_code = ErrorCode::FiniteStateMachineError; + let error_subcode = + FsmErrorSubcode::UnexpectedMessageInOpenConfirm; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + }, + // Established state + fsm::State::Established => match event { + fsm::Event::Start => None, + fsm::Event::Stop(msg) => { + self.session_close(rib, &instance.tx, msg); + Some(fsm::State::Idle) + } + fsm::Event::ConnFail => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdError(error) => { + let msg = NotificationMsg::from(error); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + fsm::Event::RcvdNotif(_) => { + self.session_close(rib, &instance.tx, None); + Some(fsm::State::Idle) + } + fsm::Event::RcvdKalive | fsm::Event::RcvdUpdate => { + self.holdtime_restart(); + None + } + fsm::Event::Timer(fsm::Timer::Hold) => { + let error_code = ErrorCode::HoldTimerExpired; + let error_subcode = 0; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + _ => { + // FSM error. + let error_code = ErrorCode::FiniteStateMachineError; + let error_subcode = + FsmErrorSubcode::UnexpectedMessageInEstablished; + let msg = NotificationMsg::new(error_code, error_subcode); + self.session_close(rib, &instance.tx, Some(msg)); + Some(fsm::State::Idle) + } + }, + }; + + // Change to next FSM state when applicable. + if let Some(next_state) = next_state + && self.state != next_state + { + // Schedule auto-start unless the peer has been manually disabled. + if next_state == fsm::State::Idle && self.config.enabled { + self.autostart_start(&instance.tx.protocol_input.nbr_timer); + } else { + self.autostart_stop(); + } + + self.fsm_state_change(instance, next_state); + } + } + + // Updates the neighbor's FSM state. + fn fsm_state_change( + &mut self, + instance: &mut InstanceUpView<'_>, + next_state: fsm::State, + ) { + Debug::NbrFsmTransition(&self.remote_addr, &self.state, &next_state) + .log(); + + // Keep track of the time that the BGP session last transitioned in or + // out of the Established state. + if self.state == fsm::State::Established + || next_state == fsm::State::Established + { + self.last_established = Some(Utc::now()); + } + + if next_state == fsm::State::Established { + // Update statistics. + self.statistics.established_transitions += 1; + + // Initialize session. + self.session_init(instance); + } + + self.state = next_state; + } + + // Sets up the connection for the BGP neighbor, spawning necessary tasks for + // TCP communication. + fn connection_setup( + &mut self, + stream: TcpStream, + conn_info: TcpConnInfo, + nbr_msg_rxp: &Sender, + #[cfg(feature = "testing")] proto_output_tx: &Sender, + ) { + // Store TCP connection information. + self.conn_info = Some(conn_info); + + // Split TCP stream into two halves. + let (read_half, write_half) = stream.into_split(); + + // Spawn neighbor TCP Tx task. + let (msg_txp, msg_txc) = mpsc::unbounded_channel(); + let cxt = EncodeCxt { + capabilities: Default::default(), + }; + let mut tx_task = tasks::nbr_tx( + self, + cxt, + write_half, + msg_txc, + #[cfg(feature = "testing")] + proto_output_tx, + ); + self.msg_txp = Some(msg_txp); + + // Spawn neighbor TCP Rx task. + let cxt = DecodeCxt { + peer_type: self.peer_type, + peer_as: self.config.peer_as, + capabilities: Default::default(), + }; + let tcp_rx_task = tasks::nbr_rx(self, cxt, read_half, nbr_msg_rxp); + self.tasks.tcp_rx = Some(tcp_rx_task); + + // No need to keep track of the Tx task since it gracefully exits as + // soon as the tx end of its mpsc channel is dropped. This ensures that + // messages sent during neighbor shutdown will be delivered. + tx_task.detach(); + } + + // Initializes the BGP session. + fn session_init(&mut self, instance: &mut InstanceUpView<'_>) { + // Compute the negotiated capabilities. + self.capabilities_nego = self + .capabilities_adv + .intersection(&self.capabilities_rcvd) + .cloned() + .collect(); + + // Update the Tx task with the negotiated capabilities. + let msg = NbrTxMsg::UpdateCapabilities(self.capabilities_nego.clone()); + let _ = self.msg_txp.as_ref().unwrap().send(msg); + + // Send initial routing updates. + self.initial_routing_update::(instance); + self.initial_routing_update::(instance); + } + + // Closes the BGP session, performing necessary cleanup and releasing resources. + fn session_close( + &mut self, + rib: &mut Rib, + instance_tx: &InstanceChannelsTx, + send_notif: Option, + ) { + // Send a notification message. + if self.state >= fsm::State::OpenSent + && let Some(msg) = send_notif + { + self.message_send(Message::Notification(msg)); + } + + // Set the ConnectRetryTimer to zero. + self.connect_retry_stop(); + + // Release all resources. + self.conn_info = None; + self.identifier = None; + self.holdtime_nego = None; + self.capabilities_adv.clear(); + self.capabilities_rcvd.clear(); + self.capabilities_nego.clear(); + self.clear_routes::(rib); + self.clear_routes::(rib); + self.tasks = Default::default(); + self.msg_txp = None; + + // Trigger the BGP Decision Process. + instance_tx.protocol_input.trigger_decision_process(); + } + + // Enqueues a single BGP message for transmission. + pub(crate) fn message_send(&mut self, msg: Message) { + Debug::NbrMsgTx(&self.remote_addr, &msg).log(); + + // Update statistics. + self.statistics.msgs_sent.update(&msg); + + // Keep track of the last sent notification. + if let Message::Notification(msg) = &msg { + self.notification_sent = Some((Utc::now(), msg.clone())); + } + + // Ignore any possible error as the connection might have gone down + // already. + let nbr_addr = self.remote_addr; + let msg = NbrTxMsg::SendMessage { nbr_addr, msg }; + let _ = self.msg_txp.as_ref().unwrap().send(msg); + } + + // Enqueues a list of BGP messages for transmission. + // + // This method is more efficient for handling a large number of messages, + // as they are sent all at once. + pub(crate) fn message_list_send(&mut self, msg_list: Vec) { + for msg in &msg_list { + Debug::NbrMsgTx(&self.remote_addr, &msg).log(); + + // Update statistics. + self.statistics.msgs_sent.update(&msg); + + // Keep track of the last sent notification. + if let Message::Notification(msg) = &msg { + self.notification_sent = Some((Utc::now(), msg.clone())); + } + } + + // Ignore any possible error as the connection might have gone down + // already. + let nbr_addr = self.remote_addr; + let msg = NbrTxMsg::SendMessageList { nbr_addr, msg_list }; + let _ = self.msg_txp.as_ref().unwrap().send(msg); + } + + // Sends a BGP OPEN message based on the local configuration. + fn open_send(&mut self, instance_cfg: &InstanceCfg, identifier: Ipv4Addr) { + // Base capabilities. + let mut capabilities: BTreeSet<_> = [ + Capability::RouteRefresh, + Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(instance_cfg.asn), + }, + ] + .into(); + + // Multiprotocol capabilities. + if let Some(afi_safi) = self.config.afi_safi.get(&AfiSafi::Ipv4Unicast) + && afi_safi.enabled + { + capabilities.insert(Capability::MultiProtocol { + afi: Afi::Ipv4, + safi: Safi::Unicast, + }); + } + if let Some(afi_safi) = self.config.afi_safi.get(&AfiSafi::Ipv6Unicast) + && afi_safi.enabled + { + capabilities.insert(Capability::MultiProtocol { + afi: Afi::Ipv6, + safi: Safi::Unicast, + }); + } + + // Keep track of the advertised capabilities. + self.capabilities_adv = capabilities.clone(); + + // Fill-in and send message. + let msg = Message::Open(OpenMsg { + version: BGP_VERSION, + my_as: instance_cfg.asn.try_into().unwrap_or(AS_TRANS), + holdtime: self.config.timers.holdtime, + identifier, + capabilities, + }); + self.message_send(msg); + } + + // Processes the received OPEN message while in the OpenSent state. + fn open_process( + &mut self, + instance: &mut InstanceUpView<'_>, + msg: OpenMsg, + ) -> fsm::State { + use crate::packet::consts::OpenMessageErrorSubcode as ErrorSubcode; + + // Validate the received message. + if let Err(error) = self.open_validate(instance, &msg) { + error.log(); + + // Close the session. + let msg = match error { + Error::NbrBadAs(..) => { + let error_code = ErrorCode::OpenMessageError; + let error_subcode = ErrorSubcode::BadPeerAs; + let msg = NotificationMsg::new(error_code, error_subcode); + Some(msg) + } + Error::NbrBadIdentifier(..) => { + let error_code = ErrorCode::OpenMessageError; + let error_subcode = ErrorSubcode::BadBgpIdentifier; + let msg = NotificationMsg::new(error_code, error_subcode); + Some(msg) + } + _ => None, + }; + self.session_close(&mut instance.state.rib, &instance.tx, msg); + + // Transition to the Idle state. + return fsm::State::Idle; + } + + // Calculate negotiated hold-time. + let holdtime_nego = + std::cmp::min(msg.holdtime, self.config.timers.holdtime); + + // Set the ConnectRetryTimer to zero. + self.connect_retry_stop(); + + // Send Keepalive message. + self.message_send(Message::Keepalive(KeepaliveMsg {})); + + // Start Keepalive interval and session hold timer. + if holdtime_nego != 0 { + self.keepalive_interval_start(holdtime_nego); + self.holdtime_start( + holdtime_nego, + &instance.tx.protocol_input.nbr_timer, + ); + } else { + self.holdtime_stop(); + } + + // Keep track of the received data. + self.identifier = Some(msg.identifier); + self.holdtime_nego = (holdtime_nego != 0).then_some(holdtime_nego); + self.capabilities_rcvd = msg.capabilities; + + // TODO: collision detection + + // Transition to the OpenConfirm state. + fsm::State::OpenConfirm + } + + // Performs semantic validation of the received BGP OPEN message. + // Syntactic errors are detected during the decoding phase. + fn open_validate( + &self, + instance: &InstanceUpView<'_>, + msg: &OpenMsg, + ) -> Result<(), Error> { + // Validate ASN. + if self.config.peer_as != msg.real_as() { + return Err(Error::NbrBadAs( + self.remote_addr, + msg.real_as(), + self.config.peer_as, + )); + } + + // Validate BGP identifier for internal peers. + if self.peer_type == PeerType::Internal + && msg.identifier == instance.state.router_id + { + return Err(Error::NbrBadIdentifier( + self.remote_addr, + msg.identifier, + )); + } + + Ok(()) + } + + // Returns the neighbor's Tx-TTL value based on the peer type and + // configuration. + pub(crate) fn tx_ttl(&self) -> u8 { + match self.peer_type { + PeerType::Internal => TTL_MAX, + PeerType::External => { + if self.config.transport.ttl_security.is_some() { + TTL_MAX + } else if self.config.transport.ebgp_multihop_enabled + && let Some(ttl) = self.config.transport.ebgp_multihop_ttl + { + ttl + } else { + 1 + } + } + } + } + + // Starts the auto-start timer. + fn autostart_start(&mut self, nbr_timerp: &Sender) { + let idle_hold_time = 1; + let task = tasks::nbr_timer( + self, + fsm::Timer::AutoStart, + idle_hold_time, + nbr_timerp, + ); + self.tasks.autostart = Some(task); + } + + // Stops the auto-start timer. + fn autostart_stop(&mut self) { + self.tasks.autostart = None; + } + + // Starts a TCP connection task to the neighbor's remote address. + fn connect(&mut self, tcp_connectp: &Sender) { + let task = tasks::tcp_connect(self, tcp_connectp); + self.tasks.connect = Some(task); + } + + // Starts the Keepalive Tx interval. + fn keepalive_interval_start(&mut self, holdtime_nego: u16) { + let interval = + self.config.timers.keepalive.unwrap_or(holdtime_nego / 3); + let task = tasks::nbr_kalive_interval(self, interval); + self.tasks.keepalive = Some(task); + } + + // Starts the session hold timer. + fn holdtime_start( + &mut self, + seconds: u16, + nbr_timerp: &Sender, + ) { + let task = + tasks::nbr_timer(self, fsm::Timer::Hold, seconds, nbr_timerp); + self.tasks.holdtime = Some(task); + } + + // Restarts the session hold timer if the negotiated HoldTime value is + // non-zero. + fn holdtime_restart(&mut self) { + if let Some(holdtime) = self.tasks.holdtime.as_mut() { + holdtime.reset(None); + } + } + + // Stops the session hold timer. + fn holdtime_stop(&mut self) { + self.tasks.holdtime = None; + } + + // Starts the connect retry timer. + fn connect_retry_start(&mut self, nbr_timerp: &Sender) { + let task = tasks::nbr_timer( + self, + fsm::Timer::ConnectRetry, + self.config.timers.connect_retry_interval, + nbr_timerp, + ); + self.tasks.connect_retry = Some(task); + } + + // Stops the connect retry timer. + fn connect_retry_stop(&mut self) { + self.tasks.connect_retry = None; + } + + // Sends an initial routing update for the specified address-family after + // the session is established. + fn initial_routing_update(&mut self, instance: &mut InstanceUpView<'_>) + where + A: AddressFamily, + { + // Check if the address-family is enabled for this session. + if !self.is_af_enabled(A::AFI, A::SAFI) { + return; + } + + // Get list of best routes for this address-family. + let table = A::table(&mut instance.state.rib.tables); + let routes = table + .prefixes + .iter() + .filter_map(|(prefix, dest)| { + dest.local.as_ref().map(|route| (prefix, &route.0)) + }) + .map(|(prefix, route)| (*prefix, route.clone())) + .collect::>(); + + // Advertise the best routes. + events::advertise_routes::( + self, + table, + &routes, + &instance.shared, + &instance.state.policy_apply_tasks, + ); + } + + // Clears the Adj-RIB-In and Adj-RIB-Out for the given address family. + fn clear_routes(&mut self, rib: &mut Rib) + where + A: AddressFamily, + { + let table = A::table(&mut rib.tables); + for (prefix, dest) in &mut table.prefixes { + dest.adj_in_pre.remove(&self.remote_addr); + dest.adj_in_post.remove(&self.remote_addr); + dest.adj_out_pre.remove(&self.remote_addr); + dest.adj_out_post.remove(&self.remote_addr); + table.queued_prefixes.insert(*prefix); + } + } + + // Check if the given address-family is enabled for this session. + pub(crate) fn is_af_enabled(&self, afi: Afi, safi: Safi) -> bool { + // Check if the corresponding multi-protocol capability has been + // negotiated. + let cap = Capability::MultiProtocol { afi, safi }; + if self.capabilities_nego.get(&cap).is_some() { + return true; + } + + // If the peer doesn't support BGP capabilities, the IPv4 unicast + // address-family is enabled by default. + if self.capabilities_nego.is_empty() + && afi == Afi::Ipv4 + && safi == Safi::Unicast + { + return true; + } + + false + } +} + +// ===== impl MessageStatistics ===== + +impl MessageStatistics { + pub(crate) fn update(&mut self, msg: &Message) { + self.total.fetch_add(1, atomic::Ordering::Relaxed); + match msg { + Message::Update(_) => { + self.updates += 1; + } + Message::Notification(_) => { + self.notifications += 1; + } + Message::RouteRefresh(_) => { + self.route_refreshes += 1; + } + _ => {} + } + } +} + +// ===== impl NeighborUpdateQueues ===== + +impl NeighborUpdateQueues { + pub(crate) fn build_updates(&mut self) -> Vec { + [ + self.ipv4_unicast.build_updates(), + self.ipv6_unicast.build_updates(), + ] + .concat() + } +} + +// ===== impl NeighborUpdateQueue ===== + +impl NeighborUpdateQueue +where + A: AddressFamily, +{ + fn build_updates(&mut self) -> Vec { + A::build_updates(self) + } +} + +impl Default for NeighborUpdateQueue +where + A: AddressFamily, +{ + fn default() -> NeighborUpdateQueue { + NeighborUpdateQueue { + reach: Default::default(), + unreach: Default::default(), + } + } +} diff --git a/holo-bgp/src/network.rs b/holo-bgp/src/network.rs new file mode 100644 index 00000000..66fd7be3 --- /dev/null +++ b/holo-bgp/src/network.rs @@ -0,0 +1,305 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::{IpAddr, SocketAddr}; +use std::sync::Arc; + +use holo_utils::ip::{AddressFamily, IpAddrExt, IpAddrKind}; +use holo_utils::socket::{ + OwnedReadHalf, OwnedWriteHalf, SocketExt, TcpConnInfo, TcpListener, + TcpSocket, TcpSocketExt, TcpStream, TcpStreamExt, TTL_MAX, +}; +use holo_utils::{capabilities, Sender, UnboundedReceiver}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::mpsc::error::SendError; + +use crate::error::{Error, IoError, NbrRxError}; +use crate::packet::message::{DecodeCxt, EncodeCxt, Message}; +use crate::tasks::messages::input::{NbrRxMsg, TcpAcceptMsg}; +use crate::tasks::messages::output::NbrTxMsg; + +const BGP_PORT: u16 = 179; + +// ===== global functions ===== + +pub(crate) fn listen_socket( + af: AddressFamily, +) -> Result { + #[cfg(not(feature = "testing"))] + { + // Create TCP socket. + let socket = socket(af)?; + + // Bind socket. + let sockaddr = SocketAddr::from((IpAddr::unspecified(af), BGP_PORT)); + socket.set_reuseaddr(true)?; + capabilities::raise(|| socket.bind(sockaddr))?; + + // GTSM Procedure: set TTL to max for outgoing packets. + match af { + AddressFamily::Ipv4 => { + socket.set_ipv4_ttl(TTL_MAX)?; + } + AddressFamily::Ipv6 => { + socket.set_ipv6_unicast_hops(TTL_MAX)?; + } + } + + // Convert the socket into a TcpListener. + let socket = socket.listen(4096)?; + + Ok(socket) + } + #[cfg(feature = "testing")] + { + Ok(TcpListener {}) + } +} + +pub(crate) fn listen_socket_md5sig_update( + socket: &TcpListener, + nbr_addr: &IpAddr, + password: Option<&str>, +) { + #[cfg(not(feature = "testing"))] + { + if let Err(error) = socket.set_md5sig(nbr_addr, password) { + IoError::TcpAuthError(error).log(); + } + } +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn listen_loop( + listener: Arc, + tcp_acceptp: Sender, +) -> Result<(), SendError> { + loop { + match listener.accept().await { + Ok((stream, _)) => match stream.conn_info() { + Ok(conn_info) => { + let msg = TcpAcceptMsg { + stream: Some(stream), + conn_info, + }; + tcp_acceptp.send(msg).await?; + } + Err(error) => { + IoError::TcpInfoError(error).log(); + } + }, + Err(error) => { + IoError::TcpAcceptError(error).log(); + } + } + } +} + +pub(crate) fn accepted_stream_init( + stream: &TcpStream, + af: AddressFamily, + ttl: u8, + ttl_security: Option, + tcp_mss: Option, +) -> Result<(), std::io::Error> { + #[cfg(not(feature = "testing"))] + { + // Set TTL. + match af { + AddressFamily::Ipv4 => stream.set_ipv4_ttl(ttl)?, + AddressFamily::Ipv6 => stream.set_ipv6_unicast_hops(ttl)?, + } + + // Set TTL security check. + if let Some(ttl_security_hops) = ttl_security { + let ttl = TTL_MAX - ttl_security_hops + 1; + match af { + AddressFamily::Ipv4 => stream.set_ipv4_minttl(ttl)?, + AddressFamily::Ipv6 => stream.set_ipv6_min_hopcount(ttl)?, + } + } + + // Set the TCP Maximum Segment Size. + if let Some(tcp_mss) = tcp_mss { + stream.set_mss(tcp_mss.into())?; + } + } + + Ok(()) +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn connect( + remote_addr: IpAddr, + local_addr: Option, + ttl: u8, + ttl_security: Option, + tcp_mss: Option, + tcp_password: &Option, +) -> Result<(TcpStream, TcpConnInfo), Error> { + let af = remote_addr.address_family(); + + // Create TCP socket. + let socket = socket(af).map_err(IoError::TcpSocketError)?; + + // Bind socket. + if let Some(local_addr) = local_addr { + let sockaddr = SocketAddr::from((local_addr, 0)); + socket + .set_reuseaddr(true) + .map_err(IoError::TcpSocketError)?; + capabilities::raise(|| socket.bind(sockaddr)) + .map_err(IoError::TcpSocketError)?; + } + + // Set TTL. + match af { + AddressFamily::Ipv4 => socket.set_ipv4_ttl(ttl), + AddressFamily::Ipv6 => socket.set_ipv6_unicast_hops(ttl), + } + .map_err(IoError::TcpSocketError)?; + + // Set TTL security check. + if let Some(ttl_security_hops) = ttl_security { + let ttl = TTL_MAX - ttl_security_hops + 1; + match af { + AddressFamily::Ipv4 => socket.set_ipv4_minttl(ttl), + AddressFamily::Ipv6 => socket.set_ipv6_min_hopcount(ttl), + } + .map_err(IoError::TcpSocketError)?; + } + + // Set the TCP Maximum Segment Size. + if let Some(tcp_mss) = tcp_mss { + socket + .set_mss(tcp_mss.into()) + .map_err(IoError::TcpSocketError)?; + } + + // Set the TCP MD5 password. + if let Some(tcp_password) = tcp_password { + socket + .set_md5sig(&remote_addr, Some(tcp_password)) + .map_err(IoError::TcpAuthError)?; + } + + // Connect to remote address on the BGP port. + let sockaddr = SocketAddr::from((remote_addr, BGP_PORT)); + let stream = socket + .connect(sockaddr) + .await + .map_err(IoError::TcpConnectError)?; + + // Obtain TCP connection address/port information. + let conn_info = stream.conn_info().map_err(IoError::TcpInfoError)?; + + Ok((stream, conn_info)) +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn nbr_write_loop( + mut stream: OwnedWriteHalf, + mut cxt: EncodeCxt, + mut nbr_msg_txc: UnboundedReceiver, +) { + while let Some(msg) = nbr_msg_txc.recv().await { + match msg { + // Send message to the peer. + NbrTxMsg::SendMessage { msg, .. } => { + let buf = msg.encode(&cxt); + if let Err(error) = stream.write_all(&buf).await { + IoError::TcpSendError(error).log(); + } + } + // Send list of messages to the peer. + NbrTxMsg::SendMessageList { msg_list, .. } => { + for msg in msg_list { + let buf = msg.encode(&cxt); + if let Err(error) = stream.write_all(&buf).await { + IoError::TcpSendError(error).log(); + } + } + } + // Update negotiated capabilities. + NbrTxMsg::UpdateCapabilities(caps) => cxt.capabilities = caps, + } + } +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn nbr_read_loop( + mut stream: OwnedReadHalf, + nbr_addr: IpAddr, + mut cxt: DecodeCxt, + nbr_msg_rxp: Sender, +) -> Result<(), SendError> { + const BUF_SIZE: usize = 65535; + let mut buf = [0; BUF_SIZE]; + let mut data = Vec::with_capacity(BUF_SIZE); + + loop { + // Read data from the network. + match stream.read(&mut buf).await { + Ok(0) => { + // Notify that the connection was closed by the remote end. + let msg = NbrRxMsg { + nbr_addr, + msg: Err(NbrRxError::TcpConnClosed(nbr_addr)), + }; + nbr_msg_rxp.send(msg).await?; + return Ok(()); + } + Ok(num_bytes) => data.extend_from_slice(&buf[..num_bytes]), + Err(error) => { + IoError::TcpRecvError(error).log(); + continue; + } + }; + + // Decode message(s). + while let Some(msg_size) = Message::get_message_len(&data) { + let msg = Message::decode(&data[0..msg_size], &cxt) + .map_err(|error| NbrRxError::MsgDecodeError(nbr_addr, error)); + data.drain(..msg_size); + + // Keep track of received capabilities as they influence how some + // messages should be decoded. + if let Ok(Message::Open(msg)) = &msg { + cxt.capabilities = msg.capabilities.clone(); + } + + // Notify that the BGP message was received. + let msg = NbrRxMsg { nbr_addr, msg }; + nbr_msg_rxp.send(msg).await?; + } + } +} + +// ===== helper functions ===== + +#[cfg(not(feature = "testing"))] +fn socket(af: AddressFamily) -> Result { + let socket = match af { + AddressFamily::Ipv4 => TcpSocket::new_v4()?, + AddressFamily::Ipv6 => { + let socket = TcpSocket::new_v6()?; + socket.set_ipv6_only(true)?; + socket + } + }; + + // Set socket options. + match af { + AddressFamily::Ipv4 => { + socket.set_ipv4_tos(libc::IPTOS_PREC_INTERNETCONTROL)?; + } + AddressFamily::Ipv6 => { + socket.set_ipv6_tclass(libc::IPTOS_PREC_INTERNETCONTROL)?; + } + } + + Ok(socket) +} diff --git a/holo-bgp/src/northbound/configuration.rs b/holo-bgp/src/northbound/configuration.rs new file mode 100644 index 00000000..a3aab947 --- /dev/null +++ b/holo-bgp/src/northbound/configuration.rs @@ -0,0 +1,1470 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +#![allow(clippy::derivable_impls)] + +use std::collections::BTreeMap; +use std::net::{IpAddr, Ipv4Addr}; +use std::sync::LazyLock as Lazy; + +use async_trait::async_trait; +use enum_as_inner::EnumAsInner; +use holo_northbound::configuration::{ + Callbacks, CallbacksBuilder, Provider, ValidationCallbacks, + ValidationCallbacksBuilder, +}; +use holo_northbound::paths::control_plane_protocol::bgp; +use holo_utils::bgp::AfiSafi; +use holo_utils::ip::IpAddrKind; +use holo_utils::policy::{ApplyPolicyCfg, DefaultPolicyType}; +use holo_utils::yang::DataNodeRefExt; +use holo_yang::TryFromYang; + +use crate::instance::Instance; +use crate::neighbor::{fsm, Neighbor, PeerType}; +use crate::network; +use crate::packet::consts::{CeaseSubcode, ErrorCode}; +use crate::packet::message::NotificationMsg; + +#[derive(Debug, Default, EnumAsInner)] +pub enum ListEntry { + #[default] + None, + AfiSafi(AfiSafi), + Neighbor(IpAddr), + NeighborAfiSafi(IpAddr, AfiSafi), +} + +#[derive(Debug)] +pub enum Resource {} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Event { + InstanceUpdate, + NeighborUpdate(IpAddr), + NeighborDelete(IpAddr), + NeighborReset(IpAddr, NotificationMsg), + NeighborUpdateAuth(IpAddr), +} + +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 asn: u32, + pub identifier: Option, + pub distance: DistanceCfg, + pub multipath: MultipathCfg, + pub route_selection: RouteSelectionCfg, + pub apply_policy: ApplyPolicyCfg, + pub afi_safi: BTreeMap, +} + +#[derive(Debug)] +pub struct DistanceCfg { + pub external: u8, + pub internal: u8, +} + +#[derive(Debug)] +pub struct MultipathCfg { + pub enabled: bool, + pub ebgp_allow_multiple_as: bool, + pub ebgp_max_paths: u32, + pub ibgp_max_paths: u32, +} + +#[derive(Debug)] +pub struct InstanceAfiSafiCfg { + pub enabled: bool, + pub multipath: MultipathCfg, + pub route_selection: RouteSelectionCfg, + pub prefix_limit: PrefixLimitCfg, + pub send_default_route: bool, + pub apply_policy: ApplyPolicyCfg, +} + +#[derive(Debug)] +pub struct NeighborCfg { + pub enabled: bool, + pub peer_as: u32, + pub local_as: Option, + pub private_as_remove: Option, + pub timers: NeighborTimersCfg, + pub transport: NeighborTransportCfg, + pub log_neighbor_state_changes: bool, + pub as_path_options: AsPathOptions, + pub apply_policy: ApplyPolicyCfg, + pub prefix_limit: PrefixLimitCfg, + pub afi_safi: BTreeMap, +} + +#[derive(Debug)] +pub struct NeighborTimersCfg { + pub connect_retry_interval: u16, + pub holdtime: u16, + pub keepalive: Option, + pub min_as_orig_interval: Option, + pub min_route_adv_interval: Option, +} + +#[derive(Debug)] +pub struct NeighborTransportCfg { + // TODO: this can be an interface name too. + pub local_addr: Option, + pub tcp_mss: Option, + pub ebgp_multihop_enabled: bool, + pub ebgp_multihop_ttl: Option, + pub passive_mode: bool, + pub ttl_security: Option, + pub secure_session_enabled: bool, + pub md5_key: Option, +} + +#[derive(Debug)] +pub struct NeighborAfiSafiCfg { + pub enabled: bool, + pub prefix_limit: PrefixLimitCfg, + pub send_default_route: bool, + pub apply_policy: ApplyPolicyCfg, +} + +#[derive(Debug)] +pub struct RouteSelectionCfg { + pub always_compare_med: bool, + pub ignore_as_path_length: bool, + pub external_compare_router_id: bool, + pub ignore_next_hop_igp_metric: bool, + pub enable_med: bool, +} + +#[derive(Debug)] +pub struct PrefixLimitCfg { + pub max_prefixes: Option, + pub warning_threshold_pct: Option, + pub teardown: bool, + pub idle_time: Option, +} + +#[derive(Debug)] +pub struct AsPathOptions { + pub allow_own_as: u8, + pub replace_peer_as: bool, + pub disable_peer_as_filter: bool, +} + +#[derive(Debug)] +pub enum PrivateAsRemove { + RemoveAll, + ReplaceAll, +} + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() + .path(bgp::global::PATH) + .create_apply(|_instance, _args| { + }) + .delete_apply(|_instance, _args| { + }) + .path(bgp::global::r#as::PATH) + .modify_apply(|instance, args| { + let asn = args.dnode.get_u32(); + instance.config.asn = asn; + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceUpdate); + }) + .path(bgp::global::identifier::PATH) + .modify_apply(|instance, args| { + let identifier = args.dnode.get_ipv4(); + instance.config.identifier = Some(identifier); + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceUpdate); + }) + .delete_apply(|instance, args| { + instance.config.identifier = None; + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceUpdate); + }) + .path(bgp::global::distance::external::PATH) + .modify_apply(|instance, args| { + let distance = args.dnode.get_u8(); + instance.config.distance.external = distance; + }) + .path(bgp::global::distance::internal::PATH) + .modify_apply(|instance, args| { + let distance = args.dnode.get_u8(); + instance.config.distance.internal = distance; + }) + .path(bgp::global::use_multiple_paths::enabled::PATH) + .modify_apply(|instance, args| { + let enabled = args.dnode.get_bool(); + instance.config.multipath.enabled = enabled; + }) + .path(bgp::global::use_multiple_paths::ebgp::allow_multiple_as::PATH) + .modify_apply(|instance, args| { + let allow = args.dnode.get_bool(); + instance.config.multipath.ebgp_allow_multiple_as = allow; + }) + .path(bgp::global::use_multiple_paths::ebgp::maximum_paths::PATH) + .modify_apply(|instance, args| { + let max = args.dnode.get_u32(); + instance.config.multipath.ebgp_max_paths = max; + }) + .path(bgp::global::use_multiple_paths::ibgp::maximum_paths::PATH) + .modify_apply(|instance, args| { + let max = args.dnode.get_u32(); + instance.config.multipath.ibgp_max_paths = max; + }) + .path(bgp::global::route_selection_options::always_compare_med::PATH) + .modify_apply(|instance, args| { + let compare = args.dnode.get_bool(); + instance.config.route_selection.always_compare_med = compare; + }) + .path(bgp::global::route_selection_options::ignore_as_path_length::PATH) + .modify_apply(|instance, args| { + let ignore = args.dnode.get_bool(); + instance.config.route_selection.ignore_as_path_length = ignore; + }) + .path(bgp::global::route_selection_options::external_compare_router_id::PATH) + .modify_apply(|instance, args| { + let compare = args.dnode.get_bool(); + instance.config.route_selection.external_compare_router_id = compare; + }) + .path(bgp::global::route_selection_options::ignore_next_hop_igp_metric::PATH) + .modify_apply(|instance, args| { + let ignore = args.dnode.get_bool(); + instance.config.route_selection.ignore_next_hop_igp_metric = ignore; + }) + .path(bgp::global::route_selection_options::enable_med::PATH) + .modify_apply(|instance, args| { + let enable = args.dnode.get_bool(); + instance.config.route_selection.enable_med = enable; + }) + .path(bgp::global::afi_safis::afi_safi::PATH) + .create_apply(|instance, args| { + let afi_safi = args.dnode.get_string_relative("./name").unwrap(); + let afi_safi = AfiSafi::try_from_yang(&afi_safi).unwrap(); + instance.config.afi_safi.insert(afi_safi, Default::default()); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + + instance.config.afi_safi.remove(&afi_safi); + }) + .lookup(|_instance, _list_entry, dnode| { + let afi_safi = dnode.get_string_relative("./name").unwrap(); + let afi_safi = AfiSafi::try_from_yang(&afi_safi).unwrap(); + ListEntry::AfiSafi(afi_safi) + }) + .path(bgp::global::afi_safis::afi_safi::enabled::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let enabled = args.dnode.get_bool(); + afi_safi.enabled = enabled; + }) + .path(bgp::global::afi_safis::afi_safi::route_selection_options::always_compare_med::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let compare = args.dnode.get_bool(); + afi_safi.route_selection.always_compare_med = compare; + }) + .path(bgp::global::afi_safis::afi_safi::route_selection_options::ignore_as_path_length::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let ignore = args.dnode.get_bool(); + afi_safi.route_selection.ignore_as_path_length = ignore; + }) + .path(bgp::global::afi_safis::afi_safi::route_selection_options::external_compare_router_id::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let compare = args.dnode.get_bool(); + afi_safi.route_selection.external_compare_router_id = compare; + }) + .path(bgp::global::afi_safis::afi_safi::route_selection_options::ignore_next_hop_igp_metric::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let ignore = args.dnode.get_bool(); + afi_safi.route_selection.ignore_next_hop_igp_metric = ignore; + }) + .path(bgp::global::afi_safis::afi_safi::route_selection_options::enable_med::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let enable = args.dnode.get_bool(); + afi_safi.route_selection.enable_med = enable; + }) + .path(bgp::global::afi_safis::afi_safi::use_multiple_paths::enabled::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let enabled = args.dnode.get_bool(); + afi_safi.multipath.enabled = enabled; + }) + .path(bgp::global::afi_safis::afi_safi::use_multiple_paths::ebgp::allow_multiple_as::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let allow = args.dnode.get_bool(); + afi_safi.multipath.ebgp_allow_multiple_as = allow; + }) + .path(bgp::global::afi_safis::afi_safi::use_multiple_paths::ebgp::maximum_paths::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.multipath.ebgp_max_paths = max; + }) + .path(bgp::global::afi_safis::afi_safi::use_multiple_paths::ibgp::maximum_paths::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.multipath.ibgp_max_paths = max; + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::import_policy::PATH) + .create_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.import_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.import_policy.remove(&policy); + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::default_import_policy::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + afi_safi.apply_policy.default_import_policy = default; + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::export_policy::PATH) + .create_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.export_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.export_policy.remove(&policy); + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::default_export_policy::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + afi_safi.apply_policy.default_export_policy = default; + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::prefix_limit::max_prefixes::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.prefix_limit.max_prefixes = Some(max); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.max_prefixes = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::prefix_limit::warning_threshold_pct::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let threshold = args.dnode.get_u8(); + afi_safi.prefix_limit.warning_threshold_pct = Some(threshold); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.warning_threshold_pct = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::prefix_limit::teardown::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let teardown = args.dnode.get_bool(); + afi_safi.prefix_limit.teardown = teardown; + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::prefix_limit::idle_time::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let idle_time = args.dnode.get_string(); + let idle_time: u32 = idle_time.parse().unwrap(); + afi_safi.prefix_limit.idle_time = Some(idle_time); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.idle_time = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::send_default_route::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let send = args.dnode.get_bool(); + afi_safi.send_default_route = send; + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::max_prefixes::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.prefix_limit.max_prefixes = Some(max); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.max_prefixes = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::warning_threshold_pct::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let threshold = args.dnode.get_u8(); + afi_safi.prefix_limit.warning_threshold_pct = Some(threshold); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.warning_threshold_pct = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::teardown::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let teardown = args.dnode.get_bool(); + afi_safi.prefix_limit.teardown = teardown; + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::idle_time::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let idle_time = args.dnode.get_string(); + let idle_time: u32 = idle_time.parse().unwrap(); + afi_safi.prefix_limit.idle_time = Some(idle_time); + }) + .delete_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.idle_time = None; + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::send_default_route::PATH) + .modify_apply(|instance, args| { + let afi_safi = args.list_entry.into_afi_safi().unwrap(); + let afi_safi = instance.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let send = args.dnode.get_bool(); + afi_safi.send_default_route = send; + }) + .path(bgp::global::apply_policy::import_policy::PATH) + .create_apply(|instance, args| { + let policy = args.dnode.get_string(); + instance.config.apply_policy.import_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let policy = args.dnode.get_string(); + instance.config.apply_policy.import_policy.remove(&policy); + }) + .path(bgp::global::apply_policy::default_import_policy::PATH) + .modify_apply(|instance, args| { + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + instance.config.apply_policy.default_import_policy = default; + }) + .path(bgp::global::apply_policy::export_policy::PATH) + .create_apply(|instance, args| { + let policy = args.dnode.get_string(); + instance.config.apply_policy.export_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let policy = args.dnode.get_string(); + instance.config.apply_policy.export_policy.remove(&policy); + }) + .path(bgp::global::apply_policy::default_export_policy::PATH) + .modify_apply(|instance, args| { + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + instance.config.apply_policy.default_export_policy = default; + }) + .path(bgp::neighbors::neighbor::PATH) + .create_apply(|instance, args| { + let nbr_addr = args.dnode.get_ip_relative("./remote-address").unwrap(); + let peer_as = args.dnode.get_u32_relative("./peer-as").unwrap(); + + let peer_type = if instance.config.asn == peer_as { + PeerType::Internal + } else { + PeerType::External + }; + let nbr = Neighbor::new(nbr_addr, peer_type); + instance.neighbors.insert(nbr_addr, nbr); + + let event_queue = args.event_queue; + event_queue.insert(Event::NeighborUpdate(nbr_addr)); + }) + .delete_apply(|_instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + + let event_queue = args.event_queue; + event_queue.insert(Event::NeighborDelete(nbr_addr)); + }) + .lookup(|_instance, _list_entry, dnode| { + let nbr_addr = dnode.get_ip_relative("./remote-address").unwrap(); + ListEntry::Neighbor(nbr_addr) + }) + .path(bgp::neighbors::neighbor::enabled::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let enabled = args.dnode.get_bool(); + nbr.config.enabled = enabled; + }) + .path(bgp::neighbors::neighbor::peer_as::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let asn = args.dnode.get_u32(); + nbr.config.peer_as = asn; + nbr.peer_type = if instance.config.asn == nbr.config.peer_as { + PeerType::Internal + } else { + PeerType::External + }; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .path(bgp::neighbors::neighbor::local_as::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let asn = args.dnode.get_u32(); + nbr.config.local_as = Some(asn); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.local_as = None; + }) + .path(bgp::neighbors::neighbor::remove_private_as::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let private_as_remove = args.dnode.get_string(); + let private_as_remove = PrivateAsRemove::try_from_yang(&private_as_remove).unwrap(); + nbr.config.private_as_remove = Some(private_as_remove); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.private_as_remove = None; + }) + .path(bgp::neighbors::neighbor::description::PATH) + .modify_apply(|_instance, _args| { + // Nothing to do. + }) + .delete_apply(|_instance, _args| { + // Nothing to do. + }) + .path(bgp::neighbors::neighbor::timers::connect_retry_interval::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let interval = args.dnode.get_u16(); + nbr.config.timers.connect_retry_interval = interval; + }) + .path(bgp::neighbors::neighbor::timers::hold_time::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let holdtime = args.dnode.get_u16(); + nbr.config.timers.holdtime = holdtime; + }) + .path(bgp::neighbors::neighbor::timers::keepalive::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let keepalive = args.dnode.get_u16(); + nbr.config.timers.keepalive = Some(keepalive); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.timers.keepalive = None; + }) + .path(bgp::neighbors::neighbor::timers::min_as_origination_interval::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let interval = args.dnode.get_u16(); + nbr.config.timers.min_as_orig_interval = Some(interval); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.timers.min_as_orig_interval = None; + }) + .path(bgp::neighbors::neighbor::timers::min_route_advertisement_interval::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let interval = args.dnode.get_u16(); + nbr.config.timers.min_route_adv_interval = Some(interval); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.timers.min_route_adv_interval = None; + }) + .path(bgp::neighbors::neighbor::transport::local_address::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let addr = args.dnode.get_ip(); + nbr.config.transport.local_addr = Some(addr); + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.transport.local_addr = None; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .path(bgp::neighbors::neighbor::transport::tcp_mss::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let tcp_mss = args.dnode.get_u16(); + nbr.config.transport.tcp_mss = Some(tcp_mss); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.transport.tcp_mss = None; + }) + .path(bgp::neighbors::neighbor::transport::ebgp_multihop::enabled::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let enabled = args.dnode.get_bool(); + nbr.config.transport.ebgp_multihop_enabled = enabled; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .path(bgp::neighbors::neighbor::transport::ebgp_multihop::multihop_ttl::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let ttl = args.dnode.get_u8(); + nbr.config.transport.ebgp_multihop_ttl = Some(ttl); + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.transport.ebgp_multihop_ttl = None; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .path(bgp::neighbors::neighbor::transport::passive_mode::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let passive_mode = args.dnode.get_bool(); + nbr.config.transport.passive_mode = passive_mode; + }) + .path(bgp::neighbors::neighbor::transport::ttl_security::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let ttl_security = args.dnode.get_u8(); + nbr.config.transport.ttl_security = Some(ttl_security); + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + }) + .path(bgp::neighbors::neighbor::transport::secure_session::enabled::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let enabled = args.dnode.get_bool(); + nbr.config.transport.secure_session_enabled = enabled; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + event_queue.insert(Event::NeighborUpdateAuth(nbr.remote_addr)); + }) + .path(bgp::neighbors::neighbor::transport::secure_session::options::md5_key_string::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let keychain = args.dnode.get_string(); + nbr.config.transport.md5_key = Some(keychain); + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + event_queue.insert(Event::NeighborUpdateAuth(nbr.remote_addr)); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.transport.md5_key = None; + + let event_queue = args.event_queue; + let msg = NotificationMsg::new( + ErrorCode::Cease, + CeaseSubcode::OtherConfigurationChange, + ); + event_queue.insert(Event::NeighborReset(nbr.remote_addr, msg)); + event_queue.insert(Event::NeighborUpdateAuth(nbr.remote_addr)); + }) + .path(bgp::neighbors::neighbor::logging_options::log_neighbor_state_changes::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let log = args.dnode.get_bool(); + nbr.config.log_neighbor_state_changes = log; + }) + .path(bgp::neighbors::neighbor::as_path_options::allow_own_as::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let allow = args.dnode.get_u8(); + nbr.config.as_path_options.allow_own_as = allow; + }) + .path(bgp::neighbors::neighbor::as_path_options::replace_peer_as::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let replace = args.dnode.get_bool(); + nbr.config.as_path_options.replace_peer_as = replace; + }) + .path(bgp::neighbors::neighbor::as_path_options::disable_peer_as_filter::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let disable = args.dnode.get_bool(); + nbr.config.as_path_options.disable_peer_as_filter = disable; + }) + .path(bgp::neighbors::neighbor::apply_policy::import_policy::PATH) + .create_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let policy = args.dnode.get_string(); + nbr.config.apply_policy.import_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let policy = args.dnode.get_string(); + nbr.config.apply_policy.import_policy.remove(&policy); + }) + .path(bgp::neighbors::neighbor::apply_policy::default_import_policy::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + nbr.config.apply_policy.default_import_policy = default; + }) + .path(bgp::neighbors::neighbor::apply_policy::export_policy::PATH) + .create_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let policy = args.dnode.get_string(); + nbr.config.apply_policy.export_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let policy = args.dnode.get_string(); + nbr.config.apply_policy.export_policy.remove(&policy); + }) + .path(bgp::neighbors::neighbor::apply_policy::default_export_policy::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + nbr.config.apply_policy.default_export_policy = default; + }) + .path(bgp::neighbors::neighbor::prefix_limit::max_prefixes::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let max = args.dnode.get_u32(); + nbr.config.prefix_limit.max_prefixes = Some(max); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.prefix_limit.max_prefixes = None; + }) + .path(bgp::neighbors::neighbor::prefix_limit::warning_threshold_pct::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let threshold = args.dnode.get_u8(); + nbr.config.prefix_limit.warning_threshold_pct = Some(threshold); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.prefix_limit.warning_threshold_pct = None; + }) + .path(bgp::neighbors::neighbor::prefix_limit::teardown::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let teardown = args.dnode.get_bool(); + nbr.config.prefix_limit.teardown = teardown; + }) + .path(bgp::neighbors::neighbor::prefix_limit::idle_time::PATH) + .modify_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let idle_time = args.dnode.get_string(); + let idle_time: u32 = idle_time.parse().unwrap(); + nbr.config.prefix_limit.idle_time = Some(idle_time); + }) + .delete_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.config.prefix_limit.idle_time = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::PATH) + .create_apply(|instance, args| { + let nbr_addr = args.list_entry.into_neighbor().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + + let afi_safi = args.dnode.get_string_relative("./name").unwrap(); + let afi_safi = AfiSafi::try_from_yang(&afi_safi).unwrap(); + nbr.config.afi_safi.insert(afi_safi, Default::default()); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + nbr.config.afi_safi.remove(&afi_safi); + }) + .lookup(|_instance, list_entry, dnode| { + let nbr_addr = list_entry.into_neighbor().unwrap(); + let afi_safi = dnode.get_string_relative("./name").unwrap(); + let afi_safi = AfiSafi::try_from_yang(&afi_safi).unwrap(); + ListEntry::NeighborAfiSafi(nbr_addr, afi_safi) + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::enabled::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let enabled = args.dnode.get_bool(); + afi_safi.enabled = enabled; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::import_policy::PATH) + .create_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.import_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.import_policy.remove(&policy); + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::default_import_policy::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + afi_safi.apply_policy.default_import_policy = default; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::export_policy::PATH) + .create_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.export_policy.insert(policy); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let policy = args.dnode.get_string(); + afi_safi.apply_policy.export_policy.remove(&policy); + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::default_export_policy::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let default = args.dnode.get_string(); + let default = DefaultPolicyType::try_from_yang(&default).unwrap(); + afi_safi.apply_policy.default_export_policy = default; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::prefix_limit::max_prefixes::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.prefix_limit.max_prefixes = Some(max); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.max_prefixes = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::prefix_limit::warning_threshold_pct::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let threshold = args.dnode.get_u8(); + afi_safi.prefix_limit.warning_threshold_pct = Some(threshold); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.warning_threshold_pct = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::prefix_limit::teardown::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let teardown = args.dnode.get_bool(); + afi_safi.prefix_limit.teardown = teardown; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::prefix_limit::idle_time::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let idle_time = args.dnode.get_string(); + let idle_time: u32 = idle_time.parse().unwrap(); + afi_safi.prefix_limit.idle_time = Some(idle_time); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.idle_time = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::send_default_route::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let send = args.dnode.get_bool(); + afi_safi.send_default_route = send; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::prefix_limit::max_prefixes::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let max = args.dnode.get_u32(); + afi_safi.prefix_limit.max_prefixes = Some(max); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.max_prefixes = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::prefix_limit::warning_threshold_pct::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let threshold = args.dnode.get_u8(); + afi_safi.prefix_limit.warning_threshold_pct = Some(threshold); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.warning_threshold_pct = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::prefix_limit::teardown::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let teardown = args.dnode.get_bool(); + afi_safi.prefix_limit.teardown = teardown; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::prefix_limit::idle_time::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let idle_time = args.dnode.get_string(); + let idle_time: u32 = idle_time.parse().unwrap(); + afi_safi.prefix_limit.idle_time = Some(idle_time); + }) + .delete_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + afi_safi.prefix_limit.idle_time = None; + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::send_default_route::PATH) + .modify_apply(|instance, args| { + let (nbr_addr, afi_safi) = args.list_entry.into_neighbor_afi_safi().unwrap(); + let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap(); + let afi_safi = nbr.config.afi_safi.get_mut(&afi_safi).unwrap(); + + let send = args.dnode.get_bool(); + afi_safi.send_default_route = send; + }) + .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::InstanceUpdate => self.update().await, + Event::NeighborUpdate(nbr_addr) => { + let Some((mut instance, neighbors)) = self.as_up() else { + return; + }; + let nbr = neighbors.get_mut(&nbr_addr).unwrap(); + + if nbr.config.enabled { + nbr.fsm_event(&mut instance, fsm::Event::Start); + } else { + let error_code = ErrorCode::Cease; + let error_subcode = CeaseSubcode::AdministrativeShutdown; + let msg = NotificationMsg::new(error_code, error_subcode); + nbr.fsm_event(&mut instance, fsm::Event::Stop(Some(msg))); + } + } + Event::NeighborDelete(nbr_addr) => { + let Some((mut instance, neighbors)) = self.as_up() else { + return; + }; + let nbr = neighbors.get_mut(&nbr_addr).unwrap(); + + // Unset neighbor's password in the listening sockets. + for listener in + instance.state.listening_sockets.iter().filter(|listener| { + listener.af == nbr_addr.address_family() + }) + { + network::listen_socket_md5sig_update( + &listener.socket, + &nbr_addr, + None, + ); + } + + // Delete neighbor. + let error_code = ErrorCode::Cease; + let error_subcode = CeaseSubcode::PeerDeConfigured; + let msg = NotificationMsg::new(error_code, error_subcode); + nbr.fsm_event(&mut instance, fsm::Event::Stop(Some(msg))); + neighbors.remove(&nbr_addr); + } + Event::NeighborReset(nbr_addr, msg) => { + let Some((mut instance, neighbors)) = self.as_up() else { + return; + }; + let nbr = neighbors.get_mut(&nbr_addr).unwrap(); + + nbr.fsm_event(&mut instance, fsm::Event::Stop(Some(msg))); + } + Event::NeighborUpdateAuth(nbr_addr) => { + let Some((instance, neighbors)) = self.as_up() else { + return; + }; + let nbr = neighbors.get_mut(&nbr_addr).unwrap(); + + // Get neighbor password. + let key = if nbr.config.transport.secure_session_enabled + && let Some(key) = &nbr.config.transport.md5_key + { + Some(key.clone()) + } else { + None + }; + + // Set/unset password in the listening sockets. + for listener in + instance.state.listening_sockets.iter().filter(|listener| { + listener.af == nbr_addr.address_family() + }) + { + network::listen_socket_md5sig_update( + &listener.socket, + &nbr_addr, + key.as_deref(), + ); + } + } + } + } +} + +// ===== configuration defaults ===== + +impl Default for InstanceCfg { + fn default() -> InstanceCfg { + InstanceCfg { + asn: 0, + identifier: None, + distance: Default::default(), + multipath: Default::default(), + route_selection: Default::default(), + apply_policy: Default::default(), + afi_safi: Default::default(), + } + } +} + +impl Default for DistanceCfg { + fn default() -> DistanceCfg { + let external = bgp::global::distance::external::DFLT; + let internal = bgp::global::distance::internal::DFLT; + + DistanceCfg { external, internal } + } +} + +impl Default for MultipathCfg { + fn default() -> MultipathCfg { + let enabled = bgp::global::use_multiple_paths::enabled::DFLT; + let ebgp_allow_multiple_as = + bgp::global::use_multiple_paths::ebgp::allow_multiple_as::DFLT; + let ebgp_max_paths = + bgp::global::use_multiple_paths::ebgp::maximum_paths::DFLT; + let ibgp_max_paths = + bgp::global::use_multiple_paths::ibgp::maximum_paths::DFLT; + + MultipathCfg { + enabled, + ebgp_allow_multiple_as, + ebgp_max_paths, + ibgp_max_paths, + } + } +} + +impl Default for InstanceAfiSafiCfg { + fn default() -> InstanceAfiSafiCfg { + // TODO: fetch defaults from YANG module + InstanceAfiSafiCfg { + enabled: false, + multipath: Default::default(), + route_selection: Default::default(), + prefix_limit: Default::default(), + send_default_route: false, + apply_policy: Default::default(), + } + } +} + +impl Default for NeighborCfg { + fn default() -> NeighborCfg { + let enabled = bgp::neighbors::neighbor::enabled::DFLT; + let log_neighbor_state_changes = + bgp::neighbors::neighbor::logging_options::log_neighbor_state_changes::DFLT; + + NeighborCfg { + enabled, + peer_as: 0, + local_as: None, + private_as_remove: None, + timers: Default::default(), + transport: Default::default(), + log_neighbor_state_changes, + as_path_options: Default::default(), + apply_policy: Default::default(), + prefix_limit: Default::default(), + afi_safi: Default::default(), + } + } +} + +impl Default for NeighborTimersCfg { + fn default() -> NeighborTimersCfg { + let connect_retry_interval = + bgp::neighbors::neighbor::timers::connect_retry_interval::DFLT; + let holdtime = bgp::neighbors::neighbor::timers::hold_time::DFLT; + + NeighborTimersCfg { + connect_retry_interval, + holdtime, + keepalive: None, + min_as_orig_interval: None, + min_route_adv_interval: None, + } + } +} + +impl Default for NeighborTransportCfg { + fn default() -> NeighborTransportCfg { + let ebgp_multihop_enabled = + bgp::neighbors::neighbor::transport::ebgp_multihop::enabled::DFLT; + let passive_mode = + bgp::neighbors::neighbor::transport::passive_mode::DFLT; + let secure_session_enabled = + bgp::neighbors::neighbor::transport::secure_session::enabled::DFLT; + + NeighborTransportCfg { + local_addr: None, + tcp_mss: None, + ebgp_multihop_enabled, + ebgp_multihop_ttl: None, + passive_mode, + ttl_security: None, + secure_session_enabled, + md5_key: None, + } + } +} + +impl Default for NeighborAfiSafiCfg { + fn default() -> NeighborAfiSafiCfg { + let enabled = + bgp::neighbors::neighbor::afi_safis::afi_safi::enabled::DFLT; + + NeighborAfiSafiCfg { + enabled, + prefix_limit: Default::default(), + send_default_route: false, + apply_policy: Default::default(), + } + } +} + +impl Default for RouteSelectionCfg { + fn default() -> RouteSelectionCfg { + // TODO: fetch defaults from YANG module + RouteSelectionCfg { + always_compare_med: false, + ignore_as_path_length: false, + external_compare_router_id: true, + ignore_next_hop_igp_metric: false, + enable_med: false, + } + } +} + +impl Default for PrefixLimitCfg { + fn default() -> PrefixLimitCfg { + // TODO: fetch defaults from YANG module + PrefixLimitCfg { + max_prefixes: None, + warning_threshold_pct: None, + teardown: false, + idle_time: None, + } + } +} + +impl Default for AsPathOptions { + fn default() -> AsPathOptions { + // TODO: fetch defaults from YANG module + AsPathOptions { + allow_own_as: 0, + replace_peer_as: false, + disable_peer_as_filter: false, + } + } +} diff --git a/holo-bgp/src/northbound/mod.rs b/holo-bgp/src/northbound/mod.rs new file mode 100644 index 00000000..0dfe9b63 --- /dev/null +++ b/holo-bgp/src/northbound/mod.rs @@ -0,0 +1,36 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +pub mod configuration; +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-bgp", "holo-bgp"] + } + + fn top_level_node(&self) -> String { + format!( + "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='{}'][name='main']/ietf-bgp:bgp", + Protocol::BGP.to_yang(), + ) + } + + fn debug_span(name: &str) -> Span { + debug_span!("bgp-instance", %name) + } +} diff --git a/holo-bgp/src/northbound/rpc.rs b/holo-bgp/src/northbound/rpc.rs new file mode 100644 index 00000000..6cdb875f --- /dev/null +++ b/holo-bgp/src/northbound/rpc.rs @@ -0,0 +1,30 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::sync::LazyLock as Lazy; + +use holo_northbound::rpc::{Callbacks, CallbacksBuilder, Provider}; + +//use holo_utils::yang::DataNodeRefExt; +//use yang2::data::Data; +use crate::instance::Instance; + +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + // TODO: YANG actions are not supported yet. + CallbacksBuilder::::default().build() +} + +// ===== impl Instance ===== + +impl Provider for Instance { + fn callbacks() -> Option<&'static Callbacks> { + Some(&CALLBACKS) + } +} diff --git a/holo-bgp/src/northbound/state.rs b/holo-bgp/src/northbound/state.rs new file mode 100644 index 00000000..8a1624b6 --- /dev/null +++ b/holo-bgp/src/northbound/state.rs @@ -0,0 +1,1938 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::Ipv4Addr; +use std::sync::{atomic, Arc, LazyLock as Lazy}; + +use enum_as_inner::EnumAsInner; +use holo_northbound::paths::control_plane_protocol::bgp; +use holo_northbound::state::{ + Callbacks, CallbacksBuilder, ListEntryKind, NodeAttributes, Provider, +}; +use holo_utils::bgp::AfiSafi; +use holo_yang::ToYang; +use ipnetwork::{Ipv4Network, Ipv6Network}; +use itertools::Itertools; + +use crate::instance::Instance; +use crate::neighbor::{fsm, Neighbor}; +use crate::packet::attribute::{ + AsPathSegment, BaseAttrs, Comm, Comms, ExtComm, ExtComms, Extv6Comm, + Extv6Comms, LargeComm, LargeComms, UnknownAttr, +}; +use crate::packet::consts::{Afi, AttrFlags, Safi}; +use crate::packet::message::{AddPathTuple, Capability}; +use crate::rib::{AttrSet, Route}; + +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); + +#[derive(Debug, Default, EnumAsInner)] +pub enum ListEntry<'a> { + #[default] + None, + GlobalAfiSafi(AfiSafi), + Neighbor(&'a Neighbor), + CapabilityAdv(usize, &'a Capability), + CapabilityRcvd(usize, &'a Capability), + CapabilityNego(String), + AddPathTuple(&'a AddPathTuple), + Rib(AfiSafi), + RibBaseAttrs(&'a Arc>), + RibComms(&'a Arc>), + RibComm(&'a Comm), + RibExtComms(&'a Arc>), + RibExtComm(&'a ExtComm), + RibExtv6Comms(&'a Arc>), + RibExtv6Comm(&'a Extv6Comm), + RibLargeComms(&'a Arc>), + RibLargeComm(&'a LargeComm), + RibAsPathSegment(&'a AsPathSegment), + RibAsPathSegmentMember(u32), + RibClusterList(Ipv4Addr), + RibNeighbor(AfiSafi, &'a Neighbor), + RibV4LocRoute(&'a Ipv4Network, &'a Route), + RibV6LocRoute(&'a Ipv6Network, &'a Route), + RibV4AdjInPreRoute(&'a Ipv4Network, &'a Route), + RibV6AdjInPreRoute(&'a Ipv6Network, &'a Route), + RibV4AdjInPostRoute(&'a Ipv4Network, &'a Route), + RibV6AdjInPostRoute(&'a Ipv6Network, &'a Route), + RibV4AdjOutPreRoute(&'a Ipv4Network, &'a Route), + RibV6AdjOutPreRoute(&'a Ipv6Network, &'a Route), + RibV4AdjOutPostRoute(&'a Ipv4Network, &'a Route), + RibV6AdjOutPostRoute(&'a Ipv6Network, &'a Route), + RouteUnknownAttr(&'a UnknownAttr), +} + +// ===== callbacks ===== + +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() + .path(bgp::global::afi_safis::afi_safi::PATH) + .get_iterate(|instance, _args| { + if instance.state.is_some() { + let iter = [AfiSafi::Ipv4Unicast, AfiSafi::Ipv6Unicast] + .into_iter() + .map(ListEntry::GlobalAfiSafi); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::global::afi_safis::afi_safi::statistics::total_paths::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::global::afi_safis::afi_safi::statistics::total_prefixes::PATH) + .get_element_u32(|instance, args| { + let afi_safi = args.list_entry.as_global_afi_safi().unwrap(); + let state = instance.state.as_ref().unwrap(); + let total = match afi_safi { + AfiSafi::Ipv4Unicast => state.rib.tables.ipv4_unicast.prefixes.len(), + AfiSafi::Ipv6Unicast => state.rib.tables.ipv6_unicast.prefixes.len(), + }; + Some(total as u32) + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::import_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::global::afi_safis::afi_safi::apply_policy::export_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::global::afi_safis::afi_safi::ipv4_unicast::prefix_limit::prefix_limit_exceeded::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::global::afi_safis::afi_safi::ipv6_unicast::prefix_limit::prefix_limit_exceeded::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::global::apply_policy::import_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::global::apply_policy::export_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::global::statistics::total_paths::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::global::statistics::total_prefixes::PATH) + .get_element_u32(|instance, _args| { + if let Some(state) = &instance.state { + let total = state.rib.tables.ipv4_unicast.prefixes.len() + + state.rib.tables.ipv6_unicast.prefixes.len(); + Some(total as u32) + } else { + None + } + }) + .path(bgp::neighbors::neighbor::PATH) + .get_iterate(|instance, _args| { + let iter = instance + .neighbors + .values() + .map(ListEntry::Neighbor); + Some(Box::new(iter)) + }) + .path(bgp::neighbors::neighbor::local_address::PATH) + .get_element_ip(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.conn_info.as_ref().map(|conn_info| conn_info.local_addr) + }) + .path(bgp::neighbors::neighbor::local_port::PATH) + .get_element_u16(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.conn_info.as_ref().map(|conn_info| conn_info.local_port) + }) + .path(bgp::neighbors::neighbor::remote_port::PATH) + .get_element_u16(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.conn_info.as_ref().map(|conn_info| conn_info.remote_port) + }) + .path(bgp::neighbors::neighbor::peer_type::PATH) + .get_element_string(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.peer_type.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::identifier::PATH) + .get_element_ipv4(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.identifier + }) + .path(bgp::neighbors::neighbor::dynamically_configured::PATH) + .get_element_empty(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::timers::negotiated_hold_time::PATH) + .get_element_u16(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.holdtime_nego + }) + .path(bgp::neighbors::neighbor::apply_policy::import_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::neighbors::neighbor::apply_policy::export_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::neighbors::neighbor::prefix_limit::prefix_limit_exceeded::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::active::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::prefixes::received::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::prefixes::sent::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::prefixes::installed::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::import_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::apply_policy::export_policy::PATH) + .get_iterate(|_instance, _args| { + // No operational data under this list. + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv4_unicast::prefix_limit::prefix_limit_exceeded::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::afi_safis::afi_safi::ipv6_unicast::prefix_limit::prefix_limit_exceeded::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::last_established::PATH) + .attributes(NodeAttributes::TIME) + .get_element_date_and_time(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.last_established + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::PATH) + .get_iterate(|_instance, args| { + let nbr = args.parent_list_entry.as_neighbor().unwrap(); + let iter = nbr + .capabilities_adv + .iter() + .enumerate() + .map(|(index, cap)| ListEntry::CapabilityAdv(index, cap)); + Some(Box::new(iter)) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::name::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_adv().unwrap(); + Some(cap.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::mpbgp::afi::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_adv().unwrap(); + cap.as_multi_protocol().map(|(afi, _)| afi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::mpbgp::safi::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_adv().unwrap(); + cap.as_multi_protocol().map(|(_, safi)| safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::mpbgp::name::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_adv().unwrap(); + cap.as_multi_protocol() + .and_then(|(afi, safi)| afi_safi_tuple(*afi, *safi)) + .map(|afi_safi| afi_safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::asn32::r#as::PATH) + .get_element_u32(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_adv().unwrap(); + cap.as_four_octet_as_number().map(|asn| asn.0) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::add_paths::afi_safis::PATH) + .get_iterate(|_instance, args| { + let (_, cap) = args.parent_list_entry.as_capability_adv().unwrap(); + if let Capability::AddPath(cap) = cap { + let iter = cap.iter().map(ListEntry::AddPathTuple); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::add_paths::afi_safis::afi::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.afi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::add_paths::afi_safis::safi::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::advertised_capabilities::value::add_paths::afi_safis::mode::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.mode.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::PATH) + .get_iterate(|_instance, args| { + let nbr = args.parent_list_entry.as_neighbor().unwrap(); + let iter = nbr + .capabilities_rcvd + .iter() + .enumerate() + .map(|(index, cap)| ListEntry::CapabilityRcvd(index, cap)); + Some(Box::new(iter)) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::name::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_rcvd().unwrap(); + Some(cap.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::mpbgp::afi::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_rcvd().unwrap(); + cap.as_multi_protocol().map(|(afi, _)| afi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::mpbgp::safi::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_rcvd().unwrap(); + cap.as_multi_protocol().map(|(_, safi)| safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::mpbgp::name::PATH) + .get_element_string(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_rcvd().unwrap(); + cap.as_multi_protocol() + .and_then(|(afi, safi)| afi_safi_tuple(*afi, *safi)) + .map(|afi_safi| afi_safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::asn32::r#as::PATH) + .get_element_u32(|_instance, args| { + let (_, cap) = args.list_entry.as_capability_rcvd().unwrap(); + cap.as_four_octet_as_number().map(|asn| asn.0) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::add_paths::afi_safis::PATH) + .get_iterate(|_instance, args| { + let (_, cap) = args.parent_list_entry.as_capability_rcvd().unwrap(); + if let Capability::AddPath(cap) = cap { + let iter = cap.iter().map(ListEntry::AddPathTuple); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::add_paths::afi_safis::afi::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.afi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::add_paths::afi_safis::safi::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.safi.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::received_capabilities::value::add_paths::afi_safis::mode::PATH) + .get_element_string(|_instance, args| { + let ap = args.list_entry.as_add_path_tuple().unwrap(); + Some(ap.mode.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::capabilities::negotiated_capabilities::PATH) + .get_iterate(|_instance, args| { + let nbr = args.parent_list_entry.as_neighbor().unwrap(); + let iter = nbr + .capabilities_adv + .iter() + .map(|cap| cap.to_yang().into()) + .dedup() + .map(ListEntry::CapabilityNego); + Some(Box::new(iter)) + }) + .get_element_string(|_instance, args| { + let cap = args.list_entry.as_capability_nego().unwrap(); + Some(cap.clone()) + }) + .path(bgp::neighbors::neighbor::errors::received::last_notification::PATH) + .attributes(NodeAttributes::TIME) + .get_element_date_and_time(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_rcvd.as_ref().map(|(time, _)| *time) + }) + .path(bgp::neighbors::neighbor::errors::received::last_error::PATH) + .get_element_string(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_rcvd.as_ref().map(|(_, notif)| notif.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::errors::received::last_error_code::PATH) + .get_element_u8(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_rcvd.as_ref().map(|(_, notif)| notif.error_code) + }) + .path(bgp::neighbors::neighbor::errors::received::last_error_subcode::PATH) + .get_element_u8(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_rcvd.as_ref().map(|(_, notif)| notif.error_subcode) + }) + .path(bgp::neighbors::neighbor::errors::received::last_error_data::PATH) + .get_element_binary(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_rcvd.as_ref().map(|(_, notif)| notif.data.clone()) + }) + .path(bgp::neighbors::neighbor::errors::sent::last_notification::PATH) + .attributes(NodeAttributes::TIME) + .get_element_date_and_time(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_sent.as_ref().map(|(time, _)| *time) + }) + .path(bgp::neighbors::neighbor::errors::sent::last_error::PATH) + .get_element_string(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_sent.as_ref().map(|(_, notif)| notif.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::errors::sent::last_error_code::PATH) + .get_element_u8(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_sent.as_ref().map(|(_, notif)| notif.error_code) + }) + .path(bgp::neighbors::neighbor::errors::sent::last_error_subcode::PATH) + .get_element_u8(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_sent.as_ref().map(|(_, notif)| notif.error_code) + }) + .path(bgp::neighbors::neighbor::errors::sent::last_error_data::PATH) + .get_element_binary(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + nbr.notification_sent.as_ref().map(|(_, notif)| notif.data.clone()) + }) + .path(bgp::neighbors::neighbor::session_state::PATH) + .get_element_string(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.state.to_yang().into()) + }) + .path(bgp::neighbors::neighbor::statistics::established_transitions::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.established_transitions) + }) + .path(bgp::neighbors::neighbor::statistics::messages::total_received::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_rcvd.total.load(atomic::Ordering::Relaxed)) + }) + .path(bgp::neighbors::neighbor::statistics::messages::total_sent::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_sent.total.load(atomic::Ordering::Relaxed)) + }) + .path(bgp::neighbors::neighbor::statistics::messages::updates_received::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_rcvd.updates) + }) + .path(bgp::neighbors::neighbor::statistics::messages::updates_sent::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_sent.updates) + }) + .path(bgp::neighbors::neighbor::statistics::messages::erroneous_updates_withdrawn::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.erroneous_updates_withdrawn) + }) + .path(bgp::neighbors::neighbor::statistics::messages::erroneous_updates_attribute_discarded::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.erroneous_updates_attribute_discarded) + }) + .path(bgp::neighbors::neighbor::statistics::messages::in_update_elapsed_time::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.in_update_elapsed_time.as_secs() as u32) + }) + .path(bgp::neighbors::neighbor::statistics::messages::notifications_received::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_rcvd.notifications) + }) + .path(bgp::neighbors::neighbor::statistics::messages::notifications_sent::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_sent.notifications) + }) + .path(bgp::neighbors::neighbor::statistics::messages::route_refreshes_received::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_rcvd.route_refreshes) + }) + .path(bgp::neighbors::neighbor::statistics::messages::route_refreshes_sent::PATH) + .attributes(NodeAttributes::COUNTER) + .get_element_u32(|_instance, args| { + let nbr = args.list_entry.as_neighbor().unwrap(); + Some(nbr.statistics.msgs_sent.route_refreshes) + }) + .path(bgp::neighbors::neighbor::statistics::queues::input::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::neighbors::neighbor::statistics::queues::output::PATH) + .get_element_u32(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::rib::attr_sets::attr_set::PATH) + .get_iterate(|instance, _args| { + if let Some(state) = &instance.state { + let iter = state + .rib + .attr_sets + .base + .tree + .values() + .map(ListEntry::RibBaseAttrs); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::attr_sets::attr_set::attributes::origin::PATH) + .get_element_string(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + Some(attr_set.value.origin.to_yang().into()) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as_path::segment::PATH) + .get_iterate(|_instance, args| { + let attr_set = args.parent_list_entry.as_rib_base_attrs().unwrap(); + let iter = attr_set + .value + .as_path + .segments + .iter() + .map(ListEntry::RibAsPathSegment); + Some(Box::new(iter)) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as_path::segment::r#type::PATH) + .get_element_string(|_instance, args| { + let aspath_seg = args.list_entry.as_rib_as_path_segment().unwrap(); + Some(aspath_seg.seg_type.to_yang().into()) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as_path::segment::member::PATH) + .get_iterate(|_instance, args| { + let aspath_seg = + args.parent_list_entry.as_rib_as_path_segment().unwrap(); + let iter = aspath_seg + .members + .iter() + .copied() + .map(ListEntry::RibAsPathSegmentMember); + Some(Box::new(iter)) + }) + .get_element_u32(|_instance, args| { + let asn = args.list_entry.as_rib_as_path_segment_member().unwrap(); + Some(*asn) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::next_hop::PATH) + .get_element_ip(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.nexthop + }) + .path(bgp::rib::attr_sets::attr_set::attributes::link_local_next_hop::PATH) + .get_element_ipv6(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.ll_nexthop + }) + .path(bgp::rib::attr_sets::attr_set::attributes::med::PATH) + .get_element_u32(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.med + }) + .path(bgp::rib::attr_sets::attr_set::attributes::local_pref::PATH) + .get_element_u32(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.local_pref + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as4_path::segment::PATH) + .get_iterate(|_instance, args| { + let attr_set = args.parent_list_entry.as_rib_base_attrs().unwrap(); + if let Some(as4_path) = &attr_set.value.as4_path { + let iter = as4_path + .segments + .iter() + .map(ListEntry::RibAsPathSegment); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as4_path::segment::r#type::PATH) + .get_element_string(|_instance, args| { + let aspath_seg = args.list_entry.as_rib_as_path_segment().unwrap(); + Some(aspath_seg.seg_type.to_yang().into()) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::as4_path::segment::member::PATH) + .get_iterate(|_instance, args| { + let aspath_seg = + args.parent_list_entry.as_rib_as_path_segment().unwrap(); + let iter = aspath_seg + .members + .iter() + .copied() + .map(ListEntry::RibAsPathSegmentMember); + Some(Box::new(iter)) + }) + .get_element_u32(|_instance, args| { + let asn = args.list_entry.as_rib_as_path_segment_member().unwrap(); + Some(*asn) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::aggregator::r#as::PATH) + .get_element_u32(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.aggregator.as_ref().map(|aggregator| aggregator.asn) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::aggregator::identifier::PATH) + .get_element_ipv4(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.aggregator.as_ref().map(|aggregator| aggregator.identifier) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::aggregator4::as4::PATH) + .get_element_u32(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.as4_aggregator.as_ref().map(|aggregator| aggregator.asn) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::aggregator4::identifier::PATH) + .get_element_ipv4(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.as4_aggregator.as_ref().map(|aggregator| aggregator.identifier) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::atomic_aggregate::PATH) + .get_element_bool(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + Some(attr_set.value.atomic_aggregate) + }) + .path(bgp::rib::attr_sets::attr_set::attributes::originator_id::PATH) + .get_element_ipv4(|_instance, args| { + let attr_set = args.list_entry.as_rib_base_attrs().unwrap(); + attr_set.value.originator_id + }) + .path(bgp::rib::attr_sets::attr_set::attributes::cluster_list::PATH) + .get_iterate(|_instance, args| { + let attr_set = args.parent_list_entry.as_rib_base_attrs().unwrap(); + if let Some(cluster_list) = &attr_set.value.cluster_list { + let iter = cluster_list.0 + .iter() + .copied() + .map(ListEntry::RibClusterList); + Some(Box::new(iter)) + } else { + None + } + }) + .get_element_ipv4(|_instance, args| { + let addr = args.list_entry.as_rib_cluster_list().unwrap(); + Some(*addr) + }) + .path(bgp::rib::communities::community::PATH) + .get_iterate(|instance, _args| { + if let Some(state) = &instance.state { + let iter = state + .rib + .attr_sets + .comm + .tree + .values() + .map(ListEntry::RibComms); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::communities::community::community::PATH) + .get_iterate(|_instance, args| { + let comms = args.parent_list_entry.as_rib_comms().unwrap(); + let iter = comms.value.0.iter().map(ListEntry::RibComm); + Some(Box::new(iter)) + }) + .get_element_string(|_instance, args| { + let comm = args.list_entry.as_rib_comm().unwrap(); + Some(comm.to_yang().into()) + }) + .path(bgp::rib::ext_communities::ext_community::PATH) + .get_iterate(|instance, _args| { + if let Some(state) = &instance.state { + let iter = state + .rib + .attr_sets + .ext_comm + .tree + .values() + .map(ListEntry::RibExtComms); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::ext_communities::ext_community::ext_community::PATH) + .get_iterate(|_instance, args| { + let comms = args.parent_list_entry.as_rib_ext_comms().unwrap(); + let iter = comms.value.0.iter().map(ListEntry::RibExtComm); + Some(Box::new(iter)) + }) + .get_element_string(|_instance, args| { + let comm = args.list_entry.as_rib_ext_comm().unwrap(); + Some(comm.to_yang().into()) + }) + .path(bgp::rib::ext_communities::ext_community::ext_community_raw::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_element_string(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::rib::ipv6_ext_communities::ipv6_ext_community::PATH) + .get_iterate(|instance, _args| { + if let Some(state) = &instance.state { + let iter = state + .rib + .attr_sets + .extv6_comm + .tree + .values() + .map(ListEntry::RibExtv6Comms); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::ipv6_ext_communities::ipv6_ext_community::ipv6_ext_community::PATH) + .get_iterate(|_instance, args| { + let comms = args.parent_list_entry.as_rib_extv6_comms().unwrap(); + let iter = comms.value.0.iter().map(ListEntry::RibExtv6Comm); + Some(Box::new(iter)) + }) + .get_element_string(|_instance, args| { + let comm = args.list_entry.as_rib_extv6_comm().unwrap(); + Some(comm.to_yang().into()) + }) + .path(bgp::rib::ipv6_ext_communities::ipv6_ext_community::ipv6_ext_community_raw::PATH) + .get_iterate(|_instance, _args| { + // TODO: implement me! + None + }) + .get_element_string(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::rib::large_communities::large_community::PATH) + .get_iterate(|instance, _args| { + if let Some(state) = &instance.state { + let iter = state + .rib + .attr_sets + .large_comm + .tree + .values() + .map(ListEntry::RibLargeComms); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::large_communities::large_community::large_community::PATH) + .get_iterate(|_instance, args| { + let comms = args.parent_list_entry.as_rib_large_comms().unwrap(); + let iter = comms.value.0.iter().map(ListEntry::RibLargeComm); + Some(Box::new(iter)) + }) + .get_element_string(|_instance, args| { + let comm = args.list_entry.as_rib_large_comm().unwrap(); + Some(comm.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::PATH) + .get_iterate(|instance, _args| { + if instance.state.is_some() { + let iter = [AfiSafi::Ipv4Unicast, AfiSafi::Ipv6Unicast] + .into_iter() + .map(ListEntry::Rib); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::PATH) + .get_iterate(|instance, args| { + let afi_safi = args.parent_list_entry.as_rib().unwrap(); + if *afi_safi == AfiSafi::Ipv4Unicast + && let Some(state) = &instance.state { + let iter = state.rib.tables.ipv4_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.local.as_ref().map(|(route, _)| { + ListEntry::RibV4LocRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v4_loc_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_loc_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::PATH) + .get_iterate(|instance, args| { + let afi_safi = *args.parent_list_entry.as_rib().unwrap(); + if afi_safi == AfiSafi::Ipv4Unicast { + let iter = instance + .neighbors + .values() + .filter(|nbr| nbr.state == fsm::State::Established) + .map(move |nbr| ListEntry::RibNeighbor(afi_safi, nbr)); + Some(Box::new(iter)) + } else { + None + } + }) + + + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv4_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_in_pre.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV4AdjInPreRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_pre_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv4_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_in_post.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV4AdjInPostRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::best_path::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v4_adj_in_post_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_in_post_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv4_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_out_pre.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV4AdjOutPreRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_pre_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv4_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_out_post.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV4AdjOutPostRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v4_adj_out_post_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v4_adj_out_post_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::PATH) + .get_iterate(|instance, args| { + let afi_safi = args.parent_list_entry.as_rib().unwrap(); + if *afi_safi == AfiSafi::Ipv6Unicast + && let Some(state) = &instance.state { + let iter = state.rib.tables.ipv6_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.local.as_ref().map(|(route, _)| { + ListEntry::RibV6LocRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v6_loc_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_loc_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::PATH) + .get_iterate(|instance, args| { + let afi_safi = *args.parent_list_entry.as_rib().unwrap(); + if afi_safi == AfiSafi::Ipv6Unicast { + let iter = instance + .neighbors + .values() + .filter(|nbr| nbr.state == fsm::State::Established) + .map(move |nbr| ListEntry::RibNeighbor(afi_safi, nbr)); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv6_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_in_post.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV6AdjInPreRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_pre_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv6_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_in_post.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV6AdjInPostRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::best_path::PATH) + .get_element_bool(|_instance, _args| { + // TODO: implement me! + None + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v6_adj_in_post_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_in_post_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv6_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_out_pre.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV6AdjOutPreRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_pre_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::PATH) + .get_iterate(|instance, args| { + let (_, nbr) = args.parent_list_entry.as_rib_neighbor().unwrap(); + if let Some(state) = &instance.state { + let iter = state.rib.tables.ipv6_unicast.prefixes.iter().filter_map( + |(prefix, dest)| { + dest.adj_out_post.get(&nbr.remote_addr).map(|route| { + ListEntry::RibV6AdjOutPostRoute(prefix, route) + }) + }, + ); + Some(Box::new(iter)) + } else { + None + } + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::attr_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + Some(route.attrs.base.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + route.attrs.comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::ext_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + route.attrs.ext_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::large_community_index::PATH) + .get_element_u64(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + route.attrs.large_comm.as_ref().map(|c| c.index) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::last_modified::PATH) + .attributes(NodeAttributes::TIME) + .get_element_timeticks(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + Some(route.last_modified) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::eligible_route::PATH) + .get_element_bool(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + Some(route.eligible) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::ineligible_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + route.ineligible_reason.as_ref().map(|r| r.to_yang().into()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::PATH) + .get_iterate(|_instance, args| { + let (_, route) = args.parent_list_entry.as_rib_v6_adj_out_post_route().unwrap(); + let iter = route.attrs.unknown.iter().map(ListEntry::RouteUnknownAttr); + Some(Box::new(iter)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::optional::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::OPTIONAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::transitive::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::TRANSITIVE)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::partial::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::PARTIAL)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::extended::PATH) + .get_element_bool(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.flags.contains(AttrFlags::EXTENDED)) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::attr_len::PATH) + .get_element_u16(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.length) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::attr_value::PATH) + .get_element_binary(|_instance, args| { + let attr = args.list_entry.as_route_unknown_attr().unwrap(); + Some(attr.value.to_vec()) + }) + .path(bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::reject_reason::PATH) + .get_element_string(|_instance, args| { + let (_, route) = args.list_entry.as_rib_v6_adj_out_post_route().unwrap(); + route.reject_reason.as_ref().map(|r| r.to_yang().into()) + }) + .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-bgp:bgp"; + + type ListEntry<'a> = ListEntry<'a>; + + fn callbacks() -> Option<&'static Callbacks> { + Some(&CALLBACKS) + } +} + +// ===== impl ListEntry ===== + +impl<'a> ListEntryKind for ListEntry<'a> { + fn get_keys(&self) -> Option { + match self { + ListEntry::None => None, + ListEntry::GlobalAfiSafi(afi_safi) => { + use bgp::global::afi_safis::afi_safi::list_keys; + let keys = list_keys(afi_safi.to_yang()); + Some(keys) + } + ListEntry::Neighbor(nbr) => { + use bgp::neighbors::neighbor::list_keys; + let keys = list_keys(nbr.remote_addr); + Some(keys) + } + ListEntry::CapabilityAdv(index, cap) => { + use bgp::neighbors::neighbor::capabilities::advertised_capabilities::list_keys; + let keys = list_keys(cap.code() as u8, index); + Some(keys) + } + ListEntry::CapabilityRcvd(index, cap) => { + use bgp::neighbors::neighbor::capabilities::received_capabilities::list_keys; + let keys = list_keys(cap.code() as u8, index); + Some(keys) + } + ListEntry::Rib(afi_safi) => { + use bgp::rib::afi_safis::afi_safi::list_keys; + let keys = list_keys(afi_safi.to_yang()); + Some(keys) + } + ListEntry::RibBaseAttrs(attr_set) => { + use bgp::rib::attr_sets::attr_set::list_keys; + let keys = list_keys(attr_set.index); + Some(keys) + } + ListEntry::RibComms(attr_set) => { + use bgp::rib::communities::community::list_keys; + let keys = list_keys(attr_set.index); + Some(keys) + } + ListEntry::RibExtComms(attr_set) => { + use bgp::rib::ext_communities::ext_community::list_keys; + let keys = list_keys(attr_set.index); + Some(keys) + } + ListEntry::RibExtv6Comms(attr_set) => { + use bgp::rib::ipv6_ext_communities::ipv6_ext_community::list_keys; + let keys = list_keys(attr_set.index); + Some(keys) + } + ListEntry::RibLargeComms(attr_set) => { + use bgp::rib::large_communities::large_community::list_keys; + let keys = list_keys(attr_set.index); + Some(keys) + } + ListEntry::RibNeighbor(_afi_safi, nbr) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::list_keys; + let keys = list_keys(nbr.remote_addr); + Some(keys) + } + ListEntry::RibV4LocRoute(prefix, route) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::loc_rib::routes::route::list_keys; + let keys = list_keys(prefix, route.origin.to_yang(), 0); + Some(keys) + } + ListEntry::RibV6LocRoute(prefix, route) => { + use bgp::rib::afi_safis::afi_safi::ipv6_unicast::loc_rib::routes::route::list_keys; + let keys = list_keys(prefix, route.origin.to_yang(), 0); + Some(keys) + } + ListEntry::RibV4AdjInPreRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV6AdjInPreRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_pre::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV4AdjInPostRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV6AdjInPostRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_in_post::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV4AdjOutPreRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV6AdjOutPreRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_pre::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV4AdjOutPostRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RibV6AdjOutPostRoute(prefix, _route) => { + use bgp::rib::afi_safis::afi_safi::ipv6_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::list_keys; + let keys = list_keys(prefix, 0); + Some(keys) + } + ListEntry::RouteUnknownAttr(attr) => { + use bgp::rib::afi_safis::afi_safi::ipv4_unicast::neighbors::neighbor::adj_rib_out_post::routes::route::unknown_attributes::unknown_attribute::list_keys; + let keys = list_keys(attr.attr_type); + Some(keys) + } + ListEntry::CapabilityNego(_) + | ListEntry::AddPathTuple(_) + | ListEntry::RibComm(_) + | ListEntry::RibExtComm(_) + | ListEntry::RibExtv6Comm(_) + | ListEntry::RibLargeComm(_) + | ListEntry::RibAsPathSegment(_) + | ListEntry::RibAsPathSegmentMember(_) + | ListEntry::RibClusterList(_) => { + // Keyless lists. + None + } + } + } +} + +// ===== helper functions ===== + +fn afi_safi_tuple(afi: Afi, safi: Safi) -> Option { + match (afi, safi) { + (Afi::Ipv4, Safi::Unicast) => Some(AfiSafi::Ipv4Unicast), + (Afi::Ipv6, Safi::Unicast) => Some(AfiSafi::Ipv6Unicast), + _ => None, + } +} diff --git a/holo-bgp/src/northbound/yang.rs b/holo-bgp/src/northbound/yang.rs new file mode 100644 index 00000000..5072f0ab --- /dev/null +++ b/holo-bgp/src/northbound/yang.rs @@ -0,0 +1,341 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::borrow::Cow; + +use holo_yang::{ToYang, TryFromYang}; +use num_traits::FromPrimitive; + +use crate::neighbor::{fsm, PeerType}; +use crate::northbound::configuration::PrivateAsRemove; +use crate::packet::consts::{ + AddPathMode, AsPathSegmentType, CeaseSubcode, ErrorCode, FsmErrorSubcode, + MessageHeaderErrorSubcode, OpenMessageErrorSubcode, + RouteRefreshErrorSubcode, Safi, UpdateMessageErrorSubcode, +}; +use crate::packet::message::{Capability, NotificationMsg}; +use crate::rib::{RouteIneligibleReason, RouteOrigin, RouteRejectReason}; + +// ===== ToYang implementations ===== + +impl ToYang for Safi { + fn to_yang(&self) -> Cow<'static, str> { + match self { + Safi::Unicast => "unicast-safi".into(), + Safi::Multicast => "multicast-safi".into(), + Safi::LabeledUnicast => "labeled-unicast-safi".into(), + Safi::MulticastVpn => "multicast-vpn-safi".into(), + Safi::Pseudowire => "pseudowire-safi".into(), + Safi::TunnelEncap => "tunnel-encap-safi".into(), + Safi::McastVpls => "mcast-vpls-safi".into(), + Safi::Tunnel => "tunnel-safi".into(), + Safi::Vpls => "vpls-safi".into(), + Safi::Mdt => "mdt-safi".into(), + Safi::V4OverV6 => "v4-over-v6-safi".into(), + Safi::V6OverV4 => "v6-over-v4-safi".into(), + Safi::L1VpnAutoDiscovery => "l1-vpn-auto-discovery-safi".into(), + Safi::Evpn => "evpn-safi".into(), + Safi::BgpLs => "bgp-ls-safi".into(), + Safi::BgpLsVpn => "bgp-ls-vpn-safi".into(), + Safi::SrTe => "sr-te-safi".into(), + Safi::SdWanCapabilities => "sd-wan-capabilities-safi".into(), + Safi::LabeledVpn => "labeled-vpn-safi".into(), + Safi::MulticastMplsVpn => "multicast-mpls-vpn-safi".into(), + Safi::RouteTarget => "route-target-safi".into(), + Safi::Ipv4FlowSpec => "ipv4-flow-spec-safi".into(), + Safi::Vpnv4FlowSpec => "vpnv4-flow-spec-safi".into(), + Safi::VpnAutoDiscovery => "vpn-auto-discovery-safi".into(), + } + } +} + +impl ToYang for AddPathMode { + fn to_yang(&self) -> Cow<'static, str> { + match self { + AddPathMode::Receive => "receive".into(), + AddPathMode::Send => "send".into(), + AddPathMode::ReceiveSend => "receive-send".into(), + } + } +} + +impl ToYang for Capability { + fn to_yang(&self) -> Cow<'static, str> { + match self { + Capability::MultiProtocol { .. } => "iana-bgp-types:mp-bgp".into(), + Capability::FourOctetAsNumber { .. } => { + "iana-bgp-types:asn32".into() + } + Capability::AddPath { .. } => "holo-bgp:add-paths".into(), + Capability::RouteRefresh => "iana-bgp-types:route-refresh".into(), + Capability::EnhancedRouteRefresh => { + "holo-bgp:enhanced-route-refresh".into() + } + } + } +} + +impl ToYang for NotificationMsg { + fn to_yang(&self) -> Cow<'static, str> { + let Some(error_code) = ErrorCode::from_u8(self.error_code) else { + return "holo-bgp:unknown-error".into(); + }; + let identity = match error_code { + ErrorCode::MessageHeaderError => { + use MessageHeaderErrorSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::Unspecific) => { + "message-header-unspecific" + } + Some(ErrorSubcode::ConnectionNotSynchronized) => { + "message-header-connection-not-synchronized" + } + Some(ErrorSubcode::BadMessageLength) => { + "message-header-bad-message-length" + } + Some(ErrorSubcode::BadMessageType) => { + "message-header-bad-message-type" + } + None => "message-header-error", + } + } + ErrorCode::OpenMessageError => { + use OpenMessageErrorSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::Unspecific) => "open-message-unspecific", + Some(ErrorSubcode::UnsupportedVersionNumber) => { + "open-unsupported-version-number" + } + Some(ErrorSubcode::BadPeerAs) => "open-bad-peer-as", + Some(ErrorSubcode::BadBgpIdentifier) => "open-bad-bgp-id", + Some(ErrorSubcode::UnsupportedOptParam) => { + "open-unsupported-optional-parameter" + } + Some(ErrorSubcode::UnacceptableHoldTime) => { + "open-unacceptable-hold-time" + } + Some(ErrorSubcode::UnsupportedCapability) => { + "open-unsupported-capability" + } + Some(ErrorSubcode::RoleMismatch) => "open-role-mismatch", + None => "open-message-error", + } + } + ErrorCode::UpdateMessageError => { + use UpdateMessageErrorSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::Unspecific) => "update-unspecific", + Some(ErrorSubcode::MalformedAttributeList) => { + "update-malformed-attribute-list" + } + Some(ErrorSubcode::UnrecognizedWellKnownAttribute) => { + "update-unrecognized-well-known-attribute" + } + Some(ErrorSubcode::MissingWellKnownAttribute) => { + "update-missing-well-known-attribute" + } + Some(ErrorSubcode::AttributeFlagsError) => { + "update-attribute-flags-error" + } + Some(ErrorSubcode::AttributeLengthError) => { + "update-attribute-length-error" + } + Some(ErrorSubcode::InvalidOriginAttribute) => { + "update-invalid-origin-attribute" + } + Some(ErrorSubcode::InvalidNexthopAttribute) => { + "update-invalid-next-hop-attribute" + } + Some(ErrorSubcode::OptionalAttributeError) => { + "open-optional-attribute-error" + } + Some(ErrorSubcode::InvalidNetworkField) => { + "open-invalid-network-field" + } + Some(ErrorSubcode::MalformedAsPath) => { + "open-malformed-as-path" + } + None => "update-message-error", + } + } + ErrorCode::HoldTimerExpired => "hold-timer-expired-error", + ErrorCode::FiniteStateMachineError => { + use FsmErrorSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::UnexpectedMessageInOpenSent) => { + "fsm-error-unexpected-in-opensent" + } + Some(ErrorSubcode::UnexpectedMessageInOpenConfirm) => { + "fsm-error-unexpected-in-openconfirm" + } + Some(ErrorSubcode::UnexpectedMessageInEstablished) => { + "fsm-error-unexpected-in-established" + } + None => "fsm-error", + } + } + ErrorCode::Cease => { + use CeaseSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::MaximumNumberofPrefixesReached) => { + "cease-max-prefixes" + } + Some(ErrorSubcode::AdministrativeShutdown) => { + "cease-admin-shutdown" + } + Some(ErrorSubcode::PeerDeConfigured) => { + "cease-peer-deconfigured" + } + Some(ErrorSubcode::AdministrativeReset) => { + "cease-admin-reset" + } + Some(ErrorSubcode::ConnectionRejected) => { + "cease-connection-rejected" + } + Some(ErrorSubcode::OtherConfigurationChange) => { + "cease-other-configuration-change" + } + Some(ErrorSubcode::ConnectionCollisionResolution) => { + "cease-connection-collision" + } + Some(ErrorSubcode::OutOfResources) => { + "cease-out-of-resources" + } + Some(ErrorSubcode::HardReset) => "cease-hard-reset", + Some(ErrorSubcode::BfdDown) => "cease-bfd-down", + None => "cease", + } + } + ErrorCode::RouteRefreshMessageError => { + use RouteRefreshErrorSubcode as ErrorSubcode; + match ErrorSubcode::from_u8(self.error_subcode) { + Some(ErrorSubcode::InvalidMessageLength) => { + "route-refresh-invalid-message-length" + } + None => "route-refresh-message-error", + } + } + }; + format!("iana-bgp-notification:{}", identity).into() + } +} + +impl ToYang for fsm::State { + fn to_yang(&self) -> Cow<'static, str> { + match self { + fsm::State::Idle => "idle".into(), + fsm::State::Connect => "connect".into(), + fsm::State::Active => "active".into(), + fsm::State::OpenSent => "opensent".into(), + fsm::State::OpenConfirm => "openconfirm".into(), + fsm::State::Established => "established".into(), + } + } +} + +impl ToYang for PeerType { + fn to_yang(&self) -> Cow<'static, str> { + match self { + PeerType::Internal => "internal".into(), + PeerType::External => "external".into(), + } + } +} + +impl ToYang for AsPathSegmentType { + fn to_yang(&self) -> Cow<'static, str> { + match self { + AsPathSegmentType::Set => "iana-bgp-types:as-set".into(), + AsPathSegmentType::Sequence => "iana-bgp-types:as-sequence".into(), + AsPathSegmentType::ConfedSequence => { + "iana-bgp-types:as-confed-sequence".into() + } + AsPathSegmentType::ConfedSet => { + "iana-bgp-types:as-confed-set".into() + } + } + } +} + +impl ToYang for RouteOrigin { + fn to_yang(&self) -> Cow<'static, str> { + match self { + RouteOrigin::Neighbor { remote_addr, .. } => { + remote_addr.to_string().into() + } + RouteOrigin::Protocol(protocol) => protocol.to_yang(), + } + } +} + +impl ToYang for RouteIneligibleReason { + fn to_yang(&self) -> Cow<'static, str> { + match self { + RouteIneligibleReason::ClusterLoop => { + "iana-bgp-rib-types:ineligible-cluster-loop".into() + } + RouteIneligibleReason::AsLoop => { + "iana-bgp-rib-types:ineligible-as-loop".into() + } + RouteIneligibleReason::Originator => { + "iana-bgp-rib-types:ineligible-originator".into() + } + RouteIneligibleReason::Confed => { + "iana-bgp-rib-types:ineligible-confed".into() + } + } + } +} + +impl ToYang for RouteRejectReason { + fn to_yang(&self) -> Cow<'static, str> { + match self { + RouteRejectReason::LocalPrefLower => { + "iana-bgp-rib-types:local-pref-lower".into() + } + RouteRejectReason::AsPathLonger => { + "iana-bgp-rib-types:as-path-longer".into() + } + RouteRejectReason::OriginTypeHigher => { + "iana-bgp-rib-types:origin-type-higher".into() + } + RouteRejectReason::MedHigher => { + "iana-bgp-rib-types:med-higher".into() + } + RouteRejectReason::PreferExternal => { + "iana-bgp-rib-types:prefer-external".into() + } + RouteRejectReason::NexthopCostHigher => { + "iana-bgp-rib-types:nexthop-cost-higher".into() + } + RouteRejectReason::HigherRouterId => { + "iana-bgp-rib-types:higher-router-id".into() + } + RouteRejectReason::HigherPeerAddress => { + "iana-bgp-rib-types:higher-peer-address".into() + } + RouteRejectReason::RejectedImportPolicy => { + "iana-bgp-rib-types:rejected-import-policy".into() + } + } + } +} + +// ===== TryFromYang implementations ===== + +impl TryFromYang for PrivateAsRemove { + fn try_from_yang(value: &str) -> Option { + match value { + "iana-bgp-types:private-as-remove-all" => { + Some(PrivateAsRemove::RemoveAll) + } + "iana-bgp-types:private-as-replace-all" => { + Some(PrivateAsRemove::ReplaceAll) + } + _ => None, + } + } +} diff --git a/holo-bgp/src/packet/attribute.rs b/holo-bgp/src/packet/attribute.rs new file mode 100644 index 00000000..f52b0324 --- /dev/null +++ b/holo-bgp/src/packet/attribute.rs @@ -0,0 +1,1440 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::{BTreeSet, HashSet, VecDeque}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use derive_new::new; +use holo_utils::bytes::{BytesExt, BytesMutExt}; +use holo_utils::ip::{Ipv4AddrExt, Ipv6AddrExt}; +use num_traits::FromPrimitive; +use serde::{Deserialize, Serialize}; + +use crate::debug::Debug; +use crate::neighbor::PeerType; +use crate::packet::consts::{ + Afi, AsPathSegmentType, AttrFlags, AttrType, Origin, Safi, +}; +use crate::packet::error::{AttrError, UpdateMessageError}; +use crate::packet::message::{ + decode_ipv4_prefix, decode_ipv6_prefix, encode_ipv4_prefix, + encode_ipv6_prefix, DecodeCxt, EncodeCxt, MpReachNlri, MpUnreachNlri, + ReachNlri, +}; + +pub const ATTR_MIN_LEN: u16 = 3; +pub const ATTR_MIN_LEN_EXT: u16 = 4; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct Attrs { + pub base: BaseAttrs, + pub comm: Option, + pub ext_comm: Option, + pub extv6_comm: Option, + pub large_comm: Option, + pub unknown: Vec, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct BaseAttrs { + pub origin: Origin, + pub as_path: AsPath, + pub as4_path: Option, + pub nexthop: Option, + pub ll_nexthop: Option, + pub med: Option, + pub local_pref: Option, + pub aggregator: Option, + pub as4_aggregator: Option, + pub atomic_aggregate: bool, + pub originator_id: Option, + pub cluster_list: Option, +} + +#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct AsPath { + pub segments: VecDeque, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct AsPathSegment { + pub seg_type: AsPathSegmentType, + pub members: VecDeque, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct Aggregator { + pub asn: u32, + pub identifier: Ipv4Addr, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct ClusterList(pub BTreeSet); + +// Re-exports for convenience. +pub type Comm = holo_utils::bgp::Comm; +pub type ExtComm = holo_utils::bgp::ExtComm; +pub type Extv6Comm = holo_utils::bgp::Extv6Comm; +pub type LargeComm = holo_utils::bgp::LargeComm; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct CommList(pub BTreeSet); + +pub trait CommType: + Clone + std::fmt::Debug + Eq + Ord + PartialEq + PartialOrd +{ + const TYPE: AttrType; + const LENGTH: usize; + + fn encode(&self, buf: &mut BytesMut); + fn decode(buf: &mut Bytes) -> Self; +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(new)] +#[derive(Deserialize, Serialize)] +pub struct UnknownAttr { + pub attr_type: u8, + pub flags: AttrFlags, + pub length: u16, + pub value: Bytes, +} + +// Useful type definitions. +pub type Comms = CommList; +pub type ExtComms = CommList; +pub type Extv6Comms = CommList; +pub type LargeComms = CommList; + +// ===== impl Attrs ===== + +impl Attrs { + pub(crate) fn encode( + &self, + buf: &mut BytesMut, + reach: &Option, + mp_reach: &Option, + mp_unreach: &Option, + cxt: &EncodeCxt, + ) { + // Check whether the 4-octet AS number capability has been negotiated. + let four_byte_asn_cap = cxt + .capabilities + .iter() + .any(|cap| cap.is_four_octet_as_number()); + + // RFC 7606 - Section 5.1: + // "The MP_REACH_NLRI or MP_UNREACH_NLRI attribute (if present) SHALL + // be encoded as the very first path attribute in an UPDATE message". + if let Some(mp_reach) = mp_reach { + mp_reach.encode(buf); + } + if let Some(mp_unreach) = mp_unreach { + mp_unreach.encode(buf); + } + + // RFC 4271 - Section 5: + // "The sender of an UPDATE message SHOULD order path attributes within + // the UPDATE message in ascending order of attribute type". + + // ORIGIN attribute. + origin::encode(self.base.origin, buf); + + // AS_PATH attribute. + self.base.as_path.encode( + buf, + AttrFlags::TRANSITIVE, + AttrType::AsPath, + four_byte_asn_cap, + ); + + // NEXT_HOP attribute. + if let Some(reach) = reach { + nexthop::encode(reach.nexthop, buf); + } + + // MULTI_EXIT_DISC attribute. + if let Some(metric) = self.base.med { + med::encode(metric, buf); + } + + // LOCAL_PREF attribute. + if let Some(local_pref) = self.base.local_pref { + local_pref::encode(local_pref, buf); + } + + // ATOMIC_AGGREGATE attribute. + if self.base.atomic_aggregate { + atomic_aggregate::encode(buf); + } + + // AGGREGATOR attribute. + if let Some(aggregator) = &self.base.aggregator { + aggregator.encode( + buf, + AttrFlags::TRANSITIVE | AttrFlags::OPTIONAL, + AttrType::Aggregator, + four_byte_asn_cap, + ); + } + + // COMMUNITIES attribute. + if let Some(comm) = &self.comm { + comm.encode(buf); + } + + // ORIGINATOR_ID attribute. + if let Some(originator_id) = self.base.originator_id { + originator_id::encode(originator_id, buf); + } + + // CLUSTER_LIST attribute. + if let Some(cluster_list) = &self.base.cluster_list { + cluster_list.encode(buf); + } + + // EXTENDED COMMUNITIES attribute. + if let Some(ext_comm) = &self.ext_comm { + ext_comm.encode(buf); + } + + // AS4_PATH attribute. + if let Some(as4_path) = &self.base.as4_path { + as4_path.encode( + buf, + AttrFlags::TRANSITIVE | AttrFlags::OPTIONAL, + AttrType::As4Path, + true, + ); + } + + // AS4_AGGREGATOR attribute. + if let Some(as4_aggregator) = &self.base.as4_aggregator { + as4_aggregator.encode( + buf, + AttrFlags::TRANSITIVE | AttrFlags::OPTIONAL, + AttrType::As4Aggregator, + true, + ); + } + + // IPv6 Address Specific Extended Community attribute. + if let Some(extv6_comm) = &self.extv6_comm { + extv6_comm.encode(buf); + } + + // LARGE_COMMUNITY attribute. + if let Some(large_comm) = &self.large_comm { + large_comm.encode(buf); + } + } + + pub(crate) fn decode( + buf: &mut Bytes, + cxt: &DecodeCxt, + nexthop: &mut Option, + nlri_present: bool, + mp_unreach: &mut Option, + mp_reach: &mut Option, + ) -> Result, UpdateMessageError> { + let mut origin = None; + let mut as_path = None; + let mut as4_path = None; + let mut med = None; + let mut local_pref = None; + let mut aggregator = None; + let mut as4_aggregator = None; + let mut atomic_aggregate = false; + let mut originator_id = None; + let mut cluster_list = None; + let mut comm = None; + let mut ext_comm = None; + let mut extv6_comm = None; + let mut large_comm = None; + let mut unknown = vec![]; + let mut withdraw = false; + + // Check whether the 4-octet AS number capability has been negotiated. + let four_byte_asn_cap = cxt + .capabilities + .iter() + .any(|cap| cap.is_four_octet_as_number()); + + // List of parsed attributes. + let mut attr_list = HashSet::new(); + + // Parse attributes. + while buf.remaining() > 0 { + if buf.remaining() < 2 { + withdraw = true; + break; + } + + // Parse attribute flags. + let attr_flags = buf.get_u8(); + let mut attr_flags = AttrFlags::from_bits_truncate(attr_flags); + + // Parse attribute type. + let attr_type_raw = buf.get_u8(); + let attr_type = AttrType::from_u8(attr_type_raw); + + // Parse attribute length. + let attr_len = if attr_flags.contains(AttrFlags::EXTENDED) { + if buf.remaining() < 2 { + withdraw = true; + break; + } + buf.get_u16() as usize + } else { + if buf.remaining() < 1 { + withdraw = true; + break; + } + buf.get_u8() as usize + }; + if attr_len > buf.remaining() { + withdraw = true; + break; + } + let mut buf = buf.copy_to_bytes(attr_len); + + // RFC 7606 - Section 3.c: + // "If the value of either the Optional or Transitive bits in the + // Attribute Flags is in conflict with their specified values, then + // the attribute MUST be treated as malformed and the + // "treat-as-withdraw" approach used". + if let Some(attr_type) = attr_type + && (attr_flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE)) + != attribute_flags(attr_type) + { + withdraw = true; + continue; + } + + // RFC 7606 - Section 3.g: + // "If the MP_REACH_NLRI attribute or the MP_UNREACH_NLRI attribute + // appears more than once in the UPDATE message, then a NOTIFICATION + // message MUST be sent with the Error Subcode "Malformed Attribute + // List". If any other attribute (whether recognized or + // unrecognized) appears more than once in an UPDATE message, then + // all the occurrences of the attribute other than the first one + // SHALL be discarded and the UPDATE message will continue to be + // processed". + if !attr_list.insert(attr_type_raw) { + if matches!( + attr_type, + Some(AttrType::MpReachNlri | AttrType::MpUnreachNlri) + ) { + return Err(UpdateMessageError::MalformedAttributeList); + } else { + continue; + } + } + + // Parse attribute value. + match attr_type { + // Known attribute. + Some(attr_type) => { + if let Err(error) = match attr_type { + AttrType::Origin => { + origin::decode(&mut buf, &mut origin) + } + AttrType::AsPath => AsPath::decode( + &mut buf, + cxt, + attr_type, + four_byte_asn_cap, + &mut as_path, + ), + AttrType::Nexthop => nexthop::decode(&mut buf, nexthop), + AttrType::Med => med::decode(&mut buf, &mut med), + AttrType::LocalPref => { + local_pref::decode(&mut buf, cxt, &mut local_pref) + } + AttrType::AtomicAggregate => atomic_aggregate::decode( + &mut buf, + &mut atomic_aggregate, + ), + AttrType::Aggregator => Aggregator::decode( + &mut buf, + attr_type, + four_byte_asn_cap, + &mut aggregator, + ), + AttrType::Communities => { + Comms::decode(&mut buf, &mut comm) + } + AttrType::OriginatorId => originator_id::decode( + &mut buf, + cxt, + &mut originator_id, + ), + AttrType::ClusterList => ClusterList::decode( + &mut buf, + cxt, + &mut cluster_list, + ), + AttrType::MpReachNlri => { + MpReachNlri::decode(&mut buf, mp_reach) + } + AttrType::MpUnreachNlri => { + MpUnreachNlri::decode(&mut buf, mp_unreach) + } + AttrType::ExtCommunities => { + ExtComms::decode(&mut buf, &mut ext_comm) + } + AttrType::As4Path => AsPath::decode( + &mut buf, + cxt, + attr_type, + four_byte_asn_cap, + &mut as4_path, + ), + AttrType::As4Aggregator => Aggregator::decode( + &mut buf, + attr_type, + four_byte_asn_cap, + &mut as4_aggregator, + ), + AttrType::Extv6Community => { + Extv6Comms::decode(&mut buf, &mut extv6_comm) + } + AttrType::LargeCommunity => { + LargeComms::decode(&mut buf, &mut large_comm) + } + } { + // Log malformed attribute. + Debug::NbrAttrError(attr_type, error).log(); + + // Process malformed attribute. + match error { + AttrError::Discard => continue, + AttrError::Withdraw => withdraw = true, + AttrError::Reset => { + return Err( + UpdateMessageError::OptionalAttributeError, + ) + } + } + } + } + // Unknown attribute. + None => { + // RFC 4271 - Section 6.3: + // "If any of the well-known mandatory attributes are not + // recognized, then the Error Subcode MUST be set to + // Unrecognized Well-known Attribute. The Data field MUST + // contain the unrecognized attribute (type, length, and + // value)". + if !attr_flags.contains(AttrFlags::OPTIONAL) { + return Err( + UpdateMessageError::UnrecognizedWellKnownAttribute, + ); + } + + // RFC 4271 - Section 9: + // "If an optional non-transitive attribute is unrecognized, + // it is quietly ignored". + if !attr_flags.contains(AttrFlags::TRANSITIVE) { + continue; + } + + // RFC 4271 - Section 9: + // "If an optional transitive attribute is unrecognized, the + // Partial bit in the attribute flags octet is set to 1, and + // the attribute is retained for propagation to other BGP + // speakers". + attr_flags.insert(AttrFlags::PARTIAL); + let attr_value = buf.copy_to_bytes(attr_len); + unknown.push(UnknownAttr::new( + attr_type_raw, + attr_flags, + attr_len as u16, + attr_value, + )); + } + } + } + + // Check for missing well-known attributes. + // + // RFC 7606 - Section 3.d: + // "If any of the well-known mandatory attributes are not present in + // an UPDATE message, then "treat-as-withdraw" MUST be used". + let mut attrs = None; + if !withdraw + && let Some(origin) = origin + && let Some(as_path) = as_path + && (local_pref.is_some() || cxt.peer_type == PeerType::External) + && (nexthop.is_some() || !nlri_present) + { + attrs = Some(Attrs { + base: BaseAttrs { + origin, + as_path, + as4_path, + nexthop: None, + ll_nexthop: None, + med, + local_pref, + aggregator, + as4_aggregator, + atomic_aggregate, + originator_id, + cluster_list, + }, + comm, + ext_comm, + extv6_comm, + large_comm, + unknown, + }); + } + Ok(attrs) + } + + pub(crate) fn length(&self) -> u16 { + let mut length = 0; + + length += origin::length(); + length += self.base.as_path.length(); + if self.base.med.is_some() { + length += med::length(); + } + if self.base.local_pref.is_some() { + length += local_pref::length(); + } + if self.base.atomic_aggregate { + length += atomic_aggregate::length(); + } + if let Some(aggregator) = &self.base.aggregator { + length += aggregator.length(); + } + if let Some(comm) = &self.comm { + length += comm.length(); + } + if self.base.originator_id.is_some() { + length += originator_id::length(); + } + if let Some(cluster_list) = &self.base.cluster_list { + length += cluster_list.length(); + } + if let Some(ext_comm) = &self.ext_comm { + length += ext_comm.length(); + } + if let Some(as4_path) = &self.base.as4_path { + length += as4_path.length(); + } + if let Some(as4_aggregator) = &self.base.as4_aggregator { + length += as4_aggregator.length(); + } + if let Some(extv6_comm) = &self.extv6_comm { + length += extv6_comm.length(); + } + if let Some(large_comm) = &self.large_comm { + length += large_comm.length(); + } + + length + } +} + +// ===== ORIGIN attribute ===== + +mod origin { + use super::*; + const LEN: u8 = 1; + + pub(super) fn encode(origin: Origin, buf: &mut BytesMut) { + buf.put_u8(AttrFlags::TRANSITIVE.bits()); + buf.put_u8(AttrType::Origin as u8); + buf.put_u8(LEN); + buf.put_u8(origin as u8); + } + + pub(super) fn decode( + buf: &mut Bytes, + origin: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() != LEN as usize { + return Err(AttrError::Withdraw); + } + + let value = buf.get_u8(); + match Origin::from_u8(value) { + Some(value) => { + *origin = Some(value); + Ok(()) + } + None => Err(AttrError::Withdraw), + } + } + + pub(super) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== impl AsPath ===== + +impl AsPath { + fn encode( + &self, + buf: &mut BytesMut, + mut attr_flags: AttrFlags, + attr_type: AttrType, + four_byte_asns: bool, + ) { + attr_flags.insert(AttrFlags::EXTENDED); + buf.put_u8(attr_flags.bits()); + buf.put_u8(attr_type as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u16(0); + + // Encode attribute data. + for segment in &self.segments { + segment.encode(buf, four_byte_asns); + } + + // Rewrite attribute length. + let attr_len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&attr_len.to_be_bytes()); + } + + fn decode( + buf: &mut Bytes, + cxt: &DecodeCxt, + attr_type: AttrType, + four_byte_asn_cap: bool, + as_path: &mut Option, + ) -> Result<(), AttrError> { + if attr_type == AttrType::As4Path && four_byte_asn_cap { + return Err(AttrError::Discard); + } + + let four_byte_asns = + four_byte_asn_cap || attr_type == AttrType::As4Path; + + // Decode AS Path segments. + let mut segments = VecDeque::new(); + while buf.remaining() > 0 { + let segment = + AsPathSegment::decode(buf, attr_type, four_byte_asns)?; + segments.push_back(segment); + } + let value = AsPath { segments }; + + // First AS check for eBGP peers. + if attr_type == AttrType::AsPath + && cxt.peer_type == PeerType::External + && value + .segments + .iter() + .find(|segment| segment.seg_type == AsPathSegmentType::Sequence) + .and_then(|segment| segment.members.front().copied()) + != Some(cxt.peer_as) + { + return Err(AttrError::Withdraw); + } + + *as_path = Some(value); + Ok(()) + } + + pub(super) fn length(&self) -> u16 { + ATTR_MIN_LEN_EXT + + self + .segments + .iter() + .map(|segment| segment.length()) + .sum::() + } + + pub(crate) fn path_length(&self) -> u32 { + self.segments + .iter() + .map(|segment| match segment.seg_type { + AsPathSegmentType::Set => 1, + AsPathSegmentType::Sequence => segment.members.iter().count(), + // RFC 5065 - Section 5.3: + // "When comparing routes using AS_PATH length, CONFED_SEQUENCE + // and CONFED_SETs SHOULD NOT be counted". + AsPathSegmentType::ConfedSequence + | AsPathSegmentType::ConfedSet => 0, + }) + .sum::() as u32 + } + + pub(crate) fn first(&self) -> Option { + self.segments + .front() + .filter(|segment| segment.seg_type == AsPathSegmentType::Sequence) + .and_then(|segment| segment.members.front().copied()) + } + + pub(crate) fn iter(&self) -> impl Iterator + '_ { + self.segments + .iter() + .flat_map(|segment| segment.members.iter().copied()) + } + + pub(crate) fn prepend(&mut self, asn: u32) { + if let Some(segment) = self.segments.front_mut() + && segment.seg_type == AsPathSegmentType::Sequence + && segment.members.len() < 255 + { + segment.members.push_front(asn); + } else { + self.segments.push_front(AsPathSegment { + seg_type: AsPathSegmentType::Sequence, + members: [asn].into(), + }); + } + } + + pub(crate) fn contains(&self, asn: u32) -> bool { + self.segments.iter().any(|segment| segment.contains(asn)) + } +} + +impl AsPathSegment { + const MIN_LEN: u16 = 2; + + fn encode(&self, buf: &mut BytesMut, four_byte_asns: bool) { + buf.put_u8(self.seg_type as u8); + buf.put_u8(self.members.len() as u8); + for member in &self.members { + encode_asn(buf, *member, four_byte_asns); + } + } + + fn decode( + buf: &mut Bytes, + attr_type: AttrType, + four_byte_asns: bool, + ) -> Result { + // Decode segment type. + let seg_type = buf.get_u8(); + let Some(seg_type) = AsPathSegmentType::from_u8(seg_type) else { + if attr_type == AttrType::AsPath { + return Err(AttrError::Withdraw); + } else { + return Err(AttrError::Discard); + } + }; + + // Decode segment length. + let seg_len = buf.get_u8(); + if seg_len == 0 { + if attr_type == AttrType::AsPath { + return Err(AttrError::Withdraw); + } else { + return Err(AttrError::Discard); + } + } + + // Decode segment members. + let members = (0..seg_len as usize) + .map(|_| decode_asn(buf, four_byte_asns)) + .collect(); + let segment = AsPathSegment { seg_type, members }; + + // RFC 7607's AS 0 processing. + if segment.contains(0) { + if attr_type == AttrType::AsPath { + return Err(AttrError::Withdraw); + } else { + return Err(AttrError::Discard); + } + } + + Ok(segment) + } + + pub(super) fn length(&self) -> u16 { + // Assume four-byte ASNs for practical purposes. + Self::MIN_LEN + self.members.len() as u16 * 4 + } + + fn contains(&self, asn: u32) -> bool { + self.members.iter().any(|member| asn == *member) + } +} + +// ===== NEXT_HOP attribute ===== + +pub(crate) mod nexthop { + use super::*; + const LEN: u8 = 4; + + pub(super) fn encode(addr: Ipv4Addr, buf: &mut BytesMut) { + buf.put_u8(AttrFlags::TRANSITIVE.bits()); + buf.put_u8(AttrType::Nexthop as u8); + buf.put_u8(LEN); + buf.put_ipv4(&addr); + } + + pub(super) fn decode( + buf: &mut Bytes, + nexthop: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() != LEN as usize { + return Err(AttrError::Withdraw); + } + + let value = buf.get_ipv4(); + *nexthop = Some(value); + Ok(()) + } + + pub(crate) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== MULTI_EXIT_DISC attribute ===== + +mod med { + use super::*; + const LEN: u8 = 4; + + pub(super) fn encode(metric: u32, buf: &mut BytesMut) { + buf.put_u8(AttrFlags::OPTIONAL.bits()); + buf.put_u8(AttrType::Med as u8); + buf.put_u8(LEN); + buf.put_u32(metric); + } + + pub(super) fn decode( + buf: &mut Bytes, + med: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() != LEN as usize { + return Err(AttrError::Withdraw); + } + + let value = buf.get_u32(); + *med = Some(value); + Ok(()) + } + + pub(super) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== LOCAL_PREF attribute ===== + +mod local_pref { + use super::*; + const LEN: u8 = 4; + + pub(super) fn encode(local_pref: u32, buf: &mut BytesMut) { + buf.put_u8(AttrFlags::TRANSITIVE.bits()); + buf.put_u8(AttrType::LocalPref as u8); + buf.put_u8(LEN); + buf.put_u32(local_pref); + } + + pub(super) fn decode( + buf: &mut Bytes, + cxt: &DecodeCxt, + local_pref: &mut Option, + ) -> Result<(), AttrError> { + if cxt.peer_type == PeerType::External { + return Err(AttrError::Discard); + } + + if buf.remaining() != LEN as usize { + return Err(AttrError::Withdraw); + } + + let value = buf.get_u32(); + *local_pref = Some(value); + Ok(()) + } + + pub(super) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== ATOMIC_AGGREGATE attribute ===== + +mod atomic_aggregate { + use super::*; + const LEN: u8 = 0; + + pub(super) fn encode(buf: &mut BytesMut) { + buf.put_u8(AttrFlags::TRANSITIVE.bits()); + buf.put_u8(AttrType::AtomicAggregate as u8); + buf.put_u8(LEN); + } + + pub(super) fn decode( + buf: &mut Bytes, + atomic_aggregate: &mut bool, + ) -> Result<(), AttrError> { + if buf.remaining() != LEN as usize { + return Err(AttrError::Discard); + } + + *atomic_aggregate = true; + Ok(()) + } + + pub(super) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== impl Aggregator ===== + +impl Aggregator { + fn encode( + &self, + buf: &mut BytesMut, + attr_flags: AttrFlags, + attr_type: AttrType, + four_byte_asns: bool, + ) { + buf.put_u8(attr_flags.bits()); + buf.put_u8(attr_type as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u8(0); + + // Encode attribute data. + encode_asn(buf, self.asn, four_byte_asns); + buf.put_ipv4(&self.identifier); + + // Rewrite attribute length. + let attr_len = buf.len() - start_pos - 1; + buf[start_pos] = attr_len as u8; + } + + fn decode( + buf: &mut Bytes, + attr_type: AttrType, + four_byte_asn_cap: bool, + aggregator: &mut Option, + ) -> Result<(), AttrError> { + if attr_type == AttrType::As4Aggregator && four_byte_asn_cap { + return Err(AttrError::Discard); + } + + let four_byte_asns = + four_byte_asn_cap || attr_type == AttrType::As4Aggregator; + let len = if four_byte_asns { 8 } else { 6 }; + if buf.remaining() != len { + return Err(AttrError::Discard); + } + + let asn = decode_asn(buf, four_byte_asns); + let identifier = buf.get_ipv4(); + + // RFC 7607's AS 0 processing. + if asn == 0 { + return Err(AttrError::Discard); + } + + *aggregator = Some(Aggregator { asn, identifier }); + Ok(()) + } + + pub(super) fn length(&self) -> u16 { + // Assume four-byte ASN for practical purposes. + ATTR_MIN_LEN + 4 + Ipv4Addr::LENGTH as u16 + } +} + +// ===== ORIGINATOR_ID attribute ===== + +mod originator_id { + use super::*; + const LEN: u8 = 4; + + pub(super) fn encode(originator_id: Ipv4Addr, buf: &mut BytesMut) { + buf.put_u8(AttrFlags::OPTIONAL.bits()); + buf.put_u8(AttrType::OriginatorId as u8); + buf.put_u8(LEN); + buf.put_ipv4(&originator_id); + } + + pub(super) fn decode( + buf: &mut Bytes, + cxt: &DecodeCxt, + originator_id: &mut Option, + ) -> Result<(), AttrError> { + if cxt.peer_type == PeerType::External { + return Err(AttrError::Discard); + } + + if buf.remaining() != LEN as usize { + return Err(AttrError::Withdraw); + } + + let value = buf.get_ipv4(); + *originator_id = Some(value); + Ok(()) + } + + pub(super) fn length() -> u16 { + ATTR_MIN_LEN + LEN as u16 + } +} + +// ===== impl ClusterList ===== + +impl ClusterList { + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8((AttrFlags::OPTIONAL | AttrFlags::EXTENDED).bits()); + buf.put_u8(AttrType::ClusterList as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u16(0); + + // Encode attribute data. + for cluster_id in &self.0 { + buf.put_ipv4(cluster_id); + } + + // Rewrite attribute length. + let attr_len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&attr_len.to_be_bytes()); + } + + fn decode( + buf: &mut Bytes, + cxt: &DecodeCxt, + cluster_list: &mut Option, + ) -> Result<(), AttrError> { + if cxt.peer_type == PeerType::External { + return Err(AttrError::Discard); + } + + if buf.remaining() == 0 || buf.remaining() % 4 != 0 { + return Err(AttrError::Withdraw); + } + + let mut list = BTreeSet::new(); + while buf.remaining() > 0 { + let cluster_id = buf.get_ipv4(); + list.insert(cluster_id); + } + + *cluster_list = Some(ClusterList(list)); + Ok(()) + } + + fn length(&self) -> u16 { + ATTR_MIN_LEN_EXT + (self.0.len() * Ipv4Addr::LENGTH) as u16 + } +} + +// ===== impl MpReachNlri ===== + +impl MpReachNlri { + pub const MIN_LEN: u16 = 5; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8((AttrFlags::OPTIONAL | AttrFlags::EXTENDED).bits()); + buf.put_u8(AttrType::MpReachNlri as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u16(0); + + // Encode attribute data. + match self { + MpReachNlri::Ipv4Unicast { prefixes, nexthop } => { + buf.put_u16(Afi::Ipv4 as u16); + buf.put_u8(Safi::Unicast as u8); + buf.put_u8(Ipv4Addr::LENGTH as u8); + buf.put_ipv4(nexthop); + buf.put_u8(0); + for prefix in prefixes { + encode_ipv4_prefix(buf, prefix); + } + } + MpReachNlri::Ipv6Unicast { + prefixes, + nexthop, + ll_nexthop, + } => { + buf.put_u16(Afi::Ipv6 as u16); + buf.put_u8(Safi::Unicast as u8); + if let Some(ll_nexthop) = ll_nexthop { + buf.put_u8((Ipv6Addr::LENGTH * 2) as u8); + buf.put_ipv6(nexthop); + buf.put_ipv6(ll_nexthop); + } else { + buf.put_u8(Ipv6Addr::LENGTH as u8); + buf.put_ipv6(nexthop); + } + buf.put_u8(0); + for prefix in prefixes { + encode_ipv6_prefix(buf, prefix); + } + } + } + + // Rewrite attribute length. + let attr_len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&attr_len.to_be_bytes()); + } + + fn decode( + buf: &mut Bytes, + mp_reach: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() < Self::MIN_LEN as usize { + return Err(AttrError::Reset); + } + + // Parse AFI. + let afi = buf.get_u16(); + let Some(afi) = Afi::from_u16(afi) else { + // Ignore unknown AFI. + return Err(AttrError::Discard); + }; + + // Parse SAFI. + let safi = buf.get_u8(); + if Safi::from_u8(safi) != Some(Safi::Unicast) { + // Ignore unsupported SAFI. + return Err(AttrError::Discard); + }; + + match afi { + Afi::Ipv4 => { + let mut prefixes = Vec::new(); + + // Parse nexthop. + let nexthop_len = buf.get_u8(); + if nexthop_len as usize != Ipv4Addr::LENGTH + || nexthop_len as usize > buf.remaining() + { + return Err(AttrError::Reset); + } + let nexthop = buf.get_ipv4(); + + // Parse prefixes. + let _reserved = buf.get_u8(); + while buf.remaining() > 0 { + if let Some(prefix) = + decode_ipv4_prefix(buf).map_err(|_| AttrError::Reset)? + { + prefixes.push(prefix); + } + } + + *mp_reach = + Some(MpReachNlri::Ipv4Unicast { prefixes, nexthop }); + } + Afi::Ipv6 => { + let mut prefixes = Vec::new(); + let mut ll_nexthop = None; + + // Parse nexthops(s). + let nexthop_len = buf.get_u8() as usize; + if (nexthop_len != Ipv6Addr::LENGTH + && nexthop_len != Ipv6Addr::LENGTH * 2) + || nexthop_len > buf.remaining() + { + return Err(AttrError::Reset); + } + let nexthop = buf.get_ipv6(); + if nexthop_len == Ipv6Addr::LENGTH * 2 { + ll_nexthop = Some(buf.get_ipv6()); + } + + // Parse prefixes. + let _reserved = buf.get_u8(); + while buf.remaining() > 0 { + if let Some(prefix) = + decode_ipv6_prefix(buf).map_err(|_| AttrError::Reset)? + { + prefixes.push(prefix); + } + } + + *mp_reach = Some(MpReachNlri::Ipv6Unicast { + prefixes, + nexthop, + ll_nexthop, + }); + } + } + + Ok(()) + } +} + +// ===== impl MpUnreachNlri ===== + +impl MpUnreachNlri { + pub const MIN_LEN: u16 = 3; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8((AttrFlags::OPTIONAL | AttrFlags::EXTENDED).bits()); + buf.put_u8(AttrType::MpUnreachNlri as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u16(0); + + // Encode attribute data. + match self { + MpUnreachNlri::Ipv4Unicast { prefixes } => { + buf.put_u16(Afi::Ipv4 as u16); + buf.put_u8(Safi::Unicast as u8); + for prefix in prefixes { + encode_ipv4_prefix(buf, prefix); + } + } + MpUnreachNlri::Ipv6Unicast { prefixes } => { + buf.put_u16(Afi::Ipv6 as u16); + buf.put_u8(Safi::Unicast as u8); + for prefix in prefixes { + encode_ipv6_prefix(buf, prefix); + } + } + } + + // Rewrite attribute length. + let attr_len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&attr_len.to_be_bytes()); + } + + fn decode( + buf: &mut Bytes, + mp_unreach: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() < Self::MIN_LEN as usize { + return Err(AttrError::Reset); + } + + // Parse AFI. + let afi = buf.get_u16(); + let Some(afi) = Afi::from_u16(afi) else { + // Ignore unknown AFI. + return Err(AttrError::Discard); + }; + + // Parse SAFI. + let safi = buf.get_u8(); + if Safi::from_u8(safi) != Some(Safi::Unicast) { + // Ignore unsupported SAFI. + return Err(AttrError::Discard); + }; + + // Parse prefixes. + match afi { + Afi::Ipv4 => { + let mut prefixes = Vec::new(); + + while buf.remaining() > 0 { + if let Some(prefix) = + decode_ipv4_prefix(buf).map_err(|_| AttrError::Reset)? + { + prefixes.push(prefix); + } + } + + *mp_unreach = Some(MpUnreachNlri::Ipv4Unicast { prefixes }); + } + Afi::Ipv6 => { + let mut prefixes = Vec::new(); + + while buf.remaining() > 0 { + if let Some(prefix) = + decode_ipv6_prefix(buf).map_err(|_| AttrError::Reset)? + { + prefixes.push(prefix); + } + } + + *mp_unreach = Some(MpUnreachNlri::Ipv6Unicast { prefixes }); + } + } + + Ok(()) + } +} + +// ===== impl Comm ===== + +impl CommType for Comm { + const TYPE: AttrType = AttrType::Communities; + const LENGTH: usize = 4; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u32(self.0); + } + + fn decode(buf: &mut Bytes) -> Self { + let value = buf.get_u32(); + Self(value) + } +} + +// ===== impl ExtComm ===== + +impl CommType for ExtComm { + const TYPE: AttrType = AttrType::ExtCommunities; + const LENGTH: usize = 8; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_slice(&self.0); + } + + fn decode(buf: &mut Bytes) -> Self { + let mut value = [0; 8]; + buf.copy_to_slice(&mut value); + Self(value) + } +} + +// ===== impl Extv6Comm ===== + +impl CommType for Extv6Comm { + const TYPE: AttrType = AttrType::Extv6Community; + const LENGTH: usize = 20; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_ipv6(&self.0); + buf.put_u32(self.1); + } + + fn decode(buf: &mut Bytes) -> Self { + let addr = buf.get_ipv6(); + let local = buf.get_u32(); + Self(addr, local) + } +} + +// ===== impl LargeComm ===== + +impl CommType for LargeComm { + const TYPE: AttrType = AttrType::LargeCommunity; + const LENGTH: usize = 12; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_slice(&self.0); + } + + fn decode(buf: &mut Bytes) -> Self { + let mut value = [0; 12]; + buf.copy_to_slice(&mut value); + Self(value) + } +} + +// ===== impl CommList ===== + +impl CommList { + fn encode(&self, buf: &mut BytesMut) { + let attr_flags = + AttrFlags::TRANSITIVE | AttrFlags::OPTIONAL | AttrFlags::EXTENDED; + buf.put_u8(attr_flags.bits()); + buf.put_u8(T::TYPE as u8); + + // The length field will be initialized later. + let start_pos = buf.len(); + buf.put_u16(0); + + // Encode attribute data. + for value in &self.0 { + value.encode(buf); + } + + // Rewrite attribute length. + let attr_len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&attr_len.to_be_bytes()); + } + + fn decode( + buf: &mut Bytes, + comm: &mut Option, + ) -> Result<(), AttrError> { + if buf.remaining() == 0 || buf.remaining() % T::LENGTH != 0 { + return Err(AttrError::Withdraw); + } + + let mut list = BTreeSet::new(); + while buf.remaining() >= T::LENGTH { + let value = T::decode(buf); + list.insert(value); + } + + *comm = Some(CommList(list)); + Ok(()) + } + + fn length(&self) -> u16 { + ATTR_MIN_LEN_EXT + (self.0.len() * T::LENGTH) as u16 + } +} + +// ===== helper functions ===== + +fn attribute_flags(attr_type: AttrType) -> AttrFlags { + match attr_type { + // Well-known. + AttrType::Origin + | AttrType::AsPath + | AttrType::Nexthop + | AttrType::LocalPref + | AttrType::AtomicAggregate => AttrFlags::TRANSITIVE, + + // Optional non-transitive. + AttrType::Med + | AttrType::OriginatorId + | AttrType::ClusterList + | AttrType::MpReachNlri + | AttrType::MpUnreachNlri => AttrFlags::OPTIONAL, + + // Optional transitive. + AttrType::Aggregator + | AttrType::Communities + | AttrType::ExtCommunities + | AttrType::As4Path + | AttrType::As4Aggregator + | AttrType::Extv6Community + | AttrType::LargeCommunity => { + AttrFlags::TRANSITIVE | AttrFlags::OPTIONAL + } + } +} + +fn encode_asn(buf: &mut BytesMut, asn: u32, four_byte_asns: bool) { + if four_byte_asns { + buf.put_u32(asn) + } else { + buf.put_u16(asn as u16) + } +} + +fn decode_asn(buf: &mut Bytes, four_byte_asns: bool) -> u32 { + if four_byte_asns { + buf.get_u32() + } else { + buf.get_u16() as u32 + } +} diff --git a/holo-bgp/src/packet/consts.rs b/holo-bgp/src/packet/consts.rs new file mode 100644 index 00000000..a85dd9bf --- /dev/null +++ b/holo-bgp/src/packet/consts.rs @@ -0,0 +1,339 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use bitflags::bitflags; +use holo_utils::ip::AddressFamily; +use num_derive::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; + +pub const BGP_VERSION: u8 = 4; +pub const AS_TRANS: u16 = 23456; + +// BGP Message Types. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-1 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum MessageType { + Open = 1, + Update = 2, + Notification = 3, + Keepalive = 4, + // RFC 2918 + RouteRefresh = 5, +} + +// BGP OPEN Optional Parameter Types. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-11 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum OpenParamType { + // RFC5492 + Capabilities = 2, +} + +// Capability Codes. +// +// IANA registry: +// https://www.iana.org/assignments/capability-codes/capability-codes.xhtml#capability-codes-2 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum CapabilityCode { + // RFC 2858 + MultiProtocol = 1, + // RFC 2918 + RouteRefresh = 2, + // RFC 5291 + OutboundRouteFiltering = 3, + // RFC 8950 + ExtendedNextHop = 5, + // RFC 8654 + ExtendedMessage = 6, + // RFC 8205 + BgpSec = 7, + // RFC 8277 + MultipleLabels = 8, + // RFC 9234 + BgpRole = 9, + // RFC 4724 + GracefulRestart = 64, + // RFC 6793 + FourOctetAsNumber = 65, + // RFC7911 + AddPath = 69, + // RFC7313 + EnhancedRouteRefresh = 70, +} + +// Send/Receive value for a per-AFI/SAFI instance of the ADD-PATH Capability. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum AddPathMode { + Receive = 1, + Send = 2, + ReceiveSend = 3, +} + +// BGP Error (Notification) Codes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-3 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum ErrorCode { + MessageHeaderError = 1, + OpenMessageError = 2, + UpdateMessageError = 3, + HoldTimerExpired = 4, + FiniteStateMachineError = 5, + Cease = 6, + // RFC 7313 + RouteRefreshMessageError = 7, +} + +// Message Header Error subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-5 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum MessageHeaderErrorSubcode { + Unspecific = 0, + ConnectionNotSynchronized = 1, + BadMessageLength = 2, + BadMessageType = 3, +} + +// OPEN Message Error subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-6 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum OpenMessageErrorSubcode { + Unspecific = 0, + UnsupportedVersionNumber = 1, + BadPeerAs = 2, + BadBgpIdentifier = 3, + UnsupportedOptParam = 4, + UnacceptableHoldTime = 6, + // RFC 5492 + UnsupportedCapability = 7, + // RFC 9234 + RoleMismatch = 11, +} + +// UPDATE Message Error subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-7 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum UpdateMessageErrorSubcode { + Unspecific = 0, + MalformedAttributeList = 1, + UnrecognizedWellKnownAttribute = 2, + MissingWellKnownAttribute = 3, + AttributeFlagsError = 4, + AttributeLengthError = 5, + InvalidOriginAttribute = 6, + InvalidNexthopAttribute = 8, + OptionalAttributeError = 9, + InvalidNetworkField = 10, + MalformedAsPath = 11, +} + +// BGP Finite State Machine Error Subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-finite-state-machine-error-subcodes +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum FsmErrorSubcode { + UnexpectedMessageInOpenSent = 1, + UnexpectedMessageInOpenConfirm = 2, + UnexpectedMessageInEstablished = 3, +} + +// BGP Cease NOTIFICATION message subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-8 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum CeaseSubcode { + MaximumNumberofPrefixesReached = 1, + AdministrativeShutdown = 2, + PeerDeConfigured = 3, + AdministrativeReset = 4, + ConnectionRejected = 5, + OtherConfigurationChange = 6, + ConnectionCollisionResolution = 7, + OutOfResources = 8, + // RFC 8538 + HardReset = 9, + // RFC 9384 + BfdDown = 10, +} + +// BGP ROUTE-REFRESH Message Error subcodes. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#route-refresh-error-subcodes +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum RouteRefreshErrorSubcode { + InvalidMessageLength = 1, +} + +// Address Family identifiers (AFI). +pub type Afi = AddressFamily; + +// Subsequent Address Family Identifiers (SAFI). +// +// IANA registry: +// https://www.iana.org/assignments/safi-namespace/safi-namespace.xhtml#safi-namespace-2 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum Safi { + Unicast = 1, + Multicast = 2, + LabeledUnicast = 4, + MulticastVpn = 5, + Pseudowire = 6, + TunnelEncap = 7, + McastVpls = 8, + Tunnel = 64, + Vpls = 65, + Mdt = 66, + V4OverV6 = 67, + V6OverV4 = 68, + L1VpnAutoDiscovery = 69, + Evpn = 70, + BgpLs = 71, + BgpLsVpn = 72, + SrTe = 73, + SdWanCapabilities = 74, + LabeledVpn = 128, + MulticastMplsVpn = 129, + RouteTarget = 132, + Ipv4FlowSpec = 133, + Vpnv4FlowSpec = 134, + VpnAutoDiscovery = 140, +} + +// BGP Path Attribute Flags. +bitflags! { + #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] + #[derive(Deserialize, Serialize)] + #[serde(transparent)] + pub struct AttrFlags: u8 { + const OPTIONAL = 0x80; + const TRANSITIVE = 0x40; + const PARTIAL = 0x20; + const EXTENDED = 0x10; + } +} + +// BGP Path Attribute Types. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum AttrType { + Origin = 1, + AsPath = 2, + Nexthop = 3, + Med = 4, + LocalPref = 5, + AtomicAggregate = 6, + Aggregator = 7, + // RFC 1997 + Communities = 8, + // RFC 4456 + OriginatorId = 9, + ClusterList = 10, + // RFC 4760 + MpReachNlri = 14, + MpUnreachNlri = 15, + // RFC 4360 + ExtCommunities = 16, + // RFC 6793 + As4Path = 17, + As4Aggregator = 18, + // RFC 6514 + //PmsiTunnel = 22, + // RFC 9012 + //TunnelEncap = 23, + // RFC 5543 + //TrafficEngineering = 24, + // RFC 5701 + Extv6Community = 25, + // RFC 7311 + //Aigp = 26, + // RFC 6514 + //PeDistinguisherLabels = 27, + // RFC-ietf-idr-rfc7752bis-16 + //BgpLs = 29, + // RFC 8092 + LargeCommunity = 32, + // RFC 8205 + //BgpSecPath = 33, + // RFC 9234 + //Otc = 35, + // RFC 9015 + //Sfp = 37, + // RFC 9026 + //BfdDiscriminator = 38, + // RFC 8669 + //BgpPrefixSid = 40, + // RFC6 368 + //AttrSet = 128, +} + +// BGP Origin. +pub type Origin = holo_utils::bgp::Origin; + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum AsPathSegmentType { + Set = 1, + Sequence = 2, + ConfedSequence = 3, + ConfedSet = 4, +} + +// Re-exports for convenience. +pub type WellKnownCommunities = holo_utils::bgp::WellKnownCommunities; + +// BGP AIGP Attribute Types. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-aigp +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum AigpType { + Aigp = 1, +} diff --git a/holo-bgp/src/packet/error.rs b/holo-bgp/src/packet/error.rs new file mode 100644 index 00000000..4c4c170a --- /dev/null +++ b/holo-bgp/src/packet/error.rs @@ -0,0 +1,166 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use serde::{Deserialize, Serialize}; + +// Type aliases. +pub type DecodeResult = Result; + +// BGP message decoding errors. +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub enum DecodeError { + MessageHeader(MessageHeaderError), + OpenMessage(OpenMessageError), + UpdateMessage(UpdateMessageError), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum MessageHeaderError { + ConnectionNotSynchronized, + BadMessageLength(u16), + BadMessageType(u8), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum OpenMessageError { + UnsupportedVersion(u8), + BadPeerAs, + BadBgpIdentifier, + UnsupportedOptParam, + UnacceptableHoldTime, + UnsupportedCapability, + MalformedOptParam, +} + +// UPDATE message errors. +// +// NOTE: many of the errors originally specified by RFC 4271 were made obsolete +// by RFC 7606. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum UpdateMessageError { + MalformedAttributeList, + UnrecognizedWellKnownAttribute, + OptionalAttributeError, + InvalidNetworkField, +} + +// Attribute errors. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum AttrError { + Discard, + Withdraw, + Reset, +} + +// ===== impl DecodeError ===== + +impl std::fmt::Display for DecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DecodeError::MessageHeader(error) => error.fmt(f), + DecodeError::OpenMessage(error) => error.fmt(f), + DecodeError::UpdateMessage(error) => error.fmt(f), + } + } +} + +impl std::error::Error for DecodeError {} + +impl From for DecodeError { + fn from(error: MessageHeaderError) -> DecodeError { + DecodeError::MessageHeader(error) + } +} + +impl From for DecodeError { + fn from(error: OpenMessageError) -> DecodeError { + DecodeError::OpenMessage(error) + } +} + +impl From for DecodeError { + fn from(error: UpdateMessageError) -> DecodeError { + DecodeError::UpdateMessage(error) + } +} + +// ===== impl MessageHeaderError ===== + +impl std::fmt::Display for MessageHeaderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MessageHeaderError::ConnectionNotSynchronized => { + write!(f, "Connection not synchronized") + } + MessageHeaderError::BadMessageLength(len) => { + write!(f, "Invalid message length: {}", len) + } + MessageHeaderError::BadMessageType(msg_type) => { + write!(f, "Invalid message type: {}", msg_type) + } + } + } +} + +// ===== impl OpenMessageError ===== + +impl std::fmt::Display for OpenMessageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "OPEN message error: ")?; + + match self { + OpenMessageError::UnsupportedVersion(version) => { + write!(f, "unsupported version number: {}", version) + } + OpenMessageError::BadPeerAs => { + write!(f, "bad peer AS") + } + OpenMessageError::BadBgpIdentifier => { + write!(f, "bad BGP identifier") + } + OpenMessageError::UnsupportedOptParam => { + write!(f, "unsupported optional parameter") + } + OpenMessageError::UnacceptableHoldTime => { + write!(f, "unacceptable hold time") + } + OpenMessageError::UnsupportedCapability => { + write!(f, "unsupported capability") + } + OpenMessageError::MalformedOptParam => { + write!(f, "malformed optional parameter") + } + } + } +} + +// ===== impl UpdateMessageError ===== + +impl std::fmt::Display for UpdateMessageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "UPDATE message error: ")?; + + match self { + UpdateMessageError::MalformedAttributeList => { + write!(f, "malformed attribute list") + } + UpdateMessageError::UnrecognizedWellKnownAttribute => { + write!(f, "unrecognized well-known attribute") + } + UpdateMessageError::OptionalAttributeError => { + write!(f, "optional attribute error") + } + UpdateMessageError::InvalidNetworkField => { + write!(f, "invalid network field") + } + } + } +} diff --git a/holo-bgp/src/packet/message.rs b/holo-bgp/src/packet/message.rs new file mode 100644 index 00000000..b108d98e --- /dev/null +++ b/holo-bgp/src/packet/message.rs @@ -0,0 +1,1047 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::cmp::Ordering; +use std::collections::BTreeSet; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use enum_as_inner::EnumAsInner; +use holo_utils::bytes::{BytesExt, BytesMutExt, TLS_BUF}; +use holo_utils::ip::{ + Ipv4AddrExt, Ipv4NetworkExt, Ipv6AddrExt, Ipv6NetworkExt, +}; +use ipnetwork::{Ipv4Network, Ipv6Network}; +use num_traits::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; + +use crate::neighbor::PeerType; +use crate::packet::attribute::Attrs; +use crate::packet::consts::{ + AddPathMode, Afi, CapabilityCode, ErrorCode, MessageHeaderErrorSubcode, + MessageType, OpenMessageErrorSubcode, OpenParamType, Safi, + UpdateMessageErrorSubcode, BGP_VERSION, +}; +use crate::packet::error::{ + DecodeError, DecodeResult, MessageHeaderError, OpenMessageError, + UpdateMessageError, +}; + +// +// BGP message. +// +// Encoding format (message header): +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + + +// | | +// + + +// | Marker | +// + + +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Length | Type | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum Message { + Open(OpenMsg), + Update(UpdateMsg), + Notification(NotificationMsg), + Keepalive(KeepaliveMsg), + RouteRefresh(RouteRefreshMsg), +} + +// +// OPEN Message. +// +// Encoding format (message body): +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+ +// | Version | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | My Autonomous System | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Hold Time | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BGP Identifier | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Opt Parm Len | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// | Optional Parameters (variable) | +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Encoding format (optional parameter): +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... +// | Parm. Type | Parm. Length | Parameter Value (variable) +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct OpenMsg { + pub version: u8, + pub my_as: u16, + pub holdtime: u16, + pub identifier: Ipv4Addr, + pub capabilities: BTreeSet, +} +// +// Capabilities Optional Parameter. +// +// Encoding format: +// +// +------------------------------+ +// | Capability Code (1 octet) | +// +------------------------------+ +// | Capability Length (1 octet) | +// +------------------------------+ +// | Capability Value (variable) | +// ~ ~ +// +------------------------------+ +// +#[derive(Clone, Debug, EnumAsInner, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum Capability { + MultiProtocol { afi: Afi, safi: Safi }, + FourOctetAsNumber { asn: FourOctetAsNumber }, + AddPath(BTreeSet), + RouteRefresh, + EnhancedRouteRefresh, +} + +#[derive(Clone, Copy, Debug)] +#[derive(Deserialize, Serialize)] +pub struct FourOctetAsNumber(pub u32); + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct AddPathTuple { + pub afi: Afi, + pub safi: Safi, + pub mode: AddPathMode, +} + +// +// UPDATE Message. +// +// Encoding format (message body): +// +// +-----------------------------------------------------+ +// | Withdrawn Routes Length (2 octets) | +// +-----------------------------------------------------+ +// | Withdrawn Routes (variable) | +// +-----------------------------------------------------+ +// | Total Path Attribute Length (2 octets) | +// +-----------------------------------------------------+ +// | Path Attributes (variable) | +// +-----------------------------------------------------+ +// | Network Layer Reachability Information (variable) | +// +-----------------------------------------------------+ +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct UpdateMsg { + pub reach: Option, + pub unreach: Option, + pub mp_reach: Option, + pub mp_unreach: Option, + pub attrs: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct ReachNlri { + pub prefixes: Vec, + pub nexthop: Ipv4Addr, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct UnreachNlri { + pub prefixes: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum MpReachNlri { + Ipv4Unicast { + prefixes: Vec, + nexthop: Ipv4Addr, + }, + Ipv6Unicast { + prefixes: Vec, + nexthop: Ipv6Addr, + ll_nexthop: Option, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum MpUnreachNlri { + Ipv4Unicast { prefixes: Vec }, + Ipv6Unicast { prefixes: Vec }, +} + +// +// NOTIFICATION Message. +// +// Encoding format (message body): +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Error code | Error subcode | Data (variable) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct NotificationMsg { + pub error_code: u8, + pub error_subcode: u8, + pub data: Vec, +} + +// +// KEEPALIVE Message. +// +// A KEEPALIVE message consists of only the message header and has a length of +// 19 octets. +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct KeepaliveMsg {} + +// +// Route-REFRESH Message. +// +// Encoding format (message body): +// +// 0 7 15 23 31 +// +-------+-------+-------+-------+ +// | AFI | Res. | SAFI | +// +-------+-------+-------+-------+ +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct RouteRefreshMsg { + pub afi: u16, + pub safi: u8, +} + +// BGP message decoding context. +pub struct EncodeCxt { + pub capabilities: BTreeSet, +} + +// BGP message decoding context. +pub struct DecodeCxt { + pub peer_type: PeerType, + pub peer_as: u32, + pub capabilities: BTreeSet, +} + +// ===== impl Message ===== + +impl Message { + pub const MIN_LEN: u16 = 19; + pub const MAX_LEN: u16 = 4096; + const MSG_LEN_POS: std::ops::Range = 16..18; + + // Encodes BGP message into a bytes buffer. + pub fn encode(&self, cxt: &EncodeCxt) -> Bytes { + TLS_BUF.with(|buf| { + let mut buf = buf.borrow_mut(); + buf.clear(); + + // Marker field. + buf.put_u128(u128::MAX); + // The length field will be initialized later. + buf.put_u16(0); + + // Message type and body. + match self { + Message::Open(msg) => msg.encode(&mut buf), + Message::Update(msg) => msg.encode(&mut buf, cxt), + Message::Notification(msg) => msg.encode(&mut buf), + Message::Keepalive(msg) => msg.encode(&mut buf), + Message::RouteRefresh(msg) => msg.encode(&mut buf), + } + + // Rewrite message length. + let msg_len = buf.len() as u16; + buf[Self::MSG_LEN_POS].copy_from_slice(&msg_len.to_be_bytes()); + + buf.clone().freeze() + }) + } + + // Decode buffer into a BGP message. + // + // This function panics if the provided buffer doesn't contain an entire + // message. + pub fn decode(data: &[u8], cxt: &DecodeCxt) -> DecodeResult { + let mut buf = Bytes::copy_from_slice(data); + + // Parse and validate marker. + let marker = buf.get_u128(); + if marker != u128::MAX { + return Err(MessageHeaderError::ConnectionNotSynchronized.into()); + } + + // Parse and validate message length. + let msg_len = buf.get_u16(); + if msg_len < Self::MIN_LEN || msg_len > Self::MAX_LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + // Parse message type. + let msg_type = buf.get_u8(); + + // Parse message body. + match MessageType::from_u8(msg_type) { + Some(MessageType::Open) => { + let msg = OpenMsg::decode(&mut buf, msg_len)?; + Ok(Message::Open(msg)) + } + Some(MessageType::Update) => { + let msg = UpdateMsg::decode(&mut buf, msg_len, cxt)?; + Ok(Message::Update(msg)) + } + Some(MessageType::Notification) => { + let msg = NotificationMsg::decode(&mut buf, msg_len)?; + Ok(Message::Notification(msg)) + } + Some(MessageType::Keepalive) => { + let msg = KeepaliveMsg::decode(&mut buf, msg_len)?; + Ok(Message::Keepalive(msg)) + } + Some(MessageType::RouteRefresh) => { + let msg = RouteRefreshMsg::decode(&mut buf, msg_len)?; + Ok(Message::RouteRefresh(msg)) + } + None => Err(MessageHeaderError::BadMessageType(msg_type).into()), + } + } + + // Parses the given buffer to determine if it contains a complete BGP + // message, and returns the length of the message if successful. + pub fn get_message_len(data: &[u8]) -> Option { + // Validate that the buffer contains sufficient space for at least the + // message header. + let buf_size = data.len(); + if buf_size < Self::MIN_LEN as usize { + return None; + } + + // Ensure the buffer is big enough to hold the entire message. + let mut buf = Bytes::copy_from_slice(&data[0..Self::MIN_LEN as usize]); + let _marker = buf.get_u128(); + let msg_len = buf.get_u16(); + if msg_len < Self::MIN_LEN || msg_len as usize > buf_size { + return None; + } + + // Return the message size. + Some(msg_len as usize) + } +} + +// ===== impl OpenMsg ===== + +impl OpenMsg { + const MIN_LEN: u16 = 29; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8(MessageType::Open as u8); + buf.put_u8(self.version); + buf.put_u16(self.my_as); + buf.put_u16(self.holdtime); + buf.put_ipv4(&self.identifier); + + // Capabilities. + let opt_param_len_pos = buf.len(); + buf.put_u8(0); + for capability in &self.capabilities { + buf.put_u8(OpenParamType::Capabilities as u8); + + // The "Parm. Length" field will be initialized later. + let param_len_pos = buf.len(); + buf.put_u8(0); + + // Encode individual capability. + capability.encode(buf); + + // Rewrite the "Parm. Length" field. + let param_len = buf.len() - param_len_pos - 1; + buf[param_len_pos] = param_len as u8; + } + + // Rewrite the "Opt Parm Len" field. + let opt_param_len = buf.len() - opt_param_len_pos - 1; + buf[opt_param_len_pos] = opt_param_len as u8; + } + + fn decode(buf: &mut Bytes, msg_len: u16) -> DecodeResult { + if msg_len < Self::MIN_LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + // Parse and validate BGP version. + let version = buf.get_u8(); + if version != BGP_VERSION { + return Err( + OpenMessageError::UnsupportedVersion(BGP_VERSION).into() + ); + } + + // Parse and validate ASN. + let my_as = buf.get_u16(); + if my_as == 0 { + return Err(OpenMessageError::BadPeerAs.into()); + } + + // Parse and validate hold time. + let holdtime = buf.get_u16(); + if holdtime == 1 || holdtime == 2 { + return Err(OpenMessageError::UnacceptableHoldTime.into()); + } + + // Parse and validate BGP identifier. + let identifier = buf.get_ipv4(); + if identifier.is_unspecified() + || identifier.is_multicast() + || identifier.is_broadcast() + { + return Err(OpenMessageError::BadBgpIdentifier.into()); + } + + // Parse and validate optional parameters. + let mut capabilities = BTreeSet::new(); + let opt_param_len = buf.get_u8(); + if opt_param_len as usize > buf.remaining() { + return Err(OpenMessageError::MalformedOptParam.into()); + } + let mut buf_opts = buf.copy_to_bytes(opt_param_len as usize); + while buf_opts.remaining() > 0 { + if buf_opts.remaining() < 2 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + let param_type = buf_opts.get_u8(); + let param_len = buf_opts.get_u8(); + if param_len as usize > buf_opts.remaining() { + return Err(OpenMessageError::MalformedOptParam.into()); + } + let mut buf_param_value = + buf_opts.copy_to_bytes(param_len as usize); + + // Parse and validate capabilities. + match OpenParamType::from_u8(param_type) { + Some(OpenParamType::Capabilities) => { + while buf_param_value.remaining() > 0 { + if let Some(cap) = + Capability::decode(&mut buf_param_value)? + { + capabilities.insert(cap); + } + } + } + None => { + return Err(OpenMessageError::UnsupportedOptParam.into()); + } + } + } + + Ok(OpenMsg { + version, + my_as, + holdtime, + identifier, + capabilities, + }) + } + + pub fn real_as(&self) -> u32 { + self.capabilities + .iter() + .find_map(|cap| { + if let Capability::FourOctetAsNumber { asn } = cap { + Some(asn.0) + } else { + None + } + }) + .unwrap_or(self.my_as as u32) + } +} + +// ===== impl Capability ===== + +impl Capability { + fn encode(&self, buf: &mut BytesMut) { + let start_pos = buf.len(); + + match self { + Capability::MultiProtocol { afi, safi } => { + buf.put_u8(CapabilityCode::MultiProtocol as u8); + buf.put_u8(0); + buf.put_u16(*afi as u16); + buf.put_u8(0); + buf.put_u8(*safi as u8); + } + Capability::FourOctetAsNumber { asn } => { + buf.put_u8(CapabilityCode::FourOctetAsNumber as u8); + buf.put_u8(0); + buf.put_u32(asn.0); + } + Capability::AddPath(tuples) => { + buf.put_u8(CapabilityCode::AddPath as u8); + buf.put_u8(0); + for tuple in tuples { + buf.put_u16(tuple.afi as u16); + buf.put_u8(tuple.safi as u8); + buf.put_u8(tuple.mode as u8); + } + } + Capability::RouteRefresh => { + buf.put_u8(CapabilityCode::RouteRefresh as u8); + buf.put_u8(0); + } + Capability::EnhancedRouteRefresh => { + buf.put_u8(CapabilityCode::EnhancedRouteRefresh as u8); + buf.put_u8(0); + } + } + + // Rewrite the "Capability Length" field. + let cap_len = buf.len() - start_pos - 2; + buf[start_pos + 1] = cap_len as u8; + } + + fn decode(buf: &mut Bytes) -> DecodeResult> { + if buf.remaining() < 2 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + let cap_type = buf.get_u8(); + let cap_len = buf.get_u8(); + if cap_len as usize > buf.remaining() { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + let mut buf_cap = buf.copy_to_bytes(cap_len as usize); + let cap = match CapabilityCode::from_u8(cap_type) { + Some(CapabilityCode::MultiProtocol) => { + if cap_len != 4 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + let afi = buf_cap.get_u16(); + let Some(afi) = Afi::from_u16(afi) else { + // Ignore unknown AFI. + return Ok(None); + }; + let _reserved = buf_cap.get_u8(); + let safi = buf_cap.get_u8(); + let Some(safi) = Safi::from_u8(safi) else { + // Ignore unknown SAFI. + return Ok(None); + }; + + Capability::MultiProtocol { afi, safi } + } + Some(CapabilityCode::FourOctetAsNumber) => { + if cap_len != 4 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + let asn = buf_cap.get_u32(); + Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(asn), + } + } + Some(CapabilityCode::AddPath) => { + if cap_len % 4 != 0 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + let mut tuples = BTreeSet::new(); + while buf_cap.remaining() > 0 { + let afi = buf_cap.get_u16(); + let Some(afi) = Afi::from_u16(afi) else { + // Ignore unknown AFI. + return Ok(None); + }; + let safi = buf_cap.get_u8(); + let Some(safi) = Safi::from_u8(safi) else { + // Ignore unknown SAFI. + return Ok(None); + }; + let mode = buf_cap.get_u8(); + let Some(mode) = AddPathMode::from_u8(mode) else { + // Ignore unknown value. + return Ok(None); + }; + tuples.insert(AddPathTuple { afi, safi, mode }); + } + Capability::AddPath(tuples) + } + Some(CapabilityCode::RouteRefresh) => { + if cap_len != 0 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + Capability::RouteRefresh + } + Some(CapabilityCode::EnhancedRouteRefresh) => { + if cap_len != 0 { + return Err(OpenMessageError::MalformedOptParam.into()); + } + + Capability::EnhancedRouteRefresh + } + _ => { + // Ignore unknown capability. + return Ok(None); + } + }; + + Ok(Some(cap)) + } + + pub fn code(&self) -> CapabilityCode { + match self { + Capability::MultiProtocol { .. } => CapabilityCode::MultiProtocol, + Capability::FourOctetAsNumber { .. } => { + CapabilityCode::FourOctetAsNumber + } + Capability::AddPath { .. } => CapabilityCode::AddPath, + Capability::RouteRefresh => CapabilityCode::RouteRefresh, + Capability::EnhancedRouteRefresh => { + CapabilityCode::EnhancedRouteRefresh + } + } + } +} + +// ===== impl FourOctetAsNumber ===== + +// The "Support for 4-octet AS number" capability is unique in that it contains +// additional data (the local AS number) other than the capability itself. +// +// Once a neighborship is formed, the negotiated capabilities result from the +// intersection of advertised and received capabilities. As the AS numbers of +// the two neighbors may differ, the intersection might not include the "Support +// for 4-octet AS number" capability. To address this, the following equality +// traits are implemented to always consider AS numbers within the capability +// as equal, even when they are not. +impl PartialEq for FourOctetAsNumber { + fn eq(&self, _other: &Self) -> bool { + true + } +} +impl Eq for FourOctetAsNumber {} +impl PartialOrd for FourOctetAsNumber { + fn partial_cmp(&self, _other: &Self) -> Option { + Some(Ordering::Equal) + } +} +impl Ord for FourOctetAsNumber { + fn cmp(&self, _other: &Self) -> Ordering { + Ordering::Equal + } +} + +// ===== impl UpdateMsg ===== + +impl UpdateMsg { + pub const MIN_LEN: u16 = 23; + + fn encode(&self, buf: &mut BytesMut, cxt: &EncodeCxt) { + buf.put_u8(MessageType::Update as u8); + + // Withdrawn Routes. + let start_pos = buf.len(); + buf.put_u16(0); + if let Some(unreach) = &self.unreach { + // Encode prefixes. + for prefix in &unreach.prefixes { + let plen = prefix.prefix(); + let prefix_bytes = prefix.ip().octets(); + let plen_wire = prefix_wire_len(plen); + buf.put_u8(plen); + buf.put(&prefix_bytes[0..plen_wire]); + } + + // Rewrite the "Withdrawn Routes Length" field. + let len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&len.to_be_bytes()); + } + + // Path Attributes. + let start_pos = buf.len(); + buf.put_u16(0); + if let Some(attrs) = &self.attrs { + // Encode path attributes. + attrs.encode( + buf, + &self.reach, + &self.mp_reach, + &self.mp_unreach, + cxt, + ); + + // Rewrite the "Total Path Attribute Length" field. + let len = (buf.len() - start_pos - 2) as u16; + buf[start_pos..start_pos + 2].copy_from_slice(&len.to_be_bytes()); + } + + // Network Layer Reachability Information. + if let Some(reach) = &self.reach { + // Encode prefixes. + for prefix in &reach.prefixes { + encode_ipv4_prefix(buf, prefix); + } + } + } + + fn decode( + buf: &mut Bytes, + msg_len: u16, + cxt: &DecodeCxt, + ) -> DecodeResult { + if msg_len < Self::MIN_LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + let mut reach = None; + let mut unreach = None; + let mut mp_reach = None; + let mut mp_unreach = None; + let mut attrs = None; + let mut nexthop = None; + + // Withdrawn Routes Length. + let wdraw_len = buf.get_u16(); + if wdraw_len as usize > buf.remaining() { + return Err(UpdateMessageError::MalformedAttributeList.into()); + } + + // Withdrawn Routes. + let mut buf_wdraw = buf.copy_to_bytes(wdraw_len as usize); + let mut prefixes = Vec::new(); + while buf_wdraw.remaining() > 0 { + if let Some(prefix) = decode_ipv4_prefix(&mut buf_wdraw)? { + prefixes.push(prefix); + } + } + if !prefixes.is_empty() { + unreach = Some(UnreachNlri { prefixes }); + } + + // Total Path Attribute Length. + if buf.remaining() < 2 { + return Err(UpdateMessageError::MalformedAttributeList.into()); + } + let attr_len = buf.get_u16(); + if attr_len as usize > buf.remaining() { + return Err(UpdateMessageError::MalformedAttributeList.into()); + } + + // Path Attributes. + if attr_len != 0 { + let mut buf_attr = buf.copy_to_bytes(attr_len as usize); + let nlri_present = + (msg_len - Self::MIN_LEN - wdraw_len - attr_len) > 0; + attrs = Attrs::decode( + &mut buf_attr, + cxt, + &mut nexthop, + nlri_present, + &mut mp_unreach, + &mut mp_reach, + )?; + } + + // Network Layer Reachability Information. + let mut prefixes = Vec::new(); + while buf.remaining() > 0 { + if let Some(prefix) = decode_ipv4_prefix(buf)? { + prefixes.push(prefix); + } + } + if !prefixes.is_empty() { + reach = Some(ReachNlri { + prefixes, + nexthop: nexthop.unwrap(), + }); + } + + Ok(UpdateMsg { + reach, + unreach, + mp_reach, + mp_unreach, + attrs, + }) + } +} + +// ===== impl NotificationMsg ===== + +impl NotificationMsg { + const MIN_LEN: u16 = 21; + + pub(crate) fn new( + error_code: impl ToPrimitive, + error_subcode: impl ToPrimitive, + ) -> Self { + NotificationMsg { + error_code: error_code.to_u8().unwrap(), + error_subcode: error_subcode.to_u8().unwrap(), + data: Default::default(), + } + } + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8(MessageType::Notification as u8); + buf.put_u8(self.error_code); + buf.put_u8(self.error_subcode); + buf.put_slice(&self.data); + } + + fn decode(buf: &mut Bytes, msg_len: u16) -> DecodeResult { + if msg_len < Self::MIN_LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + let error_code = buf.get_u8(); + let error_subcode = buf.get_u8(); + let data_len = msg_len - Self::MIN_LEN; + let data = buf.copy_to_bytes(data_len as usize).to_vec(); + + Ok(NotificationMsg { + error_code, + error_subcode, + data, + }) + } +} + +impl From for NotificationMsg { + fn from(error: DecodeError) -> NotificationMsg { + let error_code; + let error_subcode; + let data = vec![]; + + match error { + DecodeError::MessageHeader(error) => { + error_code = ErrorCode::MessageHeaderError as u8; + error_subcode = match error { + MessageHeaderError::ConnectionNotSynchronized => { + MessageHeaderErrorSubcode::ConnectionNotSynchronized + } + MessageHeaderError::BadMessageLength(..) => { + MessageHeaderErrorSubcode::BadMessageLength + } + MessageHeaderError::BadMessageType(..) => { + MessageHeaderErrorSubcode::BadMessageType + } + } as u8; + } + DecodeError::OpenMessage(error) => { + error_code = ErrorCode::OpenMessageError as u8; + error_subcode = match error { + OpenMessageError::UnsupportedVersion(..) => { + OpenMessageErrorSubcode::UnsupportedVersionNumber + } + OpenMessageError::BadPeerAs => { + OpenMessageErrorSubcode::BadPeerAs + } + OpenMessageError::BadBgpIdentifier => { + OpenMessageErrorSubcode::BadBgpIdentifier + } + OpenMessageError::UnsupportedOptParam => { + OpenMessageErrorSubcode::UnsupportedOptParam + } + OpenMessageError::UnacceptableHoldTime => { + OpenMessageErrorSubcode::UnacceptableHoldTime + } + OpenMessageError::UnsupportedCapability => { + OpenMessageErrorSubcode::UnsupportedCapability + } + OpenMessageError::MalformedOptParam => { + OpenMessageErrorSubcode::Unspecific + } + } as u8; + } + DecodeError::UpdateMessage(error) => { + error_code = ErrorCode::UpdateMessageError as u8; + error_subcode = match error { + UpdateMessageError::MalformedAttributeList => { + UpdateMessageErrorSubcode::MalformedAttributeList + } + UpdateMessageError::UnrecognizedWellKnownAttribute => { + UpdateMessageErrorSubcode::UnrecognizedWellKnownAttribute + } + UpdateMessageError::OptionalAttributeError => { + UpdateMessageErrorSubcode::OptionalAttributeError + } + UpdateMessageError::InvalidNetworkField => { + UpdateMessageErrorSubcode::InvalidNetworkField + } + } as u8; + } + } + + // TODO: set notification data. + + NotificationMsg { + error_code, + error_subcode, + data, + } + } +} + +// ===== impl KeepaliveMsg ===== + +impl KeepaliveMsg { + const LEN: u16 = 19; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8(MessageType::Keepalive as u8); + } + + fn decode(_buf: &mut Bytes, msg_len: u16) -> DecodeResult { + if msg_len != Self::LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + // A KEEPALIVE message consists of only the message header. + Ok(KeepaliveMsg {}) + } +} + +// ===== impl RouteRefreshMsg ===== + +impl RouteRefreshMsg { + const LEN: u16 = 23; + + fn encode(&self, buf: &mut BytesMut) { + buf.put_u8(MessageType::RouteRefresh as u8); + buf.put_u16(self.afi); + buf.put_u8(0); + buf.put_u8(self.safi); + } + + fn decode(buf: &mut Bytes, msg_len: u16) -> DecodeResult { + if msg_len != Self::LEN { + return Err(MessageHeaderError::BadMessageLength(msg_len).into()); + } + + let afi = buf.get_u16(); + let _reserved = buf.get_u8(); + let safi = buf.get_u8(); + Ok(RouteRefreshMsg { afi, safi }) + } +} + +// ===== helper functions ===== + +pub(crate) fn encode_ipv4_prefix(buf: &mut BytesMut, prefix: &Ipv4Network) { + // Encode prefix length. + let plen = prefix.prefix(); + buf.put_u8(plen); + + // Encode prefix address (variable length). + let prefix_bytes = prefix.ip().octets(); + let plen_wire = prefix_wire_len(plen); + buf.put(&prefix_bytes[0..plen_wire]); +} + +pub(crate) fn encode_ipv6_prefix(buf: &mut BytesMut, prefix: &Ipv6Network) { + // Encode prefix length. + let plen = prefix.prefix(); + buf.put_u8(plen); + + // Encode prefix address (variable length). + let prefix_bytes = prefix.ip().octets(); + let plen_wire = prefix_wire_len(plen); + buf.put(&prefix_bytes[0..plen_wire]); +} + +pub(crate) fn decode_ipv4_prefix( + buf: &mut Bytes, +) -> DecodeResult> { + // Parse prefix length. + let plen = buf.get_u8(); + let plen_wire = prefix_wire_len(plen); + if plen_wire > buf.remaining() || plen > Ipv4Network::MAX_PREFIXLEN { + return Err(UpdateMessageError::InvalidNetworkField.into()); + } + + // Parse prefix address (variable length). + let mut prefix_bytes = vec![0; plen_wire]; + buf.copy_to_slice(&mut prefix_bytes); + let prefix = Ipv4Addr::from_slice(&prefix_bytes); + let prefix = Ipv4Network::new(prefix, plen) + .map(|prefix| prefix.apply_mask()) + .map_err(|_| UpdateMessageError::InvalidNetworkField)?; + + // Ignore semantically incorrect prefix. + if !prefix.is_routable() { + return Ok(None); + } + + // Normalize prefix. + let prefix = prefix.apply_mask(); + + Ok(Some(prefix)) +} + +pub(crate) fn decode_ipv6_prefix( + buf: &mut Bytes, +) -> DecodeResult> { + // Parse prefix length. + let plen = buf.get_u8(); + let plen_wire = prefix_wire_len(plen); + if plen_wire > buf.remaining() || plen > Ipv6Network::MAX_PREFIXLEN { + return Err(UpdateMessageError::InvalidNetworkField.into()); + } + + // Parse prefix address (variable length). + let mut prefix_bytes = vec![0; plen_wire]; + buf.copy_to_slice(&mut prefix_bytes); + let prefix = Ipv6Addr::from_slice(&prefix_bytes); + let prefix = Ipv6Network::new(prefix, plen) + .map(|prefix| prefix.apply_mask()) + .map_err(|_| UpdateMessageError::InvalidNetworkField)?; + + // Ignore semantically incorrect prefix. + if !prefix.is_routable() { + return Ok(None); + } + + // Normalize prefix. + let prefix = prefix.apply_mask(); + + Ok(Some(prefix)) +} + +// Calculates the number of bytes required to encode a prefix. +fn prefix_wire_len(len: u8) -> usize { + (len as usize + 7) / 8 +} diff --git a/holo-bgp/src/packet/mod.rs b/holo-bgp/src/packet/mod.rs new file mode 100644 index 00000000..6aab95ab --- /dev/null +++ b/holo-bgp/src/packet/mod.rs @@ -0,0 +1,10 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +pub mod attribute; +pub mod consts; +pub mod error; +pub mod message; diff --git a/holo-bgp/src/policy.rs b/holo-bgp/src/policy.rs new file mode 100644 index 00000000..c04a2fa5 --- /dev/null +++ b/holo-bgp/src/policy.rs @@ -0,0 +1,403 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::{BTreeMap, BTreeSet}; +use std::net::IpAddr; +use std::sync::Arc; + +use derive_new::new; +use holo_utils::bgp::{AfiSafi, RouteType}; +use holo_utils::policy::{ + BgpNexthop, BgpPolicyAction, BgpPolicyCondition, BgpSetCommMethod, + BgpSetCommOptions, BgpSetMed, DefaultPolicyType, MatchSets, + MetricModification, Policy, PolicyAction, PolicyCondition, PolicyResult, + PolicyType, +}; +use holo_utils::UnboundedSender; +use ipnetwork::IpNetwork; +use serde::{Deserialize, Serialize}; + +use crate::packet::attribute::{Attrs, CommList, CommType}; +use crate::rib::RouteOrigin; +use crate::tasks::messages::input::PolicyResultMsg; + +// Represents a simplified version of `Route`, containing only information +// relevant for the application of routing policies. +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(new)] +#[derive(Deserialize, Serialize)] +pub struct RoutePolicyInfo { + pub origin: RouteOrigin, + pub attrs: Attrs, + pub route_type: RouteType, +} + +// ===== global functions ===== + +// Applies neighbor import or export routing policies to a provided list of +// routes and sends the resulting policy decisions to the specified channel. +pub(crate) fn neighbor_apply( + policy_type: PolicyType, + nbr_addr: IpAddr, + afi_safi: AfiSafi, + routes: Vec<(IpNetwork, RoutePolicyInfo)>, + policies: &Vec>, + match_sets: &MatchSets, + default_policy: DefaultPolicyType, + policy_resultp: &UnboundedSender, +) { + // Process policies for each route and collect the results. + let routes = routes + .into_iter() + .map(|(prefix, rpinfo)| { + let result = process_policies( + nbr_addr, + afi_safi, + prefix, + rpinfo, + policies, + &match_sets, + default_policy, + ); + + (prefix, result) + }) + .collect(); + + // Send the resulting policy decisions to the specified channel. + let _ = policy_resultp.send(PolicyResultMsg::Neighbor { + policy_type, + nbr_addr, + afi_safi, + routes, + }); +} + +// ===== helper functions ===== + +// Processes routing policies for a specific route and returns the policy +// result. +fn process_policies( + nbr_addr: IpAddr, + afi_safi: AfiSafi, + prefix: IpNetwork, + mut rpinfo: RoutePolicyInfo, + policies: &Vec>, + match_sets: &MatchSets, + default_policy: DefaultPolicyType, +) -> PolicyResult { + let mut matches = false; + + for stmt in policies.iter().flat_map(|policy| policy.stmts.values()) { + // Check if all conditions in the policy statement are satisfied. + if !stmt.conditions.values().all(|condition| { + process_stmt_condition( + &nbr_addr, afi_safi, &prefix, &rpinfo, condition, match_sets, + ) + }) { + continue; + } + + matches = true; + + // Process actions defined in the policy statement. + for action in stmt.actions.values() { + if !process_stmt_action(&mut rpinfo, action, match_sets) { + return PolicyResult::Reject; + } + } + } + + // Check default policy if no definition in the policy chain was + // satisfied. + if !matches && default_policy == DefaultPolicyType::RejectRoute { + return PolicyResult::Reject; + } + + PolicyResult::Accept(rpinfo) +} + +// Processes a single condition statement within a routing policy. +// +// Returns a boolean value indicating whether the condition is met. +fn process_stmt_condition( + nbr_addr: &IpAddr, + afi_safi: AfiSafi, + _prefix: &IpNetwork, + rpinfo: &RoutePolicyInfo, + condition: &PolicyCondition, + match_sets: &MatchSets, +) -> bool { + let attrs = &rpinfo.attrs; + match condition { + // "match-interface" + PolicyCondition::MatchInterface(_value) => { + // TODO + true + } + // "match-prefix-set" + PolicyCondition::MatchPrefixSet(_value) => { + // TODO + true + } + // "match-neighbor-set" + PolicyCondition::MatchNeighborSet(value) => { + let set = match_sets.neighbors.get(value).unwrap(); + set.addrs.contains(nbr_addr) + } + // "bgp-conditions" + PolicyCondition::Bgp(condition) => match condition { + // "local-pref" + BgpPolicyCondition::LocalPref { value, op } => { + match attrs.base.local_pref { + Some(local_pref) => op.compare(value, &local_pref), + None => false, + } + } + // "med" + BgpPolicyCondition::Med { value, op } => match attrs.base.med { + Some(med) => op.compare(value, &med), + None => false, + }, + // "origin-eq" + BgpPolicyCondition::Origin(origin) => attrs.base.origin == *origin, + // "match-afi-safi" + BgpPolicyCondition::MatchAfiSafi { values, match_type } => { + match_type.compare(values, &afi_safi) + } + // "match-neighbor" + BgpPolicyCondition::MatchNeighbor { value, match_type } => { + match_type.compare(value, nbr_addr) + } + // "route-type" + BgpPolicyCondition::RouteType(value) => rpinfo.route_type == *value, + // "community-count" + BgpPolicyCondition::CommCount { value, op } => match &attrs.comm { + Some(comm) => op.compare(value, &(comm.0.len() as u32)), + None => false, + }, + // "as-path-length" + BgpPolicyCondition::AsPathLen { value, op } => { + op.compare(value, &(attrs.base.as_path.path_length())) + } + // "match-community-set" + BgpPolicyCondition::MatchCommSet { value, match_type } => { + if let Some(comm) = &attrs.comm { + let set = match_sets.bgp.comms.get(value).unwrap(); + match_type.compare(&set, &comm.0) + } else { + false + } + } + // "match-ext-community-set" + BgpPolicyCondition::MatchExtCommSet { value, match_type } => { + if let Some(ext_comm) = &attrs.ext_comm { + let set = match_sets.bgp.ext_comms.get(value).unwrap(); + match_type.compare(&set, &ext_comm.0) + } else { + false + } + } + // "match-ipv6-ext-community-set" + BgpPolicyCondition::MatchExtv6CommSet { value, match_type } => { + if let Some(extv6_comm) = &attrs.extv6_comm { + let set = match_sets.bgp.extv6_comms.get(value).unwrap(); + match_type.compare(&set, &extv6_comm.0) + } else { + false + } + } + // "match-large-community-set" + BgpPolicyCondition::MatchLargeCommSet { value, match_type } => { + if let Some(large_comm) = &attrs.large_comm { + let set = match_sets.bgp.large_comms.get(value).unwrap(); + match_type.compare(&set, &large_comm.0) + } else { + false + } + } + // "match-as-path-set" + BgpPolicyCondition::MatchAsPathSet { value, match_type } => { + let set = match_sets.bgp.as_paths.get(value).unwrap(); + let asns = attrs.base.as_path.iter().collect(); + match_type.compare(&set, &asns) + } + // "match-next-hop-set" + BgpPolicyCondition::MatchNexthopSet { value, match_type } => { + let nexthop = match attrs.base.nexthop { + Some(nexthop) => BgpNexthop::Addr(nexthop), + None => BgpNexthop::NexthopSelf, + }; + let set = match_sets.bgp.nexthops.get(value).unwrap(); + match_type.compare(&set, &nexthop) + } + }, + // Ignore unsupported conditions. + _ => true, + } +} + +// Processes a single action statement within a routing policy. +// +// Returns a boolean value indicating whether the route should be accepted or +// not. +fn process_stmt_action( + rpinfo: &mut RoutePolicyInfo, + action: &PolicyAction, + match_sets: &MatchSets, +) -> bool { + let attrs = &mut rpinfo.attrs; + match action { + // "policy-result" + PolicyAction::Accept(accept) => { + return *accept; + } + // "set-metric" + PolicyAction::SetMetric { value, mod_type } => match mod_type { + MetricModification::Set => { + attrs.base.med = Some(*value); + } + MetricModification::Add => { + if let Some(med) = &mut attrs.base.med { + *med = med.saturating_add(*value); + } + } + MetricModification::Subtract => { + if let Some(med) = &mut attrs.base.med { + *med = med.saturating_sub(*value); + } + } + }, + // "bgp-actions" + PolicyAction::Bgp(action) => match action { + // "set-route-origin" + BgpPolicyAction::SetRouteOrigin(origin) => { + attrs.base.origin = *origin + } + // "set-local-pref" + BgpPolicyAction::SetLocalPref(local_pref) => { + attrs.base.local_pref = Some(*local_pref); + } + // "set-next-hop" + BgpPolicyAction::SetNexthop(set_nexthop) => { + attrs.base.nexthop = match set_nexthop { + BgpNexthop::Addr(addr) => Some(*addr), + BgpNexthop::NexthopSelf => None, + }; + } + // "set-med" + BgpPolicyAction::SetMed(set_med) => match set_med { + BgpSetMed::Add(value) => { + if let Some(med) = &mut attrs.base.med { + *med = med.saturating_add(*value); + } + } + BgpSetMed::Subtract(value) => { + if let Some(med) = &mut attrs.base.med { + *med = med.saturating_sub(*value); + } + } + BgpSetMed::Set(value) => { + attrs.base.med = Some(*value); + } + BgpSetMed::Igp => { + // TODO + } + BgpSetMed::MedPlusIgp => { + // TODO + } + }, + // "set-as-path-prepend" + BgpPolicyAction::SetAsPathPrepent { asn, repeat } => { + for _ in 0..repeat.unwrap_or(1) { + attrs.base.as_path.prepend(*asn); + } + } + // "set-community" + BgpPolicyAction::SetComm { options, method } => { + action_set_comm( + options, + method, + &match_sets.bgp.comms, + &mut attrs.comm, + ); + } + // "set-ext-community" + BgpPolicyAction::SetExtComm { options, method } => { + action_set_comm( + options, + method, + &match_sets.bgp.ext_comms, + &mut attrs.ext_comm, + ); + } + // "set-ipv6-ext-community" + BgpPolicyAction::SetExtv6Comm { options, method } => { + action_set_comm( + options, + method, + &match_sets.bgp.extv6_comms, + &mut attrs.extv6_comm, + ); + } + // "set-large-community" + BgpPolicyAction::SetLargeComm { options, method } => { + action_set_comm( + options, + method, + &match_sets.bgp.large_comms, + &mut attrs.large_comm, + ); + } + }, + // Ignore unsupported actions. + _ => {} + } + + true +} + +// Modifies the list of communities based on the specified method and options. +fn action_set_comm( + options: &BgpSetCommOptions, + method: &BgpSetCommMethod, + comm_sets: &BTreeMap>, + comm_list: &mut Option>, +) where + T: CommType, +{ + // Get list of communities. + let comms = match method { + BgpSetCommMethod::Inline(comms) => comms, + BgpSetCommMethod::Reference(set) => comm_sets.get(set).unwrap(), + }; + + // Add, remove or replace communities. + match options { + BgpSetCommOptions::Add => { + if let Some(comm_list) = comm_list { + comm_list.0.extend(comms.clone()); + } else { + *comm_list = Some(CommList(comms.clone())); + } + } + BgpSetCommOptions::Remove => { + if let Some(comm_list) = comm_list { + comm_list.0.retain(|c| !comms.contains(c)) + } + } + BgpSetCommOptions::Replace => { + *comm_list = Some(CommList(comms.clone())); + } + } + + // Remove the community list if it exists and is empty. + if let Some(list) = comm_list.as_ref() + && list.0.is_empty() + { + *comm_list = None; + } +} diff --git a/holo-bgp/src/rib.rs b/holo-bgp/src/rib.rs new file mode 100644 index 00000000..9b978a16 --- /dev/null +++ b/holo-bgp/src/rib.rs @@ -0,0 +1,559 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use std::net::{IpAddr, Ipv4Addr}; +use std::sync::Arc; +use std::time::Instant; + +use holo_utils::bgp::RouteType; +use holo_utils::ibus::IbusSender; +use holo_utils::protocol::Protocol; +use serde::{Deserialize, Serialize}; + +use crate::af::{AddressFamily, Ipv4Unicast, Ipv6Unicast}; +use crate::debug::Debug; +use crate::northbound::configuration::{ + DistanceCfg, MultipathCfg, RouteSelectionCfg, +}; +use crate::packet::attribute::{ + Attrs, BaseAttrs, Comms, ExtComms, Extv6Comms, LargeComms, UnknownAttr, +}; +use crate::policy::RoutePolicyInfo; +use crate::southbound; + +// Default values. +pub const DFLT_LOCAL_PREF: u32 = 100; +pub const DFLT_MIN_AS_ORIG_INTERVAL: u16 = 15; +pub const DFLT_MIN_ROUTE_ADV_INTERVAL_EBGP: u16 = 30; +pub const DFLT_MIN_ROUTE_ADV_INTERVAL_IBGP: u16 = 5; + +#[derive(Debug, Default)] +pub struct Rib { + pub attr_sets: AttrSetsCxt, + pub tables: RoutingTables, +} + +#[derive(Debug, Default)] +pub struct RoutingTables { + pub ipv4_unicast: RoutingTable, + pub ipv6_unicast: RoutingTable, +} + +#[derive(Debug)] +pub struct RoutingTable { + pub prefixes: BTreeMap, + pub queued_prefixes: BTreeSet, +} + +#[derive(Debug, Default)] +pub struct Destination { + pub local: Option<(Route, BTreeSet)>, + pub adj_in_pre: BTreeMap, + pub adj_in_post: BTreeMap, + pub adj_out_pre: BTreeMap, + pub adj_out_post: BTreeMap, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Route { + pub origin: RouteOrigin, + pub attrs: RouteAttrs, + pub route_type: RouteType, + pub last_modified: Instant, + pub eligible: bool, + pub ineligible_reason: Option, + pub reject_reason: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum RouteOrigin { + // Route learned from a neighbor. + Neighbor { + identifier: Ipv4Addr, + remote_addr: IpAddr, + }, + // Route was injected or redistributed from another protocol. + Protocol(Protocol), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct RouteAttrs { + pub base: Arc>, + pub comm: Option>>, + pub ext_comm: Option>>, + pub extv6_comm: Option>>, + pub large_comm: Option>>, + pub unknown: Vec, +} + +#[derive(Debug, Default)] +pub struct AttrSetsCxt { + pub base: AttrSets, + pub comm: AttrSets, + pub ext_comm: AttrSets, + pub extv6_comm: AttrSets, + pub large_comm: AttrSets, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct AttrSets { + pub tree: BTreeMap>>, + next_index: u64, +} + +#[derive(Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub struct AttrSet { + pub index: u64, + pub value: T, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RouteIneligibleReason { + ClusterLoop, + AsLoop, + Originator, + Confed, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RouteRejectReason { + LocalPrefLower, + AsPathLonger, + OriginTypeHigher, + MedHigher, + PreferExternal, + NexthopCostHigher, + HigherRouterId, + HigherPeerAddress, + RejectedImportPolicy, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum RouteCompare { + Preferred(RouteRejectReason), + LessPreferred(RouteRejectReason), + MultipathEqual, + MultipathDifferent, +} + +// ===== impl RoutingTable ===== + +impl Default for RoutingTable +where + A: AddressFamily, +{ + fn default() -> RoutingTable { + RoutingTable { + prefixes: Default::default(), + queued_prefixes: Default::default(), + } + } +} + +// ===== impl Route ===== + +impl Route { + pub(crate) fn new( + origin: RouteOrigin, + attrs: RouteAttrs, + route_type: RouteType, + ) -> Route { + Route { + origin, + attrs, + route_type, + last_modified: Instant::now(), + eligible: true, + ineligible_reason: None, + reject_reason: None, + } + } + + pub(crate) fn policy_info(&self) -> RoutePolicyInfo { + RoutePolicyInfo { + origin: self.origin, + attrs: self.attrs.get(), + route_type: self.route_type, + } + } + + fn compare( + &self, + other: &Route, + selection_cfg: &RouteSelectionCfg, + mpath_cfg: Option<&MultipathCfg>, + ) -> RouteCompare { + // Compare LOCAL_PREFERENCE attributes. + let a = self.attrs.base.value.local_pref.unwrap_or(DFLT_LOCAL_PREF); + let b = other.attrs.base.value.local_pref.unwrap_or(DFLT_LOCAL_PREF); + let reason = RouteRejectReason::LocalPrefLower; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Greater => { + return RouteCompare::Preferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + + // Compare AS_PATH lengths. + if !selection_cfg.ignore_as_path_length { + let a = self.attrs.base.value.as_path.path_length(); + let b = other.attrs.base.value.as_path.path_length(); + let reason = RouteRejectReason::AsPathLonger; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::Preferred(reason); + } + Ordering::Greater => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + } + + // Compare ORIGIN attributes. + let a = self.attrs.base.value.origin; + let b = other.attrs.base.value.origin; + let reason = RouteRejectReason::OriginTypeHigher; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::Preferred(reason); + } + Ordering::Greater => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + + // Compare MULTI_EXIT_DISC attributes. + let a_nbr_as = self.attrs.base.value.as_path.first(); + let b_nbr_as = other.attrs.base.value.as_path.first(); + if selection_cfg.always_compare_med || a_nbr_as == b_nbr_as { + let a = self.attrs.base.value.med.unwrap_or(0); + let b = other.attrs.base.value.med.unwrap_or(0); + let reason = RouteRejectReason::MedHigher; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::Preferred(reason); + } + Ordering::Greater => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + } + + // Prefer eBGP routes. + let a = self.route_type; + let b = other.route_type; + let reason = RouteRejectReason::PreferExternal; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Greater => { + return RouteCompare::Preferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + + // Compare IGP costs. + if !selection_cfg.ignore_next_hop_igp_metric { + // TODO: not implemened yet. + } + + // If multipath is enabled, routes are considered equal under specific + // conditions. + // + // TODO: implement more multipath selection knobs as documented in + // draft-lapukhov-bgp-ecmp-considerations-12 + if let Some(mpath_cfg) = mpath_cfg { + match self.route_type { + RouteType::External => { + // For eBGP, routes are considered equal if they are + // received from the same neighboring AS, unless this + // restriction is disabled by configuration. + if mpath_cfg.ebgp_allow_multiple_as || a_nbr_as == b_nbr_as + { + return RouteCompare::MultipathEqual; + } + } + RouteType::Internal => { + // For iBGP, routes are considered equal if their AS_PATH + // attributes match. + if self.attrs.base.value.as_path + == other.attrs.base.value.as_path + { + return RouteCompare::MultipathEqual; + } + } + } + + // Routes are considered different for multipath routing. + return RouteCompare::MultipathDifferent; + } + + // Compare peer BGP identifiers. + if selection_cfg.external_compare_router_id + && let ( + RouteOrigin::Neighbor { identifier: a, .. }, + RouteOrigin::Neighbor { identifier: b, .. }, + ) = (&self.origin, &other.origin) + { + let reason = RouteRejectReason::HigherRouterId; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::Preferred(reason); + } + Ordering::Greater => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + } + + // Compare peer IP addresses. + if let ( + RouteOrigin::Neighbor { remote_addr: a, .. }, + RouteOrigin::Neighbor { remote_addr: b, .. }, + ) = (&self.origin, &other.origin) + { + let reason = RouteRejectReason::HigherPeerAddress; + match a.cmp(&b) { + Ordering::Less => { + return RouteCompare::Preferred(reason); + } + Ordering::Greater => { + return RouteCompare::LessPreferred(reason); + } + Ordering::Equal => { + // Move to next tie-breaker. + } + } + } + + // "Isso non ecziste!" + unreachable!() + } +} + +// ===== impl RouteAttrs ===== + +impl RouteAttrs { + pub(crate) fn get(&self) -> Attrs { + Attrs { + base: self.base.value.clone(), + comm: self.comm.as_ref().map(|set| set.value.clone()), + ext_comm: self.ext_comm.as_ref().map(|set| set.value.clone()), + extv6_comm: self.extv6_comm.as_ref().map(|set| set.value.clone()), + large_comm: self.large_comm.as_ref().map(|set| set.value.clone()), + unknown: self.unknown.clone(), + } + } +} + +// ===== impl AttrSetsCxt ===== + +impl AttrSetsCxt { + pub(crate) fn get_route_attr_sets(&mut self, attrs: Attrs) -> RouteAttrs { + RouteAttrs { + base: self.base.get(attrs.base), + comm: attrs.comm.map(|comm| self.comm.get(comm)), + ext_comm: attrs.ext_comm.map(|c| self.ext_comm.get(c)), + extv6_comm: attrs.extv6_comm.map(|c| self.extv6_comm.get(c)), + large_comm: attrs.large_comm.map(|c| self.large_comm.get(c)), + unknown: attrs.unknown, + } + } +} + +// ===== impl AttrSets ===== + +impl AttrSets +where + T: Clone + Eq + Ord + PartialEq + PartialOrd, +{ + fn get(&mut self, attr: T) -> Arc> { + if let Some(attr_set) = self.tree.get(&attr) { + Arc::clone(attr_set) + } else { + self.next_index += 1; + let attr_set = Arc::new(AttrSet { + index: self.next_index, + value: attr.clone(), + }); + self.tree.insert(attr, Arc::clone(&attr_set)); + attr_set + } + } +} + +impl Default for AttrSets { + fn default() -> AttrSets { + AttrSets { + tree: Default::default(), + next_index: 0, + } + } +} + +// ===== helper functions ===== + +fn compute_nexthops( + dest: &Destination, + best_route: &Route, + selection_cfg: &RouteSelectionCfg, + mpath_cfg: &MultipathCfg, +) -> BTreeSet +where + A: AddressFamily, +{ + // If multipath isn't enabled, return the nexthop of the best route. + if !mpath_cfg.enabled { + return [A::nexthop_rx_extract(&best_route.attrs.base.value)].into(); + } + + // Otherwise, return as many ECMP nexthops as allowed by the configuration. + let max_paths = match best_route.route_type { + RouteType::Internal => mpath_cfg.ibgp_max_paths, + RouteType::External => mpath_cfg.ebgp_max_paths, + }; + dest.adj_in_post + .values() + .filter(|route| { + route.eligible + && route.compare(best_route, selection_cfg, Some(mpath_cfg)) + == RouteCompare::MultipathEqual + }) + .map(|route| A::nexthop_rx_extract(&route.attrs.base.value)) + .take(max_paths as usize) + .collect() +} + +// ===== global functions ===== + +pub(crate) fn best_path( + dest: &mut Destination, + local_asn: u32, + selection_cfg: &RouteSelectionCfg, +) -> Option { + let mut best_route = None; + + // Iterate over each post-policy Adj-RIB-In route for the destination. + for adj_in_route in dest.adj_in_post.values_mut() { + adj_in_route.eligible = true; + adj_in_route.reject_reason = None; + adj_in_route.ineligible_reason = None; + + // First, check if the route is eligible. + if adj_in_route.attrs.base.value.as_path.contains(local_asn) { + adj_in_route.eligible = false; + adj_in_route.ineligible_reason = + Some(RouteIneligibleReason::AsLoop); + continue; + } + + // Compare the current route with the best route found so far. + match &mut best_route { + None => { + // Initialize the best route with the first eligible route. + best_route = Some(adj_in_route) + } + Some(best_route) => { + // Update the best route if the current route is preferred. + match adj_in_route.compare(best_route, selection_cfg, None) { + RouteCompare::Preferred(reason) => { + best_route.reject_reason = Some(reason); + *best_route = adj_in_route; + } + RouteCompare::LessPreferred(reason) => { + adj_in_route.reject_reason = Some(reason); + } + RouteCompare::MultipathEqual + | RouteCompare::MultipathDifferent => unreachable!(), + } + } + } + } + + // Return a cloned copy of the best route found, if any. + best_route.cloned() +} + +pub(crate) fn loc_rib_update( + prefix: A::IpNetwork, + dest: &mut Destination, + best_route: Option, + selection_cfg: &RouteSelectionCfg, + mpath_cfg: &MultipathCfg, + distance_cfg: &DistanceCfg, + ibus_tx: &IbusSender, +) where + A: AddressFamily, +{ + if let Some(best_route) = best_route { + Debug::BestPathFound(prefix.into(), &best_route).log(); + + // Compute route nexthops, considering multipath configuration. + let nexthops = + compute_nexthops::(dest, &best_route, selection_cfg, mpath_cfg); + + // Return early if no change in Loc-RIB is needed. + if let Some((loc_route, loc_nexthops)) = &dest.local + && *loc_route == best_route + && *loc_nexthops == nexthops + { + return; + } + + // Install route in the global RIB. + southbound::tx::route_install( + ibus_tx, + prefix, + &best_route, + &nexthops, + match best_route.route_type { + RouteType::Internal => distance_cfg.internal, + RouteType::External => distance_cfg.external, + }, + ); + + // Insert route into the Loc-RIB. + dest.local = Some((best_route, nexthops)); + } else { + Debug::BestPathNotFound(prefix.into()).log(); + + // Return early if no change in Loc-RIB is needed. + if dest.local.is_none() { + return; + } + + // Uninstall route from the global RIB. + southbound::tx::route_uninstall(ibus_tx, prefix); + + // Remove route from the Loc-RIB. + dest.local = None; + } +} diff --git a/holo-bgp/src/southbound/mod.rs b/holo-bgp/src/southbound/mod.rs new file mode 100644 index 00000000..8bd8abf6 --- /dev/null +++ b/holo-bgp/src/southbound/mod.rs @@ -0,0 +1,8 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +pub mod rx; +pub mod tx; diff --git a/holo-bgp/src/southbound/rx.rs b/holo-bgp/src/southbound/rx.rs new file mode 100644 index 00000000..bb652e1c --- /dev/null +++ b/holo-bgp/src/southbound/rx.rs @@ -0,0 +1,19 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::Ipv4Addr; + +use crate::instance::Instance; + +// ===== global functions ===== + +pub(crate) async fn process_router_id_update( + instance: &mut Instance, + router_id: Option, +) { + instance.system.router_id = router_id; + instance.update().await; +} diff --git a/holo-bgp/src/southbound/tx.rs b/holo-bgp/src/southbound/tx.rs new file mode 100644 index 00000000..42fbf396 --- /dev/null +++ b/holo-bgp/src/southbound/tx.rs @@ -0,0 +1,68 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::BTreeSet; +use std::net::IpAddr; + +use holo_utils::ibus::{IbusMsg, IbusSender}; +use holo_utils::protocol::Protocol; +use holo_utils::southbound::{ + Nexthop, RouteKeyMsg, RouteMsg, RouteOpaqueAttrs, +}; +use ipnetwork::IpNetwork; + +use crate::rib::Route; + +// ===== global functions ===== + +pub(crate) fn router_id_query(ibus_tx: &IbusSender) { + let _ = ibus_tx.send(IbusMsg::RouterIdQuery); +} + +pub(crate) fn route_install( + ibus_tx: &IbusSender, + prefix: impl Into, + route: &Route, + nexthops: &BTreeSet, + distance: u8, +) { + // Fill-in nexthops. + let nexthops = nexthops + .iter() + .map(|nexthop| Nexthop::Address { + // TODO + ifindex: 0, + addr: *nexthop, + labels: vec![], + }) + .collect::>(); + + // Install route. + let msg = RouteMsg { + protocol: Protocol::BGP, + prefix: prefix.into(), + distance: distance.into(), + metric: route.attrs.base.value.med.unwrap_or(0), + tag: None, + opaque_attrs: RouteOpaqueAttrs::None, + nexthops: nexthops.clone(), + }; + let msg = IbusMsg::RouteIpAdd(msg); + let _ = ibus_tx.send(msg); +} + +pub(crate) fn route_uninstall( + ibus_tx: &IbusSender, + prefix: impl Into, +) { + // Uninstall route. + let msg = RouteKeyMsg { + protocol: Protocol::BGP, + prefix: prefix.into(), + }; + let msg = IbusMsg::RouteIpDel(msg); + let _ = ibus_tx.send(msg); +} diff --git a/holo-bgp/src/tasks.rs b/holo-bgp/src/tasks.rs new file mode 100644 index 00000000..533240a3 --- /dev/null +++ b/holo-bgp/src/tasks.rs @@ -0,0 +1,479 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::sync::{atomic, Arc}; +use std::time::Duration; + +use holo_utils::socket::{OwnedReadHalf, OwnedWriteHalf, TcpListener}; +use holo_utils::task::{IntervalTask, Task, TimeoutTask}; +use holo_utils::{Sender, UnboundedReceiver, UnboundedSender}; +use tokio::time::sleep; +use tracing::{debug_span, Instrument}; + +use crate::debug::Debug; +use crate::neighbor::{fsm, Neighbor}; +use crate::packet::message::{DecodeCxt, EncodeCxt, KeepaliveMsg, Message}; +use crate::{network, policy}; + +// +// BGP tasks diagram: +// +--------------+ +// | northbound | +// +--------------+ +// | ^ +// | | +// northbound_rx (1x) V | (1x) northbound_tx +// +--------------+ +// | | +// tcp_listener (1x) -> | | +// tcp_connect (Nx) -> | | -> (Nx) nbr_tx +// nbr_rx (Nx) -> | | -> (Nx) nbr_kalive_interval +// nbr_timer (Nx) -> | instance | +// | | +// policy_apply (Nx) -> | | -> (Nx) policy_apply +// schedule_decision_process (0/1x) -> | | +// | | +// +--------------+ +// ibus_tx (1x) | ^ (1x) ibus_rx +// | | +// V | +// +--------------+ +// | ibus | +// +--------------+ +// + +// BGP inter-task message types. +pub mod messages { + use std::collections::BTreeSet; + use std::net::IpAddr; + use std::sync::Arc; + + use holo_utils::bgp::AfiSafi; + use holo_utils::policy::{ + DefaultPolicyType, MatchSets, Policy, PolicyResult, PolicyType, + }; + use holo_utils::socket::{TcpConnInfo, TcpStream}; + use ipnetwork::IpNetwork; + use serde::{Deserialize, Serialize}; + + use crate::error::NbrRxError; + use crate::neighbor::fsm; + use crate::packet::message::{Capability, Message}; + use crate::policy::RoutePolicyInfo; + + // 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, Deserialize, Serialize)] + pub enum ProtocolMsg { + TcpAccept(TcpAcceptMsg), + TcpConnect(TcpConnectMsg), + NbrRx(NbrRxMsg), + NbrTimer(NbrTimerMsg), + PolicyResult(PolicyResultMsg), + TriggerDecisionProcess(()), + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct TcpAcceptMsg { + #[serde(skip)] + pub stream: Option, + pub conn_info: TcpConnInfo, + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct TcpConnectMsg { + #[serde(skip)] + pub stream: Option, + pub conn_info: TcpConnInfo, + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct NbrRxMsg { + pub nbr_addr: IpAddr, + pub msg: Result, + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct NbrTimerMsg { + pub nbr_addr: IpAddr, + pub timer: fsm::Timer, + } + + #[derive(Debug, Deserialize, Serialize)] + pub enum PolicyResultMsg { + Neighbor { + policy_type: PolicyType, + nbr_addr: IpAddr, + afi_safi: AfiSafi, + routes: Vec<(IpNetwork, PolicyResult)>, + }, + } + + impl TcpAcceptMsg { + pub(crate) fn stream(&mut self) -> TcpStream { + #[cfg(not(feature = "testing"))] + { + self.stream.take().unwrap() + } + #[cfg(feature = "testing")] + { + Default::default() + } + } + } + + impl TcpConnectMsg { + pub(crate) fn stream(&mut self) -> TcpStream { + #[cfg(not(feature = "testing"))] + { + self.stream.take().unwrap() + } + #[cfg(feature = "testing")] + { + Default::default() + } + } + } + } + + // Output messages (main task -> child task). + pub mod output { + use super::*; + + #[derive(Debug, Serialize)] + pub enum ProtocolMsg { + NbrTx(NbrTxMsg), + PolicyApply(PolicyApplyMsg), + } + + #[derive(Debug, Serialize)] + pub enum NbrTxMsg { + SendMessage { + nbr_addr: IpAddr, + msg: Message, + }, + SendMessageList { + nbr_addr: IpAddr, + msg_list: Vec, + }, + UpdateCapabilities(BTreeSet), + } + + #[derive(Debug, Serialize)] + pub enum PolicyApplyMsg { + Neighbor { + policy_type: PolicyType, + nbr_addr: IpAddr, + afi_safi: AfiSafi, + routes: Vec<(IpNetwork, RoutePolicyInfo)>, + policies: Vec>, + match_sets: Arc, + default_policy: DefaultPolicyType, + }, + } + } +} + +// ===== BGP tasks ===== + +// TCP listening task. +pub(crate) fn tcp_listener( + session_socket: &Arc, + tcp_acceptp: &Sender, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span1 = debug_span!("session"); + let _span1_guard = span1.enter(); + let span2 = debug_span!("input"); + let _span2_guard = span2.enter(); + + let session_socket = session_socket.clone(); + let tcp_acceptp = tcp_acceptp.clone(); + Task::spawn( + async move { + let _ = network::listen_loop(session_socket, tcp_acceptp).await; + } + .in_current_span(), + ) + } + #[cfg(feature = "testing")] + { + Task::spawn(async move { std::future::pending().await }) + } +} + +// TCP connect task. +pub(crate) fn tcp_connect( + nbr: &Neighbor, + tcp_connectp: &Sender, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span = debug_span!("neighbor", addr = %nbr.remote_addr); + let _span_guard = span.enter(); + + let remote_addr = nbr.remote_addr; + let local_addr = nbr.config.transport.local_addr; + let ttl = nbr.tx_ttl(); + let ttl_security = nbr.config.transport.ttl_security; + let tcp_mss = nbr.config.transport.tcp_mss; + let tcp_password = nbr.config.transport.md5_key.clone(); + let tcp_connectp = tcp_connectp.clone(); + Task::spawn( + async move { + loop { + let result = network::connect( + remote_addr, + local_addr, + ttl, + ttl_security, + tcp_mss, + &tcp_password, + ) + .await; + + match result { + Ok((stream, conn_info)) => { + // Send message to the parent BGP task. + let msg = messages::input::TcpConnectMsg { + stream: Some(stream), + conn_info, + }; + let _ = tcp_connectp.send(msg).await; + return; + } + Err(error) => { + error.log(); + // Wait one second before trying again. + sleep(Duration::from_secs(1)).await; + } + } + } + } + .in_current_span(), + ) + } + #[cfg(feature = "testing")] + { + Task::spawn(async move { std::future::pending().await }) + } +} + +// Neighbor TCP Rx task. +pub(crate) fn nbr_rx( + nbr: &Neighbor, + cxt: DecodeCxt, + read_half: OwnedReadHalf, + nbr_msg_rxp: &Sender, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span1 = debug_span!("neighbor", addr = %nbr.remote_addr); + let _span1_guard = span1.enter(); + let span2 = debug_span!("input"); + let _span2_guard = span2.enter(); + + let nbr_addr = nbr.remote_addr; + let nbr_msg_rxp = nbr_msg_rxp.clone(); + Task::spawn( + async move { + let _ = network::nbr_read_loop( + read_half, + nbr_addr, + cxt, + nbr_msg_rxp, + ) + .await; + } + .in_current_span(), + ) + } + #[cfg(feature = "testing")] + { + Task::spawn(async move { std::future::pending().await }) + } +} + +// Neighbor TCP Tx task. +#[cfg_attr(not(feature = "testing"), allow(unused_mut))] +pub(crate) fn nbr_tx( + nbr: &Neighbor, + cxt: EncodeCxt, + write_half: OwnedWriteHalf, + mut msg_txc: UnboundedReceiver, + #[cfg(feature = "testing")] proto_output_tx: &Sender< + messages::ProtocolOutputMsg, + >, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let span1 = debug_span!("neighbor", addr = %nbr.remote_addr); + let _span1_guard = span1.enter(); + let span2 = debug_span!("output"); + let _span2_guard = span2.enter(); + + Task::spawn( + async move { + network::nbr_write_loop(write_half, cxt, msg_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) = msg_txc.recv().await { + let msg = messages::ProtocolOutputMsg::NbrTx(msg); + let _ = proto_output_tx.send(msg).await; + } + }) + } +} + +// Neighbor timer task. +pub(crate) fn nbr_timer( + nbr: &Neighbor, + timer: fsm::Timer, + seconds: u16, + nbr_timerp: &Sender, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let nbr_timerp = nbr_timerp.clone(); + let nbr_addr = nbr.remote_addr; + + TimeoutTask::new( + Duration::from_secs(seconds.into()), + move || async move { + let msg = messages::input::NbrTimerMsg { nbr_addr, timer }; + let _ = nbr_timerp.send(msg).await; + }, + ) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} + +// Send periodic keepalive messages. +pub(crate) fn nbr_kalive_interval( + nbr: &Neighbor, + interval: u16, +) -> IntervalTask { + #[cfg(not(feature = "testing"))] + { + let msg_txp = nbr.msg_txp.as_ref().unwrap().clone(); + let nbr_addr = nbr.remote_addr; + let msg_counter = nbr.statistics.msgs_sent.total.clone(); + + IntervalTask::new( + Duration::from_secs(interval.into()), + false, + move || { + let msg_txp = msg_txp.clone(); + let msg_counter = msg_counter.clone(); + + async move { + let msg = Message::Keepalive(KeepaliveMsg {}); + Debug::NbrMsgTx(&nbr_addr, &msg).log(); + + let msg = messages::output::NbrTxMsg::SendMessage { + nbr_addr, + msg, + }; + let _ = msg_txp.send(msg); + msg_counter.fetch_add(1, atomic::Ordering::Relaxed); + } + }, + ) + } + #[cfg(feature = "testing")] + { + IntervalTask {} + } +} + +// Policy processing task. +#[cfg_attr(not(feature = "testing"), allow(unused_mut))] +pub(crate) fn policy_apply( + policy_applyc: crossbeam_channel::Receiver< + messages::output::PolicyApplyMsg, + >, + policy_resultp: &UnboundedSender, + #[cfg(feature = "testing")] proto_output_tx: &Sender< + messages::ProtocolOutputMsg, + >, +) -> Task<()> { + #[cfg(not(feature = "testing"))] + { + let policy_resultp = policy_resultp.clone(); + Task::spawn_blocking(move || { + while let Ok(msg) = policy_applyc.recv() { + match msg { + messages::output::PolicyApplyMsg::Neighbor { + policy_type, + nbr_addr, + afi_safi, + routes, + policies, + match_sets, + default_policy, + } => { + policy::neighbor_apply( + policy_type, + nbr_addr, + afi_safi, + routes, + &policies, + &match_sets, + default_policy, + &policy_resultp, + ); + } + } + } + }) + } + #[cfg(feature = "testing")] + { + let proto_output_tx = proto_output_tx.clone(); + Task::spawn(async move { + // Relay message to the test framework. + while let Ok(msg) = policy_applyc.recv() { + let msg = messages::ProtocolOutputMsg::PolicyApply(msg); + let _ = proto_output_tx.send(msg).await; + } + }) + } +} + +// Timeout to trigger the decision process. +pub(crate) fn schedule_decision_process( + decision_processp: &Sender<()>, +) -> TimeoutTask { + #[cfg(not(feature = "testing"))] + { + let decision_processp = decision_processp.clone(); + let timeout = Duration::from_millis(100); + TimeoutTask::new(timeout, move || async move { + let _ = decision_processp.send(()).await; + }) + } + #[cfg(feature = "testing")] + { + TimeoutTask {} + } +} diff --git a/holo-bgp/tests/mod.rs b/holo-bgp/tests/mod.rs new file mode 100644 index 00000000..982a3849 --- /dev/null +++ b/holo-bgp/tests/mod.rs @@ -0,0 +1,9 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +#![feature(lazy_cell)] + +mod packet; diff --git a/holo-bgp/tests/packet/keepalive.rs b/holo-bgp/tests/packet/keepalive.rs new file mode 100644 index 00000000..9218733b --- /dev/null +++ b/holo-bgp/tests/packet/keepalive.rs @@ -0,0 +1,33 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::sync::LazyLock as Lazy; + +use holo_bgp::packet::message::{KeepaliveMsg, Message}; + +use super::{test_decode_msg, test_encode_msg}; + +static KEEPALIVE1: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x13, 0x04, + ], + Message::Keepalive(KeepaliveMsg {}), + ) +}); + +#[test] +fn test_encode_keepalive1() { + let (ref bytes, ref msg) = *KEEPALIVE1; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_keepalive1() { + let (ref bytes, ref msg) = *KEEPALIVE1; + test_decode_msg(bytes, msg); +} diff --git a/holo-bgp/tests/packet/mod.rs b/holo-bgp/tests/packet/mod.rs new file mode 100644 index 00000000..4e082c59 --- /dev/null +++ b/holo-bgp/tests/packet/mod.rs @@ -0,0 +1,48 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +mod keepalive; +mod notification; +mod open; +mod route_refresh; +mod update; + +use holo_bgp::neighbor::PeerType; +use holo_bgp::packet::message::{ + Capability, DecodeCxt, EncodeCxt, FourOctetAsNumber, Message, +}; + +// +// Helper functions. +// + +fn test_encode_msg(bytes_expected: &[u8], msg: &Message) { + let cxt = EncodeCxt { + capabilities: [Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(65550), + }] + .into(), + }; + + let bytes_actual = msg.encode(&cxt); + 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_msg(bytes: &[u8], msg_expected: &Message) { + let cxt = DecodeCxt { + peer_type: PeerType::Internal, + peer_as: 65550, + capabilities: [Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(65550), + }] + .into(), + }; + + let msg_actual = Message::decode(&bytes, &cxt).unwrap(); + assert_eq!(*msg_expected, msg_actual); +} diff --git a/holo-bgp/tests/packet/notification.rs b/holo-bgp/tests/packet/notification.rs new file mode 100644 index 00000000..54e835d1 --- /dev/null +++ b/holo-bgp/tests/packet/notification.rs @@ -0,0 +1,39 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::sync::LazyLock as Lazy; + +use holo_bgp::packet::consts::{ErrorCode, MessageHeaderErrorSubcode}; +use holo_bgp::packet::message::{Message, NotificationMsg}; + +use super::{test_decode_msg, test_encode_msg}; + +static NOTIFICATION1: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x17, 0x03, 0x01, 0x02, 0xff, + 0xff, + ], + Message::Notification(NotificationMsg { + error_code: ErrorCode::MessageHeaderError as u8, + error_subcode: MessageHeaderErrorSubcode::BadMessageLength as u8, + data: vec![0xff, 0xff], + }), + ) +}); + +#[test] +fn test_encode_notification1() { + let (ref bytes, ref msg) = *NOTIFICATION1; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_notification1() { + let (ref bytes, ref msg) = *NOTIFICATION1; + test_decode_msg(bytes, msg); +} diff --git a/holo-bgp/tests/packet/open.rs b/holo-bgp/tests/packet/open.rs new file mode 100644 index 00000000..f10849ac --- /dev/null +++ b/holo-bgp/tests/packet/open.rs @@ -0,0 +1,126 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::Ipv4Addr; +use std::str::FromStr; +use std::sync::LazyLock as Lazy; + +use holo_bgp::packet::consts::{Afi, Safi, BGP_VERSION}; +use holo_bgp::packet::message::{ + Capability, FourOctetAsNumber, Message, OpenMsg, +}; + +use super::{test_decode_msg, test_encode_msg}; + +static OPEN1: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x00, 0x01, + 0x00, 0xb4, 0x01, 0x01, 0x01, 0x01, 0x00, + ], + Message::Open(OpenMsg { + version: BGP_VERSION, + my_as: 1, + holdtime: 180, + identifier: Ipv4Addr::from_str("1.1.1.1").unwrap(), + capabilities: [].into(), + }), + ) +}); + +static OPEN2: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x25, 0x01, 0x04, 0x00, 0x01, + 0x00, 0xb4, 0x01, 0x01, 0x01, 0x01, 0x08, 0x02, 0x06, 0x01, 0x04, + 0x00, 0x01, 0x00, 0x01, + ], + Message::Open(OpenMsg { + version: BGP_VERSION, + my_as: 1, + holdtime: 180, + identifier: Ipv4Addr::from_str("1.1.1.1").unwrap(), + capabilities: [Capability::MultiProtocol { + afi: Afi::Ipv4, + safi: Safi::Unicast, + }] + .into(), + }), + ) +}); + +static OPEN3: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3d, 0x01, 0x04, 0x00, 0x01, + 0x00, 0xb4, 0x01, 0x01, 0x01, 0x01, 0x20, 0x02, 0x06, 0x01, 0x04, + 0x00, 0x01, 0x00, 0x01, 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, + 0x01, 0x02, 0x06, 0x41, 0x04, 0x00, 0x01, 0x00, 0x0e, 0x02, 0x02, + 0x02, 0x00, 0x02, 0x02, 0x46, 0x00, + ], + Message::Open(OpenMsg { + version: BGP_VERSION, + my_as: 1, + holdtime: 180, + identifier: Ipv4Addr::from_str("1.1.1.1").unwrap(), + capabilities: [ + Capability::MultiProtocol { + afi: Afi::Ipv4, + safi: Safi::Unicast, + }, + Capability::MultiProtocol { + afi: Afi::Ipv6, + safi: Safi::Unicast, + }, + Capability::FourOctetAsNumber { + asn: FourOctetAsNumber(65550), + }, + Capability::RouteRefresh, + Capability::EnhancedRouteRefresh, + ] + .into(), + }), + ) +}); + +#[test] +fn test_encode_open1() { + let (ref bytes, ref msg) = *OPEN1; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_open1() { + let (ref bytes, ref msg) = *OPEN1; + test_decode_msg(bytes, msg); +} + +#[test] +fn test_encode_open2() { + let (ref bytes, ref msg) = *OPEN2; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_open2() { + let (ref bytes, ref msg) = *OPEN2; + test_decode_msg(bytes, msg); +} + +#[test] +fn test_encode_open3() { + let (ref bytes, ref msg) = *OPEN3; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_open3() { + let (ref bytes, ref msg) = *OPEN3; + test_decode_msg(bytes, msg); +} diff --git a/holo-bgp/tests/packet/route_refresh.rs b/holo-bgp/tests/packet/route_refresh.rs new file mode 100644 index 00000000..57e3851b --- /dev/null +++ b/holo-bgp/tests/packet/route_refresh.rs @@ -0,0 +1,38 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::sync::LazyLock as Lazy; + +use holo_bgp::packet::consts::{Afi, Safi}; +use holo_bgp::packet::message::{Message, RouteRefreshMsg}; + +use super::{test_decode_msg, test_encode_msg}; + +static ROUTE_REFRESH1: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x17, 0x05, 0x00, 0x01, 0x00, + 0x01, + ], + Message::RouteRefresh(RouteRefreshMsg { + afi: Afi::Ipv4 as u16, + safi: Safi::Unicast as u8, + }), + ) +}); + +#[test] +fn test_encode_route_refresh1() { + let (ref bytes, ref msg) = *ROUTE_REFRESH1; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_route_refresh1() { + let (ref bytes, ref msg) = *ROUTE_REFRESH1; + test_decode_msg(bytes, msg); +} diff --git a/holo-bgp/tests/packet/update.rs b/holo-bgp/tests/packet/update.rs new file mode 100644 index 00000000..197f1678 --- /dev/null +++ b/holo-bgp/tests/packet/update.rs @@ -0,0 +1,166 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; +use std::sync::LazyLock as Lazy; + +use holo_bgp::packet::attribute::{ + Aggregator, AsPath, AsPathSegment, Attrs, BaseAttrs, ClusterList, CommList, +}; +use holo_bgp::packet::consts::{AsPathSegmentType, Origin}; +use holo_bgp::packet::message::{ + Message, MpReachNlri, MpUnreachNlri, ReachNlri, UnreachNlri, UpdateMsg, +}; +use holo_utils::bgp::{Comm, ExtComm, Extv6Comm, LargeComm}; +use ipnetwork::{Ipv4Network, Ipv6Network}; + +use super::{test_decode_msg, test_encode_msg}; + +static UPDATE1: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00, + 0x00, + ], + Message::Update(UpdateMsg { + reach: None, + unreach: None, + mp_reach: None, + mp_unreach: None, + attrs: None, + }), + ) +}); + +static UPDATE2: Lazy<(Vec, Message)> = Lazy::new(|| { + ( + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x29, 0x02, 0x00, 0x08, 0x18, + 0x0a, 0x00, 0x01, 0x18, 0x0a, 0x00, 0x02, 0x01, 0x00, 0x90, 0x0e, + 0x00, 0x47, 0x00, 0x02, 0x01, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x07, 0xbd, + 0x19, 0x11, 0x1c, 0x84, 0x11, 0x00, 0x80, 0x20, 0x01, 0x0d, 0xb8, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x80, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x90, 0x0f, 0x00, 0x25, + 0x00, 0x02, 0x01, 0x80, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x20, + 0x01, 0x0d, 0xb8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x40, 0x01, 0x01, 0x00, 0x50, 0x02, 0x00, + 0x0e, 0x02, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, 0x40, 0x03, 0x04, 0x01, 0x01, 0x01, 0x01, + 0x80, 0x04, 0x04, 0x00, 0x00, 0x01, 0xf4, 0x40, 0x05, 0x04, 0x00, + 0x00, 0x01, 0xf4, 0x40, 0x06, 0x00, 0xc0, 0x07, 0x08, 0x00, 0x00, + 0x03, 0xe8, 0x02, 0x02, 0x02, 0x02, 0xd0, 0x08, 0x00, 0x0c, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, + 0x80, 0x09, 0x04, 0x01, 0x01, 0x01, 0x01, 0x90, 0x0a, 0x00, 0x04, + 0x03, 0x03, 0x03, 0x03, 0xd0, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0xd0, 0x19, 0x00, 0x14, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xd0, 0x20, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x20, 0x0a, 0x00, 0xff, 0x01, 0x20, 0x0a, 0x00, 0xff, 0x02, + ], + Message::Update(UpdateMsg { + reach: Some(ReachNlri { + prefixes: vec![ + Ipv4Network::from_str("10.0.255.1/32").unwrap(), + Ipv4Network::from_str("10.0.255.2/32").unwrap(), + ], + nexthop: Ipv4Addr::from_str("1.1.1.1").unwrap(), + }), + unreach: Some(UnreachNlri { + prefixes: vec![ + Ipv4Network::from_str("10.0.1.0/24").unwrap(), + Ipv4Network::from_str("10.0.2.0/24").unwrap(), + ], + }), + mp_reach: Some(MpReachNlri::Ipv6Unicast { + prefixes: vec![ + Ipv6Network::from_str("2001:db8:1::1/128").unwrap(), + Ipv6Network::from_str("2001:db8:1::2/128").unwrap(), + ], + nexthop: Ipv6Addr::from_str("3000::1").unwrap(), + ll_nexthop: Some( + Ipv6Addr::from_str("fe80::4207:bd19:111c:8411").unwrap(), + ), + }), + mp_unreach: Some(MpUnreachNlri::Ipv6Unicast { + prefixes: vec![ + Ipv6Network::from_str("2001:db8:2::1/128").unwrap(), + Ipv6Network::from_str("2001:db8:2::2/128").unwrap(), + ], + }), + attrs: Some(Attrs { + base: BaseAttrs { + origin: Origin::Igp, + as_path: AsPath { + segments: [AsPathSegment { + seg_type: AsPathSegmentType::Sequence, + members: [1, 2, 3].into(), + }] + .into(), + }, + as4_path: None, + nexthop: None, + ll_nexthop: None, + med: Some(500), + local_pref: Some(500), + aggregator: Some(Aggregator { + asn: 1000, + identifier: Ipv4Addr::from_str("2.2.2.2").unwrap(), + }), + as4_aggregator: None, + atomic_aggregate: true, + originator_id: Some(Ipv4Addr::from_str("1.1.1.1").unwrap()), + cluster_list: Some(ClusterList( + [Ipv4Addr::from_str("3.3.3.3").unwrap()].into(), + )), + }, + comm: Some(CommList([Comm(1), Comm(2), Comm(3)].into())), + ext_comm: Some(CommList( + [ExtComm([0, 0, 0, 1, 0, 0, 0, 1])].into(), + )), + extv6_comm: Some(CommList( + [Extv6Comm(Ipv6Addr::from_str("2001:db8::1").unwrap(), 1)] + .into(), + )), + large_comm: Some(CommList( + [LargeComm([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1])].into(), + )), + unknown: vec![], + }), + }), + ) +}); + +#[test] +fn test_encode_update1() { + let (ref bytes, ref msg) = *UPDATE1; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_update1() { + let (ref bytes, ref msg) = *UPDATE1; + test_decode_msg(bytes, msg); +} + +#[test] +fn test_encode_update2() { + let (ref bytes, ref msg) = *UPDATE2; + test_encode_msg(bytes, msg); +} + +#[test] +fn test_decode_update2() { + let (ref bytes, ref msg) = *UPDATE2; + test_decode_msg(bytes, msg); +} diff --git a/holo-daemon/Cargo.toml b/holo-daemon/Cargo.toml index c37dd2c1..8521cb69 100644 --- a/holo-daemon/Cargo.toml +++ b/holo-daemon/Cargo.toml @@ -32,6 +32,7 @@ yang2.workspace = true holo-interface = { path = "../holo-interface" } holo-bfd = { path = "../holo-bfd", optional = true } +holo-bgp = { path = "../holo-bgp", optional = true } holo-keychain = { path = "../holo-keychain" } holo-ldp = { path = "../holo-ldp", optional = true } holo-northbound = { path = "../holo-northbound" } @@ -54,8 +55,9 @@ name = "holod" path = "src/main.rs" [features] -default = ["bfd", "ldp", "ospf", "rip"] +default = ["bfd", "bgp", "ldp", "ospf", "rip"] bfd = ["holo-bfd"] +bgp = ["holo-bgp"] ldp = ["holo-ldp"] ospf = ["holo-ospf"] rip = ["holo-rip"] diff --git a/holo-daemon/src/northbound/yang.rs b/holo-daemon/src/northbound/yang.rs index dc02f1e9..4b92ae84 100644 --- a/holo-daemon/src/northbound/yang.rs +++ b/holo-daemon/src/northbound/yang.rs @@ -18,8 +18,14 @@ pub(crate) fn create_context() { let mut modules = Vec::new(); // Add data type modules. - for module_name in ["iana-if-type", "ietf-routing-types", "ietf-bfd-types"] - { + for module_name in [ + "iana-if-type", + "iana-bgp-notification", + "iana-bgp-rib-types", + "iana-bgp-types", + "ietf-routing-types", + "ietf-bfd-types", + ] { modules.push(module_name); } @@ -35,6 +41,11 @@ pub(crate) fn create_context() { use holo_bfd::master::Master; modules_add::(&mut modules); } + #[cfg(feature = "bgp")] + { + use holo_bgp::instance::Instance; + modules_add::(&mut modules); + } #[cfg(feature = "ldp")] { use holo_ldp::instance::Instance; diff --git a/holo-policy/src/northbound/configuration.rs b/holo-policy/src/northbound/configuration.rs index cb7553a6..6a701267 100644 --- a/holo-policy/src/northbound/configuration.rs +++ b/holo-policy/src/northbound/configuration.rs @@ -16,10 +16,9 @@ use holo_northbound::paths::routing_policy; use holo_utils::ibus::IbusMsg; use holo_utils::ip::AddressFamily; use holo_utils::policy::{ - IpPrefixRange, MatchSetRestrictedType, MatchSetType, MetricModification, - MetricType, NeighborSet, Policy, PolicyAction, PolicyActionType, - PolicyCondition, PolicyConditionType, PolicyStmt, PrefixSet, RouteLevel, - RouteType, TagSet, + IpPrefixRange, MatchSetRestrictedType, MatchSetType, MetricType, + NeighborSet, Policy, PolicyAction, PolicyActionType, PolicyCondition, + PolicyConditionType, PolicyStmt, PrefixSet, RouteLevel, RouteType, TagSet, }; use holo_utils::protocol::Protocol; use holo_utils::yang::DataNodeRefExt; @@ -210,6 +209,114 @@ fn load_callbacks() -> Callbacks { let event_queue = args.event_queue; event_queue.insert(Event::MatchSetsUpdate); }) + .path(routing_policy::defined_sets::bgp_defined_sets::as_path_sets::as_path_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::as_path_sets::as_path_set::member::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::defined_sets::bgp_defined_sets::community_sets::community_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::community_sets::community_set::member::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ext_community_sets::ext_community_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ext_community_sets::ext_community_set::member::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ipv6_ext_community_sets::ipv6_ext_community_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ipv6_ext_community_sets::ipv6_ext_community_set::member::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::defined_sets::bgp_defined_sets::large_community_sets::large_community_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::large_community_sets::large_community_set::member::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::defined_sets::bgp_defined_sets::next_hop_sets::next_hop_set::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .lookup(|_master, _list_entry, _dnode| { + // TODO: implement me! + todo!(); + }) + .path(routing_policy::defined_sets::bgp_defined_sets::next_hop_sets::next_hop_set::next_hop::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) .path(routing_policy::policy_definitions::policy_definition::PATH) .create_apply(|master, args| { let name = args.dnode.get_string_relative("./name").unwrap(); @@ -455,6 +562,252 @@ fn load_callbacks() -> Callbacks { let event_queue = args.event_queue; event_queue.insert(Event::PolicyChange(policy.name.clone())); }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::local_pref::value::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::local_pref::eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::local_pref::lt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::local_pref::gt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::med::value::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::med::eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::med::lt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::med::gt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::origin_eq::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_afi_safi::afi_safi_in::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_afi_safi::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_neighbor::neighbor_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_neighbor::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::route_type::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::community_count::community_count::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::community_count::eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::community_count::lt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::community_count::gt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::as_path_length::as_path_length::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::as_path_length::eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::as_path_length::lt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::as_path_length::gt_or_eq::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_community_set::community_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_community_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ext_community_set::ext_community_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ext_community_set::ext_community_match_kind::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ext_community_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ipv6_ext_community_set::ipv6_ext_community_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ipv6_ext_community_set::ipv6_ext_community_match_kind::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_ipv6_ext_community_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_large_community_set::large_community_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_large_community_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_as_path_set::as_path_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_as_path_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_next_hop_set::next_hop_set::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_next_hop_set::match_set_options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::policy_result::PATH) .modify_apply(|master, args| { let (policy_name, stmt_name) = args.list_entry.into_policy_stmt().unwrap(); @@ -479,49 +832,18 @@ fn load_callbacks() -> Callbacks { event_queue.insert(Event::PolicyChange(policy.name.clone())); }) .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::set_metric::metric_modification::PATH) - .modify_apply(|master, args| { - let (policy_name, stmt_name) = args.list_entry.into_policy_stmt().unwrap(); - let policy = master.policies.get_mut(&policy_name).unwrap(); - let stmt = policy.stmts.get_mut(&stmt_name).unwrap(); - - let metric_mod = args.dnode.get_string(); - let metric_mod = MetricModification::try_from_yang(&metric_mod).unwrap(); - stmt.action_add(PolicyAction::SetMetricMod(metric_mod)); - - let event_queue = args.event_queue; - event_queue.insert(Event::PolicyChange(policy.name.clone())); + .modify_apply(|_master, _args| { + // TODO: implement me! }) - .delete_apply(|master, args| { - let (policy_name, stmt_name) = args.list_entry.into_policy_stmt().unwrap(); - let policy = master.policies.get_mut(&policy_name).unwrap(); - let stmt = policy.stmts.get_mut(&stmt_name).unwrap(); - - stmt.action_remove(PolicyActionType::SetMetricMod); - - let event_queue = args.event_queue; - event_queue.insert(Event::PolicyChange(policy.name.clone())); + .delete_apply(|_master, _args| { + // TODO: implement me! }) .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::set_metric::metric::PATH) - .modify_apply(|master, args| { - let (policy_name, stmt_name) = args.list_entry.into_policy_stmt().unwrap(); - let policy = master.policies.get_mut(&policy_name).unwrap(); - let stmt = policy.stmts.get_mut(&stmt_name).unwrap(); - - let metric = args.dnode.get_u32(); - stmt.action_add(PolicyAction::SetMetric(metric)); - - let event_queue = args.event_queue; - event_queue.insert(Event::PolicyChange(policy.name.clone())); + .modify_apply(|_master, _args| { + // TODO: implement me! }) - .delete_apply(|master, args| { - let (policy_name, stmt_name) = args.list_entry.into_policy_stmt().unwrap(); - let policy = master.policies.get_mut(&policy_name).unwrap(); - let stmt = policy.stmts.get_mut(&stmt_name).unwrap(); - - stmt.action_remove(PolicyActionType::SetMetric); - - let event_queue = args.event_queue; - event_queue.insert(Event::PolicyChange(policy.name.clone())); + .delete_apply(|_master, _args| { + // TODO: implement me! }) .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::set_metric_type::metric_type::PATH) .modify_apply(|master, args| { @@ -637,6 +959,132 @@ fn load_callbacks() -> Callbacks { let event_queue = args.event_queue; event_queue.insert(Event::PolicyChange(policy.name.clone())); }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_route_origin::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_local_pref::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_next_hop::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_med::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_as_path_prepend::repeat_n::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_as_path_prepend::asn::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_community::options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_community::communities::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_community::community_set_ref::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ext_community::options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ext_community::communities::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ext_community::ext_community_set_ref::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ipv6_ext_community::options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ipv6_ext_community::communities::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ipv6_ext_community::ipv6_ext_community_set_ref::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_large_community::options::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_large_community::communities::PATH) + .create_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_large_community::large_community_set_ref::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // TODO: implement me! + }) .build() } diff --git a/holo-policy/src/northbound/mod.rs b/holo-policy/src/northbound/mod.rs index 322ebe1e..e9251e1f 100644 --- a/holo-policy/src/northbound/mod.rs +++ b/holo-policy/src/northbound/mod.rs @@ -17,7 +17,7 @@ use crate::Master; impl ProviderBase for Master { fn yang_modules() -> &'static [&'static str] { - &["ietf-routing-policy"] + &["ietf-routing-policy", "ietf-bgp-policy"] } fn top_level_node(&self) -> String { diff --git a/holo-policy/src/northbound/state.rs b/holo-policy/src/northbound/state.rs index 94e2fdb7..fd578557 100644 --- a/holo-policy/src/northbound/state.rs +++ b/holo-policy/src/northbound/state.rs @@ -56,6 +56,66 @@ fn load_callbacks() -> Callbacks { // No operational data under this list. None }) + .path(routing_policy::defined_sets::bgp_defined_sets::as_path_sets::as_path_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::as_path_sets::as_path_set::member::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::community_sets::community_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::community_sets::community_set::member::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ext_community_sets::ext_community_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ext_community_sets::ext_community_set::member::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ipv6_ext_community_sets::ipv6_ext_community_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::ipv6_ext_community_sets::ipv6_ext_community_set::member::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::large_community_sets::large_community_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::large_community_sets::large_community_set::member::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::next_hop_sets::next_hop_set::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::defined_sets::bgp_defined_sets::next_hop_sets::next_hop_set::next_hop::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) .path(routing_policy::policy_definitions::policy_definition::PATH) .get_iterate(|_master, _args| { // No operational data under this list. @@ -71,6 +131,41 @@ fn load_callbacks() -> Callbacks { // No operational data under this list. None }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_afi_safi::afi_safi_in::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::conditions::bgp_conditions::match_neighbor::neighbor_eq::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_as_path_prepend::asn::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_community::communities::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ext_community::communities::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_ipv6_ext_community::communities::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) + .path(routing_policy::policy_definitions::policy_definition::statements::statement::actions::bgp_actions::set_large_community::communities::PATH) + .get_iterate(|_master, _args| { + // No operational data under this list. + None + }) .build() } diff --git a/holo-protocol/src/lib.rs b/holo-protocol/src/lib.rs index 4e0a78ec..7e41c9c6 100644 --- a/holo-protocol/src/lib.rs +++ b/holo-protocol/src/lib.rs @@ -190,7 +190,11 @@ where InstanceMsg::Northbound(msg) } msg = instance_channels_rx.ibus.recv() => { - InstanceMsg::Ibus(msg.unwrap()) + if let Ok(msg) = msg { + InstanceMsg::Ibus(msg) + } else { + continue; + } } msg = instance_channels_rx.protocol_input.recv() => { InstanceMsg::Protocol(msg.unwrap()) diff --git a/holo-routing/Cargo.toml b/holo-routing/Cargo.toml index 5209204c..cca0e03f 100644 --- a/holo-routing/Cargo.toml +++ b/holo-routing/Cargo.toml @@ -22,6 +22,7 @@ tracing.workspace = true yang2.workspace = true holo-bfd = { path = "../holo-bfd" } +holo-bgp = { path = "../holo-bgp" } holo-ldp = { path = "../holo-ldp" } holo-northbound = { path = "../holo-northbound" } holo-ospf = { path = "../holo-ospf" } diff --git a/holo-routing/src/northbound/configuration.rs b/holo-routing/src/northbound/configuration.rs index 99f271be..9a0ce0b8 100644 --- a/holo-routing/src/northbound/configuration.rs +++ b/holo-routing/src/northbound/configuration.rs @@ -660,6 +660,7 @@ impl Provider for Master { fn nested_callbacks() -> Option> { let keys = [ holo_bfd::northbound::configuration::CALLBACKS.keys(), + holo_bgp::northbound::configuration::CALLBACKS.keys(), holo_ldp::northbound::configuration::CALLBACKS.keys(), holo_ospf::northbound::configuration::CALLBACKS_OSPFV2.keys(), holo_ospf::northbound::configuration::CALLBACKS_OSPFV3.keys(), @@ -717,6 +718,18 @@ impl Provider for Master { // Nothing to do, the BFD task runs permanently. return; } + Protocol::BGP => { + use holo_bgp::instance::Instance; + + spawn_protocol_task::( + name, + &self.nb_tx, + &self.ibus_tx, + Default::default(), + self.shared.clone(), + Some(event_recorder_config), + ) + } Protocol::DIRECT => { // This protocol type can not be configured. unreachable!() diff --git a/holo-routing/src/northbound/rpc.rs b/holo-routing/src/northbound/rpc.rs index c15368b9..93068a27 100644 --- a/holo-routing/src/northbound/rpc.rs +++ b/holo-routing/src/northbound/rpc.rs @@ -15,6 +15,7 @@ use crate::Master; impl Provider for Master { fn nested_callbacks() -> Option> { let keys = [ + holo_bgp::northbound::rpc::CALLBACKS.keys(), holo_ldp::northbound::rpc::CALLBACKS.keys(), holo_ospf::northbound::rpc::CALLBACKS_OSPFV2.keys(), holo_ospf::northbound::rpc::CALLBACKS_OSPFV3.keys(), @@ -62,6 +63,9 @@ fn find_instance( rpc: DataNodeRef<'_>, ) -> Result<(Protocol, Option), String> { let (protocol, name) = match rpc.schema().module().name() { + "ietf-bgp" => { + todo!() + } "ietf-mpls-ldp" => { let protocol = Protocol::LDP; let name = match rpc.path().as_ref() { diff --git a/holo-routing/src/northbound/state.rs b/holo-routing/src/northbound/state.rs index 723824f3..fa6ee04c 100644 --- a/holo-routing/src/northbound/state.rs +++ b/holo-routing/src/northbound/state.rs @@ -393,6 +393,7 @@ impl Provider for Master { fn nested_callbacks() -> Option> { let keys = [ holo_bfd::northbound::state::CALLBACKS.keys(), + holo_bgp::northbound::state::CALLBACKS.keys(), holo_ldp::northbound::state::CALLBACKS.keys(), holo_ospf::northbound::state::CALLBACKS_OSPFV2.keys(), holo_ospf::northbound::state::CALLBACKS_OSPFV3.keys(), diff --git a/holo-tools/yang-coverage.sh b/holo-tools/yang-coverage.sh index 8f8fda6c..91289ed1 100755 --- a/holo-tools/yang-coverage.sh +++ b/holo-tools/yang-coverage.sh @@ -14,6 +14,8 @@ cargo run --bin yang_coverage --\ -m ietf-bfd\ -m ietf-bfd-ip-mh\ -m ietf-bfd-ip-sh\ + -m ietf-bgp\ + -m ietf-bgp-policy\ -m ietf-mpls-ldp\ -m ietf-ospf\ -m ietf-ospf-sr-mpls\ diff --git a/holo-utils/Cargo.toml b/holo-utils/Cargo.toml index e892c512..87dac0e3 100644 --- a/holo-utils/Cargo.toml +++ b/holo-utils/Cargo.toml @@ -16,6 +16,7 @@ chrono.workspace = true derive-new.workspace = true enum-as-inner.workspace = true ipnetwork.workspace = true +itertools.workspace = true libc.workspace = true nix.workspace = true num-derive.workspace = true diff --git a/holo-utils/src/bgp.rs b/holo-utils/src/bgp.rs new file mode 100644 index 00000000..29aff93a --- /dev/null +++ b/holo-utils/src/bgp.rs @@ -0,0 +1,184 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +//! This file contains BGP definitions that are common to both `holo-bgp` and +//! `holo-policy`. In the future, the northbound layer should be restructured +//! so that `holo-bgp` can handle the BGP-specific policy definitions itself, +//! eliminating the need for shared definitions. + +use std::borrow::Cow; +use std::net::Ipv6Addr; + +use holo_yang::{ToYang, TryFromYang}; +use itertools::Itertools; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::FromPrimitive; +use serde::{Deserialize, Serialize}; + +// Configurable (AFI,SAFI) tuples. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum AfiSafi { + Ipv4Unicast, + Ipv6Unicast, +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum RouteType { + Internal, + External, +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum Origin { + Igp = 0, + Egp = 1, + Incomplete = 2, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct Comm(pub u32); + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct ExtComm(pub [u8; 8]); + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct Extv6Comm(pub Ipv6Addr, pub u32); + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub struct LargeComm(pub [u8; 12]); + +// BGP Well-known Communities. +// +// IANA registry: +// https://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xhtml +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(FromPrimitive, ToPrimitive)] +#[derive(Deserialize, Serialize)] +pub enum WellKnownCommunities { + NoExport = 0xFFFFFF01, + NoAdvertise = 0xFFFFFF02, + NoExportSubconfed = 0xFFFFFF03, +} + +// ===== impl AfiSafi ===== + +impl ToYang for AfiSafi { + fn to_yang(&self) -> Cow<'static, str> { + match self { + AfiSafi::Ipv4Unicast => "iana-bgp-types:ipv4-unicast".into(), + AfiSafi::Ipv6Unicast => "iana-bgp-types:ipv6-unicast".into(), + } + } +} + +impl TryFromYang for AfiSafi { + fn try_from_yang(value: &str) -> Option { + match value { + "iana-bgp-types:ipv4-unicast" => Some(AfiSafi::Ipv4Unicast), + "iana-bgp-types:ipv6-unicast" => Some(AfiSafi::Ipv6Unicast), + _ => None, + } + } +} + +// ===== impl Origin ===== + +impl ToYang for Origin { + fn to_yang(&self) -> Cow<'static, str> { + match self { + Origin::Igp => "igp".into(), + Origin::Egp => "egp".into(), + Origin::Incomplete => "incomplete".into(), + } + } +} + +// ===== impl Comm ===== + +impl ToYang for Comm { + fn to_yang(&self) -> Cow<'static, str> { + match WellKnownCommunities::from_u32(self.0) { + Some(WellKnownCommunities::NoExport) => { + "iana-bgp-community-types:no-export".into() + } + Some(WellKnownCommunities::NoAdvertise) => { + "iana-bgp-community-types:no-advertise".into() + } + Some(WellKnownCommunities::NoExportSubconfed) => { + "iana-bgp-community-types:no-export-subconfed".into() + } + None => { + let asn = self.0 >> 16; + let local = self.0 & 0xFFFF; + format!("{}:{}", asn, local).into() + } + } + } +} + +// ===== impl ExtComm ===== + +impl ToYang for ExtComm { + fn to_yang(&self) -> Cow<'static, str> { + // TODO: cover other cases instead of always using the raw format. + format!( + "raw:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.0[0], + self.0[1], + self.0[2], + self.0[3], + self.0[4], + self.0[5], + self.0[6], + self.0[7] + ) + .into() + } +} + +// ===== impl Extv6Comm ===== + +impl ToYang for Extv6Comm { + fn to_yang(&self) -> Cow<'static, str> { + // TODO: cover other cases instead of always using the raw format. + let addr = self + .0 + .segments() + .into_iter() + .map(|s| format!("{:02x}", s)) + .join(":"); + let local = self + .1 + .to_be_bytes() + .into_iter() + .map(|s| format!("{:02x}", s)) + .join(":"); + format!("ipv6-raw:{}:{}", addr, local,).into() + } +} + +// ===== impl LargeComm ===== + +impl ToYang for LargeComm { + fn to_yang(&self) -> Cow<'static, str> { + format!( + "{}:{}:{}", + u32::from_be_bytes(self.0[0..4].try_into().unwrap()), + u32::from_be_bytes(self.0[4..8].try_into().unwrap()), + u32::from_be_bytes(self.0[8..12].try_into().unwrap()), + ) + .into() + } +} diff --git a/holo-utils/src/lib.rs b/holo-utils/src/lib.rs index 27029f1f..f6f32101 100644 --- a/holo-utils/src/lib.rs +++ b/holo-utils/src/lib.rs @@ -15,6 +15,7 @@ use std::sync::{Arc, Mutex}; use pickledb::PickleDb; pub mod bfd; +pub mod bgp; pub mod bytes; pub mod capabilities; pub mod crypto; diff --git a/holo-utils/src/policy.rs b/holo-utils/src/policy.rs index 706620ee..e15a2ba8 100644 --- a/holo-utils/src/policy.rs +++ b/holo-utils/src/policy.rs @@ -13,12 +13,48 @@ use holo_yang::TryFromYang; use ipnetwork::IpNetwork; use serde::{Deserialize, Serialize}; +use crate::bgp::{self, AfiSafi, Comm, ExtComm, Extv6Comm, LargeComm, Origin}; use crate::ip::AddressFamily; use crate::protocol::Protocol; // Type aliases. pub type Policies = BTreeMap>; +// Routing policy configuration. +#[derive(Clone, Debug, Default)] +pub struct ApplyPolicyCfg { + // TODO: "ordered-by user" + pub import_policy: BTreeSet, + pub default_import_policy: DefaultPolicyType, + // TODO: "ordered-by user" + pub export_policy: BTreeSet, + pub default_export_policy: DefaultPolicyType, +} + +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub enum PolicyType { + Import, + Export, +} + +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub enum PolicyResult { + Accept(T), + Reject, +} + +// Default policy type. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum DefaultPolicyType { + // TODO should be RejectRoute + #[default] + AcceptRoute, + RejectRoute, +} + // Route type. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Deserialize, Serialize)] @@ -40,9 +76,9 @@ pub enum RouteType { #[derive(Clone, Copy, Debug)] #[derive(Deserialize, Serialize)] pub enum MetricModification { - SetMetric, - AddMetric, - SubtractMetric, + Set, + Add, + Subtract, } // Route metric types. @@ -105,6 +141,7 @@ pub struct MatchSets { pub prefixes: BTreeMap<(String, AddressFamily), PrefixSet>, pub neighbors: BTreeMap, pub tags: BTreeMap, + pub bgp: BgpMatchSets, } // List of IPv4 or IPv6 prefixes that are matched as part of a policy. @@ -132,6 +169,18 @@ pub struct TagSet { pub tags: BTreeSet, } +// BGP sets of attributes used in policy match statements. +#[derive(Clone, Debug, Default)] +#[derive(Deserialize, Serialize)] +pub struct BgpMatchSets { + pub as_paths: BTreeMap>, + pub comms: BTreeMap>, + pub ext_comms: BTreeMap>, + pub extv6_comms: BTreeMap>, + pub large_comms: BTreeMap>, + pub nexthops: BTreeMap>, +} + // Policy definition. #[derive(Clone, Debug)] #[derive(Deserialize, Serialize)] @@ -139,6 +188,7 @@ pub struct Policy { // Name of the policy. pub name: String, // List of statements. + // TODO: "ordered-by user" pub stmts: BTreeMap, } @@ -164,6 +214,7 @@ pub enum PolicyConditionType { MatchNeighborSet, MatchTagSet, MatchRouteType, + Bgp(BgpPolicyConditionType), } // Policy condition statement. @@ -177,6 +228,83 @@ pub enum PolicyCondition { MatchNeighborSet(String), MatchTagSet(String), MatchRouteType(BTreeSet), + Bgp(BgpPolicyCondition), +} + +// BGP policy condition statement type. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum BgpPolicyConditionType { + LocalPref, + Med, + Origin, + MatchAfiSafi, + MatchNeighbor, + RouteType, + CommCount, + AsPathLen, + MatchCommSet, + MatchExtCommSet, + MatchExtv6CommSet, + MatchLargeCommSet, + MatchAsPathSet, + MatchNexthopSet, +} + +// BGP policy condition statement. +#[derive(Clone, Debug, EnumAsInner)] +#[derive(Deserialize, Serialize)] +pub enum BgpPolicyCondition { + LocalPref { + value: u32, + op: BgpEqOperator, + }, + Med { + value: u32, + op: BgpEqOperator, + }, + Origin(Origin), + MatchAfiSafi { + values: BTreeSet, + match_type: MatchSetRestrictedType, + }, + MatchNeighbor { + value: BTreeSet, + match_type: MatchSetRestrictedType, + }, + RouteType(bgp::RouteType), + CommCount { + value: u32, + op: BgpEqOperator, + }, + AsPathLen { + value: u32, + op: BgpEqOperator, + }, + MatchCommSet { + value: String, + match_type: MatchSetType, + }, + MatchExtCommSet { + value: String, + match_type: MatchSetType, + }, + MatchExtv6CommSet { + value: String, + match_type: MatchSetType, + }, + MatchLargeCommSet { + value: String, + match_type: MatchSetType, + }, + MatchAsPathSet { + value: String, + match_type: MatchSetType, + }, + MatchNexthopSet { + value: String, + match_type: MatchSetRestrictedType, + }, } // Policy action statement type. @@ -185,12 +313,12 @@ pub enum PolicyCondition { pub enum PolicyActionType { Accept, SetMetric, - SetMetricMod, SetMetricType, SetRouteLevel, SetRoutePref, SetTag, SetAppTag, + Bgp(BgpPolicyActionType), } // Policy action statement. @@ -198,13 +326,113 @@ pub enum PolicyActionType { #[derive(Deserialize, Serialize)] pub enum PolicyAction { Accept(bool), - SetMetric(u32), - SetMetricMod(MetricModification), + SetMetric { + value: u32, + mod_type: MetricModification, + }, SetMetricType(MetricType), SetRouteLevel(RouteLevel), SetRoutePref(u16), SetTag(u32), SetAppTag(u32), + Bgp(BgpPolicyAction), +} + +// BGP policy action statement type. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum BgpPolicyActionType { + SetRouteOrigin, + SetLocalPref, + SetNexthop, + SetMed, + SetAsPathPrepent, + SetComm, + SetExtComm, + SetExtv6Comm, + SetLargeComm, +} + +// BGP policy action statement. +#[derive(Clone, Debug)] +#[derive(Deserialize, Serialize)] +pub enum BgpPolicyAction { + SetRouteOrigin(Origin), + SetLocalPref(u32), + SetNexthop(BgpNexthop), + SetMed(BgpSetMed), + SetAsPathPrepent { + asn: u32, + repeat: Option, + }, + SetComm { + options: BgpSetCommOptions, + method: BgpSetCommMethod, + }, + SetExtComm { + options: BgpSetCommOptions, + method: BgpSetCommMethod, + }, + SetExtv6Comm { + options: BgpSetCommOptions, + method: BgpSetCommMethod, + }, + SetLargeComm { + options: BgpSetCommOptions, + method: BgpSetCommMethod, + }, +} + +#[derive(Clone, Copy, Debug)] +#[derive(Deserialize, Serialize)] +pub enum BgpEqOperator { + Equal, + LessThanOrEqual, + GreaterThanOrEqual, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Deserialize, Serialize)] +pub enum BgpNexthop { + Addr(IpAddr), + NexthopSelf, +} + +#[derive(Clone, Debug)] +#[derive(Deserialize, Serialize)] +pub enum BgpSetMed { + Add(u32), + Subtract(u32), + Set(u32), + Igp, + MedPlusIgp, +} + +#[derive(Clone, Copy, Debug)] +#[derive(Deserialize, Serialize)] +pub enum BgpSetCommOptions { + Add, + Remove, + Replace, +} + +#[derive(Clone, Debug)] +#[derive(Deserialize, Serialize)] +pub enum BgpSetCommMethod { + Inline(BTreeSet), + Reference(String), +} + +// ===== impl DefaultPolicyType ===== + +impl TryFromYang for DefaultPolicyType { + fn try_from_yang(value: &str) -> Option { + match value { + "accept-route" => Some(DefaultPolicyType::AcceptRoute), + "reject-route" => Some(DefaultPolicyType::RejectRoute), + _ => None, + } + } } // ===== impl RouteType ===== @@ -249,9 +477,9 @@ impl TryFromYang for RouteType { impl TryFromYang for MetricModification { fn try_from_yang(value: &str) -> Option { match value { - "set-metric" => Some(MetricModification::SetMetric), - "add-metric" => Some(MetricModification::AddMetric), - "subtract-metric" => Some(MetricModification::SubtractMetric), + "set-metric" => Some(MetricModification::Set), + "add-metric" => Some(MetricModification::Add), + "subtract-metric" => Some(MetricModification::Subtract), _ => None, } } @@ -303,6 +531,19 @@ impl TryFromYang for RouteLevel { // ===== impl MatchSetType ===== +impl MatchSetType { + pub fn compare(&self, a: &BTreeSet, b: &BTreeSet) -> bool + where + T: Eq + Ord + PartialEq + PartialOrd, + { + match self { + MatchSetType::Any => !a.is_disjoint(b), + MatchSetType::All => a.is_superset(b), + MatchSetType::Invert => a.is_disjoint(b), + } + } +} + impl TryFromYang for MatchSetType { fn try_from_yang(identity: &str) -> Option { match identity { @@ -316,6 +557,18 @@ impl TryFromYang for MatchSetType { // ===== impl MatchSetRestrictedType ===== +impl MatchSetRestrictedType { + pub fn compare(&self, a: &BTreeSet, b: &T) -> bool + where + T: Eq + Ord + PartialEq + PartialOrd, + { + match self { + MatchSetRestrictedType::Any => a.contains(b), + MatchSetRestrictedType::Invert => !a.contains(b), + } + } +} + impl TryFromYang for MatchSetRestrictedType { fn try_from_yang(identity: &str) -> Option { match identity { @@ -384,6 +637,9 @@ impl PolicyCondition { PolicyCondition::MatchRouteType(..) => { PolicyConditionType::MatchRouteType } + PolicyCondition::Bgp(cond) => { + PolicyConditionType::Bgp(cond.as_type()) + } } } } @@ -394,13 +650,107 @@ impl PolicyAction { fn as_type(&self) -> PolicyActionType { match self { PolicyAction::Accept(..) => PolicyActionType::Accept, - PolicyAction::SetMetric(..) => PolicyActionType::SetMetric, - PolicyAction::SetMetricMod(..) => PolicyActionType::SetMetricMod, + PolicyAction::SetMetric { .. } => PolicyActionType::SetMetric, PolicyAction::SetMetricType(..) => PolicyActionType::SetMetricType, PolicyAction::SetRouteLevel(..) => PolicyActionType::SetRouteLevel, PolicyAction::SetRoutePref(..) => PolicyActionType::SetRoutePref, PolicyAction::SetTag(..) => PolicyActionType::SetTag, PolicyAction::SetAppTag(..) => PolicyActionType::SetAppTag, + PolicyAction::Bgp(action) => { + PolicyActionType::Bgp(action.as_type()) + } + } + } +} + +// ===== impl BgpPolicyCondition ===== + +impl BgpPolicyCondition { + fn as_type(&self) -> BgpPolicyConditionType { + match self { + BgpPolicyCondition::LocalPref { .. } => { + BgpPolicyConditionType::LocalPref + } + BgpPolicyCondition::Med { .. } => BgpPolicyConditionType::Med, + BgpPolicyCondition::Origin(..) => BgpPolicyConditionType::Origin, + BgpPolicyCondition::MatchAfiSafi { .. } => { + BgpPolicyConditionType::MatchAfiSafi + } + BgpPolicyCondition::MatchNeighbor { .. } => { + BgpPolicyConditionType::MatchNeighbor + } + BgpPolicyCondition::RouteType(..) => { + BgpPolicyConditionType::RouteType + } + BgpPolicyCondition::CommCount { .. } => { + BgpPolicyConditionType::CommCount + } + BgpPolicyCondition::AsPathLen { .. } => { + BgpPolicyConditionType::AsPathLen + } + BgpPolicyCondition::MatchCommSet { .. } => { + BgpPolicyConditionType::MatchCommSet + } + BgpPolicyCondition::MatchExtCommSet { .. } => { + BgpPolicyConditionType::MatchExtCommSet + } + BgpPolicyCondition::MatchExtv6CommSet { .. } => { + BgpPolicyConditionType::MatchExtv6CommSet + } + BgpPolicyCondition::MatchLargeCommSet { .. } => { + BgpPolicyConditionType::MatchLargeCommSet + } + BgpPolicyCondition::MatchAsPathSet { .. } => { + BgpPolicyConditionType::MatchAsPathSet + } + BgpPolicyCondition::MatchNexthopSet { .. } => { + BgpPolicyConditionType::MatchNexthopSet + } + } + } +} + +// ===== impl BgpPolicyAction ===== + +impl BgpPolicyAction { + fn as_type(&self) -> BgpPolicyActionType { + match self { + BgpPolicyAction::SetRouteOrigin(..) => { + BgpPolicyActionType::SetRouteOrigin + } + BgpPolicyAction::SetLocalPref(..) => { + BgpPolicyActionType::SetLocalPref + } + BgpPolicyAction::SetNexthop(..) => BgpPolicyActionType::SetNexthop, + BgpPolicyAction::SetMed(..) => BgpPolicyActionType::SetMed, + BgpPolicyAction::SetAsPathPrepent { .. } => { + BgpPolicyActionType::SetAsPathPrepent + } + BgpPolicyAction::SetComm { .. } => BgpPolicyActionType::SetComm, + BgpPolicyAction::SetExtComm { .. } => { + BgpPolicyActionType::SetExtComm + } + BgpPolicyAction::SetExtv6Comm { .. } => { + BgpPolicyActionType::SetExtv6Comm + } + BgpPolicyAction::SetLargeComm { .. } => { + BgpPolicyActionType::SetLargeComm + } + } + } +} + +// ===== impl BgpEqOperator ===== + +impl BgpEqOperator { + pub fn compare(&self, a: &T, b: &T) -> bool + where + T: Eq + Ord + PartialEq + PartialOrd, + { + match self { + BgpEqOperator::Equal => *a == *b, + BgpEqOperator::LessThanOrEqual => *a <= *b, + BgpEqOperator::GreaterThanOrEqual => *a >= *b, } } } diff --git a/holo-utils/src/protocol.rs b/holo-utils/src/protocol.rs index f25096de..15d48073 100644 --- a/holo-utils/src/protocol.rs +++ b/holo-utils/src/protocol.rs @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "lowercase")] pub enum Protocol { BFD, + BGP, DIRECT, LDP, OSPFV2, @@ -32,6 +33,7 @@ impl std::fmt::Display for Protocol { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Protocol::BFD => write!(f, "bfd"), + Protocol::BGP => write!(f, "bgp"), Protocol::DIRECT => write!(f, "direct"), Protocol::LDP => write!(f, "ldp"), Protocol::OSPFV2 => write!(f, "ospfv2"), @@ -49,6 +51,7 @@ impl FromStr for Protocol { fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { "bfd" => Ok(Protocol::BFD), + "bgp" => Ok(Protocol::BGP), "direct" => Ok(Protocol::DIRECT), "ldp" => Ok(Protocol::LDP), "ospfv2" => Ok(Protocol::OSPFV2), @@ -65,6 +68,7 @@ impl ToYang for Protocol { fn to_yang(&self) -> Cow<'static, str> { match self { Protocol::BFD => "ietf-bfd-types:bfdv1".into(), + Protocol::BGP => "ietf-bgp:bgp".into(), Protocol::DIRECT => "ietf-routing:direct".into(), Protocol::LDP => "ietf-mpls-ldp:mpls-ldp".into(), Protocol::OSPFV2 => "ietf-ospf:ospfv2".into(), @@ -80,6 +84,7 @@ impl TryFromYang for Protocol { fn try_from_yang(identity: &str) -> Option { match identity { "ietf-bfd-types:bfdv1" => Some(Protocol::BFD), + "ietf-bgp:bgp" => Some(Protocol::BGP), "ietf-routing:direct" => Some(Protocol::DIRECT), "ietf-mpls-ldp:mpls-ldp" => Some(Protocol::LDP), "ietf-ospf:ospfv2" => Some(Protocol::OSPFV2), diff --git a/holo-yang/modules/augmentations/holo-bgp.yang b/holo-yang/modules/augmentations/holo-bgp.yang new file mode 100644 index 00000000..96ccb62d --- /dev/null +++ b/holo-yang/modules/augmentations/holo-bgp.yang @@ -0,0 +1,78 @@ +module holo-bgp { + yang-version 1.1; + namespace "http://holo-routing.org/yang/holo-bgp"; + prefix holo-bgp; + + import ietf-routing { + prefix rt; + } + + import iana-bgp-types { + prefix bt; + } + + import iana-bgp-notification { + prefix bn; + } + + import ietf-bgp { + prefix bgp; + } + + organization + "Holo Routing Stack"; + + description + "This module defines augment statements for the ietf-bgp + module."; + + /* + * Identities. + */ + + identity unknown-error { + base bn:bgp-notification; + description + "Unknown error code"; + } + + identity graceful-restart { + base bt:bgp-capability; + description + "Graceful restart functionality"; + reference + "RFC 4724: Graceful Restart Mechanism for BGP."; + } + + identity add-paths { + base bt:bgp-capability; + description + "Advertisement of multiple paths for the same address prefix + without the new paths implicitly replacing any previous + ones."; + reference + "RFC 7911: Advertisement of Multiple Paths in BGP."; + } + + identity enhanced-route-refresh { + base bt:bgp-capability; + description + "The BGP enhanced route-refresh functionality"; + reference + "RFC 7313: Enhanced Route Refresh Capability for BGP-4"; + } + + /* + * Augmentations. + */ + + augment "/rt:routing/rt:control-plane-protocols/" + + "rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/" + + "bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:md5" { + leaf md5-key-string { + type string; + description + "Key string in ASCII format."; + } + } +} diff --git a/holo-yang/modules/deviations/ietf-bgp-holo-deviations.yang b/holo-yang/modules/deviations/ietf-bgp-holo-deviations.yang new file mode 100644 index 00000000..21552544 --- /dev/null +++ b/holo-yang/modules/deviations/ietf-bgp-holo-deviations.yang @@ -0,0 +1,4826 @@ +module ietf-bgp-holo-deviations { + yang-version 1.1; + namespace "http://holo-routing.org/yang/ietf-bgp-holo-deviations"; + prefix ietf-bgp-holo-deviations; + + import ietf-routing { + prefix rt; + } + + import ietf-bgp { + prefix bgp; + } + + organization + "Holo Routing Stack"; + + description + "This module defines deviation statements for the ietf-bgp + module."; + + /* + * Default values + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:distance/bgp:external" { + deviate add { + default "20"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:distance/bgp:internal" { + deviate add { + default "200"; + } + } + + /* + * Other deviations + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:peer-as" { + deviate add { + mandatory "true"; + } + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:session-state" { + deviate add { + config "false"; + } + } + + /* + * Not supported nodes + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:identifier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:distance" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:distance/bgp:external" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:distance/bgp:internal" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:confederation" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:confederation/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:confederation/bgp:identifier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:confederation/bgp:member-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:ebgp/bgp:maximum-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:ibgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:use-multiple-paths/bgp:ibgp/bgp:maximum-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:always-compare-med" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:ignore-as-path-length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:external-compare-router-id" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:advertise-inactive-routes" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:enable-aigp" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:ignore-next-hop-igp-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:enable-med" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:med-plus-igp" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:med-plus-igp/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:med-plus-igp/bgp:igp-multiplier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:med-plus-igp/bgp:med-multiplier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:statistics" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:statistics/bgp:total-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:statistics/bgp:total-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:always-compare-med" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:ignore-as-path-length" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:external-compare-router-id" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:advertise-inactive-routes" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:enable-aigp" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:ignore-next-hop-igp-metric" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:enable-med" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:med-plus-igp" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:med-plus-igp/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:med-plus-igp/bgp:igp-multiplier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:route-selection-options/bgp:med-plus-igp/bgp:med-multiplier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp/bgp:maximum-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ibgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ibgp/bgp:maximum-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:statistics" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:statistics/bgp:total-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:global/bgp:statistics/bgp:total-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:remote-address" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:peer-group" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:local-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:local-port" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:remote-port" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:peer-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:identifier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:dynamically-configured" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:local-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:remove-private-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:description" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:connect-retry-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:hold-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:negotiated-hold-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:keepalive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:min-as-origination-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:min-route-advertisement-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:local-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:tcp-mss" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:mtu-discovery" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:ebgp-multihop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:ebgp-multihop/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:ebgp-multihop/bgp:multihop-ttl" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:passive-mode" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ao" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ao/bgp:ao-keychain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:md5" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:md5/bgp:md5-keychain" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ipsec" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ipsec/bgp:sa" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:treat-as-withdraw" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:logging-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:logging-options/bgp:log-neighbor-state-changes" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:route-reflector" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:route-reflector/bgp:cluster-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:route-reflector/bgp:client" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:as-path-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:as-path-options/bgp:allow-own-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:as-path-options/bgp:replace-peer-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:as-path-options/bgp:disable-peer-as-filter" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:use-multiple-paths" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:active" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:prefixes/bgp:received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:prefixes/bgp:sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:prefixes/bgp:installed" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:last-established" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:code" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:mpbgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:mpbgp/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:mpbgp/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:mpbgp/bgp:name" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:unknown-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:restart-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi-safi-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi-safi-unknown-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:asn32" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:asn32/bgp:as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:add-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:advertised-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:mode" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:code" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:mpbgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:mpbgp/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:mpbgp/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:mpbgp/bgp:name" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:unknown-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:restart-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi-safi-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:graceful-restart/bgp:afi-safis/bgp:afi-safi-unknown-flags" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:asn32" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:asn32/bgp:as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:add-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:afi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:received-capabilities/bgp:value/bgp:add-paths/bgp:afi-safis/bgp:mode" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:capabilities/bgp:negotiated-capabilities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-notification" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-error" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-error-code" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-error-subcode" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-encapsulated-error" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-encapsulated-error-code" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-encapsulated-error-subcode" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:received/bgp:last-error-data" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-notification" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-error" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-error-code" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-error-subcode" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-encapsulated-error" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-encapsulated-error-code" { + deviate not-supported; + } + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-encapsulated-error-subcode" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:errors/bgp:sent/bgp:last-error-data" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:established-transitions" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:total-received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:total-sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:updates-received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:updates-sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:erroneous-updates-withdrawn" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:erroneous-updates-attribute-discarded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:in-update-elapsed-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:notifications-received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:notifications-sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:route-refreshes-received" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:messages/bgp:route-refreshes-sent" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:queues" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:queues/bgp:input" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:statistics/bgp:queues/bgp:output" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:peer-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:local-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:remove-private-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:description" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:connect-retry-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:hold-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:negotiated-hold-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:keepalive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:min-as-origination-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:timers/bgp:min-route-advertisement-interval" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:local-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:tcp-mss" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:mtu-discovery" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:ebgp-multihop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:ebgp-multihop/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:ebgp-multihop/bgp:multihop-ttl" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:passive-mode" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ao" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ao/bgp:ao-keychain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:md5" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:md5/bgp:md5-keychain" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ipsec" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:transport/bgp:secure-session/bgp:options/bgp:option/bgp:ipsec/bgp:sa" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:treat-as-withdraw" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:logging-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:logging-options/bgp:log-neighbor-state-changes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:route-reflector" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:route-reflector/bgp:cluster-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:route-reflector/bgp:client" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:as-path-options" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:as-path-options/bgp:allow-own-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:as-path-options/bgp:replace-peer-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:as-path-options/bgp:disable-peer-as-filter" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:use-multiple-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:dynamic-peers" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:dynamic-peers/bgp:dynamic-peer-list" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:dynamic-peers/bgp:dynamic-peer-list/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:enabled" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:use-multiple-paths/bgp:ebgp/bgp:allow-multiple-as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-import-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:apply-policy/bgp:default-export-policy" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:send-default-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-labeled-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-unicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv4-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l3vpn-ipv6-multicast/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-vpls/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:max-prefixes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:warning-threshold-pct" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:teardown" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:idle-time" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:peer-groups/bgp:peer-group/bgp:afi-safis/bgp:afi-safi/bgp:l2vpn-evpn/bgp:prefix-limit/bgp:prefix-limit-exceeded" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:origin" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as-path" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as-path/bgp:segment" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as-path/bgp:segment/bgp:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as-path/bgp:segment/bgp:member" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:next-hop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:link-local-next-hop" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:med" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:local-pref" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as4-path" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as4-path/bgp:segment" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as4-path/bgp:segment/bgp:type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:as4-path/bgp:segment/bgp:member" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator/bgp:as" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator/bgp:identifier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator4" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator4/bgp:as4" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aggregator4/bgp:identifier" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:atomic-aggregate" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:originator-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:cluster-list" { + deviate not-supported; + } + */ + + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:attr-sets/bgp:attr-set/bgp:attributes/bgp:aigp-metric" { + deviate not-supported; + } + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:communities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:communities/bgp:community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:communities/bgp:community/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:communities/bgp:community/bgp:community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ext-communities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ext-communities/bgp:ext-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ext-communities/bgp:ext-community/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ext-communities/bgp:ext-community/bgp:ext-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ext-communities/bgp:ext-community/bgp:ext-community-raw" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ipv6-ext-communities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ipv6-ext-communities/bgp:ipv6-ext-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ipv6-ext-communities/bgp:ipv6-ext-community/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ipv6-ext-communities/bgp:ipv6-ext-community/bgp:ipv6-ext-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:ipv6-ext-communities/bgp:ipv6-ext-community/bgp:ipv6-ext-community-raw" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:large-communities" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:large-communities/bgp:large-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:large-communities/bgp:large-community/bgp:index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:large-communities/bgp:large-community/bgp:large-community" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:name" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:origin" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:neighbor-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:best-path" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv4-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:origin" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:loc-rib/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:neighbor-address" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-pre/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:best-path" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-in-post/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-pre/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:prefix" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:path-id" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:attr-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:ext-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:large-community-index" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:last-modified" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:eligible-route" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:ineligible-reason" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-type" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:optional" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:transitive" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:partial" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:extended" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-len" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:unknown-attributes/bgp:unknown-attribute/bgp:attr-value" { + deviate not-supported; + } + */ + + /* + deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/bgp:bgp/bgp:rib/bgp:afi-safis/bgp:afi-safi/bgp:ipv6-unicast/bgp:neighbors/bgp:neighbor/bgp:adj-rib-out-post/bgp:routes/bgp:route/bgp:reject-reason" { + deviate not-supported; + } + */ +} diff --git a/holo-yang/src/lib.rs b/holo-yang/src/lib.rs index 5b7d3e49..5c2247c5 100644 --- a/holo-yang/src/lib.rs +++ b/holo-yang/src/lib.rs @@ -115,11 +115,15 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { EmbeddedModuleKey::new("ietf-tcp-common", Some("2023-04-17"), None, None) => include_str!("../modules/ietf/ietf-tcp-common@2023-04-17.yang"), // IETF Holo augmentations + EmbeddedModuleKey::new("holo-bgp", None, None, None) => + include_str!("../modules/augmentations/holo-bgp.yang"), EmbeddedModuleKey::new("holo-ospf", None, None, None) => include_str!("../modules/augmentations/holo-ospf.yang"), EmbeddedModuleKey::new("holo-ospf-dev", None, None, None) => include_str!("../modules/augmentations/holo-ospf-dev.yang"), // IETF Holo deviations + EmbeddedModuleKey::new("ietf-bgp-holo-deviations", None, None, None) => + include_str!("../modules/deviations/ietf-bgp-holo-deviations.yang"), EmbeddedModuleKey::new("ietf-mpls-ldp-holo-deviations", None, None, None) => include_str!("../modules/deviations/ietf-mpls-ldp-holo-deviations.yang"), EmbeddedModuleKey::new("ietf-interfaces-holo-deviations", None, None, None) => @@ -157,10 +161,14 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = Lazy::new(|| { vec![ "iana-if-type", + "iana-bgp-notification", + "iana-bgp-types", "ietf-bfd-ip-mh", "ietf-bfd-ip-sh", "ietf-bfd-types", "ietf-bfd", + "ietf-bgp", + "ietf-bgp-policy", "ietf-routing-types", "ietf-interfaces", "ietf-ip", @@ -178,6 +186,8 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = "ietf-ospf-sr-mpls", "ietf-ospfv3-extended-lsa", "ietf-rip", + "ietf-tcp", + "holo-bgp", "holo-ospf", "holo-ospf-dev", ] @@ -187,6 +197,10 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = pub static YANG_FEATURES: Lazy>> = Lazy::new(|| { hashmap! { + "iana-bgp-types" => vec![ + "route-refresh", + "ttl-security", + ], "ietf-bfd-types" => vec![ "client-base-cfg-parms", "single-minimum-interval",