Implemented route manager redistributing routes to peer handlers (not yet announcing to peers)
Some checks failed
Rust / build (push) Has been cancelled
Some checks failed
Rust / build (push) Has been cancelled
This commit is contained in:
3
crates/server/src/path/mod.rs
Normal file
3
crates/server/src/path/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
/// Contains structures for working with BGP paths (data associated with a route to a prefix).
|
||||
pub mod path_data;
|
||||
pub mod path_set;
|
||||
95
crates/server/src/path/path_data.rs
Normal file
95
crates/server/src/path/path_data.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
|
||||
use bgp_packet::path_attributes::{OriginPathAttribute, PathAttribute};
|
||||
|
||||
use super::path_set::PathSource;
|
||||
|
||||
/// 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 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>,
|
||||
/// 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 PartialOrd for PathData {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PathData {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Compare local_pref.
|
||||
match self.local_pref.cmp(&other.local_pref) {
|
||||
Ordering::Equal => {}
|
||||
ord => return ord,
|
||||
}
|
||||
|
||||
// Prefer paths that are locally configured.
|
||||
if matches!(self.path_source, PathSource::LocallyConfigured)
|
||||
&& !matches!(other.path_source, PathSource::LocallyConfigured)
|
||||
{
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
// Compare path length.
|
||||
match self.as_path.len().cmp(&other.as_path.len()) {
|
||||
Ordering::Equal => {}
|
||||
ord => return ord,
|
||||
}
|
||||
|
||||
// IGP < EGP < INCOMPLETE
|
||||
match (self.origin as u8).cmp(&(other.origin as u8)) {
|
||||
Ordering::Equal => {}
|
||||
ord => return ord,
|
||||
}
|
||||
|
||||
// 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 Ordering::Less;
|
||||
}
|
||||
}
|
||||
|
||||
// As a discriminator of last resort, prefer older routes.
|
||||
self.learn_time.cmp(&other.learn_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PathData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.origin == other.origin
|
||||
&& self.nexthop == other.nexthop
|
||||
&& self.path_source == other.path_source
|
||||
&& self.local_pref == other.local_pref
|
||||
&& self.med == other.med
|
||||
&& self.as_path == other.as_path
|
||||
&& self.path_attributes == other.path_attributes
|
||||
&& self.learn_time == other.learn_time
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for PathData {}
|
||||
132
crates/server/src/path/path_set.rs
Normal file
132
crates/server/src/path/path_set.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use std::{
|
||||
collections::{btree_set, BTreeMap, BTreeSet},
|
||||
net::Ipv4Addr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bgp_packet::nlri::NLRI;
|
||||
use eyre::{bail, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::path_data::PathData;
|
||||
|
||||
#[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),
|
||||
}
|
||||
|
||||
impl PartialEq for PathSource {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::BGPPeer(l0), Self::BGPPeer(r0)) => l0 == r0,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PathSet<A> {
|
||||
addr: A,
|
||||
prefixlen: u8,
|
||||
nlri: NLRI,
|
||||
/// Sorted map keyed by the BGP Identifier of the peer that sent the route.
|
||||
peer_paths: BTreeMap<Ipv4Addr, Arc<PathData>>,
|
||||
paths: BTreeSet<Arc<PathData>>,
|
||||
}
|
||||
|
||||
impl<A> PathSet<A> {
|
||||
pub fn new(addr: A, prefixlen: u8, nlri: NLRI) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
prefixlen,
|
||||
nlri,
|
||||
peer_paths: Default::default(),
|
||||
paths: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr<'a>(&'a self) -> &'a A {
|
||||
&self.addr
|
||||
}
|
||||
|
||||
pub fn prefixlen(&self) -> u8 {
|
||||
self.prefixlen
|
||||
}
|
||||
|
||||
pub fn nlri<'a>(&'a self) -> &'a NLRI {
|
||||
&self.nlri
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.paths.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.paths.len()
|
||||
}
|
||||
|
||||
pub fn get_by_announcer(&self, announcer: &Ipv4Addr) -> Option<Arc<PathData>> {
|
||||
self.peer_paths.get(announcer).cloned()
|
||||
}
|
||||
|
||||
/// Inserts a PathData from a given announcer, returning a PathData if the best
|
||||
/// route has been updated.
|
||||
pub fn insert_pathdata(
|
||||
&mut self,
|
||||
announcer: &Ipv4Addr,
|
||||
path_data: &Arc<PathData>,
|
||||
) -> Option<Arc<PathData>> {
|
||||
let previous_best = self.paths.first().cloned();
|
||||
if let Some(existing) = self.peer_paths.get_mut(announcer) {
|
||||
// Path exists already so we must first remove it from self.paths.
|
||||
self.paths.remove(existing);
|
||||
// Add the new path to self.paths.
|
||||
self.paths.insert(path_data.clone());
|
||||
// Update it in the peer_paths map.
|
||||
*existing = path_data.clone();
|
||||
} else {
|
||||
// Path does not yet exist so we just add it in both structures.
|
||||
self.paths.insert(path_data.clone());
|
||||
self.peer_paths.insert(*announcer, path_data.clone());
|
||||
}
|
||||
let next_best = self.paths.first().cloned();
|
||||
// If the best path has changed, return the new best.
|
||||
if previous_best != next_best {
|
||||
return next_best;
|
||||
}
|
||||
// Update has not changed the best path.
|
||||
return None;
|
||||
}
|
||||
|
||||
/// Removes a path from the PathSet.
|
||||
pub fn remove_pathdata(
|
||||
&mut self,
|
||||
announcer: &Ipv4Addr,
|
||||
nlri: &NLRI,
|
||||
) -> Result<Option<Arc<PathData>>> {
|
||||
let previous_best = self.paths.first().cloned();
|
||||
if self.peer_paths.contains_key(&announcer) {
|
||||
self.peer_paths.remove(&announcer);
|
||||
self.paths
|
||||
.retain(|e| e.path_source != PathSource::BGPPeer(*announcer));
|
||||
} else {
|
||||
bail!("cannot remove pathdata for NLRI {} from {}, as it is not present in PathSet.peer_paths",
|
||||
nlri, announcer);
|
||||
}
|
||||
let next_best = self.paths.first().cloned();
|
||||
// If the best path has changed, return the new best.
|
||||
if previous_best != next_best {
|
||||
return Ok(next_best);
|
||||
}
|
||||
// Update has not changed the best path.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
/// Iterator over the paths contained in this PathSet.
|
||||
pub fn path_iter<'a>(&'a self) -> btree_set::Iter<'a, Arc<PathData>> {
|
||||
self.paths.iter()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user