Start new route table implementation

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet committed 2024-06-28 10:28:58 +02:00
1 parent 1cd078f304
commit c477222dc2
2 files changed
+373

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ pub mod peer_manager;
pub mod router;
mod router_id;
mod routing_table;
mod routing_table_2;
mod seqno_cache;
mod sequence_number;
mod source_table;
+372
View File
@@ -0,0 +1,372 @@
#![allow(dead_code)]
use std::{
net::{IpAddr, Ipv6Addr},
ops::{Deref, DerefMut},
sync::{Arc, Mutex, MutexGuard},
};
use ip_network_table_deps_treebitmap::IpLookupTable;
use tokio::{
select,
sync::mpsc,
task::AbortHandle,
time::{Duration, Instant},
};
use tokio_util::sync::CancellationToken;
use tracing::{debug, warn};
use crate::{
metric::Metric, peer::Peer, sequence_number::SeqNo, source_table::SourceKey, subnet::Subnet,
};
/// RouteKey uniquely defines a route via a peer.
#[derive(Debug, Clone, PartialEq)]
pub struct RouteKey {
subnet: Subnet,
neighbor: Peer,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RouteEntry {
source: SourceKey,
neighbour: Peer,
metric: Metric,
seqno: SeqNo,
selected: bool,
expires: Instant,
}
impl RouteEntry {
pub fn new(
source: SourceKey,
neighbour: Peer,
metric: Metric,
seqno: SeqNo,
selected: bool,
expires: Instant,
) -> Self {
Self {
source,
neighbour,
metric,
seqno,
selected,
expires,
}
}
}
/// The routing table holds a list of route entries for every known subnet.
pub struct RoutingTable {
writer: Arc<Mutex<left_right::WriteHandle<RoutingTableInner, RoutingTableOplogEntry>>>,
reader: left_right::ReadHandle<RoutingTableInner>,
expired_route_entry_sink: mpsc::Sender<RouteKey>,
cancel_token: CancellationToken,
}
#[derive(Default)]
struct RoutingTableInner {
table: IpLookupTable<Ipv6Addr, Arc<RouteList>>,
}
/// The RouteList holds all routes for a specific subnet.
// By convention, if a route is selected, it will always be at index 0 in the list.
#[derive(Default, Clone)]
pub struct RouteList {
list: Vec<(Arc<AbortHandle>, RouteEntry)>,
}
impl RouteList {
/// Create a new empty RouteList
fn new() -> Self {
Self::default()
}
/// Checks if there are any actual routes in the list.
#[inline]
fn is_empty(&self) -> bool {
self.list.is_empty()
}
/// Get a reference to the [`RouteEntry`] specified by the [`RouteKey`] from the list.
pub fn entry(&self, route_key: &RouteKey) -> Option<&RouteEntry> {
self.list
.iter()
.map(|(_, e)| e)
.find(|entry| entry.neighbour == route_key.neighbor)
}
/// Get [`RouteEntryGuard`] containing a [`RouteEntry`] for the given [`RouteKey`].
pub fn entry_mut(&mut self, route_key: &RouteKey) -> RouteEntryGuard<'_, PossiblyEmpty> {
let entry_identifier = if let Some(pos) = self
.list
.iter_mut()
.map(|(_, e)| e)
.position(|entry| entry.neighbour == route_key.neighbor)
{
EntryIdentifier::Pos(pos)
} else {
EntryIdentifier::None
};
RouteEntryGuard {
route_list: self,
entry_identifier,
timer_duration: None,
_marker: std::marker::PhantomData,
}
}
}
pub struct RouteEntryGuard<'a, O> {
route_list: &'a mut RouteList,
entry_identifier: EntryIdentifier,
timer_duration: Option<Duration>,
_marker: std::marker::PhantomData<O>,
}
/// Marker to allow [`RouteEntryGuard::or_insert`] to be called once.
pub struct PossiblyEmpty;
/// Maker to clearify [`RouteEntryGuard::or_insert`] has been called, and we can always deref the
/// pointer.
pub struct Occupied;
/// Specified how the entry we want from the list can be found.
enum EntryIdentifier {
/// The given entry exists in the RouteList at the specified position.
Pos(usize),
/// A new Route Entry has been inserted which is not part of the RouteList yet.
New(RouteEntry),
/// No entry is found in the list, and we haven't been given one either.
None,
}
impl<'a> RouteEntryGuard<'a, PossiblyEmpty> {
/// Inserts the given [`RouteEntry`] if it does not exist yet. If an entry already exists, this
/// does nothing.
pub fn or_insert(mut self, re: RouteEntry) -> RouteEntryGuard<'a, Occupied> {
if matches!(self.entry_identifier, EntryIdentifier::None) {
self.entry_identifier = EntryIdentifier::New(re)
};
// SAFETY: Transmuting to the same struct with a different marker.
unsafe { std::mem::transmute(self) }
}
}
impl Deref for RouteEntryGuard<'_, Occupied> {
type Target = RouteEntry;
fn deref(&self) -> &Self::Target {
match self.entry_identifier {
EntryIdentifier::Pos(pos) => &self.route_list.list[pos].1,
EntryIdentifier::New(ref re) => re,
EntryIdentifier::None => {
unreachable!()
}
}
}
}
impl DerefMut for RouteEntryGuard<'_, Occupied> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self.entry_identifier {
EntryIdentifier::Pos(pos) => &mut self.route_list.list[pos].1,
EntryIdentifier::New(ref mut re) => re,
EntryIdentifier::None => {
unreachable!()
}
}
}
}
impl<O> Drop for RouteEntryGuard<'_, O> {
fn drop(&mut self) {
todo!()
}
}
enum RoutingTableOplogEntry {}
/// Hold an exclusive write lock over the routing table. While this item is in scope, no other
/// calls can get a mutable refernce to the content of a routing table. Once this guard goes out of
/// scope, changes to the contained RouteList will be applied.
pub struct WriteGuard<'a> {
writer: MutexGuard<'a, left_right::WriteHandle<RoutingTableInner, RoutingTableOplogEntry>>,
/// Owned copy of the RouteList, this is populated once mutable access the the RouteList has
/// been requested.
//owned: Option<Arc<RouteList>>,
value: Arc<RouteList>,
owned: bool,
/// Did the RouteList exist initially?
exists: bool,
/// The subnet we are writing to.
subnet: Subnet,
}
impl RoutingTable {
/// Create a new empty RoutingTable.
///
/// # Panics
///
/// This will panic if not executed in the context of a tokio runtime.
pub fn new() -> Self {
let (writer, reader) = left_right::new();
let writer = Arc::new(Mutex::new(writer));
let (sink, mut stream) = mpsc::channel(1);
let cancel_token = CancellationToken::new();
{
let cancel_token = cancel_token.clone();
tokio::spawn(async move {
loop {
select! {
_ = cancel_token.cancelled() => {
debug!("Cancel token awaited, aborting route expiration cleanup task");
return;
}
erk = stream.recv() => {
if let Some(expired_route_key) = erk {
debug!(route_key = ?expired_route_key, "Route expired");
todo!();
} else {
debug!("All expired route key sinks closed");
break
}
}
}
}
warn!("Expired route cleanup task exits");
});
}
RoutingTable {
writer,
reader,
expired_route_entry_sink: sink,
cancel_token,
}
}
/// Get a list of all routes for the given subnet. Changes to the RoutingTable after this
/// method returns will not be visible and require this method to be called again to be
/// observed.
pub fn routes(&self, subnet: Subnet) -> Option<Arc<RouteList>> {
let subnet_ip = if let IpAddr::V6(ip) = subnet.address() {
ip
} else {
return None;
};
self.reader
.enter()
.expect("Write handle is saved on the router so it is not dropped yet.")
.table
.exact_match(subnet_ip, subnet.prefix_len().into())
.cloned()
}
pub fn routes_mut(&self, subnet: Subnet) -> WriteGuard {
let subnet_address = if let IpAddr::V6(ip) = subnet.address() {
ip
} else {
panic!("IP v4 addresses are not supported")
};
let writer = self.writer.lock().unwrap();
let value = writer
.enter()
.expect("Enter through write handle is always valid")
.table
.exact_match(subnet_address, subnet.prefix_len().into())
.cloned();
let exists = value.is_some();
WriteGuard {
writer,
// If we didn't find a route list in the route table we create a new empty list,
// therefore we immediately own it.
owned: value.is_none(),
value: if let Some(value) = value {
value
} else {
Arc::new(RouteList::new())
},
exists,
subnet,
}
}
}
impl Deref for WriteGuard<'_> {
type Target = RouteList;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl DerefMut for WriteGuard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.owned = true;
Arc::make_mut(&mut self.value)
}
}
impl Drop for WriteGuard<'_> {
fn drop(&mut self) {
// If owned is false, we never got a mutable reference, and the existing value is a
// reference to the already existing value in the route table. So we don't have to do
// anything here.
if !self.owned {
return;
}
match self.exists {
// The route list did not exist, and now it is not empty, so an entry was added. We
// need to add the route list to the routing table.
false if !self.value.is_empty() => {
todo!();
}
// There was an existing route list which is now empty, to the entry for this subnet
// needs to be deleted in the routing table.
true if self.value.is_empty() => {
todo!();
}
// The value already existed, and was mutably accessed, so it was dissociated. Update
// the routing table to point to the new value.
true => {
todo!()
}
// The value did not exist and is still empty, so nothing was added. Nothing to do
// here.
false => {}
}
}
}
impl left_right::Absorb<RoutingTableOplogEntry> for RoutingTableInner {
fn absorb_first(&mut self, _operation: &mut RoutingTableOplogEntry, _other: &Self) {
todo!()
}
fn sync_with(&mut self, first: &Self) {
for (k, ss, v) in first.table.iter() {
self.table.insert(k, ss, v.clone());
}
}
}
impl Drop for RoutingTable {
fn drop(&mut self) {
self.cancel_token.cancel();
}
}