use alloc::vec::Vec;
use assert_matches::assert_matches;
use net_types::ip::{Ip, IpAddr, IpVersionMarker, Ipv4, Ipv6};
use net_types::{SpecifiedAddr, Witness as _};
use netstack3_base::sync::{PrimaryRc, RwLock};
use netstack3_base::{
AnyDevice, ContextPair, DeferredResourceRemovalContext, DeviceIdContext, Inspector,
InspectorDeviceExt, ReferenceNotifiersExt as _, RemoveResourceResultWithContext,
StrongDeviceIdentifier, WrapBroadcastMarker,
};
use crate::internal::base::{
self, IpLayerBindingsContext, IpLayerContext, IpLayerIpExt, IpRouteTableContext,
IpRouteTablesContext, IpStateContext as _, ResolveRouteError, RoutingTableId,
};
use crate::internal::device::{
IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceIpExt,
};
use crate::internal::routing::rules::{Marks, Rule};
use crate::internal::routing::RoutingTable;
use crate::internal::types::{
Destination, Entry, EntryAndGeneration, Metric, NextHop, OrderedEntry, ResolvedRoute,
RoutableIpAddr,
};
pub struct RoutesApi<I: Ip, C>(C, IpVersionMarker<I>);
impl<I: Ip, C> RoutesApi<I, C> {
pub fn new(ctx: C) -> Self {
Self(ctx, IpVersionMarker::new())
}
}
impl<I, C> RoutesApi<I, C>
where
I: IpLayerIpExt + IpDeviceIpExt,
C: ContextPair,
C::CoreContext: RoutesApiCoreContext<I, C::BindingsContext>,
C::BindingsContext:
RoutesApiBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
{
fn core_ctx(&mut self) -> &mut C::CoreContext {
let Self(pair, IpVersionMarker { .. }) = self;
pair.core_ctx()
}
pub fn new_table(
&mut self,
) -> RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
self.core_ctx().with_ip_routing_tables_mut(|tables| {
let new_table = PrimaryRc::new(RwLock::new(RoutingTable::default()));
let table_id = RoutingTableId::new(PrimaryRc::clone_strong(&new_table));
assert_matches!(tables.insert(table_id.clone(), new_table), None);
table_id
})
}
pub fn remove_table(
&mut self,
id: RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
) -> RemoveResourceResultWithContext<(), C::BindingsContext> {
assert!(id != self.main_table_id(), "main table should never be removed");
self.core_ctx().with_ip_routing_tables_mut(|tables| {
let table = assert_matches!(
tables.remove(&id),
Some(removed) => removed
);
C::BindingsContext::unwrap_or_notify_with_new_reference_notifier(table, |_| ())
})
}
pub fn main_table_id(
&mut self,
) -> RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
self.core_ctx().main_table_id()
}
pub fn collect_routes_into<
X: From<Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
T: Extend<X>,
>(
&mut self,
table_id: &RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
target: &mut T,
) {
self.core_ctx().with_ip_routing_table(table_id, |_core_ctx, table| {
target.extend(table.iter_table().cloned().map(Into::into))
})
}
pub fn collect_main_table_routes_into<
X: From<Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
T: Extend<X>,
>(
&mut self,
target: &mut T,
) {
self.core_ctx().with_main_ip_routing_table(|_core_ctx, table| {
target.extend(table.iter_table().cloned().map(Into::into))
})
}
pub fn fold_routes<B, F>(&mut self, init: B, mut cb: F) -> B
where
F: FnMut(
B,
&RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
&Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
) -> B,
{
self.core_ctx().with_ip_routing_tables(|ctx, tables| {
tables.keys().fold(init, |state, table_id| {
ctx.with_ip_routing_table(table_id, |_ctx, table| {
table.iter_table().fold(state, |state, entry| cb(state, table_id, entry))
})
})
})
}
pub fn resolve_route(
&mut self,
destination: Option<RoutableIpAddr<I::Addr>>,
) -> Result<
ResolvedRoute<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
ResolveRouteError,
> {
base::resolve_output_route_to_destination(
self.core_ctx(),
None,
None,
destination,
&Marks::default(),
)
}
pub fn select_device_for_gateway(
&mut self,
gateway: SpecifiedAddr<I::Addr>,
) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
self.core_ctx().with_main_ip_routing_table_mut(|core_ctx, table| {
table.lookup(core_ctx, None, *gateway).and_then(
|Destination { next_hop: found_next_hop, device: found_device }| {
match found_next_hop {
NextHop::RemoteAsNeighbor => Some(found_device),
NextHop::Broadcast(marker) => {
I::map_ip::<_, ()>(
WrapBroadcastMarker(marker),
|WrapBroadcastMarker(())| (),
|WrapBroadcastMarker(never)| match never {},
);
Some(found_device)
}
NextHop::Gateway(_intermediary_gateway) => None,
}
},
)
})
}
pub fn inspect<
'a,
N: Inspector + InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
>(
&mut self,
inspector: &mut N,
) {
self.core_ctx().with_main_ip_routing_table(|_core_ctx, table| {
for Entry { subnet, device, gateway, metric } in table.iter_table() {
inspector.record_unnamed_child(|inspector| {
inspector.record_display("Destination", subnet);
N::record_device(inspector, "InterfaceId", device);
match gateway {
Some(gateway) => {
inspector.record_ip_addr("Gateway", gateway.get());
}
None => {
inspector.record_str("Gateway", "[NONE]");
}
}
let (metric, tracks_interface) = match metric {
Metric::MetricTracksInterface(metric) => (metric, true),
Metric::ExplicitMetric(metric) => (metric, false),
};
inspector.record_uint("Metric", *metric);
inspector.record_bool("MetricTracksInterface", tracks_interface);
});
}
})
}
pub fn set_routes(
&mut self,
table_id: &RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
mut entries: Vec<
EntryAndGeneration<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
>,
) {
entries.sort_unstable_by(|a, b| {
OrderedEntry::<'_, _, _>::from(a).cmp(&OrderedEntry::<'_, _, _>::from(b))
});
self.core_ctx().with_ip_routing_table_mut(table_id, |_core_ctx, table| {
table.table = entries;
});
}
pub fn set_rules(
&mut self,
rules: Vec<Rule<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
) {
self.core_ctx().with_rules_table_mut(|_core_ctx, rule_table| {
rule_table.replace(rules);
})
}
#[cfg(feature = "testutils")]
pub fn list_table_ids(
&mut self,
) -> Vec<RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>> {
self.core_ctx().with_ip_routing_tables(|_ctx, tables| tables.keys().cloned().collect())
}
}
pub struct RoutesAnyApi<C>(C);
impl<C> RoutesAnyApi<C> {
pub fn new(ctx: C) -> Self {
Self(ctx)
}
}
impl<C> RoutesAnyApi<C>
where
C: ContextPair,
C::CoreContext: RoutesApiCoreContext<Ipv4, C::BindingsContext>
+ RoutesApiCoreContext<Ipv6, C::BindingsContext>,
C::BindingsContext: RoutesApiBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
+ RoutesApiBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
{
fn ip<I: Ip>(&mut self) -> RoutesApi<I, &mut C> {
let Self(pair) = self;
RoutesApi::new(pair)
}
#[cfg(feature = "testutils")]
pub fn get_all_routes_in_main_table(
&mut self,
) -> Vec<
crate::internal::types::EntryEither<
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
>,
> {
let mut vec = Vec::new();
self.ip::<Ipv4>().collect_main_table_routes_into(&mut vec);
self.ip::<Ipv6>().collect_main_table_routes_into(&mut vec);
vec
}
pub fn select_device_for_gateway(
&mut self,
gateway: SpecifiedAddr<IpAddr>,
) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
match gateway.into() {
IpAddr::V4(gateway) => self.ip::<Ipv4>().select_device_for_gateway(gateway),
IpAddr::V6(gateway) => self.ip::<Ipv6>().select_device_for_gateway(gateway),
}
}
}
pub trait RoutesApiBindingsContext<I, D>:
IpDeviceBindingsContext<I, D> + IpLayerBindingsContext<I, D> + DeferredResourceRemovalContext
where
D: StrongDeviceIdentifier,
I: IpLayerIpExt + IpDeviceIpExt,
{
}
impl<I, D, BC> RoutesApiBindingsContext<I, D> for BC
where
D: StrongDeviceIdentifier,
I: IpLayerIpExt + IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, D>
+ IpLayerBindingsContext<I, D>
+ DeferredResourceRemovalContext,
{
}
pub trait RoutesApiCoreContext<I, BC>:
IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>
where
I: IpLayerIpExt + IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
+ IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
{
}
impl<I, BC, CC> RoutesApiCoreContext<I, BC> for CC
where
CC: IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>,
I: IpLayerIpExt + IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
+ IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
{
}