Cleanups and filtering.
Some checks failed
Rust / build (push) Has been cancelled

This commit is contained in:
Rayhaan Jaufeerally
2024-07-25 09:57:05 +00:00
parent 8546c37dac
commit 0cd3a120d0
13 changed files with 262 additions and 194 deletions

View File

@ -13,7 +13,6 @@
// limitations under the License.
use crate::config::PeerConfig;
use crate::data_structures::RouteAnnounce;
use crate::data_structures::RouteUpdate;
use crate::peer::PeerCommands;
@ -21,10 +20,15 @@ use std::cmp::Eq;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::convert::TryInto;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::sync::Mutex;
use bgp_packet::nlri::NLRI;
use bgp_packet::path_attributes::OriginPathAttribute;
use bgp_packet::path_attributes::PathAttribute;
use chrono::{DateTime, Utc};
use eyre::{bail, eyre};
use ip_network_table_deps_treebitmap::address::Address;
use serde::Serialize;
use tokio::sync::broadcast;
@ -37,53 +41,84 @@ use super::data_structures::RouteWithdraw;
type PeerInterface = mpsc::UnboundedSender<PeerCommands>;
/// Path is a structure to contain a specific route via one nexthop.
#[derive(Debug, Clone, Serialize)]
pub enum PathSource {
LocallyConfigured,
/// BGPPeer represents a path that has been learned from a BGP peer,
/// and contains the Router ID of the peer.
BGPPeer(Ipv4Addr),
}
/// PathData is a structure to contain a specific route via one nexthop.
/// Note that currently there is an assumption that there is only
/// one route per peer per prefix, but when ADD-PATH support is added
/// this will no longer hold true.
#[derive(Debug, Clone, Serialize)]
pub struct Path {
pub struct PathData {
/// The origin through which this path was learned. This is set to EGP when learned from
/// another peer, set to IGP when statically configured or from another control plane.
pub origin: OriginPathAttribute,
/// The nexthop that traffic can be sent to.
pub nexthop: Vec<u8>,
pub peer_name: String,
/// Where this path was learned from.
pub path_source: PathSource,
/// The local pref of this path.
pub local_pref: u32,
/// The multi exit discriminator of this path.
pub med: u32,
/// The path of autonomous systems to the destination along this path.
pub as_path: Vec<u32>,
/// Path attributes received from the peer.
pub path_attributes: Vec<PathAttribute>,
/// When the path was learned.
pub learn_time: DateTime<Utc>,
}
impl PartialEq for Path {
fn eq(&self, other: &Path) -> bool {
// Local pref
impl PartialEq for PathData {
fn eq(&self, other: &PathData) -> bool {
// Local pref.
if self.local_pref > other.local_pref {
return true;
}
// AS path length
// Prefer paths that are locally originated.
if matches!(self.path_source, PathSource::LocallyConfigured) {
return true;
}
// AS path length.
if self.as_path.len() < other.as_path.len() {
return true;
}
// TODO: Origin
// MED lower is better
if self.med < other.med {
// IGP < EGP < INCOMPLETE
if (self.origin as u8) < (other.origin as u8) {
return true;
}
// Use peer name as discriminator of last resort
self.peer_name < other.peer_name
// MED lower is better, only checked if the announcing ASN is the same.
if let (Some(announcing_as_self), Some(announcing_as_other)) =
(self.as_path.last(), other.as_path.last())
{
if announcing_as_self == announcing_as_other && self.med < other.med {
return true;
}
}
// Pick the oldest path to prefer more stable ones.
self.learn_time < other.learn_time
}
}
impl Eq for Path {}
impl Eq for PathData {}
#[derive(Debug, Clone, Serialize)]
pub struct PathSet<A> {
pub addr: A,
pub prefixlen: u8,
pub nlri: NLRI,
// paths is stored in a BTreeMap which is sorted and allows us to efficiently
// find the best path.
pub paths: BTreeMap<String, Path>,
/// Sorted map keyed by the BGP Identifier of the peer that sent the route.
pub paths: BTreeMap<Ipv4Addr, Arc<PathData>>,
}
/// RibSnapshot contians a version number and the dump of all the routes.
@ -141,7 +176,7 @@ where
})
}
pub async fn run(&mut self) -> Result<(), String> {
pub async fn run(&mut self) -> eyre::Result<()> {
loop {
let next = tokio::select! {
cmd = self.mgr_rx.recv() => cmd,
@ -162,7 +197,7 @@ where
},
None => {
warn!("All senders of the manager channel have been dropped, manager exiting!");
return Err("Manager exited due to channel closure".to_string());
bail!("Manager exited due to channel closure");
}
}
}
@ -206,26 +241,30 @@ where
}
}
fn handle_update(&mut self, update: RouteUpdate) -> Result<(), String> {
fn handle_update(&mut self, update: RouteUpdate) -> eyre::Result<()> {
match update {
RouteUpdate::Announce(announce) => self.handle_announce(announce),
RouteUpdate::Withdraw(withdraw) => self.handle_withdraw(withdraw),
}
}
fn handle_announce(&mut self, update: RouteAnnounce) -> Result<(), String> {
let peer_name = update.peer.clone();
let nexthop = update.nexthop;
for nlri in update.prefixes {
fn handle_announce(&mut self, update: (Vec<NLRI>, Arc<PathData>)) -> eyre::Result<()> {
let peer_router_id = match update.1.path_source {
PathSource::LocallyConfigured => {
bail!("handle_announce should not be called with a LocallyConfigured route")
}
PathSource::BGPPeer(peer_id) => peer_id,
};
for nlri in update.0 {
// Increment the epoch on every NLRI processed.
self.epoch += 1;
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
let addr: A = nlri.clone().try_into().map_err(|e| eyre!(e.to_string()))?;
let prefixlen = nlri.prefixlen;
if let Some(path_set_wrapped) = self.rib.exact_match(addr, prefixlen.into()) {
let mut path_set = path_set_wrapped.lock().unwrap();
// There is already this prefix in the RIB, check if this is a
// reannouncement or fresh announcement.
match path_set.paths.get_mut(&update.peer) {
match path_set.paths.get_mut(&peer_router_id) {
// Peer already announced this route before.
Some(existing) => {
trace!(
@ -233,20 +272,11 @@ where
addr,
prefixlen
);
existing.nexthop = nexthop.clone();
existing.path_attributes = update.path_attributes.clone();
*existing = update.1.clone();
}
// First time that this peer is announcing the route.
None => {
let path = Path {
nexthop: nexthop.clone(),
peer_name: peer_name.clone(),
local_pref: update.local_pref,
med: update.med,
as_path: update.as_path.clone(),
path_attributes: update.path_attributes.clone(),
};
path_set.paths.insert(update.peer.clone(), path);
path_set.paths.insert(peer_router_id, update.1.clone());
}
}
@ -265,15 +295,7 @@ where
nlri,
paths: BTreeMap::new(),
};
let path = Path {
nexthop: nexthop.clone(),
peer_name: peer_name.clone(),
local_pref: update.local_pref,
med: update.med,
as_path: update.as_path.clone(),
path_attributes: update.path_attributes.clone(),
};
path_set.paths.insert(peer_name.clone(), path);
path_set.paths.insert(peer_router_id, update.1.clone());
self.rib
.insert(addr, prefixlen.into(), Mutex::new(path_set.clone()));
@ -285,18 +307,18 @@ where
Ok(())
}
fn handle_withdraw(&mut self, update: RouteWithdraw) -> Result<(), String> {
fn handle_withdraw(&mut self, update: RouteWithdraw) -> eyre::Result<()> {
for nlri in update.prefixes {
self.epoch += 1;
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
let addr: A = nlri.clone().try_into().map_err(|e| eyre!(e.to_string()))?;
let mut pathset_empty = false;
if let Some(path_set_wrapped) = self.rib.exact_match(addr, nlri.prefixlen.into()) {
let mut path_set = path_set_wrapped.lock().unwrap();
let removed = path_set.paths.remove(&update.peer);
let removed = path_set.paths.remove(&update.peer_id);
if removed.is_none() {
warn!(
"Got a withdrawal for route {} from {}, which was not in RIB",
nlri, update.peer
nlri, update.peer_id
);
}
// Ignore errors sending due to no active receivers on the channel.
@ -309,7 +331,7 @@ where
} else {
warn!(
"Got a withdrawal for route {} from {}, which was not in RIB",
nlri, update.peer
nlri, update.peer_id
);
}
if pathset_empty {
@ -329,16 +351,20 @@ where
#[cfg(test)]
mod tests {
use crate::rib_manager::PathData;
use crate::rib_manager::PathSource;
use crate::rib_manager::RibManager;
use crate::rib_manager::RouteAnnounce;
use crate::rib_manager::RouteManagerCommands;
use crate::rib_manager::RouteUpdate;
use bgp_packet::constants::AddressFamilyIdentifier;
use bgp_packet::nlri::NLRI;
use bgp_packet::path_attributes::OriginPathAttribute;
use chrono::Utc;
use std::net::Ipv6Addr;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
@ -351,32 +377,37 @@ mod tests {
let nexthop = Ipv6Addr::new(0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0x1);
// Send an update to the manager and check that it adds it to the RIB.
let announce = RouteAnnounce {
peer: "Some peer".to_string(),
prefixes: vec![NLRI {
afi: AddressFamilyIdentifier::Ipv6,
prefixlen: 32,
prefix: vec![0x20, 0x01, 0xd, 0xb8],
}],
let path_data = PathData {
as_path: vec![65536],
local_pref: 0,
med: 0,
nexthop: nexthop.octets().to_vec(),
path_attributes: vec![],
origin: OriginPathAttribute::EGP,
path_source: PathSource::BGPPeer("1.2.3.4".parse().unwrap()),
learn_time: Utc::now(),
};
let prefixes = vec![NLRI {
afi: AddressFamilyIdentifier::Ipv6,
prefixlen: 32,
prefix: vec![0x20, 0x01, 0xd, 0xb8],
}];
// Manually drive the manager instead of calling run to not deal with async in tests.
assert_eq!(
rib_manager.handle_update(RouteUpdate::Announce(announce)),
Ok(())
);
assert!(rib_manager
.handle_update(RouteUpdate::Announce((prefixes, Arc::new(path_data))))
.is_ok());
let addr = Ipv6Addr::from_str("2001:db8::").unwrap();
let prefixlen: u32 = 32;
let lookup_result = rib_manager.lookup_path_exact(addr, prefixlen).unwrap();
assert_eq!(lookup_result.paths.len(), 1);
let path_result = lookup_result.paths.get("Some peer").unwrap();
let path_result = lookup_result
.paths
.get(&"1.2.3.4".parse().unwrap())
.unwrap();
assert_eq!(path_result.nexthop, nexthop.octets().to_vec());
}
}