netstack3_ip/
api.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Defines the public API exposed to bindings by the IP module.
6
7use 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/// The options for [`RoutesApi::resolve_route`] api.
36#[derive(Debug, Clone, Default)]
37pub struct RouteResolveOptions {
38    /// The marks for the resolution.
39    pub marks: Marks,
40}
41
42/// The routes API for a specific IP version `I`.
43pub struct RoutesApi<I: Ip, C>(C, IpVersionMarker<I>);
44
45impl<I: Ip, C> RoutesApi<I, C> {
46    /// Creates a new API instance.
47    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    /// Allocates a new table in Core.
67    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    /// Removes a table in Core.
81    ///
82    /// # Panics
83    ///
84    /// Panics if `id` is the main table id.
85    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    /// Gets the core ID of the main table.
100    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    /// Collects all the routes in the specified table into `target`.
107    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    /// Collects all the routes in the main table into `target`.
121    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    /// Like the Iterator fold accumulator.
134    ///
135    /// Applies the given `cb` to each route across all routing tables.
136    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    /// Resolve the route to a given destination.
154    ///
155    /// Returns `Some` [`ResolvedRoute`] with details for reaching the destination,
156    /// or `None` if the destination is unreachable.
157    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    /// Selects the device to use for gateway routes when the device was
169    /// unspecified by the client.
170    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    /// Writes rules and routes tables information to the provided `inspector`.
195    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    /// Writes rules table information to the provided `inspector`.
209    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    /// Writes routing table information to the provided `inspector`.
264    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    /// Replaces the entire route table atomically.
303    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        // Make sure to sort the entries _before_ taking the routing table lock.
311        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    /// Replaces the entire rule table atomically.
320    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    /// Gets all table IDs.
330    #[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
338/// The routes API interacting with all IP versions.
339pub struct RoutesAnyApi<C>(C);
340
341impl<C> RoutesAnyApi<C> {
342    /// Creates a new API instance.
343    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    /// Gets all the installed routes.
364    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    /// Like [`RoutesApi::select_device_for_gateway`] but for any IP version.
378    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
389/// A marker trait for all the bindings context traits required to fulfill the
390/// [`RoutesApi`].
391pub 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
409/// A marker trait for all the core context traits required to fulfill the
410/// [`RoutesApi`].
411pub 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}