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, route_preference } in
302 table.iter_table()
303 {
304 inspector.record_unnamed_child(|inspector| {
305 inspector.record_display("Destination", subnet);
306 N::record_device(inspector, "InterfaceId", device);
307 match gateway {
308 Some(gateway) => {
309 inspector.record_ip_addr("Gateway", gateway.get());
310 }
311 None => {
312 inspector.record_str("Gateway", "[NONE]");
313 }
314 }
315 let (metric, tracks_interface) = match metric {
316 Metric::MetricTracksInterface(metric) => (metric, true),
317 Metric::ExplicitMetric(metric) => (metric, false),
318 };
319 inspector.record_uint("Metric", *metric);
320 inspector.record_bool("MetricTracksInterface", tracks_interface);
321 inspector.record_debug("RoutePreference", route_preference);
322 });
323 }
324 })
325 })
326 }
327 });
328 }
329
330 pub fn set_routes(
332 &mut self,
333 table_id: &RoutesApiTableId<I, C>,
334 mut entries: Vec<
335 EntryAndGeneration<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
336 >,
337 ) {
338 entries.sort_unstable_by(|a, b| {
340 OrderedEntry::<'_, _, _>::from(a).cmp(&OrderedEntry::<'_, _, _>::from(b))
341 });
342 self.core_ctx().with_ip_routing_table_mut(table_id, |_core_ctx, table| {
343 table.table = entries;
344 });
345 }
346
347 pub fn set_rules(
349 &mut self,
350 rules: Vec<
351 Rule<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId, C::BindingsContext>,
352 >,
353 ) {
354 self.core_ctx().with_rules_table_mut(|_core_ctx, rule_table| {
355 rule_table.replace(rules);
356 })
357 }
358
359 #[cfg(feature = "testutils")]
361 pub fn list_table_ids(&mut self) -> Vec<RoutesApiTableId<I, C>> {
362 self.core_ctx().with_ip_routing_tables(|_ctx, tables| tables.keys().cloned().collect())
363 }
364}
365
366pub struct RoutesAnyApi<C>(C);
368
369impl<C> RoutesAnyApi<C> {
370 pub fn new(ctx: C) -> Self {
372 Self(ctx)
373 }
374}
375
376impl<C> RoutesAnyApi<C>
377where
378 C: ContextPair,
379 C::CoreContext: RoutesApiCoreContext<Ipv4, C::BindingsContext>
380 + RoutesApiCoreContext<Ipv6, C::BindingsContext>,
381 C::BindingsContext: RoutesApiBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
382 + RoutesApiBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
383 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
384{
385 fn ip<I: Ip>(&mut self) -> RoutesApi<I, &mut C> {
386 let Self(pair) = self;
387 RoutesApi::new(pair)
388 }
389
390 #[cfg(feature = "testutils")]
391 pub fn get_all_routes_in_main_table(
393 &mut self,
394 ) -> Vec<
395 crate::internal::types::EntryEither<
396 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
397 >,
398 > {
399 let mut vec = Vec::new();
400 self.ip::<Ipv4>().collect_main_table_routes_into(&mut vec);
401 self.ip::<Ipv6>().collect_main_table_routes_into(&mut vec);
402 vec
403 }
404
405 pub fn select_device_for_gateway(
407 &mut self,
408 gateway: SpecifiedAddr<IpAddr>,
409 ) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
410 match gateway.into() {
411 IpAddr::V4(gateway) => self.ip::<Ipv4>().select_device_for_gateway(gateway),
412 IpAddr::V6(gateway) => self.ip::<Ipv6>().select_device_for_gateway(gateway),
413 }
414 }
415}
416
417pub trait RoutesApiBindingsContext<I, D>:
420 IpDeviceBindingsContext<I, D> + IpLayerBindingsContext<I, D> + DeferredResourceRemovalContext
421where
422 D: StrongDeviceIdentifier,
423 I: IpLayerIpExt + IpDeviceIpExt,
424{
425}
426
427impl<I, D, BC> RoutesApiBindingsContext<I, D> for BC
428where
429 D: StrongDeviceIdentifier,
430 I: IpLayerIpExt + IpDeviceIpExt,
431 BC: IpDeviceBindingsContext<I, D>
432 + IpLayerBindingsContext<I, D>
433 + DeferredResourceRemovalContext,
434{
435}
436
437pub trait RoutesApiCoreContext<I, BC>:
440 IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>
441where
442 I: IpLayerIpExt + IpDeviceIpExt,
443 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
444 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
445{
446}
447
448impl<I, BC, CC> RoutesApiCoreContext<I, BC> for CC
449where
450 CC: IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>,
451 I: IpLayerIpExt + IpDeviceIpExt,
452 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
453 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
454{
455}