diff --git a/holo-daemon/src/northbound/yang.rs b/holo-daemon/src/northbound/yang.rs index cbb8b235..42428cba 100644 --- a/holo-daemon/src/northbound/yang.rs +++ b/holo-daemon/src/northbound/yang.rs @@ -70,8 +70,8 @@ pub(crate) fn create_context() { } #[cfg(feature = "vrrp")] { - use holo_vrrp::instance::Instance; - modules_add::(&mut modules); + use holo_vrrp::interface::Interface; + modules_add::(&mut modules); } // Create YANG context and load all required modules and their deviations. diff --git a/holo-interface/Cargo.toml b/holo-interface/Cargo.toml index 46509c10..cd98905f 100644 --- a/holo-interface/Cargo.toml +++ b/holo-interface/Cargo.toml @@ -17,6 +17,7 @@ ipnetwork.workspace = true netlink-packet-route.workspace = true netlink-packet-core.workspace = true netlink-sys.workspace = true +regex.workspace = true rtnetlink.workspace = true tokio.workspace = true tracing.workspace = true @@ -26,5 +27,10 @@ holo-northbound = { path = "../holo-northbound" } holo-utils = { path = "../holo-utils" } holo-yang = { path = "../holo-yang" } +holo-vrrp = { path = "../holo-vrrp", optional = true } + [lints] workspace = true + +[features] +vrrp = ["holo-vrrp"] diff --git a/holo-interface/src/interface.rs b/holo-interface/src/interface.rs index cdb7070c..f0924907 100644 --- a/holo-interface/src/interface.rs +++ b/holo-interface/src/interface.rs @@ -9,6 +9,7 @@ use std::net::{IpAddr, Ipv4Addr}; use bitflags::bitflags; use generational_arena::{Arena, Index}; +use holo_northbound::NbDaemonSender; use holo_utils::ibus::IbusSender; use holo_utils::ip::Ipv4NetworkExt; use holo_utils::southbound::{AddressFlags, InterfaceFlags}; @@ -38,6 +39,7 @@ pub struct Interface { pub flags: InterfaceFlags, pub addresses: BTreeMap, pub owner: Owner, + pub vrrp: Option, } #[derive(Debug)] @@ -123,6 +125,7 @@ impl Interfaces { flags: InterfaceFlags::default(), addresses: Default::default(), owner: Owner::CONFIG, + vrrp: None, }; let iface_idx = self.arena.insert(iface); @@ -214,6 +217,7 @@ impl Interfaces { flags, addresses: Default::default(), owner: Owner::SYSTEM, + vrrp: None, }; // Notify protocol instances about the interface update. diff --git a/holo-interface/src/northbound/configuration.rs b/holo-interface/src/northbound/configuration.rs index 53b6e915..f0dc9fe6 100644 --- a/holo-interface/src/northbound/configuration.rs +++ b/holo-interface/src/northbound/configuration.rs @@ -4,21 +4,23 @@ // SPDX-License-Identifier: MIT // -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use std::net::IpAddr; use std::sync::LazyLock as Lazy; use async_trait::async_trait; use enum_as_inner::EnumAsInner; use holo_northbound::configuration::{ - self, Callbacks, CallbacksBuilder, Provider, ValidationCallbacks, - ValidationCallbacksBuilder, + self, Callbacks, CallbacksBuilder, ConfigChanges, Provider, + ValidationCallbacks, ValidationCallbacksBuilder, }; use holo_northbound::yang::interfaces; +use holo_northbound::{CallbackKey, NbDaemonSender}; use holo_utils::yang::DataNodeRefExt; use ipnetwork::IpNetwork; use crate::interface::Owner; +use crate::northbound::REGEX_VRRP; use crate::{netlink, Master}; static VALIDATION_CALLBACKS: Lazy = @@ -189,7 +191,7 @@ fn load_callbacks() -> Callbacks { let old_plen = iface.config.addr_list.insert(addr, plen).unwrap(); let event_queue = args.event_queue; - event_queue.insert(Event::AddressUninstall(ifname.clone(), addr, old_plen)); + //event_queue.insert(Event::AddressUninstall(ifname.clone(), addr, old_plen)); event_queue.insert(Event::AddressInstall(ifname, addr, plen)); }) .delete_apply(|_master, _args| { @@ -298,6 +300,41 @@ impl Provider for Master { Some(&CALLBACKS) } + fn nested_callbacks() -> Option> { + let keys: Vec> = vec![ + #[cfg(feature = "vrrp")] + holo_vrrp::northbound::configuration::CALLBACKS.keys(), + ]; + + Some(keys.concat()) + } + + fn relay_changes( + &self, + changes: ConfigChanges, + ) -> Vec<(ConfigChanges, NbDaemonSender)> { + // Create hash table that maps changes to the appropriate child + // instances. + let mut changes_map: HashMap = HashMap::new(); + for change in changes { + // HACK: parse interface name from VRRP configuration changes. + let caps = REGEX_VRRP.captures(&change.1).unwrap(); + let ifname = caps.get(1).unwrap().as_str().to_owned(); + + // Move configuration change to the appropriate interface bucket. + changes_map.entry(ifname).or_default().push(change); + } + changes_map + .into_iter() + .filter_map(|(ifname, changes)| { + self.interfaces + .get_by_name(&ifname) + .and_then(|iface| iface.vrrp.clone()) + .map(|nb_tx| (changes, nb_tx)) + }) + .collect::>() + } + async fn process_event(&mut self, event: Event) { match event { Event::InterfaceDelete(ifname) => { diff --git a/holo-interface/src/northbound/mod.rs b/holo-interface/src/northbound/mod.rs index dcb2034d..9d4572ff 100644 --- a/holo-interface/src/northbound/mod.rs +++ b/holo-interface/src/northbound/mod.rs @@ -7,8 +7,12 @@ pub mod configuration; pub mod state; +use std::sync::LazyLock as Lazy; + use holo_northbound::rpc::Provider; +use holo_northbound::yang::interfaces; use holo_northbound::ProviderBase; +use regex::Regex; use tracing::{debug_span, Span}; use crate::Master; @@ -36,3 +40,15 @@ impl ProviderBase for Master { // No RPC/Actions to implement. impl Provider for Master {} + +// ===== regular expressions ===== + +// Matches on the protocol type and instance name of a YANG path. +static REGEX_VRRP_STR: Lazy = Lazy::new(|| { + format!( + r"{}\[name='(.+?)'\]/ietf-ip:ipv4/ietf-vrrp:vrrp/*", + interfaces::interface::PATH + ) +}); +pub static REGEX_VRRP: Lazy = + Lazy::new(|| Regex::new(®EX_VRRP_STR).unwrap()); diff --git a/holo-interface/src/northbound/state.rs b/holo-interface/src/northbound/state.rs index 1b9086cd..91e594fb 100644 --- a/holo-interface/src/northbound/state.rs +++ b/holo-interface/src/northbound/state.rs @@ -4,26 +4,45 @@ // SPDX-License-Identifier: MIT // +use std::borrow::Cow; use std::sync::LazyLock as Lazy; +use enum_as_inner::EnumAsInner; use holo_northbound::state::{ Callbacks, CallbacksBuilder, ListEntryKind, Provider, }; +use holo_northbound::yang::interfaces; +use holo_northbound::{CallbackKey, NbDaemonSender}; +use crate::interface::Interface; use crate::Master; pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); -#[derive(Debug, Default)] -pub enum ListEntry { +#[derive(Debug, Default, EnumAsInner)] +pub enum ListEntry<'a> { #[default] None, + Interface(&'a Interface), } // ===== callbacks ===== fn load_callbacks() -> Callbacks { - CallbacksBuilder::default().build() + CallbacksBuilder::::default() + .path(interfaces::interface::PATH) + .get_iterate(|master, _args| { + let iter = master.interfaces.iter().map(ListEntry::Interface); + Some(Box::new(iter)) + }) + .get_object(|_master, args| { + use interfaces::interface::Interface; + let iface = args.list_entry.as_interface().unwrap(); + Box::new(Interface { + name: Cow::Borrowed(&iface.name), + }) + }) + .build() } // ===== impl Master ===== @@ -31,13 +50,29 @@ fn load_callbacks() -> Callbacks { impl Provider for Master { const STATE_PATH: &'static str = "/ietf-interfaces:interfaces"; - type ListEntry<'a> = ListEntry; + type ListEntry<'a> = ListEntry<'a>; fn callbacks() -> Option<&'static Callbacks> { Some(&CALLBACKS) } + + fn nested_callbacks() -> Option> { + let keys: Vec> = vec![ + #[cfg(feature = "vrrp")] + holo_vrrp::northbound::state::CALLBACKS.keys(), + ]; + + Some(keys.concat()) + } } // ===== impl ListEntry ===== -impl ListEntryKind for ListEntry {} +impl<'a> ListEntryKind for ListEntry<'a> { + fn child_task(&self) -> Option { + match self { + ListEntry::Interface(iface) => iface.vrrp.clone(), + _ => None, + } + } +} diff --git a/holo-vrrp/Cargo.toml b/holo-vrrp/Cargo.toml index 412044d2..88956851 100644 --- a/holo-vrrp/Cargo.toml +++ b/holo-vrrp/Cargo.toml @@ -43,5 +43,3 @@ workspace = true [features] default = [] testing = [] - - diff --git a/holo-vrrp/src/debug.rs b/holo-vrrp/src/debug.rs index b5705575..aca90a15 100644 --- a/holo-vrrp/src/debug.rs +++ b/holo-vrrp/src/debug.rs @@ -8,25 +8,17 @@ use std::net::IpAddr; use tracing::{debug, debug_span}; -use crate::packet::VRRPPacket; +use crate::packet::VRRPPacket as Packet; // VRRP debug messages. #[derive(Debug)] pub enum Debug<'a> { InstanceCreate, InstanceDelete, - InstanceStart, - InstanceStop(InstanceInactiveReason), - // Network - PacketRx(&'a IpAddr, &'a VRRPPacket), - PacketTx(&'a IpAddr, &'a VRRPPacket), -} -// Reason why an VRRP instance is inactive. -#[derive(Debug)] -pub enum InstanceInactiveReason { - AdminDown, - MissingRouterId, + // Network + PacketRx(&'a IpAddr, &'a Packet), + PacketTx(&'a IpAddr, &'a Packet), } // ===== impl Debug ===== @@ -35,16 +27,10 @@ impl<'a> Debug<'a> { // Log debug message using the tracing API. pub(crate) fn log(&self) { match self { - Debug::InstanceCreate - | Debug::InstanceDelete - | Debug::InstanceStart => { + Debug::InstanceCreate | Debug::InstanceDelete => { // Parent span(s): vrrp-instance debug!("{}", self); } - Debug::InstanceStop(reason) => { - // Parent span(s): vrrp-instance - debug!(%reason, "{}", self); - } Debug::PacketRx(src, packet) => { // Parent span(s): vrrp-instance debug_span!("network").in_scope(|| { @@ -84,18 +70,3 @@ impl<'a> std::fmt::Display for Debug<'a> { } } } - -// ===== impl InstanceInactiveReason ===== - -impl std::fmt::Display for InstanceInactiveReason { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - InstanceInactiveReason::AdminDown => { - write!(f, "administrative status down") - } - InstanceInactiveReason::MissingRouterId => { - write!(f, "missing router-id") - } - } - } -} diff --git a/holo-vrrp/src/error.rs b/holo-vrrp/src/error.rs index 4c9005ae..d3b6358e 100644 --- a/holo-vrrp/src/error.rs +++ b/holo-vrrp/src/error.rs @@ -161,4 +161,4 @@ fn with_source(error: E) -> String { } else { error.to_string() } -} \ No newline at end of file +} diff --git a/holo-vrrp/src/events.rs b/holo-vrrp/src/events.rs index 9c41cf4b..43b5e7f6 100644 --- a/holo-vrrp/src/events.rs +++ b/holo-vrrp/src/events.rs @@ -16,6 +16,7 @@ pub(crate) fn process_packet( _instance: &mut Instance, _src: IpAddr, _packet: DecodeResult, + ) -> Result<(), Error> { // TODO diff --git a/holo-vrrp/src/instance.rs b/holo-vrrp/src/instance.rs index f9507e04..24f6c52f 100644 --- a/holo-vrrp/src/instance.rs +++ b/holo-vrrp/src/instance.rs @@ -6,264 +6,123 @@ use std::net::Ipv4Addr; -use async_trait::async_trait; -use holo_protocol::{ - InstanceChannelsTx, InstanceShared, MessageReceiver, ProtocolInstance, -}; -use holo_utils::ibus::IbusMsg; -use holo_utils::protocol::Protocol; -use holo_utils::{Receiver, Sender, UnboundedReceiver, UnboundedSender}; -use tokio::sync::mpsc; +use chrono::{DateTime, Utc}; -use crate::debug::{Debug, InstanceInactiveReason}; -use crate::error::{Error, IoError}; use crate::northbound::configuration::InstanceCfg; -use crate::tasks::messages::input::NetRxPacketMsg; -use crate::tasks::messages::{ProtocolInputMsg, ProtocolOutputMsg}; -use crate::{events, southbound}; #[derive(Debug)] pub struct Instance { - // Instance name. - pub name: String, - // Instance system data. - pub system: InstanceSys, // Instance configuration data. pub config: InstanceCfg, // Instance state data. - pub state: Option, - // Instance Tx channels. - pub tx: InstanceChannelsTx, - // Shared data. - pub shared: InstanceShared, -} - -#[derive(Debug, Default)] -pub struct InstanceSys { - // System Router ID. - pub router_id: Option, + pub state: InstanceState, } #[derive(Debug)] pub struct InstanceState { - // Instance Router ID. - pub router_id: Ipv4Addr, + pub state: State, + pub last_adv_src: Option, + pub up_time: Option>, + pub last_event: Event, + pub new_master_reason: MasterReason, + // TODO: interval/timer tasks + pub statistics: Statistics, } -#[derive(Clone, Debug)] -pub struct ProtocolInputChannelsTx { - // Packet Rx event. - pub net_packet_rx: Sender, +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum State { + Initialize, + Backup, + Master, } -#[derive(Debug)] -pub struct ProtocolInputChannelsRx { - // Packet Rx event. - pub net_packet_rx: Receiver, +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Event { + None, + Startup, + Shutdown, + HigherPriorityBackup, + MasterTimeout, + InterfaceUp, + InterfaceDown, + NoPrimaryIpAddress, + PrimaryIpAddress, + NoVirtualIpAddresses, + VirtualIpAddresses, + PreemptHoldTimeout, + LowerPriorityMaster, + OwnerPreempt, } -// ===== impl Instance ===== - -/* -impl Instance { - // Checks if the instance needs to be started or stopped in response to a - // northbound or southbound event. - // - // Note: Router ID updates are ignored if the instance is already active. - pub(crate) async fn update(&mut self) { - let router_id = self.get_router_id(); - - match self.is_ready(router_id) { - Ok(()) if !self.is_active() => { - self.start(router_id.unwrap()).await; - } - Err(reason) if self.is_active() => { - self.stop(reason); - } - _ => (), - } - } - - // Starts the BGP instance. - async fn start(&mut self, router_id: Ipv4Addr) { - Debug::InstanceStart.log(); - - match InstanceState::new(router_id, &self.tx).await { - Ok(state) => { - // Store instance initial state. - self.state = Some(state); - } - Err(error) => { - Error::InstanceStartError(Box::new(error)).log(); - } - } - } - - // Stops the BGP instance. - fn stop(&mut self, reason: InstanceInactiveReason) { - let Some((mut instance, neighbors)) = self.as_up() else { - return; - }; - - Debug::InstanceStop(reason).log(); - - // Stop neighbors. - let error_code = ErrorCode::Cease; - let error_subcode = CeaseSubcode::AdministrativeShutdown; - for nbr in neighbors.values_mut() { - let msg = NotificationMsg::new(error_code, error_subcode); - nbr.fsm_event(&mut instance, fsm::Event::Stop(Some(msg))); - } - - // Clear instance state. - self.state = None; - } - - // Returns whether the BGP instance is operational. - fn is_active(&self) -> bool { - self.state.is_some() - } - - // Returns whether the instance is ready for BGP operation. - fn is_ready( - &self, - router_id: Option, - ) -> Result<(), InstanceInactiveReason> { - if router_id.is_none() { - return Err(InstanceInactiveReason::MissingRouterId); - } - - Ok(()) - } - - // Retrieves the Router ID from configuration or system information. - // Prioritizes the configured Router ID, using the system's Router ID as a - // fallback. - fn get_router_id(&self) -> Option { - self.config.identifier.or(self.system.router_id) - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MasterReason { + NotMaster, + Priority, + Preempted, + NoResponse, } -*/ - -#[async_trait] -impl ProtocolInstance for Instance { - const PROTOCOL: Protocol = Protocol::VRRP; - type ProtocolInputMsg = ProtocolInputMsg; - type ProtocolOutputMsg = ProtocolOutputMsg; - type ProtocolInputChannelsTx = ProtocolInputChannelsTx; - type ProtocolInputChannelsRx = ProtocolInputChannelsRx; +#[derive(Debug)] +pub struct Statistics { + pub discontinuity_time: DateTime, + pub master_transitions: u32, + pub adv_rcvd: u64, + pub adv_sent: u64, + pub interval_errors: u64, + pub priority_zero_pkts_rcvd: u64, + pub priority_zero_pkts_sent: u64, + pub invalid_type_pkts_rcvd: u64, + pub pkt_length_errors: u64, + pub checksum_errors: u64, + pub version_errors: u64, + pub vrid_errors: u64, + pub ip_ttl_errors: u64, +} - async fn new( - name: String, - shared: InstanceShared, - tx: InstanceChannelsTx, - ) -> Instance { - Debug::InstanceCreate.log(); +// ===== impl Instance ===== +impl Instance { + pub(crate) fn new() -> Self { Instance { - name, - system: Default::default(), config: Default::default(), - state: None, - tx, - shared, - } - } - - async fn init(&mut self) { - // Request information about the system Router ID. - southbound::router_id_query(&self.tx.ibus); - } - - async fn shutdown(mut self) { - // TODO - // Ensure instance is disabled before exiting. - //self.stop(InstanceInactiveReason::AdminDown); - Debug::InstanceDelete.log(); - } - - async fn process_ibus_msg(&mut self, msg: IbusMsg) { - if let Err(error) = process_ibus_msg(self, msg).await { - error.log(); - } - } - - fn process_protocol_msg(&mut self, msg: ProtocolInputMsg) { - if let Err(error) = match msg { - // Received network packet. - ProtocolInputMsg::NetRxPacket(msg) => { - events::process_packet(self, msg.src, msg.packet) - } - } { - error.log(); + state: InstanceState::new(), } } - - fn protocol_input_channels( - ) -> (ProtocolInputChannelsTx, ProtocolInputChannelsRx) { - let (net_packet_rxp, net_packet_rxc) = mpsc::channel(4); - - let tx = ProtocolInputChannelsTx { - net_packet_rx: net_packet_rxp, - }; - let rx = ProtocolInputChannelsRx { - net_packet_rx: net_packet_rxc, - }; - - (tx, rx) - } - - #[cfg(feature = "testing")] - fn test_dir() -> String { - format!("{}/tests/conformance", env!("CARGO_MANIFEST_DIR"),) - } } // ===== impl InstanceState ===== -impl InstanceState {} - -// ===== impl ProtocolInputChannelsRx ===== - -#[async_trait] -impl MessageReceiver for ProtocolInputChannelsRx { - async fn recv(&mut self) -> Option { - tokio::select! { - biased; - msg = self.net_packet_rx.recv() => { - msg.map(ProtocolInputMsg::NetRxPacket) - } +impl InstanceState { + pub(crate) fn new() -> Self { + InstanceState { + state: State::Initialize, + last_adv_src: None, + up_time: None, + last_event: Event::None, + new_master_reason: MasterReason::NotMaster, + statistics: Default::default(), } } } -// ===== helper functions ===== - -async fn process_ibus_msg( - instance: &mut Instance, - msg: IbusMsg, -) -> Result<(), Error> { - match msg { - // Router ID update notification. - IbusMsg::RouterIdUpdate(router_id) => { - southbound::process_router_id_update(instance, router_id).await; - } - // Interface update notification. - IbusMsg::InterfaceUpd(msg) => { - southbound::process_iface_update(instance, msg); +// ===== impl Statistics ===== + +impl Default for Statistics { + fn default() -> Self { + Statistics { + discontinuity_time: Utc::now(), + master_transitions: 0, + adv_rcvd: 0, + adv_sent: 0, + interval_errors: 0, + priority_zero_pkts_rcvd: 0, + priority_zero_pkts_sent: 0, + invalid_type_pkts_rcvd: 0, + pkt_length_errors: 0, + checksum_errors: 0, + version_errors: 0, + vrid_errors: 0, + ip_ttl_errors: 0, } - // Interface address addition notification. - IbusMsg::InterfaceAddressAdd(msg) => { - southbound::process_addr_add(instance, msg); - } - // Interface address delete notification. - IbusMsg::InterfaceAddressDel(msg) => { - southbound::process_addr_del(instance, msg); - } - // Ignore other events. - _ => {} } - - Ok(()) } diff --git a/holo-vrrp/src/interface.rs b/holo-vrrp/src/interface.rs new file mode 100644 index 00000000..65d9fc0a --- /dev/null +++ b/holo-vrrp/src/interface.rs @@ -0,0 +1,229 @@ +// +// Copyright (c) The Holo Core Contributors +// +// SPDX-License-Identifier: MIT +// + +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::Arc; + +use async_trait::async_trait; +use holo_protocol::{ + InstanceChannelsTx, InstanceShared, MessageReceiver, ProtocolInstance, +}; +use holo_utils::ibus::IbusMsg; +use holo_utils::protocol::Protocol; +use holo_utils::socket::{AsyncFd, Socket}; +use holo_utils::southbound::InterfaceFlags; +use holo_utils::task::Task; +use holo_utils::{Receiver, Sender, UnboundedSender}; +use ipnetwork::Ipv4Network; +use tokio::sync::mpsc; + +use crate::error::{Error, IoError}; +use crate::instance::Instance; +use crate::tasks::messages::input::NetRxPacketMsg; +use crate::tasks::messages::output::NetTxPacketMsg; +use crate::tasks::messages::{ProtocolInputMsg, ProtocolOutputMsg}; +use crate::{events, network, southbound, tasks}; + +#[derive(Debug)] +pub struct Interface { + // Interface name. + pub name: String, + // Interface system data. + pub system: InterfaceSys, + // Raw sockets and Tx/Rx tasks. + pub net: InterfaceNet, + // Interface VRRP instances. + pub instances: BTreeMap, + // Tx channels. + pub tx: InstanceChannelsTx, + // Shared data. + pub shared: InstanceShared, +} + +#[derive(Debug, Default)] +pub struct InterfaceSys { + // Interface flags. + pub flags: InterfaceFlags, + // Interface index. + pub ifindex: Option, + // Interface IPv4 addresses. + pub addresses: BTreeSet, +} + +#[derive(Debug)] +pub struct InterfaceNet { + // Raw sockets. + pub socket_vrrp: Arc>, + pub socket_arp: Arc>, + // Network Tx/Rx tasks. + _net_tx_task: Task<()>, + _net_rx_task: Task<()>, + // Network Tx output channel. + pub net_tx_packetp: UnboundedSender, +} + +#[derive(Clone, Debug)] +pub struct ProtocolInputChannelsTx { + // Packet Rx event. + pub net_packet_rx: Sender, +} + +#[derive(Debug)] +pub struct ProtocolInputChannelsRx { + // Packet Rx event. + pub net_packet_rx: Receiver, +} + +// ===== impl Interface ===== + +#[async_trait] +impl ProtocolInstance for Interface { + const PROTOCOL: Protocol = Protocol::VRRP; + + type ProtocolInputMsg = ProtocolInputMsg; + type ProtocolOutputMsg = ProtocolOutputMsg; + type ProtocolInputChannelsTx = ProtocolInputChannelsTx; + type ProtocolInputChannelsRx = ProtocolInputChannelsRx; + + async fn new( + name: String, + shared: InstanceShared, + tx: InstanceChannelsTx, + ) -> Interface { + // TODO: proper error handling + let net = InterfaceNet::new(&name, &tx) + .expect("Failed to initialize VRRP network tasks"); + Interface { + name, + system: Default::default(), + net, + instances: Default::default(), + tx, + shared, + } + } + + async fn process_ibus_msg(&mut self, msg: IbusMsg) { + if let Err(error) = process_ibus_msg(self, msg).await { + error.log(); + } + } + + fn process_protocol_msg(&mut self, msg: ProtocolInputMsg) { + if let Err(error) = match msg { + // Received network packet. + ProtocolInputMsg::NetRxPacket(msg) => { + events::process_packet(self, msg.src, msg.packet) + } + } { + error.log(); + } + } + + fn protocol_input_channels( + ) -> (ProtocolInputChannelsTx, ProtocolInputChannelsRx) { + let (net_packet_rxp, net_packet_rxc) = mpsc::channel(4); + + let tx = ProtocolInputChannelsTx { + net_packet_rx: net_packet_rxp, + }; + let rx = ProtocolInputChannelsRx { + net_packet_rx: net_packet_rxc, + }; + + (tx, rx) + } + + #[cfg(feature = "testing")] + fn test_dir() -> String { + format!("{}/tests/conformance", env!("CARGO_MANIFEST_DIR"),) + } +} + +// ===== impl InterfaceNet ===== + +impl InterfaceNet { + fn new( + ifname: &str, + instance_channels_tx: &InstanceChannelsTx, + ) -> Result { + // Create raw sockets. + let socket_vrrp = network::socket_vrrp(ifname) + .map_err(IoError::SocketError) + .and_then(|socket| { + AsyncFd::new(socket).map_err(IoError::SocketError) + }) + .map(Arc::new)?; + let socket_arp = network::socket_arp(ifname) + .map_err(IoError::SocketError) + .and_then(|socket| { + AsyncFd::new(socket).map_err(IoError::SocketError) + }) + .map(Arc::new)?; + + // Start network Tx/Rx tasks. + let (net_tx_packetp, net_tx_packetc) = mpsc::unbounded_channel(); + let mut net_tx_task = tasks::net_tx( + socket_vrrp.clone(), + socket_arp.clone(), + net_tx_packetc, + #[cfg(feature = "testing")] + &instance_channels_tx.protocol_output, + ); + let net_rx_task = tasks::net_rx( + socket_vrrp.clone(), + &instance_channels_tx.protocol_input.net_packet_rx, + ); + + Ok(InterfaceNet { + socket_vrrp, + socket_arp, + _net_tx_task: net_tx_task, + _net_rx_task: net_rx_task, + net_tx_packetp, + }) + } +} + +// ===== impl ProtocolInputChannelsRx ===== + +#[async_trait] +impl MessageReceiver for ProtocolInputChannelsRx { + async fn recv(&mut self) -> Option { + tokio::select! { + biased; + msg = self.net_packet_rx.recv() => { + msg.map(ProtocolInputMsg::NetRxPacket) + } + } + } +} + +// ===== helper functions ===== + +async fn process_ibus_msg( + interface: &mut Interface, + msg: IbusMsg, +) -> Result<(), Error> { + match msg { + // Interface update notification. + IbusMsg::InterfaceUpd(msg) => { + southbound::process_iface_update(interface, msg); + } + // Interface address addition notification. + IbusMsg::InterfaceAddressAdd(msg) => { + southbound::process_addr_add(interface, msg); + } + // Interface address delete notification. + IbusMsg::InterfaceAddressDel(msg) => { + southbound::process_addr_del(interface, msg); + } + // Ignore other events. + _ => {} + } + + Ok(()) +} diff --git a/holo-vrrp/src/lib.rs b/holo-vrrp/src/lib.rs index ba0c0ee3..bf8ed561 100644 --- a/holo-vrrp/src/lib.rs +++ b/holo-vrrp/src/lib.rs @@ -14,6 +14,8 @@ pub mod debug; pub mod error; pub mod events; pub mod instance; +pub mod interface; + pub mod network; pub mod northbound; pub mod packet; diff --git a/holo-vrrp/src/network.rs b/holo-vrrp/src/network.rs index 4cdb2ff8..33636dcf 100644 --- a/holo-vrrp/src/network.rs +++ b/holo-vrrp/src/network.rs @@ -4,20 +4,87 @@ // SPDX-License-Identifier: MIT // -use libc::ETH_P_ALL; -use socket2::{Socket, Domain, Type, Protocol}; -use capctl::caps; -use holo_utils::capabilities; - -fn socket() -> Result { - let socket = capabilities::raise(|| { - Socket::new( - Domain::IPV4, - Type::RAW, - Some(Protocol::from(112)) - ) - })?; - - socket.set_broadcast(true); - Ok(socket) +use std::net::IpAddr; +use std::sync::Arc; + +use holo_utils::socket::{AsyncFd, Socket}; +use holo_utils::{Sender, UnboundedReceiver}; +use tokio::sync::mpsc::error::SendError; + +use crate::error::IoError; +use crate::packet::Packet; +use crate::tasks::messages::input::NetRxPacketMsg; +use crate::tasks::messages::output::NetTxPacketMsg; + +pub(crate) fn socket_vrrp(_ifname: &str) -> Result { + #[cfg(not(feature = "testing"))] + { + todo!() + } + #[cfg(feature = "testing")] + { + Ok(Socket {}) + } +} + +pub(crate) fn socket_arp(_ifname: &str) -> Result { + #[cfg(not(feature = "testing"))] + { + todo!() + } + #[cfg(feature = "testing")] + { + Ok(Socket {}) + } +} + +#[cfg(not(feature = "testing"))] +async fn send_packet_vrrp( + _socket: &AsyncFd, + _src: IpAddr, + _dst: IpAddr, + _packet: Packet, +) -> Result<(), IoError> { + todo!() +} + +#[cfg(not(feature = "testing"))] +async fn send_packet_arp( + _socket: &AsyncFd, + // TODO: add other params +) -> Result<(), IoError> { + todo!() +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn write_loop( + socket_vrrp: Arc>, + socket_arp: Arc>, + mut net_tx_packetc: UnboundedReceiver, +) { + while let Some(msg) = net_tx_packetc.recv().await { + match msg { + NetTxPacketMsg::Vrrp { packet, src, dst } => { + if let Err(error) = + send_packet_vrrp(&socket_vrrp, src, dst, packet).await + { + error.log(); + } + } + NetTxPacketMsg::Arp {} => { + if let Err(error) = send_packet_arp(&socket_arp).await { + error.log(); + } + } + } + } +} + +#[cfg(not(feature = "testing"))] +pub(crate) async fn read_loop( + _socket_vrrp: Arc>, + _net_packet_rxp: Sender, +) -> Result<(), SendError> { + // TODO: receive VRRP packets + todo!() } diff --git a/holo-vrrp/src/northbound/configuration.rs b/holo-vrrp/src/northbound/configuration.rs index 56b6b28b..7d9fb408 100644 --- a/holo-vrrp/src/northbound/configuration.rs +++ b/holo-vrrp/src/northbound/configuration.rs @@ -6,8 +6,7 @@ #![allow(clippy::derivable_impls)] -use std::collections::{BTreeMap, HashMap}; -use std::net::{IpAddr, Ipv4Addr}; +use std::collections::BTreeSet; use std::sync::LazyLock as Lazy; use async_trait::async_trait; @@ -18,127 +17,124 @@ use holo_northbound::configuration::{ }; use holo_northbound::yang::interfaces; use holo_utils::yang::DataNodeRefExt; -use holo_yang::TryFromYang; + +use ipnetwork::Ipv4Network; use crate::instance::Instance; +use crate::interface::Interface; #[derive(Debug, Default, EnumAsInner)] pub enum ListEntry { #[default] None, + + Vrid(u8), } #[derive(Debug)] pub enum Resource {} #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum Event {} + +pub enum Event { + InstanceCreate { vrid: u8 }, + InstanceDelete { vrid: u8 }, +} pub static VALIDATION_CALLBACKS: Lazy = Lazy::new(load_validation_callbacks); -pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); // ===== configuration structs ===== #[derive(Debug)] -pub struct InstanceCfg {} +pub struct InstanceCfg { + pub log_state_change: bool, + pub preempt: bool, + pub priority: u8, + pub advertise_interval: u8, + pub virtual_addresses: BTreeSet, +} // ===== callbacks ===== -fn load_callbacks() -> Callbacks { - CallbacksBuilder::::default() +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() .path(interfaces::interface::ipv4::vrrp::vrrp_instance::PATH) - .create_apply(|_instance, _args| { - // TODO: implement me! + .create_apply(|interface, args| { + let vrid = args.dnode.get_u8_relative("./vrid").unwrap(); + let instance = Instance::new(); + interface.instances.insert(vrid, instance); + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceCreate { vrid }); }) - .delete_apply(|_instance, _args| { - // TODO: implement me! + .delete_apply(|_interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + + let event_queue = args.event_queue; + event_queue.insert(Event::InstanceDelete { vrid }); }) - .lookup(|_instance, _list_entry, _dnode| { - // TODO: implement me! - todo!(); + .lookup(|_instance, _list_entry, dnode| { + let vrid = dnode.get_u8_relative("./vrid").unwrap(); + ListEntry::Vrid(vrid) }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::version::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! + .modify_apply(|_interface, _args| { + // Nothing to do. }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::log_state_change::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! + .modify_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let log_state_change = args.dnode.get_bool(); + instance.config.log_state_change = log_state_change; }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::preempt::enabled::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::preempt::hold_time::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! + .modify_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let preempt = args.dnode.get_bool(); + instance.config.preempt = preempt; }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::priority::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::accept_mode::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .delete_apply(|_instance, _args| { - // TODO: implement me! + .modify_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let priority = args.dnode.get_u8(); + instance.config.priority = priority; }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::advertise_interval_sec::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .delete_apply(|_instance, _args| { - // TODO: implement me! - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::advertise_interval_centi_sec::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .delete_apply(|_instance, _args| { - // TODO: implement me! - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::track::interfaces::interface::PATH) - .create_apply(|_instance, _args| { - // TODO: implement me! - }) - .delete_apply(|_instance, _args| { - // TODO: implement me! - }) - .lookup(|_instance, _list_entry, _dnode| { - // TODO: implement me! - todo!(); - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::track::interfaces::interface::priority_decrement::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! - }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::track::networks::network::PATH) - .create_apply(|_instance, _args| { - // TODO: implement me! - }) - .delete_apply(|_instance, _args| { - // TODO: implement me! - }) - .lookup(|_instance, _list_entry, _dnode| { - // TODO: implement me! - todo!(); + .modify_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let advertise_interval = args.dnode.get_u8(); + instance.config.advertise_interval = advertise_interval; }) - .path(interfaces::interface::ipv4::vrrp::vrrp_instance::track::networks::network::priority_decrement::PATH) - .modify_apply(|_instance, _args| { - // TODO: implement me! + .delete_apply(|_interface, _args| { + // Nothing to do. }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::virtual_ipv4_addresses::virtual_ipv4_address::PATH) - .create_apply(|_instance, _args| { - // TODO: implement me! + .create_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let addr = args.dnode.get_prefix4(); + instance.config.virtual_addresses.insert(addr); }) - .delete_apply(|_instance, _args| { - // TODO: implement me! + .delete_apply(|interface, args| { + let vrid = args.list_entry.into_vrid().unwrap(); + let instance = interface.instances.get_mut(&vrid).unwrap(); + + let addr = args.dnode.get_prefix4(); + instance.config.virtual_addresses.remove(&addr); }) .lookup(|_instance, _list_entry, _dnode| { - // TODO: implement me! - todo!(); + ListEntry::None }) .build() } @@ -147,10 +143,10 @@ fn load_validation_callbacks() -> ValidationCallbacks { ValidationCallbacksBuilder::default().build() } -// ===== impl Instance ===== +// ===== impl Interface ===== #[async_trait] -impl Provider for Instance { +impl Provider for Interface { type ListEntry = ListEntry; type Event = Event; type Resource = Resource; @@ -159,12 +155,20 @@ impl Provider for Instance { Some(&VALIDATION_CALLBACKS) } - fn callbacks() -> Option<&'static Callbacks> { + fn callbacks() -> Option<&'static Callbacks> { Some(&CALLBACKS) } async fn process_event(&mut self, event: Event) { - // TODO + + match event { + Event::InstanceCreate { vrid } => { + // TODO + } + Event::InstanceDelete { vrid } => { + // TODO + } + } } } @@ -172,6 +176,19 @@ impl Provider for Instance { impl Default for InstanceCfg { fn default() -> InstanceCfg { - InstanceCfg {} + use interfaces::interface::ipv4::vrrp; + + let log_state_change = vrrp::vrrp_instance::log_state_change::DFLT; + let preempt = vrrp::vrrp_instance::preempt::enabled::DFLT; + let priority = vrrp::vrrp_instance::priority::DFLT; + let advertise_interval = + vrrp::vrrp_instance::advertise_interval_sec::DFLT; + InstanceCfg { + log_state_change, + preempt, + priority, + advertise_interval, + virtual_addresses: Default::default(), + } } } diff --git a/holo-vrrp/src/northbound/mod.rs b/holo-vrrp/src/northbound/mod.rs index 0110cf58..df822d23 100644 --- a/holo-vrrp/src/northbound/mod.rs +++ b/holo-vrrp/src/northbound/mod.rs @@ -10,14 +10,13 @@ pub mod state; pub mod yang; use holo_northbound::ProviderBase; -use holo_yang::ToYang; use tracing::{debug_span, Span}; -use crate::instance::Instance; +use crate::interface::Interface; -// ===== impl Instance ===== +// ===== impl Interface ===== -impl ProviderBase for Instance { +impl ProviderBase for Interface { fn yang_modules() -> &'static [&'static str] { &["ietf-vrrp", "holo-vrrp"] } @@ -27,10 +26,10 @@ impl ProviderBase for Instance { String::new() } - fn debug_span(name: &str) -> Span { - debug_span!("vrrp-instance", %name) + fn debug_span(interface: &str) -> Span { + debug_span!("vrrp", %interface) } } // No RPC/Actions to implement. -impl holo_northbound::rpc::Provider for Instance {} +impl holo_northbound::rpc::Provider for Interface {} diff --git a/holo-vrrp/src/northbound/notification.rs b/holo-vrrp/src/northbound/notification.rs index 8151c7f1..e1245243 100644 --- a/holo-vrrp/src/northbound/notification.rs +++ b/holo-vrrp/src/northbound/notification.rs @@ -4,4 +4,26 @@ // SPDX-License-Identifier: MIT // -// TODO +use std::borrow::Cow; +use std::net::Ipv4Addr; + +use holo_northbound::{notification, yang, NbProviderSender}; +use holo_yang::ToYang; + +use crate::instance::MasterReason; + +// ===== global functions ===== + +pub(crate) fn new_master_event( + nb_tx: &NbProviderSender, + addr: Ipv4Addr, + reason: MasterReason, +) { + use yang::vrrp_new_master_event::{self, VrrpNewMasterEvent}; + + let data = VrrpNewMasterEvent { + master_ip_address: Some(Cow::Owned(addr.into())), + new_master_reason: Some(reason.to_yang()), + }; + notification::send(nb_tx, vrrp_new_master_event::PATH, data); +} diff --git a/holo-vrrp/src/northbound/state.rs b/holo-vrrp/src/northbound/state.rs index f6df591b..9dd015be 100644 --- a/holo-vrrp/src/northbound/state.rs +++ b/holo-vrrp/src/northbound/state.rs @@ -5,98 +5,90 @@ // use std::borrow::Cow; -use std::sync::{atomic, Arc, LazyLock as Lazy}; +use std::sync::LazyLock as Lazy; + use enum_as_inner::EnumAsInner; use holo_northbound::state::{ Callbacks, CallbacksBuilder, ListEntryKind, Provider, }; -use holo_northbound::yang::{interfaces, vrrp}; +use holo_northbound::yang::interfaces; use holo_utils::option::OptionExt; use holo_yang::ToYang; use crate::instance::Instance; +use crate::interface::Interface; -pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); +pub static CALLBACKS: Lazy> = Lazy::new(load_callbacks); #[derive(Debug, Default, EnumAsInner)] -pub enum ListEntry { +pub enum ListEntry<'a> { #[default] None, + Instance(u8, &'a Instance), } // ===== callbacks ===== -fn load_callbacks() -> Callbacks { - CallbacksBuilder::::default() +fn load_callbacks() -> Callbacks { + CallbacksBuilder::::default() .path(interfaces::interface::ipv4::vrrp::vrrp_instance::PATH) - .get_iterate(|_instance, _args| { - // TODO: implement me! - None + .get_iterate(|interface, _args| { + let iter = interface.instances.iter().map(|(vrid, instance)| ListEntry::Instance(*vrid, instance)); + Some(Box::new(iter)) }) - .get_object(|_instance, _args| { + .get_object(|_interface, args| { use interfaces::interface::ipv4::vrrp::vrrp_instance::VrrpInstance; + let (vrid, instance) = args.list_entry.as_instance().unwrap(); Box::new(VrrpInstance { - vrid: todo!(), - state: todo!(), - is_owner: todo!(), - last_adv_source: todo!(), - up_datetime: todo!(), - master_down_interval: todo!(), - skew_time: todo!(), - last_event: todo!(), - new_master_reason: todo!(), + vrid: *vrid, + state: Some(instance.state.state.to_yang()), + // TODO + is_owner: None, + last_adv_source: instance.state.last_adv_src.map(std::convert::Into::into).map(Cow::Owned).ignore_in_testing(), + up_datetime: instance.state.up_time.as_ref().ignore_in_testing(), + // TODO + master_down_interval: None, + // TODO + skew_time: None, + last_event: Some(instance.state.last_event.to_yang()).ignore_in_testing(), + new_master_reason: Some(instance.state.new_master_reason.to_yang()), }) }) .path(interfaces::interface::ipv4::vrrp::vrrp_instance::statistics::PATH) - .get_object(|_instance, _args| { + .get_object(|_interface, args| { use interfaces::interface::ipv4::vrrp::vrrp_instance::statistics::Statistics; + let (_, instance) = args.list_entry.as_instance().unwrap(); + let statistics = &instance.state.statistics; Box::new(Statistics { - discontinuity_datetime: todo!(), - master_transitions: todo!(), - advertisement_rcvd: todo!(), - advertisement_sent: todo!(), - priority_zero_pkts_rcvd: todo!(), - priority_zero_pkts_sent: todo!(), - invalid_type_pkts_rcvd: todo!(), - packet_length_errors: todo!(), - }) - }) - .path(vrrp::PATH) - .get_object(|_instance, _args| { - use vrrp::Vrrp; - Box::new(Vrrp { - virtual_routers: todo!(), - interfaces: todo!(), - }) - }) - .path(vrrp::statistics::PATH) - .get_object(|_instance, _args| { - use vrrp::statistics::Statistics; - Box::new(Statistics { - discontinuity_datetime: todo!(), - checksum_errors: todo!(), - version_errors: todo!(), - vrid_errors: todo!(), - ip_ttl_errors: todo!(), + discontinuity_datetime: Some(&statistics.discontinuity_time).ignore_in_testing(), + master_transitions: Some(statistics.master_transitions).ignore_in_testing(), + advertisement_rcvd: Some(statistics.adv_rcvd).ignore_in_testing(), + advertisement_sent: Some(statistics.adv_sent).ignore_in_testing(), + interval_errors: Some(statistics.interval_errors).ignore_in_testing(), + priority_zero_pkts_rcvd: Some(statistics.priority_zero_pkts_rcvd).ignore_in_testing(), + priority_zero_pkts_sent: Some(statistics.priority_zero_pkts_sent).ignore_in_testing(), + invalid_type_pkts_rcvd: Some(statistics.invalid_type_pkts_rcvd).ignore_in_testing(), + packet_length_errors: Some(statistics.pkt_length_errors).ignore_in_testing(), }) }) .build() } -// ===== impl Instance ===== -impl Provider for Instance { +// ===== impl Interface ===== + +impl Provider for Interface { // TODO const STATE_PATH: &'static str = ""; - type ListEntry<'a> = ListEntry; + type ListEntry<'a> = ListEntry<'a>; - fn callbacks() -> Option<&'static Callbacks> { + fn callbacks() -> Option<&'static Callbacks> { Some(&CALLBACKS) } } // ===== impl ListEntry ===== -impl ListEntryKind for ListEntry {} +impl<'a> ListEntryKind for ListEntry<'a> {} diff --git a/holo-vrrp/src/northbound/yang.rs b/holo-vrrp/src/northbound/yang.rs index 8151c7f1..5e38cd4d 100644 --- a/holo-vrrp/src/northbound/yang.rs +++ b/holo-vrrp/src/northbound/yang.rs @@ -4,4 +4,70 @@ // SPDX-License-Identifier: MIT // -// TODO +use std::borrow::Cow; + +use holo_yang::ToYang; + +use crate::instance::{Event, MasterReason, State}; + +// ===== ToYang implementations ===== + +impl ToYang for State { + fn to_yang(&self) -> Cow<'static, str> { + match self { + State::Initialize => "ietf-vrrp:initialize".into(), + State::Backup => "ietf-vrrp::backup".into(), + State::Master => "ietf-vrrp::master".into(), + } + } +} + +impl ToYang for Event { + fn to_yang(&self) -> Cow<'static, str> { + match self { + Event::None => "ietf-vrrp:vrrp-event-none".into(), + Event::Startup => "ietf-vrrp:vrrp-event-startup".into(), + Event::Shutdown => "ietf-vrrp:vrrp-event-shutdown".into(), + Event::HigherPriorityBackup => { + "ietf-vrrp:vrrp-event-higher-priority-backup".into() + } + Event::MasterTimeout => { + "ietf-vrrp:vrrp-event-master-timeout".into() + } + Event::InterfaceUp => "ietf-vrrp:vrrp-event-interface-up".into(), + Event::InterfaceDown => { + "ietf-vrrp:vrrp-event-interface-down".into() + } + Event::NoPrimaryIpAddress => { + "ietf-vrrp:vrrp-event-no-primary-ip-address".into() + } + Event::PrimaryIpAddress => { + "ietf-vrrp:vrrp-event-primary-ip-address".into() + } + Event::NoVirtualIpAddresses => { + "ietf-vrrp:vrrp-event-no-virtual-ip-addresses".into() + } + Event::VirtualIpAddresses => { + "ietf-vrrp:vrrp-event-virtual-ip-addresses".into() + } + Event::PreemptHoldTimeout => { + "ietf-vrrp:vrrp-event-preempt-hold-timeout".into() + } + Event::LowerPriorityMaster => { + "ietf-vrrp:vrrp-event-lower-priority-master".into() + } + Event::OwnerPreempt => "ietf-vrrp:vrrp-event-owner-preempt".into(), + } + } +} + +impl ToYang for MasterReason { + fn to_yang(&self) -> Cow<'static, str> { + match self { + MasterReason::NotMaster => "not-master".into(), + MasterReason::Priority => "priority".into(), + MasterReason::Preempted => "preempted".into(), + MasterReason::NoResponse => "no-response".into(), + } + } +} diff --git a/holo-vrrp/src/packet.rs b/holo-vrrp/src/packet.rs index 99dd648d..bda406b5 100644 --- a/holo-vrrp/src/packet.rs +++ b/holo-vrrp/src/packet.rs @@ -9,6 +9,7 @@ use std::net::{IpAddr, Ipv4Addr}; //use bitflags::bitflags; use bytes::{Buf, BufMut, Bytes, BytesMut}; use holo_utils::bytes::{BytesExt, BytesMutExt}; + use serde::{Deserialize, Serialize}; // Type aliases. @@ -374,6 +375,29 @@ mod checksum { second = carry as u16; } result +======= +pub struct Packet { + // TODO +} + +// VRRP decode errors. +#[derive(Debug, Eq, PartialEq)] +#[derive(Deserialize, Serialize)] +pub enum DecodeError { + // TODO +} + +// ===== impl Packet ===== + +impl Packet { + // Encodes VRRP packet into a bytes buffer. + pub fn encode(&self) -> BytesMut { + todo!() + } + + // Decodes VRRP packet from a bytes buffer. + pub fn decode(_data: &[u8]) -> Result { + todo!() } } @@ -415,6 +439,7 @@ impl std::fmt::Display for PacketLengthError { write!(f, "Count_ip not corresponding with no of bytes in packet") }, } + } } diff --git a/holo-vrrp/src/tasks.rs b/holo-vrrp/src/tasks.rs index db3b5a67..509bfd76 100644 --- a/holo-vrrp/src/tasks.rs +++ b/holo-vrrp/src/tasks.rs @@ -39,11 +39,10 @@ use crate::debug::Debug; // BGP inter-task message types. pub mod messages { use std::net::IpAddr; - use std::sync::Arc; use serde::{Deserialize, Serialize}; - use crate::packet::{DecodeError, VRRPPacket}; + use crate::packet::{DecodeError, Packet}; // Type aliases. pub type ProtocolInputMsg = input::ProtocolMsg; @@ -62,7 +61,7 @@ pub mod messages { #[derive(Debug, Deserialize, Serialize)] pub struct NetRxPacketMsg { pub src: IpAddr, - pub packet: Result, + pub packet: Result, } } @@ -76,10 +75,15 @@ pub mod messages { } #[derive(Clone, Debug, Serialize)] - pub struct NetTxPacketMsg { - pub packet: VRRPPacket, - pub src: IpAddr, - pub dst: IpAddr, + pub enum NetTxPacketMsg { + Vrrp { + packet: Packet, + src: IpAddr, + dst: IpAddr, + }, + Arp { + // TODO + }, } } } @@ -88,7 +92,7 @@ pub mod messages { // Network Rx task. pub(crate) fn net_rx( - socket: Arc>, + socket_vrrp: Arc>, net_packet_rxp: &Sender, ) -> Task<()> { #[cfg(not(feature = "testing"))] @@ -118,7 +122,8 @@ pub(crate) fn net_rx( // Network Tx task. #[allow(unused_mut)] pub(crate) fn net_tx( - socket: Arc>, + socket_vrrp: Arc>, + socket_arp: Arc>, mut net_packet_txc: UnboundedReceiver, #[cfg(feature = "testing")] proto_output_tx: &Sender< messages::ProtocolOutputMsg, diff --git a/holo-yang/modules/augmentations/holo-vrrp.yang b/holo-yang/modules/augmentations/holo-vrrp.yang index cd269eb7..7eedcb71 100644 --- a/holo-yang/modules/augmentations/holo-vrrp.yang +++ b/holo-yang/modules/augmentations/holo-vrrp.yang @@ -3,10 +3,24 @@ module holo-vrrp { namespace "http://holo-routing.org/yang/holo-vrrp"; prefix holo-vrrp; + import ietf-routing { + prefix rt; + } + organization "Holo Routing Stack"; description "This module defines augment statements for the ietf-vrrp module."; + + /* + * Identities. + */ + + identity vrrp { + base rt:routing-protocol; + description + "VRRP protocol."; + } } diff --git a/holo-yang/modules/deviations/ietf-vrrp-holo-deviations.yang b/holo-yang/modules/deviations/ietf-vrrp-holo-deviations.yang index 51323f6d..7eb57066 100644 --- a/holo-yang/modules/deviations/ietf-vrrp-holo-deviations.yang +++ b/holo-yang/modules/deviations/ietf-vrrp-holo-deviations.yang @@ -40,6 +40,15 @@ module ietf-vrrp-holo-deviations { } */ + deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:version" { + deviate replace { + mandatory false; + } + deviate add { + default "vrrp:vrrp-v2"; + } + } + /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:version" { deviate not-supported; @@ -76,11 +85,9 @@ module ietf-vrrp-holo-deviations { } */ - /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:accept-mode" { deviate not-supported; } - */ /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:advertise-interval-choice" { @@ -100,11 +107,9 @@ module ietf-vrrp-holo-deviations { } */ - /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:advertise-interval-choice/vrrp:v3" { deviate not-supported; } - */ /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:advertise-interval-choice/vrrp:v3/vrrp:advertise-interval-centi-sec" { @@ -112,12 +117,10 @@ module ietf-vrrp-holo-deviations { } */ - /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:track" { deviate not-supported; } - */ - + /* deviation "/if:interfaces/if:interface/ip:ipv4/vrrp:vrrp/vrrp:vrrp-instance/vrrp:track/vrrp:interfaces" { deviate not-supported; @@ -524,11 +527,9 @@ module ietf-vrrp-holo-deviations { } */ - /* deviation "/vrrp:vrrp" { deviate not-supported; } - */ /* deviation "/vrrp:vrrp/vrrp:virtual-routers" { @@ -596,11 +597,9 @@ module ietf-vrrp-holo-deviations { } */ - /* deviation "/vrrp:vrrp-protocol-error-event" { deviate not-supported; } - */ /* deviation "/vrrp:vrrp-protocol-error-event/vrrp:protocol-error-reason" { diff --git a/holo-yang/src/lib.rs b/holo-yang/src/lib.rs index 7a41be7e..36875eae 100644 --- a/holo-yang/src/lib.rs +++ b/holo-yang/src/lib.rs @@ -256,6 +256,9 @@ pub static YANG_FEATURES: Lazy>> = "ietf-segment-routing-common" => vec![ "sid-last-hop-behavior", ], + "ietf-vrrp" => vec![ + "validate-interval-errors", + ], } });