Skip to main content

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::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/// The options for [`RoutesApi::resolve_route`] api.
38#[derive(Debug, Clone, Default)]
39pub struct RouteResolveOptions {
40    /// The marks for the resolution.
41    pub marks: Marks,
42}
43
44/// The table ID type for the routes API.
45pub type RoutesApiTableId<I, C> = RoutingTableId<
46    I,
47    <<C as ContextPair>::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
48    <C as ContextPair>::BindingsContext,
49>;
50
51/// The routes API for a specific IP version `I`.
52pub struct RoutesApi<I: Ip, C>(C, IpVersionMarker<I>);
53
54impl<I: Ip, C> RoutesApi<I, C> {
55    /// Creates a new API instance.
56    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    /// Allocates a new table in Core.
76    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    /// Removes a table in Core.
91    ///
92    /// # Panics
93    ///
94    /// Panics if `id` is the main table id.
95    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    /// Gets the core ID of the main table.
110    pub fn main_table_id(&mut self) -> RoutesApiTableId<I, C> {
111        self.core_ctx().main_table_id()
112    }
113
114    /// Collects all the routes in the specified table into `target`.
115    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    /// Collects all the routes in the main table into `target`.
129    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    /// Like the Iterator fold accumulator.
142    ///
143    /// Applies the given `cb` to each route across all routing tables.
144    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    /// Resolve the route to a given destination.
162    ///
163    /// Returns `Some` [`ResolvedRoute`] with details for reaching the destination,
164    /// or `None` if the destination is unreachable.
165    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    /// Selects the device to use for gateway routes when the device was
177    /// unspecified by the client.
178    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    /// Writes rules and routes tables information to the provided `inspector`.
203    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    /// Writes rules table information to the provided `inspector`.
218    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    /// Writes routing table information to the provided `inspector`.
291    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    /// Replaces the entire route table atomically.
331    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        // Make sure to sort the entries _before_ taking the routing table lock.
339        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    /// Replaces the entire rule table atomically.
348    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    /// Gets all table IDs.
360    #[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
366/// The routes API interacting with all IP versions.
367pub struct RoutesAnyApi<C>(C);
368
369impl<C> RoutesAnyApi<C> {
370    /// Creates a new API instance.
371    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    /// Gets all the installed routes.
392    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    /// Like [`RoutesApi::select_device_for_gateway`] but for any IP version.
406    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
417/// A marker trait for all the bindings context traits required to fulfill the
418/// [`RoutesApi`].
419pub 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
437/// A marker trait for all the core context traits required to fulfill the
438/// [`RoutesApi`].
439pub 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}