Skip to content

Commit

Permalink
VRRP WIP
Browse files Browse the repository at this point in the history
Signed-off-by: Renato Westphal <[email protected]>
  • Loading branch information
rwestphal committed Jun 23, 2024
1 parent c2da241 commit 46e3db0
Show file tree
Hide file tree
Showing 35 changed files with 3,319 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"holo-routing",
"holo-tools",
"holo-utils",
"holo-vrrp",
"holo-yang",
]
default-members = ["holo-daemon"]
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ Holo supports the following IETF RFCs and Internet drafts:
* RFC 2453 - RIP Version 2
* RFC 4822 - RIPv2 Cryptographic Authentication

##### VRRP

* RFC 3768 - Virtual Router Redundancy Protocol (VRRP)

##### IETF YANG implementation coverage

| Module | Configuration | State | RPCs | Notifications | Total |
Expand All @@ -234,6 +238,7 @@ Holo supports the following IETF RFCs and Internet drafts:
| ietf-routing@2018-03-13 | 100.00% | 85.71% | - | - | [92.31%](http://westphal.com.br/holo/ietf-routing.html) |
| ietf-segment-routing-mpls@2021-05-26 | 62.50% | 0.00% | - | 23.53% | [32.76%](http://westphal.com.br/holo/ietf-segment-routing-mpls.html) |
| ietf-segment-routing@2021-05-26 | 100.00% | - | - | - | [100.00%](http://westphal.com.br/holo/ietf-segment-routing.html) |
| ietf-vrrp@2018-03-13 | 25.53% | 40.00% | - | 25.00% | [31.73%](http://westphal.com.br/holo/[email protected]) |

## Funding

Expand Down
3 changes: 3 additions & 0 deletions holo-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ holo-protocol = { path = "../holo-protocol" }
holo-rip = { path = "../holo-rip", optional = true }
holo-routing = { path = "../holo-routing", optional = true }
holo-utils = { path = "../holo-utils" }
holo-vrrp = { path = "../holo-vrrp", optional = true }
holo-yang = { path = "../holo-yang" }

[build-dependencies]
Expand All @@ -67,6 +68,7 @@ default = [
"ldp",
"ospf",
"rip",
"vrrp",
]

# Base components
Expand All @@ -81,6 +83,7 @@ bgp = ["holo-bgp", "holo-routing/bgp"]
ldp = ["holo-ldp", "holo-routing/ldp"]
ospf = ["holo-ospf", "holo-routing/ospf"]
rip = ["holo-rip", "holo-routing/rip"]
vrrp = ["holo-vrrp", "holo-interface/vrrp"]

# Other features
io_uring = ["tokio-uring"]
5 changes: 5 additions & 0 deletions holo-daemon/src/northbound/yang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ pub(crate) fn create_context() {
modules_add::<Instance<Ripv2>>(&mut modules);
modules_add::<Instance<Ripng>>(&mut modules);
}
#[cfg(feature = "vrrp")]
{
use holo_vrrp::interface::Interface;
modules_add::<Interface>(&mut modules);
}

// Create YANG context and load all required modules and their deviations.
let mut yang_ctx = yang::new_context();
Expand Down
6 changes: 6 additions & 0 deletions holo-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
4 changes: 4 additions & 0 deletions holo-interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -38,6 +39,7 @@ pub struct Interface {
pub flags: InterfaceFlags,
pub addresses: BTreeMap<IpNetwork, InterfaceAddress>,
pub owner: Owner,
pub vrrp: Option<NbDaemonSender>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -123,6 +125,7 @@ impl Interfaces {
flags: InterfaceFlags::default(),
addresses: Default::default(),
owner: Owner::CONFIG,
vrrp: None,
};

let iface_idx = self.arena.insert(iface);
Expand Down Expand Up @@ -214,6 +217,7 @@ impl Interfaces {
flags,
addresses: Default::default(),
owner: Owner::SYSTEM,
vrrp: None,
};

// Notify protocol instances about the interface update.
Expand Down
43 changes: 40 additions & 3 deletions holo-interface/src/northbound/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidationCallbacks> =
Expand Down Expand Up @@ -298,6 +300,41 @@ impl Provider for Master {
Some(&CALLBACKS)
}

fn nested_callbacks() -> Option<Vec<CallbackKey>> {
let keys: Vec<Vec<CallbackKey>> = 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<String, ConfigChanges> = 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::<Vec<_>>()
}

async fn process_event(&mut self, event: Event) {
match event {
Event::InterfaceDelete(ifname) => {
Expand Down
16 changes: 16 additions & 0 deletions holo-interface/src/northbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> = Lazy::new(|| {
format!(
r"{}\[name='(.+?)'\]/ietf-ip:ipv4/ietf-vrrp:vrrp/*",
interfaces::interface::PATH
)
});
pub static REGEX_VRRP: Lazy<Regex> =
Lazy::new(|| Regex::new(&REGEX_VRRP_STR).unwrap());
45 changes: 40 additions & 5 deletions holo-interface/src/northbound/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,75 @@
// 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<Callbacks<Master>> = 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<Master> {
CallbacksBuilder::default().build()
CallbacksBuilder::<Master>::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 =====

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<Master>> {
Some(&CALLBACKS)
}

fn nested_callbacks() -> Option<Vec<CallbackKey>> {
let keys: Vec<Vec<CallbackKey>> = 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<NbDaemonSender> {
match self {
ListEntry::Interface(iface) => iface.vrrp.clone(),
_ => None,
}
}
}
3 changes: 2 additions & 1 deletion holo-tools/yang-coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ cargo run --bin yang_coverage --\
-m ietf-ospf\
-m ietf-ospf-sr-mpls\
-m ietf-ospfv3-extended-lsa\
-m ietf-rip
-m ietf-rip\
-m ietf-vrrp
5 changes: 5 additions & 0 deletions holo-utils/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum Protocol {
RIPV2,
RIPNG,
STATIC,
VRRP,
}

// ===== impl Protocol =====
Expand All @@ -41,6 +42,7 @@ impl std::fmt::Display for Protocol {
Protocol::RIPV2 => write!(f, "ripv2"),
Protocol::RIPNG => write!(f, "ripng"),
Protocol::STATIC => write!(f, "static"),
Protocol::VRRP => write!(f, "vrrp"),
}
}
}
Expand All @@ -59,6 +61,7 @@ impl FromStr for Protocol {
"ripv2" => Ok(Protocol::RIPV2),
"ripng" => Ok(Protocol::RIPNG),
"static" => Ok(Protocol::STATIC),
"vrrp" => Ok(Protocol::VRRP),
_ => Err(()),
}
}
Expand All @@ -76,6 +79,7 @@ impl ToYang for Protocol {
Protocol::RIPV2 => "ietf-rip:ripv2".into(),
Protocol::RIPNG => "ietf-rip:ripng".into(),
Protocol::STATIC => "ietf-routing:static".into(),
Protocol::VRRP => "holo-vrrp:vrrp".into(),
}
}
}
Expand All @@ -92,6 +96,7 @@ impl TryFromYang for Protocol {
"ietf-rip:ripv2" => Some(Protocol::RIPV2),
"ietf-rip:ripng" => Some(Protocol::RIPNG),
"ietf-routing:static" => Some(Protocol::STATIC),
"holo-vrrp:vrrp" => Some(Protocol::VRRP),
_ => None,
}
}
Expand Down
43 changes: 43 additions & 0 deletions holo-vrrp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "holo-vrrp"
version.workspace = true
authors.workspace = true # TODO
license.workspace = true
edition.workspace = true

[dependencies]
async-trait.workspace = true
bitflags.workspace = true
bytes.workspace = true
chrono.workspace = true
derive-new.workspace = true
enum-as-inner.workspace = true
ipnetwork.workspace = true
itertools.workspace = true
libc.workspace = true
num-derive.workspace = true
num-traits.workspace = true
rand.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
tokio.workspace = true
tracing.workspace = true
yang2.workspace = true

holo-northbound = { path = "../holo-northbound" }
holo-protocol = { path = "../holo-protocol" }
holo-utils = { path = "../holo-utils" }
holo-yang = { path = "../holo-yang" }

[dev-dependencies]
holo-vrrp = { path = ".", features = ["testing"] }
holo-protocol = { path = "../holo-protocol", features = ["testing"] }
holo-utils = { path = "../holo-utils", features = ["testing"] }

[lints]
workspace = true

[features]
default = []
testing = []
Loading

0 comments on commit 46e3db0

Please sign in to comment.