diff --git a/holo-routing/src/lib.rs b/holo-routing/src/lib.rs index 31f0bbb2..53e1c483 100644 --- a/holo-routing/src/lib.rs +++ b/holo-routing/src/lib.rs @@ -84,25 +84,29 @@ impl Master { loop { tokio::select! { - Some(request) = nb_rx.recv() => { - process_northbound_msg( - self, - &mut resources, - request, - ) - .await; - } - Ok(msg) = ibus_rx.recv() => { - ibus::process_msg(self, msg); - } - Some(_) = self.rib.update_queue_rx.recv() => { - self.rib - .process_rib_update_queue( - &self.netlink_handle, - &self.ibus_tx, - ) - .await; - } + Some(request) = nb_rx.recv() => { + process_northbound_msg( + self, + &mut resources, + request, + ) + .await; + } + Ok(msg) = ibus_rx.recv() => { + ibus::process_msg(self, msg); + } + Some(_) = self.rib.update_queue_rx.recv() => { + self.rib + .process_rib_update_queue( + &self.netlink_handle, + &self.ibus_tx, + ) + .await; + } + Some(_) = self.birt.update_queue_rx.recv() => { + self.birt + .process_birt_update_queue().await; + } } } } diff --git a/holo-routing/src/rib.rs b/holo-routing/src/rib.rs index d2dfc338..4f2ad874 100644 --- a/holo-routing/src/rib.rs +++ b/holo-routing/src/rib.rs @@ -10,7 +10,9 @@ use std::net::IpAddr; use bitflags::bitflags; use chrono::{DateTime, Utc}; use derive_new::new; -use holo_utils::bier::{BfrId, BirtEntry, Bsl, SubDomainId}; +use holo_utils::bier::{ + self, BfrId, Bift, BirtEntry, Bitstring, Bsl, SubDomainId, +}; use holo_utils::ibus::IbusSender; use holo_utils::ip::{AddressFamily, IpNetworkExt, Ipv4AddrExt, Ipv6AddrExt}; use holo_utils::mpls::Label; @@ -40,9 +42,12 @@ pub struct Rib { pub update_queue_rx: UnboundedReceiver<()>, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Birt { pub entries: BTreeMap<(SubDomainId, BfrId, Bsl), BirtEntry>, + pub bier_update_queue: BTreeSet, + pub update_queue_tx: UnboundedSender<()>, + pub update_queue_rx: UnboundedReceiver<()>, } #[derive(Clone, Debug, new)] @@ -74,6 +79,7 @@ impl Birt { if let Some(nexthop) = msg.nexthops.first() && let Nexthop::Address { addr, .. } = nexthop { + // Insert or update the entry in the BIRT self.entries .entry((msg.bier_info.sd_id, bfr_id, *bsl)) .and_modify(|be| be.bfr_nbr = *addr) @@ -81,6 +87,9 @@ impl Birt { bfr_prefix: msg.prefix.ip(), bfr_nbr: (*addr), }); + + // Add BIER route to the update queue + self.bier_update_queue_add(bfr_id); } }); } @@ -88,6 +97,48 @@ impl Birt { pub(crate) fn bier_nbr_del(&mut self, msg: BierNbrUninstallMsg) { let _ = self.entries.remove(&(msg.sd_id, msg.bfr_id, msg.bsl)); } + + pub(crate) async fn process_birt_update_queue(&mut self) { + let mut bift = Bift::new(); + + // Compute Forwarding BitMasks (F-BMs) + for ((sd_id, bfr_id, bsl), nbr) in &self.entries { + let bfr_bs = + Bitstring::from(*bfr_id, *bsl).expect("Invalid BFR-ID"); + + // Pattern matching is mandatory as Bitstring does not implement Copy, hence cannot use Entry interface + let key = (*sd_id, nbr.bfr_nbr, bfr_bs.si); + match bift.get_mut(&key) { + Some((e, v)) => { + e.mut_or(bfr_bs).unwrap(); + v.push(*bfr_id); + } + None => { + let _ = bift.insert(key, (bfr_bs, vec![*bfr_id])); + } + } + } + + bier::bift_sync(&bift).await; + } + + // Adds BIER route to the update queue. + fn bier_update_queue_add(&mut self, bfr_id: BfrId) { + self.bier_update_queue.insert(bfr_id); + let _ = self.update_queue_tx.send(()); + } +} + +impl Default for Birt { + fn default() -> Self { + let (update_queue_tx, update_queue_rx) = mpsc::unbounded_channel(); + Self { + entries: Default::default(), + bier_update_queue: Default::default(), + update_queue_tx, + update_queue_rx, + } + } } // ===== impl Rib ===== diff --git a/holo-utils/Cargo.toml b/holo-utils/Cargo.toml index 9a9f71ab..b7a27fb5 100644 --- a/holo-utils/Cargo.toml +++ b/holo-utils/Cargo.toml @@ -31,6 +31,7 @@ tracing.workspace = true yang3.workspace = true holo-yang = { path = "../holo-yang" } +reqwest = "0.12.8" [lints.rust] rust_2018_idioms = "warn" diff --git a/holo-utils/src/bier.rs b/holo-utils/src/bier.rs index f82653bb..d53a4bfd 100644 --- a/holo-utils/src/bier.rs +++ b/holo-utils/src/bier.rs @@ -18,6 +18,49 @@ use crate::ip::AddressFamily; pub type SubDomainId = u8; pub type BfrId = u16; +pub type SetIdentifier = u8; +pub type Bift = + HashMap<(SubDomainId, IpAddr, SetIdentifier), (Bitstring, Vec)>; + +pub async fn bift_sync(bift: &Bift) { + for ((_sd_id, nbr, _si), (bs, ids)) in bift.iter() { + /* List the position of bits that are enabled in the bitstring, this is required by the + Bitvectors of Fastclick but this not ideal. + FIXME: Find a better way to share a bitstring with Fastclick + */ + let bs = bs + .bs + .iter() + .enumerate() + .flat_map(|(idx, b)| { + (0..8) + .into_iter() + .filter_map(|i| { + if b & (1 << i) != 0 { + Some(format!("{}", idx * 8 + i)) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + .join(","); + + // TODO: Batch route addition when multiple BfrIds have the same F-BM + for id in ids { + let body = format!("{:#?} {} {:#?}", id, bs, nbr); + // TODO: Use gRPC rather than plain HTTP + let client = reqwest::Client::new(); + let _res = client + // TODO: Make Fastclick BIFT URI configurable through YANG model + .post("http://127.0.0.1/bift/add") + .body(body.clone()) + .send() + .await; + } + } +} #[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Deserialize, Serialize)] @@ -178,25 +221,53 @@ impl TryFrom for Bsl { #[derive(Serialize, Deserialize)] pub struct Bitstring { bsl: Bsl, - bs: BytesMut, + pub bs: BytesMut, + pub si: SetIdentifier, } impl Bitstring { pub fn new(bsl: Bsl) -> Self { + let len: usize = bsl.into(); Self { bsl, - bs: BytesMut::zeroed(bsl.into()), + bs: BytesMut::zeroed(len / 8), + si: 0, } } - pub fn from(id: BfrId, bsl: Bsl) -> Self { - // pub fn from(bfr: BfrId) -> Self { - // TODO: Ensure value fit in bitstring and use SI if required. - let byte = id / 8; - let idx = (id % 8) + 1; + pub fn or(&self, rhs: Self) -> Result { + if self.si != rhs.si || self.bsl != rhs.bsl { + return Err(()); + } + let mut ret = Self::new(self.bsl); + ret.si = self.si; + for i in 0..self.bs.len() { + ret.bs[i] = self.bs[i] | rhs.bs[i]; + } + return Ok(ret); + } + + pub fn mut_or(&mut self, rhs: Self) -> Result<(), ()> { + let bs = self.or(rhs)?; + self.bs = bs.bs; + Ok(()) + } + + pub fn from(id: BfrId, bsl: Bsl) -> Result { + if id == 0 { + return Err(()); + } let mut bs = Self::new(bsl); + let bsl = usize::from(bsl); + bs.si = ((id - 1) as usize / bsl) as u8; + + let id = (id - bs.si as u16 * bsl as u16) - 1; + + let byte = id / 8; + let idx = id % 8; bs.bs[byte as usize] |= 1 << idx; - bs + + Ok(bs) } pub fn bsl(&self) -> Bsl { @@ -272,3 +343,72 @@ impl ToYang for BierEncapsulationType { } } } + +#[cfg(test)] +mod test_bitstring { + use super::*; + + fn get_64bits_bs(id: BfrId) -> Bitstring { + let bsl = Bsl::try_from(1).unwrap(); // 64-bit + Bitstring::from(id, bsl).unwrap() + } + + #[test] + fn test_bfr_id_0() { + let bsl = Bsl::try_from(1).unwrap(); + match Bitstring::from(0, bsl) { + Ok(_) => { + assert!(false); + } + Err(_) => {} + } + } + + #[test] + fn test_bsl_64bits_id_1() { + let bs = get_64bits_bs(1); + assert!(bs.si == 0); + assert!(bs.bsl == Bsl::_64); + println!("{:#x?}", bs.bs); + assert!(bs.bs[0] == 0b1); + assert!(bs.bs[1] == 0); + assert!(bs.bs[2] == 0); + assert!(bs.bs[3] == 0); + assert!(bs.bs[4] == 0); + assert!(bs.bs[5] == 0); + assert!(bs.bs[6] == 0); + assert!(bs.bs[7] == 0); + } + + #[test] + fn test_bsl_64bits_si0() { + let bs = get_64bits_bs(64); + assert!(bs.si == 0); + assert!(bs.bsl == Bsl::_64); + println!("{:#x?}", bs.bs); + assert!(bs.bs[0] == 0); + assert!(bs.bs[1] == 0); + assert!(bs.bs[2] == 0); + assert!(bs.bs[3] == 0); + assert!(bs.bs[4] == 0); + assert!(bs.bs[5] == 0); + assert!(bs.bs[6] == 0); + assert!(bs.bs[7] == 0b1000_0000); + } + + #[test] + fn test_bsl_64bits_si1() { + let bs = get_64bits_bs(65); + assert!(bs.si == 1); + assert!(bs.bsl == Bsl::_64); + println!("{:#x?}", bs.bs); + assert!(bs.bs[0] == 1); + assert!(bs.bs[1] == 0); + assert!(bs.bs[2] == 0); + assert!(bs.bs[3] == 0); + assert!(bs.bs[4] == 0); + assert!(bs.bs[5] == 0); + assert!(bs.bs[6] == 0); + assert!(bs.bs[7] == 0); + } +}