From 2f4dff63c6e4001fa748beedbf49ddb58f8dd64b Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Fri, 12 Apr 2024 17:39:05 -0300 Subject: [PATCH 1/4] interface: implement IP address configuration Example configuration (CLI): ``` interfaces interface eth-rt2 type iana-if-type:ethernetCsmacd ! ipv4 address 10.0.1.1 prefix-length 24 ! ``` Example configuration (JSON): ``` { "ietf-interfaces:interfaces": { "interface": [ { "name": "eth-rt2", "type": "iana-if-type:ethernetCsmacd", "ietf-ip:ipv4": { "address": [ { "ip": "10.0.1.1", "prefix-length": 24 } ] } } } } } ``` This commit also includes a refactoring to allow configuring interfaces before they exist at the OS-level. Then, once the interface becomes available, its pre-existing configuration is applied. Signed-off-by: Renato Westphal --- README.md | 2 +- holo-interface/Cargo.toml | 2 + holo-interface/src/ibus.rs | 8 +- holo-interface/src/interface.rs | 119 ++++++++++++-- holo-interface/src/lib.rs | 29 ++-- holo-interface/src/netlink.rs | 110 ++++++++++--- .../src/northbound/configuration.rs | 146 ++++++++++++++++-- holo-interface/src/northbound/state.rs | 12 +- .../deviations/ietf-ip-holo-deviations.yang | 56 ------- 9 files changed, 368 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 0620741a..df364b52 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ Holo supports the following IETF RFCs and Internet drafts: | 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-ip@2018-01-09 | 43.48% | 0.00% | - | - | [33.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) | | ietf-ipv6-unicast-routing@2018-03-13 | 40.62% | 100.00% | - | - | [45.71%](http://westphal.com.br/holo/ietf-ipv6-unicast-routing.html) | | ietf-key-chain@2017-04-18 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-key-chain.html) | diff --git a/holo-interface/Cargo.toml b/holo-interface/Cargo.toml index 6c835d79..46509c10 100644 --- a/holo-interface/Cargo.toml +++ b/holo-interface/Cargo.toml @@ -7,6 +7,8 @@ edition.workspace = true [dependencies] async-trait.workspace = true +bitflags.workspace = true +capctl.workspace = true derive-new.workspace = true enum-as-inner.workspace = true futures.workspace = true diff --git a/holo-interface/src/ibus.rs b/holo-interface/src/ibus.rs index 43001cec..ec13a2ac 100644 --- a/holo-interface/src/ibus.rs +++ b/holo-interface/src/ibus.rs @@ -24,8 +24,8 @@ pub(crate) fn process_msg(master: &mut Master, msg: IbusMsg) { notify_interface_update( &master.ibus_tx, iface.name.clone(), - iface.ifindex, - iface.mtu, + iface.ifindex.unwrap_or(0), + iface.mtu.unwrap_or(0), iface.flags, ); @@ -44,8 +44,8 @@ pub(crate) fn process_msg(master: &mut Master, msg: IbusMsg) { notify_interface_update( &master.ibus_tx, iface.name.clone(), - iface.ifindex, - iface.mtu, + iface.ifindex.unwrap_or(0), + iface.mtu.unwrap_or(0), iface.flags, ); diff --git a/holo-interface/src/interface.rs b/holo-interface/src/interface.rs index 377b6ef8..86f0e042 100644 --- a/holo-interface/src/interface.rs +++ b/holo-interface/src/interface.rs @@ -7,13 +7,15 @@ use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, Ipv4Addr}; +use bitflags::bitflags; use generational_arena::{Arena, Index}; use holo_utils::ibus::IbusSender; use holo_utils::ip::Ipv4NetworkExt; use holo_utils::southbound::{AddressFlags, InterfaceFlags}; use ipnetwork::{IpNetwork, Ipv4Network}; -use crate::ibus; +use crate::northbound::configuration::InterfaceCfg; +use crate::{ibus, netlink}; #[derive(Debug, Default)] pub struct Interfaces { @@ -30,10 +32,12 @@ pub struct Interfaces { #[derive(Debug)] pub struct Interface { pub name: String, - pub ifindex: u32, - pub mtu: u32, + pub config: InterfaceCfg, + pub ifindex: Option, + pub mtu: Option, pub flags: InterfaceFlags, pub addresses: BTreeMap, + pub owner: Owner, } #[derive(Debug)] @@ -42,25 +46,80 @@ pub struct InterfaceAddress { pub flags: AddressFlags, } +bitflags! { + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct Owner: u8 { + const CONFIG = 0x01; + const SYSTEM = 0x02; + } +} + +// ===== impl Interface ===== + +impl Interface { + // Applies the interface configuration. + // + // This method should only be called after the interface has been created + // at the OS-level. + async fn apply_config( + &self, + ifindex: u32, + netlink_handle: &rtnetlink::Handle, + ) { + // Install interface addresses. + for addr in &self.config.addr_list { + netlink::addr_install(netlink_handle, ifindex, addr).await; + } + } +} + // ===== impl Interfaces ===== impl Interfaces { + // Adds an interface. + pub(crate) fn add(&mut self, ifname: String) { + if let Some(iface) = self.get_mut_by_name(&ifname) { + iface.owner.insert(Owner::CONFIG); + return; + } + + // If the interface does not exist, create a new entry. + let iface = Interface { + name: ifname.clone(), + config: Default::default(), + ifindex: None, + mtu: None, + flags: InterfaceFlags::default(), + addresses: Default::default(), + owner: Owner::CONFIG, + }; + + let iface_idx = self.arena.insert(iface); + self.name_tree.insert(ifname.clone(), iface_idx); + } + // Adds or updates the interface with the specified attributes. - pub(crate) fn update( + pub(crate) async fn update( &mut self, ifname: String, ifindex: u32, mtu: u32, flags: InterfaceFlags, + netlink_handle: &rtnetlink::Handle, ibus_tx: Option<&IbusSender>, ) { - match self.ifindex_tree.get(&ifindex).copied() { + match self + .ifindex_tree + .get(&ifindex) + .or_else(|| self.name_tree.get(&ifname)) + .copied() + { Some(iface_idx) => { let iface = &mut self.arena[iface_idx]; // If nothing of interest has changed, return early. if iface.name == ifname - && iface.mtu == mtu + && iface.mtu == Some(mtu) && iface.flags == flags { return; @@ -72,17 +131,28 @@ impl Interfaces { iface.name.clone_from(&ifname); self.name_tree.insert(ifname.clone(), iface_idx); } - iface.mtu = mtu; + iface.owner.insert(Owner::SYSTEM); + iface.mtu = Some(mtu); iface.flags = flags; + + // In case the interface exists only in the configuration, + // initialize its ifindex and apply any pre-existing + // configuration options. + if iface.ifindex.is_none() { + iface.ifindex = Some(ifindex); + iface.apply_config(ifindex, netlink_handle).await; + } } None => { // If the interface does not exist, create a new entry. let iface = Interface { name: ifname.clone(), - ifindex, - mtu, + config: Default::default(), + ifindex: Some(ifindex), + mtu: Some(mtu), flags, addresses: Default::default(), + owner: Owner::SYSTEM, }; let iface_idx = self.arena.insert(iface); @@ -98,24 +168,45 @@ impl Interfaces { } // Removes the specified interface identified by its ifindex. - pub(crate) fn remove( + pub(crate) async fn remove( &mut self, - ifindex: u32, + ifname: &str, + owner: Owner, + netlink_handle: &rtnetlink::Handle, ibus_tx: Option<&IbusSender>, ) { - let Some(iface_idx) = self.ifindex_tree.get(&ifindex).copied() else { + let Some(iface_idx) = self.name_tree.get(ifname).copied() else { return; }; + let iface = &mut self.arena[iface_idx]; + + // When the interface is unconfigured, uninstall all configured + // addresses associated with it. + if owner == Owner::CONFIG + && let Some(ifindex) = iface.ifindex + { + for addr in &iface.config.addr_list { + netlink::addr_uninstall(netlink_handle, ifindex, addr).await; + } + } + + // Remove interface only when it's both not present in the configuration + // and not available in the kernel. + iface.owner.remove(owner); + if !iface.owner.is_empty() { + return; + } // Notify protocol instances. - let iface = &self.arena[iface_idx]; if let Some(ibus_tx) = ibus_tx { ibus::notify_interface_del(ibus_tx, iface.name.clone()); } // Remove interface. self.name_tree.remove(&iface.name); - self.ifindex_tree.remove(&iface.ifindex); + if let Some(ifindex) = iface.ifindex { + self.ifindex_tree.remove(&ifindex); + } self.arena.remove(iface_idx); // Check if the Router ID needs to be updated. diff --git a/holo-interface/src/lib.rs b/holo-interface/src/lib.rs index 9a72f98e..98adb930 100644 --- a/holo-interface/src/lib.rs +++ b/holo-interface/src/lib.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT // -#![feature(lazy_cell)] +#![feature(lazy_cell, let_chains)] mod ibus; mod interface; @@ -21,6 +21,7 @@ use tokio::sync::mpsc; use tracing::Instrument; use crate::interface::Interfaces; +use crate::netlink::NetlinkMonitor; #[derive(Debug)] pub struct Master { @@ -28,6 +29,8 @@ pub struct Master { pub nb_tx: NbProviderSender, // Internal bus Tx channel. pub ibus_tx: IbusSender, + // Netlink socket. + pub netlink_handle: rtnetlink::Handle, // List of interfaces. pub interfaces: Interfaces, } @@ -39,12 +42,10 @@ impl Master { &mut self, mut nb_rx: NbDaemonReceiver, mut ibus_rx: IbusReceiver, + mut netlink_rx: NetlinkMonitor, ) { let mut resources = vec![]; - // Netlink initialization. - let mut netlink_monitor = netlink::init(self).await; - loop { tokio::select! { Some(request) = nb_rx.recv() => { @@ -55,12 +56,12 @@ impl Master { ) .await; } - Some((msg, _)) = netlink_monitor.next() => { - netlink::process_msg(self, msg); - } Ok(msg) = ibus_rx.recv() => { ibus::process_msg(self, msg); } + Some((msg, _)) = netlink_rx.next() => { + netlink::process_msg(self, msg).await; + } } } } @@ -76,15 +77,25 @@ pub fn start( let (nb_daemon_tx, nb_daemon_rx) = mpsc::channel(4); tokio::spawn(async move { - let span = Master::debug_span(""); + // Initialize netlink socket. + let (netlink_handle, netlink_rx) = netlink::init().await; + let mut master = Master { nb_tx, ibus_tx, + netlink_handle, interfaces: Default::default(), }; + // Fetch interface information from the kernel. + netlink::start(&mut master).await; + // Run task main loop. - master.run(nb_daemon_rx, ibus_rx).instrument(span).await; + let span = Master::debug_span(""); + master + .run(nb_daemon_rx, ibus_rx, netlink_rx) + .instrument(span) + .await; }); nb_daemon_tx diff --git a/holo-interface/src/netlink.rs b/holo-interface/src/netlink.rs index 26fef597..913e7c5d 100644 --- a/holo-interface/src/netlink.rs +++ b/holo-interface/src/netlink.rs @@ -8,6 +8,7 @@ use std::net::{Ipv4Addr, Ipv6Addr}; +use capctl::caps::CapState; use futures::channel::mpsc::UnboundedReceiver; use futures::TryStreamExt; use holo_utils::southbound::InterfaceFlags; @@ -20,14 +21,22 @@ use netlink_packet_route::constants::{ use netlink_packet_route::rtnl::RtnlMessage; use netlink_packet_route::{AddressMessage, LinkMessage}; use netlink_sys::{AsyncSocket, SocketAddr}; -use rtnetlink::new_connection; -use tracing::trace; +use rtnetlink::{new_connection, Handle}; +use tracing::{error, trace}; +use crate::interface::Owner; use crate::Master; +pub type NetlinkMonitor = + UnboundedReceiver<(NetlinkMessage, SocketAddr)>; + // ===== helper functions ===== -fn process_newlink_msg(master: &mut Master, msg: LinkMessage, notify: bool) { +async fn process_newlink_msg( + master: &mut Master, + msg: LinkMessage, + notify: bool, +) { use netlink_packet_route::link::nlas::Nla; trace!(?msg, "received RTM_NEWLINK message"); @@ -58,18 +67,29 @@ fn process_newlink_msg(master: &mut Master, msg: LinkMessage, notify: bool) { let ibus_tx = notify.then_some(&master.ibus_tx); master .interfaces - .update(ifname, ifindex, mtu, flags, ibus_tx); + .update(ifname, ifindex, mtu, flags, &master.netlink_handle, ibus_tx) + .await; } -fn process_dellink_msg(master: &mut Master, msg: LinkMessage, notify: bool) { +async fn process_dellink_msg( + master: &mut Master, + msg: LinkMessage, + notify: bool, +) { trace!(?msg, "received RTM_DELLINK message"); // Fetch interface ifindex. let ifindex = msg.header.index; // Remove interface. - let ibus_tx = notify.then_some(&master.ibus_tx); - master.interfaces.remove(ifindex, ibus_tx); + if let Some(iface) = master.interfaces.get_by_ifindex(ifindex) { + let ibus_tx = notify.then_some(&master.ibus_tx); + let ifname = iface.name.clone(); + master + .interfaces + .remove(&ifname, Owner::SYSTEM, &master.netlink_handle, ibus_tx) + .await; + } } fn process_newaddr_msg(master: &mut Master, msg: AddressMessage, notify: bool) { @@ -155,14 +175,47 @@ fn parse_address( // ===== global functions ===== -pub(crate) fn process_msg( +pub(crate) async fn addr_install( + handle: &Handle, + ifindex: u32, + addr: &IpNetwork, +) { + // Create netlink request. + let request = handle.address().add(ifindex, addr.ip(), addr.prefix()); + + // Execute request. + if let Err(error) = request.execute().await { + error!(%ifindex, %addr, %error, "failed to install interface address"); + } +} + +pub(crate) async fn addr_uninstall( + handle: &Handle, + ifindex: u32, + addr: &IpNetwork, +) { + // Create netlink request. + let mut request = handle.address().add(ifindex, addr.ip(), addr.prefix()); + + // Execute request. + let request = handle.address().del(request.message_mut().clone()); + if let Err(error) = request.execute().await { + error!(%ifindex, %addr, %error, "failed to uninstall interface address"); + } +} + +pub(crate) async fn process_msg( master: &mut Master, msg: NetlinkMessage, ) { if let NetlinkPayload::InnerMessage(msg) = msg.payload { match msg { - RtnlMessage::NewLink(msg) => process_newlink_msg(master, msg, true), - RtnlMessage::DelLink(msg) => process_dellink_msg(master, msg, true), + RtnlMessage::NewLink(msg) => { + process_newlink_msg(master, msg, true).await + } + RtnlMessage::DelLink(msg) => { + process_dellink_msg(master, msg, true).await + } RtnlMessage::NewAddress(msg) => { process_newaddr_msg(master, msg, true) } @@ -174,26 +227,19 @@ pub(crate) fn process_msg( } } -pub(crate) async fn init( - master: &mut Master, -) -> UnboundedReceiver<(NetlinkMessage, SocketAddr)> { - // Create netlink socket. - let (conn, handle, _) = - new_connection().expect("Failed to create netlink socket"); - tokio::spawn(conn); - +pub(crate) async fn start(master: &mut Master) { // Fetch interface information. - let mut links = handle.link().get().execute(); + let mut links = master.netlink_handle.link().get().execute(); while let Some(msg) = links .try_next() .await .expect("Failed to fetch interface information") { - process_newlink_msg(master, msg, false); + process_newlink_msg(master, msg, false).await; } // Fetch address information. - let mut addresses = handle.address().get().execute(); + let mut addresses = master.netlink_handle.address().get().execute(); while let Some(msg) = addresses .try_next() .await @@ -201,6 +247,26 @@ pub(crate) async fn init( { process_newaddr_msg(master, msg, false); } +} + +pub(crate) async fn init() -> (Handle, NetlinkMonitor) { + // Create netlink socket. + let (conn, handle, _) = + new_connection().expect("Failed to create netlink socket"); + + // Spawn the netlink connection on a separate thread with permanent elevated + // capabilities. + std::thread::spawn(|| { + // Raise capabilities. + let mut caps = CapState::get_current().unwrap(); + caps.effective = caps.permitted; + if let Err(error) = caps.set_current() { + error!("failed to update current capabilities: {}", error); + } + + // Serve requests initiated by the netlink handle. + futures::executor::block_on(conn) + }); // Start netlink monitor. let (mut conn, _, monitor) = @@ -216,5 +282,5 @@ pub(crate) async fn init( .expect("Failed to bind netlink socket"); tokio::spawn(conn); - monitor + (handle, monitor) } diff --git a/holo-interface/src/northbound/configuration.rs b/holo-interface/src/northbound/configuration.rs index 55d1adbf..12a9e5f8 100644 --- a/holo-interface/src/northbound/configuration.rs +++ b/holo-interface/src/northbound/configuration.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT // +use std::collections::BTreeSet; use std::sync::LazyLock as Lazy; use async_trait::async_trait; @@ -12,8 +13,11 @@ use holo_northbound::configuration::{ self, Callbacks, CallbacksBuilder, Provider, }; use holo_northbound::paths::interfaces; +use holo_utils::yang::DataNodeRefExt; +use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; -use crate::Master; +use crate::interface::Owner; +use crate::{netlink, Master}; static CALLBACKS: Lazy> = Lazy::new(load_callbacks); @@ -22,32 +26,53 @@ static CALLBACKS: Lazy> = pub enum ListEntry { #[default] None, + Interface(String), + Address(String, IpNetwork), } #[derive(Debug)] pub enum Resource {} #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum Event {} +pub enum Event { + InterfaceDelete(String), + AddressInstall(String, IpNetwork), + AddressUninstall(String, IpNetwork), +} + +// ===== configuration structs ===== + +#[derive(Debug, Default)] +pub struct InterfaceCfg { + pub addr_list: BTreeSet, +} // ===== callbacks ===== fn load_callbacks() -> Callbacks { CallbacksBuilder::::default() .path(interfaces::interface::PATH) - .create_apply(|_master, _args| { - // TODO: implement me! + .create_apply(|master, args| { + let ifname = args.dnode.get_string_relative("./name").unwrap(); + + master.interfaces.add(ifname); }) - .delete_apply(|_master, _args| { - // TODO: implement me! + .delete_apply(|_master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + + let event_queue = args.event_queue; + event_queue.insert(Event::InterfaceDelete(ifname)); + }) + .lookup(|_instance, _list_entry, dnode| { + let ifname = dnode.get_string_relative("./name").unwrap(); + ListEntry::Interface(ifname) }) - .lookup(|_instance, _list_entry, _dnode| ListEntry::None) .path(interfaces::interface::description::PATH) .modify_apply(|_master, _args| { - // TODO: implement me! + // Nothing to do. }) .delete_apply(|_master, _args| { - // TODO: implement me! + // Nothing to do. }) .path(interfaces::interface::r#type::PATH) .modify_apply(|_master, _args| { @@ -68,6 +93,42 @@ fn load_callbacks() -> Callbacks { .modify_apply(|_context, _args| { // TODO: implement me! }) + .path(interfaces::interface::ipv4::address::PATH) + .create_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let addr = args.dnode.get_ipv4_relative("./ip").unwrap(); + let plen = args.dnode.get_u8_relative("./prefix-length").unwrap(); + let addr = Ipv4Network::new(addr, plen).unwrap().into(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.addr_list.insert(addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::AddressInstall(ifname, addr)); + }) + .delete_apply(|master, args| { + let (ifname, addr) = args.list_entry.into_address().unwrap(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.addr_list.remove(&addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::AddressUninstall(ifname, addr)); + }) + .lookup(|_master, list_entry, dnode| { + let ifname = list_entry.into_interface().unwrap(); + let addr = dnode.get_ipv4_relative("./ip").unwrap(); + let plen = dnode.get_u8_relative("./prefix-length").unwrap(); + let addr = Ipv4Network::new(addr, plen).unwrap(); + ListEntry::Address(ifname, addr.into()) + }) + .path(interfaces::interface::ipv4::address::prefix_length::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) + .delete_apply(|_master, _args| { + // Nothing to do. + }) .path(interfaces::interface::ipv6::PATH) .create_apply(|_context, _args| { // TODO: implement me! @@ -79,6 +140,39 @@ fn load_callbacks() -> Callbacks { .modify_apply(|_context, _args| { // TODO: implement me! }) + .path(interfaces::interface::ipv6::address::PATH) + .create_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let addr = args.dnode.get_ipv6_relative("./ip").unwrap(); + let plen = args.dnode.get_u8_relative("./prefix-length").unwrap(); + let addr = Ipv6Network::new(addr, plen).unwrap().into(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.addr_list.insert(addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::AddressInstall(ifname, addr)); + }) + .delete_apply(|master, args| { + let (ifname, addr) = args.list_entry.into_address().unwrap(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.addr_list.remove(&addr); + + let event_queue = args.event_queue; + event_queue.insert(Event::AddressUninstall(ifname, addr)); + }) + .lookup(|_master, list_entry, dnode| { + let ifname = list_entry.into_interface().unwrap(); + let addr = dnode.get_ipv6_relative("./ip").unwrap(); + let plen = dnode.get_u8_relative("./prefix-length").unwrap(); + let addr = Ipv6Network::new(addr, plen).unwrap(); + ListEntry::Address(ifname, addr.into()) + }) + .path(interfaces::interface::ipv6::address::prefix_length::PATH) + .modify_apply(|_master, _args| { + // TODO: implement me! + }) .build() } @@ -93,4 +187,38 @@ impl Provider for Master { fn callbacks() -> Option<&'static Callbacks> { Some(&CALLBACKS) } + + async fn process_event(&mut self, event: Event) { + match event { + Event::InterfaceDelete(ifname) => { + self.interfaces + .remove(&ifname, Owner::CONFIG, &self.netlink_handle, None) + .await; + } + Event::AddressInstall(ifname, addr) => { + // If the interface is active, install the address using the + // netlink handle. + if let Some(iface) = self.interfaces.get_by_name(&ifname) + && let Some(ifindex) = iface.ifindex + { + netlink::addr_install(&self.netlink_handle, ifindex, &addr) + .await; + } + } + Event::AddressUninstall(ifname, addr) => { + // If the interface is active, uninstall the address using the + // netlink handle. + if let Some(iface) = self.interfaces.get_by_name(&ifname) + && let Some(ifindex) = iface.ifindex + { + netlink::addr_uninstall( + &self.netlink_handle, + ifindex, + &addr, + ) + .await; + } + } + } + } } diff --git a/holo-interface/src/northbound/state.rs b/holo-interface/src/northbound/state.rs index c4dbd111..9f28f34d 100644 --- a/holo-interface/src/northbound/state.rs +++ b/holo-interface/src/northbound/state.rs @@ -27,7 +27,17 @@ fn load_callbacks() -> Callbacks { CallbacksBuilder::default() .path(interfaces::interface::PATH) .get_iterate(|_master, _args| { - // TODO: implement me! + // No operational data under this list. + None + }) + .path(interfaces::interface::ipv4::address::PATH) + .get_iterate(|_context, _args| { + // No operational data under this list. + None + }) + .path(interfaces::interface::ipv6::address::PATH) + .get_iterate(|_context, _args| { + // No operational data under this list. None }) .build() diff --git a/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang b/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang index c730279c..168111f5 100644 --- a/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang +++ b/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang @@ -26,45 +26,9 @@ module ietf-ip-holo-deviations { deviate not-supported; } - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address" { - deviate not-supported; - } - - /* - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:ip" { - deviate not-supported; - } - */ - - /* - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet" { - deviate not-supported; - } - */ - - /* - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:prefix-length" { - deviate not-supported; - } - */ - - /* - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:prefix-length/ip:prefix-length" { - deviate not-supported; - } - */ - - /* - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask" { - deviate not-supported; - } - */ - - /* deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:origin" { deviate not-supported; } - */ deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" { deviate not-supported; @@ -96,33 +60,13 @@ module ietf-ip-holo-deviations { deviate not-supported; } - deviation "/if:interfaces/if:interface/ip:ipv6/ip:address" { - deviate not-supported; - } - - /* - deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:ip" { - deviate not-supported; - } - */ - - /* - deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:prefix-length" { - deviate not-supported; - } - */ - - /* deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:origin" { deviate not-supported; } - */ - /* deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" { deviate not-supported; } - */ deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" { deviate not-supported; From f09a83f6594bd4efa75247917962a99b78c0b517 Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 13 Apr 2024 13:15:08 -0300 Subject: [PATCH 2/4] interface: implement admin status configuration Signed-off-by: Renato Westphal --- holo-interface/src/interface.rs | 8 ++++ holo-interface/src/netlink.rs | 19 +++++++++ .../src/northbound/configuration.rs | 42 +++++++++++++++++-- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/holo-interface/src/interface.rs b/holo-interface/src/interface.rs index 86f0e042..a47bb0ab 100644 --- a/holo-interface/src/interface.rs +++ b/holo-interface/src/interface.rs @@ -66,6 +66,14 @@ impl Interface { ifindex: u32, netlink_handle: &rtnetlink::Handle, ) { + // Set administrative status. + netlink::admin_status_change( + netlink_handle, + ifindex, + self.config.enabled, + ) + .await; + // Install interface addresses. for addr in &self.config.addr_list { netlink::addr_install(netlink_handle, ifindex, addr).await; diff --git a/holo-interface/src/netlink.rs b/holo-interface/src/netlink.rs index 913e7c5d..b5c0efab 100644 --- a/holo-interface/src/netlink.rs +++ b/holo-interface/src/netlink.rs @@ -175,6 +175,25 @@ fn parse_address( // ===== global functions ===== +pub(crate) async fn admin_status_change( + handle: &Handle, + ifindex: u32, + enabled: bool, +) { + // Create netlink request. + let request = handle.link().set(ifindex); + let request = if enabled { + request.up() + } else { + request.down() + }; + + // Execute request. + if let Err(error) = request.execute().await { + error!(%ifindex, %enabled, %error, "failed to change interface's admin status"); + } +} + pub(crate) async fn addr_install( handle: &Handle, ifindex: u32, diff --git a/holo-interface/src/northbound/configuration.rs b/holo-interface/src/northbound/configuration.rs index 12a9e5f8..5fae67a5 100644 --- a/holo-interface/src/northbound/configuration.rs +++ b/holo-interface/src/northbound/configuration.rs @@ -36,14 +36,16 @@ pub enum Resource {} #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum Event { InterfaceDelete(String), + AdminStatusChange(String, bool), AddressInstall(String, IpNetwork), AddressUninstall(String, IpNetwork), } // ===== configuration structs ===== -#[derive(Debug, Default)] +#[derive(Debug)] pub struct InterfaceCfg { + pub enabled: bool, pub addr_list: BTreeSet, } @@ -79,8 +81,15 @@ fn load_callbacks() -> Callbacks { // TODO: implement me! }) .path(interfaces::interface::enabled::PATH) - .modify_apply(|_master, _args| { - // TODO: implement me! + .modify_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let enabled = args.dnode.get_bool(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.enabled = enabled; + + let event_queue = args.event_queue; + event_queue.insert(Event::AdminStatusChange(ifname, enabled)); }) .path(interfaces::interface::ipv4::PATH) .create_apply(|_context, _args| { @@ -195,6 +204,20 @@ impl Provider for Master { .remove(&ifname, Owner::CONFIG, &self.netlink_handle, None) .await; } + Event::AdminStatusChange(ifname, enabled) => { + // If the interface is active, change its administrative status + // using the netlink handle. + if let Some(iface) = self.interfaces.get_by_name(&ifname) + && let Some(ifindex) = iface.ifindex + { + netlink::admin_status_change( + &self.netlink_handle, + ifindex, + enabled, + ) + .await; + } + } Event::AddressInstall(ifname, addr) => { // If the interface is active, install the address using the // netlink handle. @@ -222,3 +245,16 @@ impl Provider for Master { } } } + +// ===== configuration defaults ===== + +impl Default for InterfaceCfg { + fn default() -> InterfaceCfg { + let enabled = interfaces::interface::enabled::DFLT; + + InterfaceCfg { + enabled, + addr_list: Default::default(), + } + } +} From be4b67d3dc534029581bc4ebbf0d0b9326c0783a Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 13 Apr 2024 13:59:30 -0300 Subject: [PATCH 3/4] interface: implement MTU configuration Of particular note, the ietf-interfaces module has different leafs to configure MTU for IPv4 and IPv6, but Linux supports a single MTU for all L3 protocols. As such, a validation callback was added to ensure the IPv4 and IPv6 MTUs are the same when both are configured. Signed-off-by: Renato Westphal --- README.md | 2 +- holo-interface/src/interface.rs | 5 ++ holo-interface/src/netlink.rs | 10 +++ .../src/northbound/configuration.rs | 73 ++++++++++++++++++- .../deviations/ietf-ip-holo-deviations.yang | 8 -- 5 files changed, 88 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index df364b52..4aad9b02 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ Holo supports the following IETF RFCs and Internet drafts: | 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 | 43.48% | 0.00% | - | - | [33.33%](http://westphal.com.br/holo/ietf-ip.html) | +| ietf-ip@2018-01-09 | 52.17% | 0.00% | - | - | [40.00%](http://westphal.com.br/holo/ietf-ip.html) | | ietf-ipv4-unicast-routing@2018-03-13 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-ipv4-unicast-routing.html) | | ietf-ipv6-unicast-routing@2018-03-13 | 40.62% | 100.00% | - | - | [45.71%](http://westphal.com.br/holo/ietf-ipv6-unicast-routing.html) | | ietf-key-chain@2017-04-18 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-key-chain.html) | diff --git a/holo-interface/src/interface.rs b/holo-interface/src/interface.rs index a47bb0ab..f596b43a 100644 --- a/holo-interface/src/interface.rs +++ b/holo-interface/src/interface.rs @@ -74,6 +74,11 @@ impl Interface { ) .await; + // Set MTU. + if let Some(mtu) = self.config.mtu { + netlink::mtu_change(netlink_handle, ifindex, mtu).await; + } + // Install interface addresses. for addr in &self.config.addr_list { netlink::addr_install(netlink_handle, ifindex, addr).await; diff --git a/holo-interface/src/netlink.rs b/holo-interface/src/netlink.rs index b5c0efab..7682b490 100644 --- a/holo-interface/src/netlink.rs +++ b/holo-interface/src/netlink.rs @@ -194,6 +194,16 @@ pub(crate) async fn admin_status_change( } } +pub(crate) async fn mtu_change(handle: &Handle, ifindex: u32, mtu: u32) { + // Create netlink request. + let request = handle.link().set(ifindex).mtu(mtu); + + // Execute request. + if let Err(error) = request.execute().await { + error!(%ifindex, %mtu, %error, "failed to change interface's MTU"); + } +} + pub(crate) async fn addr_install( handle: &Handle, ifindex: u32, diff --git a/holo-interface/src/northbound/configuration.rs b/holo-interface/src/northbound/configuration.rs index 5fae67a5..90cc8d40 100644 --- a/holo-interface/src/northbound/configuration.rs +++ b/holo-interface/src/northbound/configuration.rs @@ -10,7 +10,8 @@ use std::sync::LazyLock as Lazy; use async_trait::async_trait; use enum_as_inner::EnumAsInner; use holo_northbound::configuration::{ - self, Callbacks, CallbacksBuilder, Provider, + self, Callbacks, CallbacksBuilder, Provider, ValidationCallbacks, + ValidationCallbacksBuilder, }; use holo_northbound::paths::interfaces; use holo_utils::yang::DataNodeRefExt; @@ -19,6 +20,8 @@ use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use crate::interface::Owner; use crate::{netlink, Master}; +static VALIDATION_CALLBACKS: Lazy = + Lazy::new(load_validation_callbacks); static CALLBACKS: Lazy> = Lazy::new(load_callbacks); @@ -37,6 +40,7 @@ pub enum Resource {} pub enum Event { InterfaceDelete(String), AdminStatusChange(String, bool), + MtuChange(String, u32), AddressInstall(String, IpNetwork), AddressUninstall(String, IpNetwork), } @@ -46,6 +50,7 @@ pub enum Event { #[derive(Debug)] pub struct InterfaceCfg { pub enabled: bool, + pub mtu: Option, pub addr_list: BTreeSet, } @@ -102,6 +107,23 @@ fn load_callbacks() -> Callbacks { .modify_apply(|_context, _args| { // TODO: implement me! }) + .path(interfaces::interface::ipv4::mtu::PATH) + .modify_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let mtu = args.dnode.get_u16() as u32; + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.mtu = Some(mtu); + + let event_queue = args.event_queue; + event_queue.insert(Event::MtuChange(ifname, mtu)); + }) + .delete_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.mtu = None; + }) .path(interfaces::interface::ipv4::address::PATH) .create_apply(|master, args| { let ifname = args.list_entry.into_interface().unwrap(); @@ -149,6 +171,23 @@ fn load_callbacks() -> Callbacks { .modify_apply(|_context, _args| { // TODO: implement me! }) + .path(interfaces::interface::ipv6::mtu::PATH) + .modify_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let mtu = args.dnode.get_u32(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.mtu = Some(mtu); + + let event_queue = args.event_queue; + event_queue.insert(Event::MtuChange(ifname, mtu)); + }) + .delete_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.mtu = None; + }) .path(interfaces::interface::ipv6::address::PATH) .create_apply(|master, args| { let ifname = args.list_entry.into_interface().unwrap(); @@ -185,6 +224,23 @@ fn load_callbacks() -> Callbacks { .build() } +fn load_validation_callbacks() -> ValidationCallbacks { + ValidationCallbacksBuilder::default() + .path(interfaces::interface::PATH) + .validate(|args| { + // Validate MTUs. + if let Some(mtu4) = args.dnode.get_u16_relative("./ipv4/mtu") + && let Some(mtu6) = args.dnode.get_u32_relative("./ipv6/mtu") + && mtu4 as u32 != mtu6 + { + return Err("IPv4 MTU and IPv6 MTU must be the same".to_owned()); + } + + Ok(()) + }) + .build() +} + // ===== impl Master ===== #[async_trait] @@ -193,6 +249,10 @@ impl Provider for Master { type Event = Event; type Resource = Resource; + fn validation_callbacks() -> Option<&'static ValidationCallbacks> { + Some(&VALIDATION_CALLBACKS) + } + fn callbacks() -> Option<&'static Callbacks> { Some(&CALLBACKS) } @@ -218,6 +278,16 @@ impl Provider for Master { .await; } } + Event::MtuChange(ifname, mtu) => { + // If the interface is active, change its MTU using the netlink + // handle. + if let Some(iface) = self.interfaces.get_by_name(&ifname) + && let Some(ifindex) = iface.ifindex + { + netlink::mtu_change(&self.netlink_handle, ifindex, mtu) + .await; + } + } Event::AddressInstall(ifname, addr) => { // If the interface is active, install the address using the // netlink handle. @@ -254,6 +324,7 @@ impl Default for InterfaceCfg { InterfaceCfg { enabled, + mtu: None, addr_list: Default::default(), } } diff --git a/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang b/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang index 168111f5..9fd39817 100644 --- a/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang +++ b/holo-yang/modules/deviations/ietf-ip-holo-deviations.yang @@ -22,10 +22,6 @@ module ietf-ip-holo-deviations { deviate not-supported; } - deviation "/if:interfaces/if:interface/ip:ipv4/ip:mtu" { - deviate not-supported; - } - deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:origin" { deviate not-supported; } @@ -56,10 +52,6 @@ module ietf-ip-holo-deviations { deviate not-supported; } - deviation "/if:interfaces/if:interface/ip:ipv6/ip:mtu" { - deviate not-supported; - } - deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:origin" { deviate not-supported; } From fb06e350afb2eaaeb586d48f9c23836c07fdcbe9 Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 13 Apr 2024 20:30:48 -0300 Subject: [PATCH 4/4] interface: implement VLAN subinterfaces configuration Example configuration (CLI): ``` [snip] interfaces interface eth-rt2.10 type ietf-if-extensions:ethSubInterface encapsulation dot1q-vlan outer-tag vlan-id 10 parent-interface eth-rt2 ! ``` Example configuration (JSON): ``` { "ietf-interfaces:interfaces": { "interface": [ [snip] { "name": "eth-rt2.10", "type": "ietf-if-extensions:ethSubInterface", "ietf-if-extensions:encapsulation": { "ietf-if-vlan-encapsulation:dot1q-vlan": { "outer-tag": { "vlan-id": 10 } } }, "ietf-if-extensions:parent-interface": "eth-rt2" } ] } } ``` Signed-off-by: Renato Westphal --- README.md | 2 + holo-interface/src/interface.rs | 21 +- holo-interface/src/netlink.rs | 15 + .../src/northbound/configuration.rs | 48 + holo-interface/src/northbound/mod.rs | 7 +- holo-tools/yang-coverage.sh | 2 + .../ietf-if-extensions-holo-deviations.yang | 28 + ...if-vlan-encapsulation-holo-deviations.yang | 51 + .../ieee/ieee802-dot1q-types@2022-01-19.yang | 980 ++++++++++++++++++ .../ietf/ietf-if-extensions@2023-01-26.yang | 535 ++++++++++ ...ietf-if-vlan-encapsulation@2023-01-26.yang | 155 +++ holo-yang/src/lib.rs | 20 + 12 files changed, 1862 insertions(+), 2 deletions(-) create mode 100644 holo-yang/modules/deviations/ietf-if-extensions-holo-deviations.yang create mode 100644 holo-yang/modules/deviations/ietf-if-vlan-encapsulation-holo-deviations.yang create mode 100644 holo-yang/modules/ieee/ieee802-dot1q-types@2022-01-19.yang create mode 100644 holo-yang/modules/ietf/ietf-if-extensions@2023-01-26.yang create mode 100644 holo-yang/modules/ietf/ietf-if-vlan-encapsulation@2023-01-26.yang diff --git a/README.md b/README.md index 4aad9b02..8f25ba22 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,8 @@ Holo supports the following IETF RFCs and Internet drafts: | 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-if-extensions@2023-01-26 | 100.00% | 0.00% | - | - | [50.00%](http://westphal.com.br/holo/ietf-if-extensions.html) | +| ietf-if-vlan-encapsulation@2023-01-26 | 42.86% | - | - | - | [42.86%](http://westphal.com.br/holo/ietf-if-vlan-encapsulation.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 | 52.17% | 0.00% | - | - | [40.00%](http://westphal.com.br/holo/ietf-ip.html) | | ietf-ipv4-unicast-routing@2018-03-13 | 100.00% | 100.00% | - | - | [100.00%](http://westphal.com.br/holo/ietf-ipv4-unicast-routing.html) | diff --git a/holo-interface/src/interface.rs b/holo-interface/src/interface.rs index f596b43a..756c68cf 100644 --- a/holo-interface/src/interface.rs +++ b/holo-interface/src/interface.rs @@ -65,6 +65,7 @@ impl Interface { &self, ifindex: u32, netlink_handle: &rtnetlink::Handle, + interfaces: &Interfaces, ) { // Set administrative status. netlink::admin_status_change( @@ -74,6 +75,22 @@ impl Interface { ) .await; + // Create VLAN subinterface. + if let Some(vlan_id) = self.config.vlan_id + && self.ifindex.is_none() + && let Some(parent) = &self.config.parent + && let Some(parent) = interfaces.get_by_name(parent) + && let Some(parent_ifindex) = parent.ifindex + { + netlink::vlan_create( + netlink_handle, + self.name.clone(), + parent_ifindex, + vlan_id, + ) + .await; + } + // Set MTU. if let Some(mtu) = self.config.mtu { netlink::mtu_change(netlink_handle, ifindex, mtu).await; @@ -153,7 +170,9 @@ impl Interfaces { // configuration options. if iface.ifindex.is_none() { iface.ifindex = Some(ifindex); - iface.apply_config(ifindex, netlink_handle).await; + + let iface = &self.arena[iface_idx]; + iface.apply_config(ifindex, netlink_handle, self).await; } } None => { diff --git a/holo-interface/src/netlink.rs b/holo-interface/src/netlink.rs index 7682b490..d32952e5 100644 --- a/holo-interface/src/netlink.rs +++ b/holo-interface/src/netlink.rs @@ -204,6 +204,21 @@ pub(crate) async fn mtu_change(handle: &Handle, ifindex: u32, mtu: u32) { } } +pub(crate) async fn vlan_create( + handle: &Handle, + name: String, + parent_ifindex: u32, + vlan_id: u16, +) { + // Create netlink request. + let request = handle.link().add().vlan(name, parent_ifindex, vlan_id); + + // Execute request. + if let Err(error) = request.execute().await { + error!(%parent_ifindex, %vlan_id, %error, "failed to create VLAN interface"); + } +} + pub(crate) async fn addr_install( handle: &Handle, ifindex: u32, diff --git a/holo-interface/src/northbound/configuration.rs b/holo-interface/src/northbound/configuration.rs index 90cc8d40..6b4a3cc8 100644 --- a/holo-interface/src/northbound/configuration.rs +++ b/holo-interface/src/northbound/configuration.rs @@ -41,6 +41,7 @@ pub enum Event { InterfaceDelete(String), AdminStatusChange(String, bool), MtuChange(String, u32), + VlanCreate(String, u16), AddressInstall(String, IpNetwork), AddressUninstall(String, IpNetwork), } @@ -51,6 +52,8 @@ pub enum Event { pub struct InterfaceCfg { pub enabled: bool, pub mtu: Option, + pub parent: Option, + pub vlan_id: Option, pub addr_list: BTreeSet, } @@ -96,6 +99,31 @@ fn load_callbacks() -> Callbacks { let event_queue = args.event_queue; event_queue.insert(Event::AdminStatusChange(ifname, enabled)); }) + .path(interfaces::interface::parent_interface::PATH) + .modify_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let parent = args.dnode.get_string(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.parent = Some(parent); + }) + .delete_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.parent = None; + }) + .path(interfaces::interface::encapsulation::dot1q_vlan::outer_tag::vlan_id::PATH) + .modify_apply(|master, args| { + let ifname = args.list_entry.into_interface().unwrap(); + let vlan_id = args.dnode.get_u16(); + + let iface = master.interfaces.get_mut_by_name(&ifname).unwrap(); + iface.config.vlan_id = Some(vlan_id); + + let event_queue = args.event_queue; + event_queue.insert(Event::VlanCreate(ifname, vlan_id)); + }) .path(interfaces::interface::ipv4::PATH) .create_apply(|_context, _args| { // TODO: implement me! @@ -288,6 +316,24 @@ impl Provider for Master { .await; } } + Event::VlanCreate(ifname, vlan_id) => { + // If the parent interface is active, create VLAN subinterface + // using the netlink handle. + if let Some(iface) = self.interfaces.get_by_name(&ifname) + && iface.ifindex.is_none() + && let Some(parent) = &iface.config.parent + && let Some(parent) = self.interfaces.get_by_name(parent) + && let Some(parent_ifindex) = parent.ifindex + { + netlink::vlan_create( + &self.netlink_handle, + iface.name.clone(), + parent_ifindex, + vlan_id, + ) + .await; + } + } Event::AddressInstall(ifname, addr) => { // If the interface is active, install the address using the // netlink handle. @@ -325,6 +371,8 @@ impl Default for InterfaceCfg { InterfaceCfg { enabled, mtu: None, + parent: None, + vlan_id: None, addr_list: Default::default(), } } diff --git a/holo-interface/src/northbound/mod.rs b/holo-interface/src/northbound/mod.rs index d8b02c9d..dcb2034d 100644 --- a/holo-interface/src/northbound/mod.rs +++ b/holo-interface/src/northbound/mod.rs @@ -17,7 +17,12 @@ use crate::Master; impl ProviderBase for Master { fn yang_modules() -> &'static [&'static str] { - &["ietf-interfaces", "ietf-ip"] + &[ + "ietf-if-extensions", + "ietf-if-vlan-encapsulation", + "ietf-interfaces", + "ietf-ip", + ] } fn top_level_node(&self) -> String { diff --git a/holo-tools/yang-coverage.sh b/holo-tools/yang-coverage.sh index 91289ed1..446bd24b 100755 --- a/holo-tools/yang-coverage.sh +++ b/holo-tools/yang-coverage.sh @@ -2,6 +2,8 @@ cargo run --bin yang_coverage --\ -m ietf-interfaces\ + -m ietf-if-extensions\ + -m ietf-if-vlan-encapsulation\ -m ietf-ip\ -m ietf-ipv4-unicast-routing\ -m ietf-ipv6-unicast-routing\ diff --git a/holo-yang/modules/deviations/ietf-if-extensions-holo-deviations.yang b/holo-yang/modules/deviations/ietf-if-extensions-holo-deviations.yang new file mode 100644 index 00000000..f598ee60 --- /dev/null +++ b/holo-yang/modules/deviations/ietf-if-extensions-holo-deviations.yang @@ -0,0 +1,28 @@ +module ietf-if-extensions-holo-deviations { + yang-version 1.1; + namespace "http://holo-routing.org/yang/ietf-if-extensions-holo-deviations"; + prefix ietf-if-extensions-holo-deviations; + + import ietf-interfaces { + prefix if; + } + + import ietf-if-extensions { + prefix if-ext; + } + + organization + "Holo Routing Stack"; + + description + "This module defines deviation statements for the ietf-if-extensions + module."; + + deviation "/if:interfaces/if:interface/if:statistics/if-ext:in-discard-unknown-encaps" { + deviate not-supported; + } + + deviation "/if:interfaces/if:interface/if-ext:forwarding-mode" { + deviate not-supported; + } +} diff --git a/holo-yang/modules/deviations/ietf-if-vlan-encapsulation-holo-deviations.yang b/holo-yang/modules/deviations/ietf-if-vlan-encapsulation-holo-deviations.yang new file mode 100644 index 00000000..cfaeb083 --- /dev/null +++ b/holo-yang/modules/deviations/ietf-if-vlan-encapsulation-holo-deviations.yang @@ -0,0 +1,51 @@ +module ietf-if-vlan-encapsulation-holo-deviations { + yang-version 1.1; + namespace "http://holo-routing.org/yang/ietf-if-vlan-encapsulation-holo-deviations"; + prefix ietf-if-vlan-encapsulation-holo-deviations; + + import ietf-interfaces { + prefix if; + } + + import ietf-if-extensions { + prefix if-ext; + } + + import ietf-if-vlan-encapsulation { + prefix if-vlan; + } + + organization + "Holo Routing Stack"; + + description + "This module defines deviation statements for the ietf-if-vlan-encapsulation + module."; + + deviation "/if:interfaces/if:interface/if-ext:encapsulation/if-ext:encaps-type/if-vlan:dot1q-vlan/if-vlan:dot1q-vlan/if-vlan:outer-tag" { + deviate delete { + must 'tag-type = "dot1q-types:s-vlan" or ' + + 'tag-type = "dot1q-types:c-vlan"'; + } + } + + deviation "/if:interfaces/if:interface/if-ext:encapsulation/if-ext:encaps-type/if-vlan:dot1q-vlan/if-vlan:dot1q-vlan/if-vlan:outer-tag/if-vlan:tag-type" { + deviate not-supported; + } + + deviation "/if:interfaces/if:interface/if-ext:encapsulation/if-ext:encaps-type/if-vlan:dot1q-vlan/if-vlan:dot1q-vlan/if-vlan:second-tag" { + deviate not-supported; + } + + /* + deviation "/if:interfaces/if:interface/if-ext:encapsulation/if-ext:encaps-type/if-vlan:dot1q-vlan/if-vlan:dot1q-vlan/if-vlan:second-tag/if-vlan:tag-type" { + deviate not-supported; + } + */ + + /* + deviation "/if:interfaces/if:interface/if-ext:encapsulation/if-ext:encaps-type/if-vlan:dot1q-vlan/if-vlan:dot1q-vlan/if-vlan:second-tag/if-vlan:vlan-id" { + deviate not-supported; + } + */ +} diff --git a/holo-yang/modules/ieee/ieee802-dot1q-types@2022-01-19.yang b/holo-yang/modules/ieee/ieee802-dot1q-types@2022-01-19.yang new file mode 100644 index 00000000..cfc46ed9 --- /dev/null +++ b/holo-yang/modules/ieee/ieee802-dot1q-types@2022-01-19.yang @@ -0,0 +1,980 @@ +module ieee802-dot1q-types { + namespace urn:ieee:std:802.1Q:yang:ieee802-dot1q-types; + prefix dot1q-types; + import ietf-yang-types { + prefix yang; + } + organization + "IEEE 802.1 Working Group"; + contact + "WG-URL: http://ieee802.org/1/ + WG-EMail: stds-802-1-l@ieee.org + + Contact: IEEE 802.1 Working Group Chair + Postal: C/O IEEE 802.1 Working Group + IEEE Standards Association + 445 Hoes Lane + Piscataway, NJ 08854 + USA + + E-mail: stds-802-1-chairs@ieee.org"; + description + "Common types used within dot1Q-bridge modules. + + Copyright (C) IEEE (2022). + + This version of this YANG module is part of IEEE Std 802.1Q; see the + standard itself for full legal notices."; + revision 2022-01-19 { + description + "Published as part of IEEE Std 802.1Q-2022."; + reference + "IEEE Std 802.1Q-2022, Bridges and Bridged Networks."; + } + revision 2020-06-04 { + description + "Published as part of IEEE Std 802.1Qcx-2020. Second version."; + reference + "IEEE Std 802.1Qcx-2020, Bridges and Bridged Networks - YANG Data + Model for Connectivity Fault Management."; + } + revision 2018-03-07 { + description + "Published as part of IEEE Std 802.1Q-2018. Initial version."; + reference + "IEEE Std 802.1Q-2018, Bridges and Bridged Networks."; + } + identity dot1q-vlan-type { + description + "Base identity from which all 802.1Q VLAN tag types are derived + from."; + } + identity c-vlan { + base dot1q-vlan-type; + description + "An 802.1Q Customer VLAN, using the 81-00 EtherType"; + reference + "5.5 of IEEE Std 802.1Q-2022"; + } + identity s-vlan { + base dot1q-vlan-type; + description + "An 802.1Q Service VLAN, using the 88-A8 EtherType originally + introduced in 802.1ad, and incorporated into 802.1Q (2011)"; + reference + "5.6 of IEEE Std 802.1Q-2022"; + } + identity transmission-selection-algorithm { + description + "Specify the transmission selection algorithms of IEEE Std + 802.1Q-2022 Table 8-6"; + } + identity strict-priority { + base transmission-selection-algorithm; + description + "Indicates the strict priority transmission selection algorithm."; + reference + "Table 8-6 of IEEE Std 802.1Q-2022"; + } + identity credit-based-shaper { + base transmission-selection-algorithm; + description + "Indicates the credit based shaper transmission selection + algorithm."; + reference + "Table 8-6 of IEEE Std 802.1Q-2022"; + } + identity enhanced-transmission-selection { + base transmission-selection-algorithm; + description + "Indicates the enhanced transmission selection algorithm."; + reference + "Table 8-6 of IEEE Std 802.1Q-2022"; + } + identity asynchronous-traffic-shaping { + base transmission-selection-algorithm; + description + "Indicates the asynchronous transmission selection algorithm."; + reference + "Table 8-6 of IEEE Std 802.1Q-2022"; + } + identity vendor-specific { + base transmission-selection-algorithm; + description + "Indicates a vendor specific transmission selection algorithm."; + reference + "Table 8-6 of IEEE Std 802.1Q-2022"; + } + typedef name-type { + type string { + length "0..32"; + } + description + "A text string of up to 32 characters, of locally determined + significance."; + } + typedef port-number-type { + type uint32 { + range "1..4095"; + } + description + "The port number of the Bridge port for which this entry contains + Bridge management information."; + } + typedef priority-type { + type uint8 { + range "0..7"; + } + description + "A range of priorities from 0 to 7 (inclusive). The Priority Code + Point (PCP) is a 3-bit field that refers to the class of service + associated with an 802.1Q VLAN tagged frame. The field specifies a + priority value between 0 and 7, these values can be used by + quality of service (QoS) to prioritize different classes of + traffic."; + } + typedef vid-range-type { + type string { + pattern + "([1-9]"+ + "[0-9]{0,3}"+ + "(-[1-9][0-9]{0,3})?"+ + "(,[1-9][0-9]{0,3}(-[1-9][0-9]{0,3})?)*)"; + } + description + "A list of VLAN Ids, or non overlapping VLAN ranges, in ascending + order, between 1 and 4094. + + This type is used to match an ordered list of VLAN Ids, or + contiguous ranges of VLAN Ids. Valid VLAN Ids must be in the range + 1 to 4094, and included in the list in non overlapping ascending + order. + + For example: 1,10-100,250,500-1000"; + } + typedef vlanid { + type uint16 { + range "1..4094"; + } + description + "The vlanid type uniquely identifies a VLAN. This is the 12-bit + VLAN-ID used in the VLAN Tag header. The range is defined by the + referenced specification. This type is in the value set and its + semantics equivalent to the VlanId textual convention of the + SMIv2."; + } + typedef vlan-index-type { + type uint32 { + range "1..4094 | 4096..4294967295"; + } + description + "A value used to index per-VLAN tables. Values of 0 and 4095 are + not permitted. The range of valid VLAN indices. If the value is + greater than 4095, then it represents a VLAN with scope local to + the particular agent, i.e., one without a global VLAN-ID assigned + to it. Such VLANs are outside the scope of IEEE 802.1Q, but it is + convenient to be able to manage them in the same way using this + YANG module."; + reference + "9.6 of IEEE Std 802.1Q-2022"; + } + typedef mstid-type { + type uint32 { + range "1..4094"; + } + description + "In an MSTP Bridge, an MSTID, i.e., a value used to identify a + spanning tree (or MST) instance"; + reference + "13.8 of IEEE Std 802.1Q-2022"; + } + typedef pcp-selection-type { + type enumeration { + enum 8P0D { + description + "8 priorities, 0 drop eligible"; + } + enum 7P1D { + description + "7 priorities, 1 drop eligible"; + } + enum 6P2D { + description + "6 priorities, 2 drop eligible"; + } + enum 5P3D { + description + "5 priorities, 3 drop eligible"; + } + } + description + "Priority Code Point selection types."; + reference + "12.6.2.5.3, 6.9.3 of IEEE Std 802.1Q-2022"; + } + typedef protocol-frame-format-type { + type enumeration { + enum Ethernet { + description + "Ethernet frame format"; + } + enum rfc1042 { + description + "RFC 1042 frame format"; + } + enum snap8021H { + description + "SNAP 802.1H frame format"; + } + enum snapOther { + description + "Other SNAP frame format"; + } + enum llcOther { + description + "Other LLC frame format"; + } + } + description + "A value representing the frame format to be matched."; + reference + "12.10.1.7.1 of IEEE Std 802.1Q-2022"; + } + typedef ethertype-type { + type string { + pattern "[0-9a-fA-F]{2}-[0-9a-fA-F]{2}"; + } + description + "The EtherType value represented in the canonical order defined by + IEEE 802. The canonical representation uses uppercase characters."; + reference + "9.2 of IEEE Std 802-2014"; + } + typedef dot1q-tag-type { + type identityref { + base dot1q-vlan-type; + } + description + "Identifies a specific 802.1Q tag type"; + reference + "9.5 IEEE Std 802.1Q-2022"; + } + typedef traffic-class-type { + type uint8 { + range "0..7"; + } + description + "This is the numerical value associated with a traffic class in a + Bridge. Larger values are associated with higher priority traffic + classes."; + reference + "3.273 of IEEE Std 802.1Q-2022"; + } + grouping dot1q-tag-classifier-grouping { + description + "A grouping which represents an 802.1Q VLAN, matching both the + EtherType and a single VLAN Id."; + leaf tag-type { + type dot1q-tag-type; + mandatory true; + description + "VLAN type"; + } + leaf vlan-id { + type vlanid; + mandatory true; + description + "VLAN Id"; + } + } + grouping dot1q-tag-or-any-classifier-grouping { + description + "A grouping which represents an 802.1Q VLAN, matching both the + EtherType and a single VLAN Id or 'any' to match on any VLAN Id."; + leaf tag-type { + type dot1q-tag-type; + mandatory true; + description + "VLAN type"; + } + leaf vlan-id { + type union { + type vlanid; + type enumeration { + enum any { + value 4095; + description + "Matches 'any' VLAN in the range 1 to 4094 that is not + matched by a more specific VLAN Id match"; + } + } + } + mandatory true; + description + "VLAN Id or any"; + } + } + grouping dot1q-tag-ranges-classifier-grouping { + description + "A grouping which represents an 802.1Q VLAN that matches a range + of VLAN Ids."; + leaf tag-type { + type dot1q-tag-type; + mandatory true; + description + "VLAN type"; + } + leaf vlan-ids { + type vid-range-type; + mandatory true; + description + "VLAN Ids"; + } + } + grouping dot1q-tag-ranges-or-any-classifier-grouping { + description + "A grouping which represents an 802.1Q VLAN, matching both the + EtherType and a single VLAN Id, ordered list of ranges, or 'any' + to match on any VLAN Id."; + leaf tag-type { + type dot1q-tag-type; + mandatory true; + description + "VLAN type"; + } + leaf vlan-id { + type union { + type vid-range-type; + type enumeration { + enum any { + value 4095; + description + "Matches 'any' VLAN in the range 1 to 4094."; + } + } + } + mandatory true; + description + "VLAN Ids or any"; + } + } + grouping priority-regeneration-table-grouping { + description + "The priority regeneration table provides the ability to map + incoming priority values on a per-Port basis, under management + control."; + reference + "6.9.4 of IEEE Std 802.1Q-2022"; + leaf priority0 { + type priority-type; + default "0"; + description + "Priority 0"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority1 { + type priority-type; + default "1"; + description + "Priority 1"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority2 { + type priority-type; + default "2"; + description + "Priority 2"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority3 { + type priority-type; + default "3"; + description + "Priority 3"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority4 { + type priority-type; + default "4"; + description + "Priority 4"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority5 { + type priority-type; + default "5"; + description + "Priority 5"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority6 { + type priority-type; + default "6"; + description + "Priority 6"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + leaf priority7 { + type priority-type; + default "7"; + description + "Priority 7"; + reference + "12.6.2.3, 6.9.4 of IEEE Std 802.1Q-2022"; + } + } + grouping pcp-decoding-table-grouping { + description + "The Priority Code Point decoding table enables the decoding of + the priority and drop-eligible parameters from the PCP."; + reference + "6.9.3 of IEEE Std 802.1Q-2022"; + list pcp-decoding-map { + key "pcp"; + description + "This map associates the priority code point field found in the + VLAN to a priority and drop eligible value based upon the + priority code point selection type."; + leaf pcp { + type pcp-selection-type; + description + "The priority code point selection type."; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + list priority-map { + key "priority-code-point"; + description + "This map associated a priority code point value to priority + and drop eligible parameters."; + leaf priority-code-point { + type priority-type; + description + "Priority associated with the pcp."; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + leaf priority { + type priority-type; + description + "Priority associated with the pcp."; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + leaf drop-eligible { + type boolean; + description + "Drop eligible value for pcp"; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + } + } + } + grouping pcp-encoding-table-grouping { + description + "The Priority Code Point encoding table encodes the priority and + drop-eligible parameters in the PCP field of the VLAN tag."; + reference + "12.6.2.9, 6.9.3 of IEEE Std 802.1Q-2022"; + list pcp-encoding-map { + key "pcp"; + description + "This map associated the priority and drop-eligible parameters + to the priority used to encode the PCP of the VLAN based upon + the priority code point selection type."; + leaf pcp { + type pcp-selection-type; + description + "The priority code point selection type."; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + list priority-map { + key "priority dei"; + description + "This map associated the priority and drop-eligible parameters + to the priority code point field of the VLAN tag."; + leaf priority { + type priority-type; + description + "Priority associated with the pcp."; + reference + "12.6.2.7, 6.9.3 of IEEE Std 802.1Q-2022"; + } + leaf dei { + type boolean; + description + "The drop eligible value."; + reference + "12.6.2, 8.6.6 of IEEE Std 802.1Q-2022"; + } + leaf priority-code-point { + type priority-type; + description + "PCP value for priority when DEI value"; + reference + "12.6.2.9, 6.9.3 of IEEE Std 802.1Q-2022"; + } + } + } + } + grouping service-access-priority-table-grouping { + description + "The Service Access Priority Table associates a received priority + with a serice access priority."; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + leaf priority0 { + type priority-type; + default "0"; + description + "Service access priority value for priority 0"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority1 { + type priority-type; + default "1"; + description + "Service access priority value for priority 1"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority2 { + type priority-type; + default "2"; + description + "Service access priority value for priority 2"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority3 { + type priority-type; + default "3"; + description + "Service access priority value for priority 3"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority4 { + type priority-type; + default "4"; + description + "Service access priority value for priority 4"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority5 { + type priority-type; + default "5"; + description + "Service access priority value for priority 5"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority6 { + type priority-type; + default "6"; + description + "Service access priority value for priority 6"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + leaf priority7 { + type priority-type; + default "7"; + description + "Service access priority value for priority 7"; + reference + "12.6.2.17, 6.13.1 of IEEE Std 802.1Q-2022"; + } + } + grouping traffic-class-table-grouping { + description + "The Traffic Class Table models the operations that can be + performed on, or inquire about, the current contents of the + Traffic Class Table (8.6.6) for a given Port."; + reference + "12.6.3, 8.6.6 of IEEE Std 802.1Q-2022"; + list traffic-class-map { + key "priority"; + description + "The priority index into the traffic class table."; + leaf priority { + type priority-type; + description + "The priority of the traffic class entry."; + reference + "8.6.6 of IEEE Std 802.1Q-2022"; + } + list available-traffic-class { + key "num-traffic-class"; + description + "The traffic class index associated with a given priority + within the traffic class table."; + reference + "8.6.6 of IEEE Std 802.1Q-2022"; + leaf num-traffic-class { + type uint8 { + range "1..8"; + } + description + "The available number of traffic classes."; + reference + "8.6.6 of IEEE Std 802.1Q-2022"; + } + leaf traffic-class { + type traffic-class-type; + description + "The traffic class index associated with a given traffic + class entry."; + reference + "8.6.6 of IEEE Std 802.1Q-2022"; + } + } + } + } + grouping transmission-selection-table-grouping { + description + "The Transmission Selection Algorithm Table models the operations + that can be performed on, or inquire about, the current contents + of the Transmission Selection Algorithm Table (12.20.2) for a + given Port."; + reference + "12.20.2, 8.6.8 of IEEE Std 802.1Q-2022"; + list transmission-selection-algorithm-map { + key "traffic-class"; + description + "The traffic class to index into the transmission selection + table."; + leaf traffic-class { + type traffic-class-type; + description + "The traffic class of the entry."; + reference + "8.6.6 of IEEE Std 802.1Q-2022"; + } + leaf transmission-selection-algorithm { + type identityref { + base dot1q-types:transmission-selection-algorithm; + } + description + "Transmission selection algorithm"; + reference + "8.6.8, Table 8-6 of IEEE Std 802.1Q-2022"; + } + } + } + grouping port-map-grouping { + description + "A set of control indicators, one for each Port. A Port Map, + containing a control element for each outbound Port"; + reference + "8.8.1, 8.8.2 of IEEE Std 802.1Q-2022"; + list port-map { + key "port-ref"; + description + "The list of entries composing the port map."; + leaf port-ref { + type port-number-type; + description + "The interface port reference associated with this map."; + reference + "8.8.1 of IEEE Std 802.1Q-2022"; + } + choice map-type { + description + "Type of port map"; + container static-filtering-entries { + description + "Static filtering entries attributes."; + leaf control-element { + type enumeration { + enum forward { + description + "Forwarded, independently of any dynamic filtering + information held by the FDB."; + } + enum filter { + description + "Filtered, independently of any dynamic filtering + information."; + } + enum forward-filter { + description + "Forwarded or filtered on the basis of dynamic + filtering information, or on the basis of the default + Group filtering behavior for the outbound Port (8.8.6) + if no dynamic filtering information is present + specifically for the MAC address."; + } + } + description + "containing a control element for each outbound Port, + specifying that a frame with a destination MAC address, + and in the case of VLAN Bridge components, VID that meets + this specification."; + reference + "8.8.1 of IEEE Std 802.1Q-2022"; + } + leaf connection-identifier { + type port-number-type; + description + "A Port MAP may contain a connection identifier (8.8.12) + for each outbound port. The connection identifier may be + associated with the Bridge Port value maintained in a + Dynamic Filtering Entry of the FDB for Bridge Ports."; + reference + "8.8.1, 8.8.12 of IEEE Std 802.1Q-2022"; + } + } + container static-vlan-registration-entries { + description + "Static VLAN registration entries."; + leaf registrar-admin-control { + type enumeration { + enum fixed-new-ignored { + description + "Registration Fixed (New ignored)."; + } + enum fixed-new-propagated { + description + "Registration Fixed (New propagated."; + } + enum forbidden { + description + "Registration Forbidden."; + } + enum normal { + description + "Normal Registration."; + } + } + description + "The Registrar Administrative Control values for MVRP and + MIRP for the VID."; + reference + "8.8.2 of IEEE Std 802.1Q-2022"; + } + leaf vlan-transmitted { + type enumeration { + enum tagged { + description + "VLAN-tagged"; + } + enum untagged { + description + "VLAN-untagged"; + } + } + description + "Whether frames are to be VLAN-tagged or untagged when + transmitted."; + reference + "8.8.2 of IEEE Std 802.1Q-2022"; + } + } + container mac-address-registration-entries { + description + "MAC address registration entries attributes."; + leaf control-element { + type enumeration { + enum registered { + description + "Forwarded, independently of any dynamic filtering + information held by the FDB."; + } + enum not-registered { + description + "Filtered, independently of any dynamic filtering + information."; + } + } + description + "containing a control element for each outbound Port, + specifying that a frame with a destination MAC address, + and in the case of VLAN Bridge components, VID that meets + this specification."; + reference + "8.8.4 of IEEE Std 802.1Q-2022"; + } + } + container dynamic-vlan-registration-entries { + description + "Dynamic VLAN registration entries attributes."; + leaf control-element { + type enumeration { + enum registered { + description + "Forwarded, independently of any dynamic filtering + information held by the FDB."; + } + } + description + "containing a control element for each outbound Port, + specifying that a frame with a destination MAC address, + and in the case of VLAN Bridge components, VID that meets + this specification."; + reference + "8.8.5 of IEEE Std 802.1Q-2022"; + } + } + container dynamic-reservation-entries { + description + "Dynamic reservation entries attributes."; + leaf control-element { + type enumeration { + enum forward { + description + "Forwarded, independently of any dynamic filtering + information held by the FDB."; + } + enum filter { + description + "Filtered, independently of any dynamic filtering + information."; + } + } + description + "Containing a control element for each outbound Port, + specifying that a frame with a destination MAC address, + and in the case of VLAN Bridge components, VID that meets + this specification."; + reference + "8.8.7 of IEEE Std 802.1Q-2022"; + } + } + container dynamic-filtering-entries { + description + "Dynamic filtering entries attributes."; + leaf control-element { + type enumeration { + enum forward { + description + "Forwarded, independently of any dynamic filtering + information held by the FDB."; + } + } + description + "Containing a control element for each outbound Port, + specifying that a frame with a destination MAC address, + and in the case of VLAN Bridge components, VID that meets + this specification."; + reference + "8.8.3 of IEEE Std 802.1Q-2022"; + } + } + } + } + } + grouping bridge-port-statistics-grouping { + description + "Grouping of bridge port statistics."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + leaf delay-exceeded-discards { + type yang:counter64; + description + "The number of frames discarded by this port due to excessive + transit delay through the Bridge. It is incremented by both + transparent and source route Bridges."; + reference + "12.6.1.1.3, 8.6.6 of IEEE Std 802.1Q-2022"; + } + leaf mtu-exceeded-discards { + type yang:counter64; + description + "The number of frames discarded by this port due to an excessive + size. It is incremented by both transparent and source route + Bridges."; + reference + "Item g) in 12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf frame-rx { + type yang:counter64; + description + "The number of frames that have been received by this port from + its segment. Note that a frame received on the interface + corresponding to this port is only counted by this object if and + only if it is for a protocol being processed by the local + bridging function, including Bridge management frames."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf octets-rx { + type yang:counter64; + description + "The total number of octets in all valid frames received + (including BPDUs, frames addressed to the Bridge as an end + station, and frames that were submitted to the Forwarding + Process)."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf frame-tx { + type yang:counter64; + description + "The number of frames that have been transmitted by this port to + its segment. Note that a frame transmitted on the interface + corresponding to this port is only counted by this object if and + only if it is for a protocol being processed by the local + bridging function, including Bridge management frames."; + } + leaf octets-tx { + type yang:counter64; + description + "The total number of octets that have been transmitted by this + port to its segment."; + } + leaf discard-inbound { + type yang:counter64; + description + "Count of received valid frames that were discarded (i.e., + filtered) by the Forwarding Process."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf forward-outbound { + type yang:counter64; + description + "The number of frames forwarded to the associated MAC Entity + (8.5)."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf discard-lack-of-buffers { + type yang:counter64; + description + "The count of frames that were to be transmitted through the + associated Port but were discarded due to lack of buffers."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf discard-transit-delay-exceeded { + type yang:counter64; + description + "The number of frames discarded by this port due to excessive + transit delay through the Bridge. It is incremented by both + transparent and source route Bridges."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + leaf discard-on-error { + type yang:counter64; + description + "The number of frames that were to be forwarded on the + associated MAC but could not be transmitted (e.g., frame would + be too large, 6.5.8)."; + reference + "12.6.1.1.3 of IEEE Std 802.1Q-2022"; + } + } +} diff --git a/holo-yang/modules/ietf/ietf-if-extensions@2023-01-26.yang b/holo-yang/modules/ietf/ietf-if-extensions@2023-01-26.yang new file mode 100644 index 00000000..fdcb0bcc --- /dev/null +++ b/holo-yang/modules/ietf/ietf-if-extensions@2023-01-26.yang @@ -0,0 +1,535 @@ +module ietf-if-extensions { + yang-version 1.1; + + namespace "urn:ietf:params:xml:ns:yang:ietf-if-extensions"; + + prefix if-ext; + + import ietf-yang-types { + prefix yang; + reference "RFC 6991: Common YANG Data Types"; + } + + import ietf-interfaces { + prefix if; + reference + "RFC 8343: A YANG Data Model For Interface Management"; + } + + import iana-if-type { + prefix ianaift; + reference "RFC 7224: IANA Interface Type YANG Module"; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + Editor: Robert Wilton + "; + + description + "This module contains common definitions for extending the IETF + interface YANG model (RFC 8343) with common configurable layer 2 + properties. + + Copyright (c) 2023 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Revised BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX + (https://www.rfc-editor.org/info/rfcXXXX); see the RFC itself + for full legal notices. + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here."; + + revision 2023-01-26 { + description + "Initial revision."; + + reference + "RFC XXXX, Common Interface Extension YANG Data Models"; + } + + feature link-flap-suppression { + description + "This feature indicates that configurable interface link + delay is supported, which is a feature is used to limit the + propagation of very short interface link state flaps."; + reference "RFC XXXX, Section 2.1 Link Flap Suppression"; + } + + feature dampening { + description + "This feature indicates that the device supports interface + dampening, which is a feature that is used to limit the + propagation of interface link state flaps over longer + periods."; + reference "RFC XXXX, Section 2.2 Dampening"; + } + + feature loopback { + description + "This feature indicates that configurable interface loopback is + supported."; + reference "RFC XXXX, Section 2.4 Loopback"; + } + + feature max-frame-size { + description + "This feature indicates that the device supports configuring or + reporting the maximum frame size on interfaces."; + reference "RFC XXXX, Section 2.5 Maximum Frame Size"; + } + + feature sub-interfaces { + description + "This feature indicates that the device supports the + instantiation of sub-interfaces. Sub-interfaces are defined + as logical child interfaces that allow features and forwarding + decisions to be applied to a subset of the traffic processed + on the specified parent interface."; + reference "RFC XXXX, Section 2.6 Sub-interface"; + } + + /* + * Define common identities to help allow interface types to be + * assigned properties. + */ + identity sub-interface { + description + "Base type for generic sub-interfaces. + + New or custom interface types can derive from this type to + inherit generic sub-interface configuration."; + reference "RFC XXXX, Section 2.6 Sub-interface"; + } + + identity ethSubInterface{ + base ianaift:l2vlan; + base sub-interface; + + description + "This identity represents the child sub-interface of any + interface types that uses Ethernet framing (with or without + 802.1Q tagging)."; + } + + identity loopback { + description "Base identity for interface loopback options"; + reference "RFC XXXX, Section 2.4"; + } + identity internal { + base loopback; + description + "All egress traffic on the interface is internally looped back + within the interface to be received on the ingress path."; + reference "RFC XXXX, Section 2.4"; + } + identity line { + base loopback; + description + "All ingress traffic received on the interface is internally + looped back within the interface to the egress path."; + reference "RFC XXXX, Section 2.4"; + } + identity connector { + base loopback; + description + "The interface has a physical loopback connector attached that + loops all egress traffic back into the interface's ingress + path, with equivalent semantics to loopback internal."; + reference "RFC XXXX, Section 2.4"; + } + + identity forwarding-mode { + description "Base identity for forwarding-mode options."; + reference "RFC XXXX, Section 2.7"; + } + identity physical { + base forwarding-mode; + description + "Physical layer forwarding. This includes DWDM or OTN based + optical switching."; + reference "RFC XXXX, Section 2.7"; + } + identity data-link { + base forwarding-mode; + description + "Layer 2 based forwarding, such as Ethernet/VLAN based + switching, or L2VPN services."; + reference "RFC XXXX, Section 2.7"; + } + identity network { + base forwarding-mode; + description + "Network layer based forwarding, such as IP, MPLS, or L3VPNs."; + reference "RFC XXXX, Section 2.7"; + } + + /* + * Augments the IETF interfaces model with leaves to configure + * and monitor link-flap-suppression on an interface. + */ + augment "/if:interfaces/if:interface" { + description + "Augments the IETF interface model with optional common + interface level commands that are not formally covered by any + specific standard."; + + /* + * Defines standard YANG for the Link Flap Suppression feature. + */ + container link-flap-suppression { + if-feature "link-flap-suppression"; + description + "Holds link flap related feature configuration."; + leaf down { + type uint32; + units milliseconds; + description + "Delays the propagation of a 'loss of carrier signal' event + that would cause the interface state to go down, i.e. the + command allows short link flaps to be suppressed. The + configured value indicates the minimum time interval (in + milliseconds) that the link signal must be continuously + down before the interface state is brought down. If not + configured, the behavior on loss of link signal is + vendor/interface specific, but with the general + expectation that there should be little or no delay."; + } + leaf up { + type uint32; + units milliseconds; + description + "Defines the minimum time interval (in milliseconds) that + the link signal must be continuously present and error + free before the interface state is allowed to transition + from down to up. If not configured, the behavior is + vendor/interface specific, but with the general + expectation that sufficient default delay should be used + to ensure that the interface is stable when enabled before + being reported as being up. Configured values that are + too low for the hardware capabilties may be rejected."; + } + leaf carrier-transitions { + type yang:counter64; + units transitions; + config false; + description + "Defines the number of times the underlying link state + has changed to, or from, state up. This counter should be + incremented even if the high layer interface state changes + are being suppressed by a running link flap suppression + timer."; + } + leaf timer-running { + type enumeration { + enum none { + description + "No link flap suppression timer is running."; + } + enum up { + description + "link-flap-suppression up timer is running. The + underlying link state is up, but interface state is + not reported as up."; + } + enum down { + description + "link-flap-suppression down timer is running. + Interface state is reported as up, but the underlying + link state is actually down."; + } + } + config false; + description + "Reports whether a link flap suppression timer is actively + running, in which case the interface state does not match + the underlying link state."; + } + + reference "RFC XXXX, Section 2.1 Link Flap Suppression"; + } + + /* + * Augments the IETF interfaces model with a container to hold + * generic interface dampening + */ + container dampening { + if-feature "dampening"; + presence + "Enable interface link flap dampening with default settings + (that are vendor/device specific)."; + description + "Interface dampening limits the propagation of interface link + state flaps over longer periods."; + reference "RFC XXXX, Section 2.2 Dampening"; + + leaf half-life { + type uint32; + units seconds; + description + "The time (in seconds) after which a penalty would be half + its original value. Once the interface has been assigned + a penalty, the penalty is decreased at a decay rate + equivalent to the half-life. For some devices, the + allowed values may be restricted to particular multiples + of seconds. The default value is vendor/device + specific."; + reference "RFC XXXX, Section 2.3.2 Half-Life Period"; + } + + leaf reuse { + type uint32; + description + "Penalty value below which a stable interface is + unsuppressed (i.e. brought up) (no units). The default + value is vendor/device specific. The penalty value for a + link up->down state change is 1000 units."; + reference "RFC XXXX, Section 2.2.3 Reuse Threshold"; + } + + leaf suppress { + type uint32; + description + "Limit at which an interface is suppressed (i.e. held down) + when its penalty exceeds that limit (no units). The value + must be greater than the reuse threshold. The default + value is vendor/device specific. The penalty value for a + link up->down state change is 1000 units."; + reference "RFC XXXX, Section 2.2.1 Suppress Threshold"; + } + + leaf max-suppress-time { + type uint32; + units seconds; + description + "Maximum time (in seconds) that an interface can be + suppressed before being unsuppressed if no further link + up->down state change penalties have been applied. This + value effectively acts as a ceiling that the penalty value + cannot exceed. The default value is vendor/device + specific."; + reference "RFC XXXX, Section 2.2.4 Maximum Suppress Time"; + } + + leaf penalty { + type uint32; + config false; + description + "The current penalty value for this interface. When the + penalty value exceeds the 'suppress' leaf then the + interface is suppressed (i.e. held down)."; + reference "RFC XXXX, Section 2.2 Dampening"; + } + + leaf suppressed { + type boolean; + config false; + description + "Represents whether the interface is suppressed (i.e. held + down) because the 'penalty' leaf value exceeds the + 'suppress' leaf."; + reference "RFC XXXX, Section 2.2 Dampening"; + } + + leaf time-remaining { + when '../suppressed = "true"' { + description + "Only suppressed interfaces have a time remaining."; + } + type uint32; + units seconds; + config false; + description + "For a suppressed interface, this leaf represents how long + (in seconds) that the interface will remain suppressed + before it is allowed to go back up again."; + reference "RFC XXXX, Section 2.2 Dampening"; + } + } + + /* + * Various types of interfaces support a configurable layer 2 + * encapsulation, any that are supported by YANG should be + * listed here. + * + * Different encapsulations can hook into the common encaps-type + * choice statement. + */ + container encapsulation { + when + "derived-from-or-self(../if:type, + 'ianaift:ethernetCsmacd') or + derived-from-or-self(../if:type, + 'ianaift:ieee8023adLag') or + derived-from-or-self(../if:type, 'ianaift:pos') or + derived-from-or-self(../if:type, + 'ianaift:atmSubInterface') or + derived-from-or-self(../if:type, 'ianaift:l2vlan') or + derived-from-or-self(../if:type, 'ethSubInterface')" { + + description + "All interface types that can have a configurable L2 + encapsulation."; + } + + description + "Holds the OSI layer 2 encapsulation associated with an + interface."; + choice encaps-type { + description + "Extensible choice of layer 2 encapsulations"; + reference "RFC XXXX, Section 2.3 Encapsulation"; + } + } + + /* + * Various types of interfaces support loopback configuration, + * any that are supported by YANG should be listed here. + */ + leaf loopback { + when "derived-from-or-self(../if:type, + 'ianaift:ethernetCsmacd') or + derived-from-or-self(../if:type, 'ianaift:sonet') or + derived-from-or-self(../if:type, 'ianaift:atm') or + derived-from-or-self(../if:type, 'ianaift:otnOtu')" { + description + "All interface types that support loopback configuration."; + } + if-feature "loopback"; + type identityref { + base loopback; + } + description "Enables traffic loopback."; + reference "RFC XXXX, Section 2.4 Loopback"; + } + + /* + * Allows the maximum frame size to be configured or reported. + */ + leaf max-frame-size { + if-feature "max-frame-size"; + type uint32 { + range "64 .. max"; + } + description + "The maximum size of layer 2 frames that may be transmitted + or received on the interface (including any frame header, + maximum frame payload size, and frame checksum sequence). + + If configured, the max-frame-size also limits the maximum + frame size of any child sub-interfaces. The MTU available + to higher layer protocols is restricted to the maximum frame + payload size, and MAY be further restricted by explicit + layer 3 or protocol specific MTU configuration."; + + reference "RFC XXXX, Section 2.5 Maximum Frame Size"; + } + + /* + * Augments the IETF interfaces model with a leaf that indicates + * which mode, or layer, is being used to forward the traffic. + */ + leaf forwarding-mode { + type identityref { + base forwarding-mode; + } + config false; + + description + "The forwarding mode that the interface is operating in."; + reference "RFC XXXX, Section 2.7 Forwarding Mode"; + } + } + + /* + * Add generic support for sub-interfaces. + * + * This should be extended to cover all interface types that are + * child interfaces of other interfaces. + */ + augment "/if:interfaces/if:interface" { + when "derived-from(if:type, 'sub-interface') or + derived-from-or-self(if:type, 'ianaift:l2vlan') or + derived-from-or-self(if:type, 'ianaift:atmSubInterface') or + derived-from-or-self(if:type, 'ianaift:frameRelay')" { + description + "Any ianaift:types that explicitly represent sub-interfaces + or any types that derive from the sub-interface identity."; + } + if-feature "sub-interfaces"; + + description + "Adds a parent interface field to interfaces that model + sub-interfaces."; + leaf parent-interface { + + type if:interface-ref; + + mandatory true; + description + "This is the reference to the parent interface of this + sub-interface."; + reference "RFC XXXX, Section 2.6 Sub-interface"; + } + } + + /* + * Add discard counter for unknown sub-interface encapsulation + */ + augment "/if:interfaces/if:interface/if:statistics" { + when "derived-from-or-self(../if:type, + 'ianaift:ethernetCsmacd') or + derived-from-or-self(../if:type, + 'ianaift:ieee8023adLag') or + derived-from-or-self(../if:type, 'ianaift:ifPwType')" { + description + "Applies to interfaces that can demultiplex ingress frames to + sub-interfaces."; + } + if-feature "sub-interfaces"; + + description + "Augment the interface model statistics with a sub-interface + demux discard counter."; + leaf in-discard-unknown-encaps { + type yang:counter64; + units frames; + description + "A count of the number of frames that were well formed, but + otherwise discarded because their encapsulation does not + classify the frame to the interface or any child + sub-interface. E.g., a frame might be discarded because the + it has an unknown VLAN Id, or does not have a VLAN Id when + one is expected. + + For consistency, frames counted against this counter are + also counted against the IETF interfaces statistics. In + particular, they are included in in-octets and in-discards, + but are not included in in-unicast-pkts, in-multicast-pkts + or in-broadcast-pkts, because they are not delivered to a + higher layer. + + Discontinuities in the values of this counter can occur at + re-initialization of the management system, and at other + times as indicated by the value of the 'discontinuity-time' + leaf defined in the ietf-interfaces YANG module + (RFC 8343)."; + } + } +} diff --git a/holo-yang/modules/ietf/ietf-if-vlan-encapsulation@2023-01-26.yang b/holo-yang/modules/ietf/ietf-if-vlan-encapsulation@2023-01-26.yang new file mode 100644 index 00000000..c49775e4 --- /dev/null +++ b/holo-yang/modules/ietf/ietf-if-vlan-encapsulation@2023-01-26.yang @@ -0,0 +1,155 @@ +module ietf-if-vlan-encapsulation { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-if-vlan-encapsulation"; + prefix if-vlan; + + import ietf-interfaces { + prefix if; + reference + "RFC 8343: A YANG Data Model For Interface Management"; + } + + import iana-if-type { + prefix ianaift; + reference + "RFC 7224: IANA Interface Type YANG Module"; + } + + import ieee802-dot1q-types { + prefix dot1q-types; + revision-date 2022-01-19; + reference + "IEEE Std 802.1Q-2022: IEEE Standard for Local and + metropolitan area networks -- Bridges and Bridged Networks"; + } + + import ietf-if-extensions { + prefix if-ext; + reference + "RFC XXXX: Common Interface Extension YANG Data Models"; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + Editor: Robert Wilton + "; + + description + "This YANG module models configuration to classify IEEE 802.1Q + VLAN tagged Ethernet traffic by exactly matching the tag type + and VLAN identifier of one or two 802.1Q VLAN tags in the frame. + + Copyright (c) 2023 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Revised BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX + (https://www.rfc-editor.org/info/rfcXXXX); see the RFC itself + for full legal notices. + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here."; + + revision 2023-01-26 { + description + "Latest draft revision"; + reference + "RFC XXXX: Sub-interface VLAN YANG Data Models"; + } + + augment "/if:interfaces/if:interface/if-ext:encapsulation/" + + "if-ext:encaps-type" { + when "derived-from-or-self(../if:type, + 'ianaift:ethernetCsmacd') or + derived-from-or-self(../if:type, + 'ianaift:ieee8023adLag') or + derived-from-or-self(../if:type, 'ianaift:l2vlan') or + derived-from-or-self(../if:type, + 'if-ext:ethSubInterface')" { + description + "Applies only to Ethernet-like interfaces and + sub-interfaces."; + } + + description + "Augment the generic interface encapsulation with basic 802.1Q + VLAN tag classifications"; + + case dot1q-vlan { + container dot1q-vlan { + + description + "Classifies 802.1Q VLAN tagged Ethernet frames to a + sub-interface (or interface) by exactly matching the + number of tags, tag type(s) and VLAN identifier(s). + + Only frames matching the classification configured on a + sub-interface/interface are processed on that + sub-interface/interface. + + Frames that do not match any sub-interface are processed + directly on the parent interface, if it is associated with + a forwarding instance, otherwise they are dropped."; + + container outer-tag { + must 'tag-type = "dot1q-types:s-vlan" or ' + + 'tag-type = "dot1q-types:c-vlan"' { + + error-message + "Only C-VLAN and S-VLAN tags can be matched."; + + description + "For IEEE 802.1Q interoperability, only C-VLAN and + S-VLAN tags are matched."; + } + + description + "Specifies the VLAN tag values to match against the + outermost (first) 802.1Q VLAN tag in the frame."; + + uses dot1q-types:dot1q-tag-classifier-grouping; + } + + container second-tag { + must '../outer-tag/tag-type = "dot1q-types:s-vlan" and ' + + 'tag-type = "dot1q-types:c-vlan"' { + + error-message + "When matching two 802.1Q VLAN tags, the outermost + (first) tag in the frame MUST be specified and be of + S-VLAN type and the second tag in the frame must be of + C-VLAN tag type."; + + description + "For IEEE 802.1Q interoperability, when matching two + 802.1Q VLAN tags, it is REQUIRED that the outermost + tag exists and is an S-VLAN, and the second tag is a + C-VLAN."; + } + + presence "Classify frames that have two 802.1Q VLAN tags."; + + description + "Specifies the VLAN tag values to match against the + second outermost 802.1Q VLAN tag in the frame."; + + uses dot1q-types:dot1q-tag-classifier-grouping; + } + } + } + } +} diff --git a/holo-yang/src/lib.rs b/holo-yang/src/lib.rs index d5e38933..25227378 100644 --- a/holo-yang/src/lib.rs +++ b/holo-yang/src/lib.rs @@ -27,6 +27,9 @@ pub static YANG_CTX: OnceLock> = OnceLock::new(); // default doesn't support loading YANG modules from the filesystem. pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { hashmap! { + // IEEE modules + EmbeddedModuleKey::new("ieee802-dot1q-types", Some("2022-01-19"), None, None) => + include_str!("../modules/ieee/ieee802-dot1q-types@2022-01-19.yang"), // IETF modules EmbeddedModuleKey::new("iana-bfd-types", Some("2021-10-21"), None, None) => include_str!("../modules/ietf/iana-bfd-types@2021-10-21.yang"), @@ -70,6 +73,10 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { include_str!("../modules/ietf/ietf-bgp-rib-tables@2023-07-05.yang"), EmbeddedModuleKey::new("ietf-bgp-policy", Some("2023-07-05"), None, None) => include_str!("../modules/ietf/ietf-bgp-policy@2023-07-05.yang"), + EmbeddedModuleKey::new("ietf-if-extensions", Some("2023-01-26"), None, None) => + include_str!("../modules/ietf/ietf-if-extensions@2023-01-26.yang"), + EmbeddedModuleKey::new("ietf-if-vlan-encapsulation", Some("2023-01-26"), None, None) => + include_str!("../modules/ietf/ietf-if-vlan-encapsulation@2023-01-26.yang"), EmbeddedModuleKey::new("ietf-interfaces", Some("2018-02-20"), None, None) => include_str!("../modules/ietf/ietf-interfaces@2018-02-20.yang"), EmbeddedModuleKey::new("ietf-ip", Some("2018-02-22"), None, None) => @@ -126,6 +133,10 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { 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-if-extensions-holo-deviations", None, None, None) => + include_str!("../modules/deviations/ietf-if-extensions-holo-deviations.yang"), + EmbeddedModuleKey::new("ietf-if-vlan-encapsulation-holo-deviations", None, None, None) => + include_str!("../modules/deviations/ietf-if-vlan-encapsulation-holo-deviations.yang"), EmbeddedModuleKey::new("ietf-interfaces-holo-deviations", None, None, None) => include_str!("../modules/deviations/ietf-interfaces-holo-deviations.yang"), EmbeddedModuleKey::new("ietf-ip-holo-deviations", None, None, None) => @@ -160,6 +171,9 @@ pub static YANG_EMBEDDED_MODULES: Lazy = Lazy::new(|| { pub static YANG_IMPLEMENTED_MODULES: Lazy> = Lazy::new(|| { vec![ + // IEEE modules + "ieee802-dot1q-types", + // IETF modules "iana-if-type", "iana-bgp-notification", "iana-bgp-rib-types", @@ -171,6 +185,8 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = "ietf-bgp", "ietf-bgp-policy", "ietf-routing-types", + "ietf-if-extensions", + "ietf-if-vlan-encapsulation", "ietf-interfaces", "ietf-ip", "ietf-key-chain", @@ -188,6 +204,7 @@ pub static YANG_IMPLEMENTED_MODULES: Lazy> = "ietf-ospfv3-extended-lsa", "ietf-rip", "ietf-tcp", + // IETF Holo augmentations "holo-bgp", "holo-ospf", "holo-ospf-dev", @@ -210,6 +227,9 @@ pub static YANG_FEATURES: Lazy>> = "hex-key-string", "independent-send-accept-lifetime", ], + "ietf-if-extensions" => vec![ + "sub-interfaces", + ], "ietf-ospf" => vec![ "bfd", "explicit-router-id",