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, 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/// 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                        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    /// Writes routing table information to the provided `inspector`.
277    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    /// Replaces the entire route table atomically.
316    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        // Make sure to sort the entries _before_ taking the routing table lock.
324        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    /// Replaces the entire rule table atomically.
333    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    /// Gets all table IDs.
343    #[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
362/// The routes API interacting with all IP versions.
363pub struct RoutesAnyApi<C>(C);
364
365impl<C> RoutesAnyApi<C> {
366    /// Creates a new API instance.
367    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    /// Gets all the installed routes.
388    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    /// Like [`RoutesApi::select_device_for_gateway`] but for any IP version.
402    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
413/// A marker trait for all the bindings context traits required to fulfill the
414/// [`RoutesApi`].
415pub 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
433/// A marker trait for all the core context traits required to fulfill the
434/// [`RoutesApi`].
435pub 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}