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