1use alloc::format;
8use alloc::string::{String, ToString as _};
9use alloc::vec::Vec;
10use assert_matches::assert_matches;
11use net_types::ip::{Ip, IpAddr, IpVersionMarker, Ipv4, Ipv6};
12use net_types::{SpecifiedAddr, Witness as _};
13
14use netstack3_base::sync::{PrimaryRc, RwLock};
15use netstack3_base::{
16 AnyDevice, ContextPair, DeferredResourceRemovalContext, DeviceIdContext, Inspector,
17 InspectorDeviceExt, MarkDomain, Marks, ReferenceNotifiersExt as _,
18 RemoveResourceResultWithContext, StrongDeviceIdentifier, WrapBroadcastMarker,
19};
20
21use crate::internal::base::{
22 self, IpLayerBindingsContext, IpLayerContext, IpLayerIpExt, IpRouteTableContext,
23 IpRouteTablesContext, IpStateContext as _, ResolveRouteError, RoutingTableId,
24};
25use crate::internal::device::{
26 IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceIpExt,
27};
28use crate::internal::routing::rules::{MarkMatcher, Rule, RuleAction, RuleMatcher};
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 let source_address_matcher = source_address_matcher
220 .map_or_else(|| String::new(), |m| m.0.to_string());
221 inspector.record_str("SourceAddressMatcher", &source_address_matcher);
222 inspector.record_debug("TrafficOriginMatcher", traffic_origin_matcher);
223 inspector.record_child("MarkMatchers", |inspector| {
224 for (domain, matcher) in
225 mark_matchers.iter().filter_map(|(d, m)| m.map(|m| (d, m)))
226 {
227 let domain_str = match domain {
228 MarkDomain::Mark1 => "Mark1",
229 MarkDomain::Mark2 => "Mark2",
230 };
231 match matcher {
232 MarkMatcher::Unmarked => {
233 inspector.record_str(domain_str, "Unmarked")
234 }
235 MarkMatcher::Marked { start, end, mask } => inspector
236 .record_child(domain_str, |inspector| {
237 inspector.record_str("Mask", &format!("{mask:#010x}"));
238 inspector.record_str(
239 "Range",
240 &format!("{start:#x}..{end:#x}"),
241 );
242 }),
243 }
244 }
245 });
246 });
247 inspector.record_child("Action", |inspector| match action {
248 RuleAction::Unreachable => inspector.record_str("Action", "Unreachable"),
249 RuleAction::Lookup(table_id) => {
250 let bindings_id = core_ctx
251 .with_ip_routing_table(table_id, |_core_ctx, table| {
252 table.bindings_id
253 });
254 let bindings_id = bindings_id.unwrap_or(main_table_id);
255 inspector.record_str("Lookup", &format!("{bindings_id}"))
256 }
257 })
258 })
259 }
260 })
261 }
262
263 pub fn inspect_routes<
265 N: Inspector + InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
266 >(
267 &mut self,
268 inspector: &mut N,
269 main_table_id: u32,
270 ) {
271 self.core_ctx().with_ip_routing_tables(|core_ctx, tables| {
272 for table_id in tables.keys() {
273 core_ctx.with_ip_routing_table(table_id, |_core_ctx, table| {
274 let bindings_id = table.bindings_id.unwrap_or(main_table_id);
275 inspector.record_display_child(bindings_id, |inspector| {
276 for Entry { subnet, device, gateway, metric } in table.iter_table() {
277 inspector.record_unnamed_child(|inspector| {
278 inspector.record_display("Destination", subnet);
279 N::record_device(inspector, "InterfaceId", device);
280 match gateway {
281 Some(gateway) => {
282 inspector.record_ip_addr("Gateway", gateway.get());
283 }
284 None => {
285 inspector.record_str("Gateway", "[NONE]");
286 }
287 }
288 let (metric, tracks_interface) = match metric {
289 Metric::MetricTracksInterface(metric) => (metric, true),
290 Metric::ExplicitMetric(metric) => (metric, false),
291 };
292 inspector.record_uint("Metric", *metric);
293 inspector.record_bool("MetricTracksInterface", tracks_interface);
294 });
295 }
296 })
297 })
298 }
299 });
300 }
301
302 pub fn set_routes(
304 &mut self,
305 table_id: &RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
306 mut entries: Vec<
307 EntryAndGeneration<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
308 >,
309 ) {
310 entries.sort_unstable_by(|a, b| {
312 OrderedEntry::<'_, _, _>::from(a).cmp(&OrderedEntry::<'_, _, _>::from(b))
313 });
314 self.core_ctx().with_ip_routing_table_mut(table_id, |_core_ctx, table| {
315 table.table = entries;
316 });
317 }
318
319 pub fn set_rules(
321 &mut self,
322 rules: Vec<Rule<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>>,
323 ) {
324 self.core_ctx().with_rules_table_mut(|_core_ctx, rule_table| {
325 rule_table.replace(rules);
326 })
327 }
328
329 #[cfg(feature = "testutils")]
331 pub fn list_table_ids(
332 &mut self,
333 ) -> Vec<RoutingTableId<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>> {
334 self.core_ctx().with_ip_routing_tables(|_ctx, tables| tables.keys().cloned().collect())
335 }
336}
337
338pub struct RoutesAnyApi<C>(C);
340
341impl<C> RoutesAnyApi<C> {
342 pub fn new(ctx: C) -> Self {
344 Self(ctx)
345 }
346}
347
348impl<C> RoutesAnyApi<C>
349where
350 C: ContextPair,
351 C::CoreContext: RoutesApiCoreContext<Ipv4, C::BindingsContext>
352 + RoutesApiCoreContext<Ipv6, C::BindingsContext>,
353 C::BindingsContext: RoutesApiBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
354 + RoutesApiBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
355 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId: Ord,
356{
357 fn ip<I: Ip>(&mut self) -> RoutesApi<I, &mut C> {
358 let Self(pair) = self;
359 RoutesApi::new(pair)
360 }
361
362 #[cfg(feature = "testutils")]
363 pub fn get_all_routes_in_main_table(
365 &mut self,
366 ) -> Vec<
367 crate::internal::types::EntryEither<
368 <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
369 >,
370 > {
371 let mut vec = Vec::new();
372 self.ip::<Ipv4>().collect_main_table_routes_into(&mut vec);
373 self.ip::<Ipv6>().collect_main_table_routes_into(&mut vec);
374 vec
375 }
376
377 pub fn select_device_for_gateway(
379 &mut self,
380 gateway: SpecifiedAddr<IpAddr>,
381 ) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> {
382 match gateway.into() {
383 IpAddr::V4(gateway) => self.ip::<Ipv4>().select_device_for_gateway(gateway),
384 IpAddr::V6(gateway) => self.ip::<Ipv6>().select_device_for_gateway(gateway),
385 }
386 }
387}
388
389pub trait RoutesApiBindingsContext<I, D>:
392 IpDeviceBindingsContext<I, D> + IpLayerBindingsContext<I, D> + DeferredResourceRemovalContext
393where
394 D: StrongDeviceIdentifier,
395 I: IpLayerIpExt + IpDeviceIpExt,
396{
397}
398
399impl<I, D, BC> RoutesApiBindingsContext<I, D> for BC
400where
401 D: StrongDeviceIdentifier,
402 I: IpLayerIpExt + IpDeviceIpExt,
403 BC: IpDeviceBindingsContext<I, D>
404 + IpLayerBindingsContext<I, D>
405 + DeferredResourceRemovalContext,
406{
407}
408
409pub trait RoutesApiCoreContext<I, BC>:
412 IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>
413where
414 I: IpLayerIpExt + IpDeviceIpExt,
415 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
416 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
417{
418}
419
420impl<I, BC, CC> RoutesApiCoreContext<I, BC> for CC
421where
422 CC: IpLayerContext<I, BC> + IpDeviceConfigurationContext<I, BC>,
423 I: IpLayerIpExt + IpDeviceIpExt,
424 BC: IpDeviceBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>
425 + IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>,
426{
427}