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