1use alloc::vec::Vec;
8use assert_matches::assert_matches;
9use net_types::ip::{Ip, IpAddr, IpVersionMarker, Ipv4, Ipv6};
10use net_types::{SpecifiedAddr, Witness as _};
11
12use netstack3_base::sync::PrimaryRc;
13use netstack3_base::{
14 AnyDevice, ContextPair, DeferredResourceRemovalContext, DeviceIdContext, InspectableValue,
15 Inspector, InspectorDeviceExt, MarkDomain, MarkMatcher, Marks, ReferenceNotifiersExt as _,
16 RemoveResourceResultWithContext, StrongDeviceIdentifier, SubnetMatcher, WrapBroadcastMarker,
17};
18
19use crate::IpRoutingBindingsTypes;
20use crate::internal::base::{
21 self, BaseRoutingTableState, IpLayerBindingsContext, IpLayerContext, IpLayerIpExt,
22 IpRouteTableContext, IpRouteTablesContext, IpStateContext as _, ResolveRouteError,
23 RoutingTableCookie, RoutingTableId,
24};
25use crate::internal::device::{
26 IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceIpExt,
27};
28use crate::internal::routing::rules::{
29 Rule, RuleAction, RuleMatcher, RulesTable, TrafficOriginMatcher,
30};
31use crate::internal::types::{
32 Destination, Entry, EntryAndGeneration, Metric, NextHop, OrderedEntry, ResolvedRoute,
33 RoutableIpAddr,
34};
35use diagnostics_traits::InspectorRouteTableExt;
36
37#[derive(Debug, Clone, Default)]
39pub struct RouteResolveOptions {
40 pub marks: Marks,
42}
43
44pub type RoutesApiTableId<I, C> = RoutingTableId<
46 I,
47 <<C as ContextPair>::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
48 <C as ContextPair>::BindingsContext,
49>;
50
51pub struct RoutesApi<I: Ip, C>(C, IpVersionMarker<I>);
53
54impl<I: Ip, C> RoutesApi<I, C> {
55 pub fn new(ctx: C) -> Self {
57 Self(ctx, IpVersionMarker::new())
58 }
59}
60
61impl<I, C> RoutesApi<I, C>
62where
63 I: IpLayerIpExt + IpDeviceIpExt,
64 C: ContextPair,
65 C::CoreContext: RoutesApiCoreContext<I, C::BindingsContext>,
66 C::BindingsContext:
67 RoutesApiBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
68 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
69{
70 fn core_ctx(&mut self) -> &mut C::CoreContext {
71 let Self(pair, IpVersionMarker { .. }) = self;
72 pair.core_ctx()
73 }
74
75 pub fn new_table(
77 &mut self,
78 bindings_id: <C::BindingsContext as IpRoutingBindingsTypes>::RoutingTableId,
79 ) -> RoutesApiTableId<I, C> {
80 self.core_ctx().with_ip_routing_tables_mut(|tables| {
81 let new_table = PrimaryRc::new(BaseRoutingTableState::with_bindings_id(
82 RoutingTableCookie::BindingsId(bindings_id),
83 ));
84 let table_id = RoutingTableId::new(PrimaryRc::clone_strong(&new_table));
85 assert_matches!(tables.insert(table_id.clone(), new_table), None);
86 table_id
87 })
88 }
89
90 pub fn remove_table(
96 &mut self,
97 id: RoutesApiTableId<I, C>,
98 ) -> RemoveResourceResultWithContext<(), C::BindingsContext> {
99 assert!(id != self.main_table_id(), "main table should never be removed");
100 self.core_ctx().with_ip_routing_tables_mut(|tables| {
101 let table = assert_matches!(
102 tables.remove(&id),
103 Some(removed) => removed
104 );
105 C::BindingsContext::unwrap_or_notify_with_new_reference_notifier(table, |_| ())
106 })
107 }
108
109 pub fn main_table_id(&mut self) -> RoutesApiTableId<I, C> {
111 self.core_ctx().main_table_id()
112 }
113
114 pub fn collect_routes_into<
116 X: From<Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
117 T: Extend<X>,
118 >(
119 &mut self,
120 table_id: &RoutesApiTableId<I, C>,
121 target: &mut T,
122 ) {
123 self.core_ctx().with_ip_routing_table(table_id, |_core_ctx, table| {
124 target.extend(table.iter_table().cloned().map(Into::into))
125 })
126 }
127
128 pub fn collect_main_table_routes_into<
130 X: From<Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
131 T: Extend<X>,
132 >(
133 &mut self,
134 target: &mut T,
135 ) {
136 self.core_ctx().with_main_ip_routing_table(|_core_ctx, table| {
137 target.extend(table.iter_table().cloned().map(Into::into))
138 })
139 }
140
141 pub fn fold_routes<B, F>(&mut self, init: B, mut cb: F) -> B
145 where
146 F: FnMut(
147 B,
148 &RoutesApiTableId<I, C>,
149 &Entry<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
150 ) -> B,
151 {
152 self.core_ctx().with_ip_routing_tables(|ctx, tables| {
153 tables.keys().fold(init, |state, table_id| {
154 ctx.with_ip_routing_table(table_id, |_ctx, table| {
155 table.iter_table().fold(state, |state, entry| cb(state, table_id, entry))
156 })
157 })
158 })
159 }
160
161 pub fn resolve_route(
166 &mut self,
167 destination: Option<RoutableIpAddr<I::Addr>>,
168 RouteResolveOptions { marks }: &RouteResolveOptions,
169 ) -> Result<
170 ResolvedRoute<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
171 ResolveRouteError,
172 > {
173 base::resolve_output_route_to_destination(self.core_ctx(), None, None, destination, marks)
174 }
175
176 pub fn select_device_for_gateway(
179 &mut self,
180 gateway: SpecifiedAddr<I::Addr>,
181 ) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
182 self.core_ctx().with_main_ip_routing_table_mut(|core_ctx, table| {
183 table.lookup(core_ctx, None, *gateway).and_then(
184 |Destination { next_hop: found_next_hop, device: found_device }| {
185 match found_next_hop {
186 NextHop::RemoteAsNeighbor => Some(found_device),
187 NextHop::Broadcast(marker) => {
188 I::map_ip::<_, ()>(
189 WrapBroadcastMarker(marker),
190 |WrapBroadcastMarker(())| (),
191 |WrapBroadcastMarker(never)| match never {},
192 );
193 Some(found_device)
194 }
195 NextHop::Gateway(_intermediary_gateway) => None,
196 }
197 },
198 )
199 })
200 }
201
202 pub fn inspect<N>(&mut self, inspector: &mut N)
204 where
205 N: Inspector,
206 for<'a> N::ChildInspector<'a>: InspectorRouteTableExt<RoutesApiTableId<I, C>>
207 + InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
208 {
209 inspector.record_child("Rules", |inspector| {
210 self.inspect_rules(inspector);
211 });
212 inspector.record_child("RoutingTables", |inspector| {
213 self.inspect_routes(inspector);
214 })
215 }
216
217 pub fn inspect_rules<N>(&mut self, inspector: &mut N)
219 where
220 N: Inspector + InspectorRouteTableExt<RoutesApiTableId<I, C>>,
221 {
222 self.core_ctx().with_rules_table(
223 |_core_ctx,
224 rule_table: &RulesTable<
225 _,
226 _,
227 C::BindingsContext,
228 >| {
229 for Rule {
230 matcher:
231 RuleMatcher { source_address_matcher, traffic_origin_matcher, mark_matchers },
232 action,
233 } in rule_table.iter()
234 {
235 inspector.record_unnamed_child(|inspector| {
236 inspector.record_child("Matchers", |inspector| {
237 if let Some(SubnetMatcher(subnet)) = source_address_matcher {
238 inspector.record_display("SourceAddressFrom", subnet);
239 }
240 if let Some(matcher) = traffic_origin_matcher {
241 match matcher {
242 TrafficOriginMatcher::NonLocal => {
243 inspector.record_str("TrafficOrigin", "NonLocal")
244 }
245 TrafficOriginMatcher::Local { bound_device_matcher } => inspector
246 .record_child("LocalOrigin", |inspector| {
247 if let Some(bound_device_matcher) = bound_device_matcher {
248 bound_device_matcher.record("BoundDevice", inspector);
249 }
250 }),
251 }
252 }
253 for (domain, matcher) in
254 mark_matchers.iter().filter_map(|(d, m)| m.map(|m| (d, m)))
255 {
256 let domain_str = match domain {
257 MarkDomain::Mark1 => "Mark1",
258 MarkDomain::Mark2 => "Mark2",
259 };
260 match matcher {
261 MarkMatcher::Unmarked => {
262 inspector.record_str(domain_str, "Unmarked")
263 }
264 MarkMatcher::Marked { start, end, mask, invert } => {
265 inspector.record_child(domain_str, |inspector| {
266 inspector.record_uint("Mask", mask);
267 inspector.record_child("Range", |inspector| {
268 inspector.record_uint("StartInclusive", start);
269 inspector.record_uint("EndInclusive", end);
270 });
271 inspector.record_bool("Invert", invert);
272 })
273 }
274 }
275 }
276 });
277 match action {
278 RuleAction::Unreachable => inspector.record_str("Action", "Unreachable"),
279 RuleAction::Lookup(table_id) => {
280 inspector.record_child("Action", |inspector| {
281 N::record_route_table(inspector, "Lookup", table_id)
282 });
283 }
284 }
285 })
286 }
287 })
288 }
289
290 pub fn inspect_routes<N>(&mut self, inspector: &mut N)
292 where
293 N: Inspector
294 + InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
295 + InspectorRouteTableExt<RoutesApiTableId<I, C>>,
296 {
297 self.core_ctx().with_ip_routing_tables(|core_ctx, tables| {
298 for table_id in tables.keys() {
299 core_ctx.with_ip_routing_table(table_id, |_core_ctx, table| {
300 inspector.record_display_child(N::display_route_table(table_id), |inspector| {
301 for Entry { subnet, device, gateway, metric } in table.iter_table() {
302 inspector.record_unnamed_child(|inspector| {
303 inspector.record_display("Destination", subnet);
304 N::record_device(inspector, "InterfaceId", device);
305 match gateway {
306 Some(gateway) => {
307 inspector.record_ip_addr("Gateway", gateway.get());
308 }
309 None => {
310 inspector.record_str("Gateway", "[NONE]");
311 }
312 }
313 let (metric, tracks_interface) = match metric {
314 Metric::MetricTracksInterface(metric) => (metric, true),
315 Metric::ExplicitMetric(metric) => (metric, false),
316 };
317 inspector.record_uint("Metric", *metric);
318 inspector.record_bool("MetricTracksInterface", tracks_interface);
319 });
320 }
321 })
322 })
323 }
324 });
325 }
326
327 pub fn set_routes(
329 &mut self,
330 table_id: &RoutesApiTableId<I, C>,
331 mut entries: Vec<
332 EntryAndGeneration<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
333 >,
334 ) {
335 entries.sort_unstable_by(|a, b| {
337 OrderedEntry::<'_, _, _>::from(a).cmp(&OrderedEntry::<'_, _, _>::from(b))
338 });
339 self.core_ctx().with_ip_routing_table_mut(table_id, |_core_ctx, table| {
340 table.table = entries;
341 });
342 }
343
344 pub fn set_rules(
346 &mut self,
347 rules: Vec<
348 Rule<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId, C::BindingsContext>,
349 >,
350 ) {
351 self.core_ctx().with_rules_table_mut(|_core_ctx, rule_table| {
352 rule_table.replace(rules);
353 })
354 }
355
356 #[cfg(feature = "testutils")]
358 pub fn list_table_ids(&mut self) -> Vec<RoutesApiTableId<I, C>> {
359 self.core_ctx().with_ip_routing_tables(|_ctx, tables| tables.keys().cloned().collect())
360 }
361}
362
363pub struct RoutesAnyApi<C>(C);
365
366impl<C> RoutesAnyApi<C> {
367 pub fn new(ctx: C) -> Self {
369 Self(ctx)
370 }
371}
372
373impl<C> RoutesAnyApi<C>
374where
375 C: ContextPair,
376 C::CoreContext: RoutesApiCoreContext<Ipv4, C::BindingsContext>
377 + RoutesApiCoreContext<Ipv6, C::BindingsContext>,
378 C::BindingsContext: RoutesApiBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
379 + RoutesApiBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
380 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
381{
382 fn ip<I: Ip>(&mut self) -> RoutesApi<I, &mut C> {
383 let Self(pair) = self;
384 RoutesApi::new(pair)
385 }
386
387 #[cfg(feature = "testutils")]
388 pub fn get_all_routes_in_main_table(
390 &mut self,
391 ) -> Vec<
392 crate::internal::types::EntryEither<
393 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
394 >,
395 > {
396 let mut vec = Vec::new();
397 self.ip::<Ipv4>().collect_main_table_routes_into(&mut vec);
398 self.ip::<Ipv6>().collect_main_table_routes_into(&mut vec);
399 vec
400 }
401
402 pub fn select_device_for_gateway(
404 &mut self,
405 gateway: SpecifiedAddr<IpAddr>,
406 ) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
407 match gateway.into() {
408 IpAddr::V4(gateway) => self.ip::<Ipv4>().select_device_for_gateway(gateway),
409 IpAddr::V6(gateway) => self.ip::<Ipv6>().select_device_for_gateway(gateway),
410 }
411 }
412}
413
414pub trait RoutesApiBindingsContext<I, D>:
417 IpDeviceBindingsContext<I, D> + IpLayerBindingsContext<I, D> + DeferredResourceRemovalContext
418where
419 D: StrongDeviceIdentifier,
420 I: IpLayerIpExt + IpDeviceIpExt,
421{
422}
423
424impl<I, D, BC> RoutesApiBindingsContext<I, D> for BC
425where
426 D: StrongDeviceIdentifier,
427 I: IpLayerIpExt + IpDeviceIpExt,
428 BC: IpDeviceBindingsContext<I, D>
429 + IpLayerBindingsContext<I, D>
430 + DeferredResourceRemovalContext,
431{
432}
433
434pub trait RoutesApiCoreContext<I, BC>:
437 IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>
438where
439 I: IpLayerIpExt + IpDeviceIpExt,
440 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
441 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
442{
443}
444
445impl<I, BC, CC> RoutesApiCoreContext<I, BC> for CC
446where
447 CC: IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>,
448 I: IpLayerIpExt + IpDeviceIpExt,
449 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
450 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
451{
452}