netstack3_ip/device/
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//! Device IP API.
6
7use alloc::vec::Vec;
8
9use either::Either;
10use log::trace;
11use net_types::ip::{
12    AddrSubnet, AddrSubnetEither, GenericOverIp, Ip, IpAddr, IpAddress, IpVersionMarker, Ipv4,
13    Ipv4Addr, Ipv6, Ipv6Addr,
14};
15use net_types::{SpecifiedAddr, Witness as _};
16use netstack3_base::{
17    AnyDevice, ContextPair, DeviceIdContext, DeviceIdentifier as _, EventContext as _, ExistsError,
18    Inspector, Instant, InstantBindingsTypes, IpAddressId as _, NotFoundError, ReferenceNotifiers,
19    RemoveResourceResult, RemoveResourceResultWithContext,
20};
21use thiserror::Error;
22
23use crate::internal::device::config::{
24    IpDeviceConfigurationAndFlags, IpDeviceConfigurationHandler,
25    PendingIpDeviceConfigurationUpdate, UpdateIpConfigurationError,
26};
27use crate::internal::device::state::{
28    CommonAddressProperties, IpAddressData, IpAddressFlags, IpDeviceConfiguration, Ipv4AddrConfig,
29    Ipv6AddrConfig, Ipv6AddrManualConfig,
30};
31use crate::internal::device::{
32    self, AddressRemovedReason, DelIpAddr, IpDeviceAddressContext as _, IpDeviceBindingsContext,
33    IpDeviceConfigurationContext, IpDeviceEvent, IpDeviceIpExt, IpDeviceStateContext as _,
34};
35use crate::internal::gmp::{GmpHandler as _, GmpStateContext};
36use crate::internal::routing::IpRoutingDeviceContext;
37use crate::internal::types::RawMetric;
38
39/// Provides an API for dealing with devices at the IP layer, aka interfaces.
40pub struct DeviceIpApi<I: Ip, C>(C, IpVersionMarker<I>);
41
42impl<I: Ip, C> DeviceIpApi<I, C> {
43    /// Creates a new API instance.
44    pub fn new(ctx: C) -> Self {
45        Self(ctx, IpVersionMarker::new())
46    }
47}
48
49impl<I, C> DeviceIpApi<I, C>
50where
51    I: IpDeviceIpExt,
52    C: ContextPair,
53    C::CoreContext: IpDeviceConfigurationContext<I, C::BindingsContext>
54        + IpDeviceConfigurationHandler<I, C::BindingsContext>
55        + IpRoutingDeviceContext<I>,
56    C::BindingsContext:
57        IpDeviceBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
58{
59    fn core_ctx(&mut self) -> &mut C::CoreContext {
60        let Self(pair, IpVersionMarker { .. }) = self;
61        pair.core_ctx()
62    }
63
64    fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
65        let Self(pair, IpVersionMarker { .. }) = self;
66        pair.contexts()
67    }
68
69    /// Like [`DeviceIpApi::add_ip_addr_subnet_with_config`] with a default
70    /// address configuration.
71    pub fn add_ip_addr_subnet(
72        &mut self,
73        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
74        addr_subnet: AddrSubnet<I::Addr>,
75    ) -> Result<(), AddIpAddrSubnetError> {
76        self.add_ip_addr_subnet_with_config(device, addr_subnet, Default::default())
77    }
78
79    /// Adds an IP address and associated subnet to this device.
80    ///
81    /// If Duplicate Address Detection (DAD) is enabled, begins performing DAD.
82    ///
83    /// For IPv6, this function also joins the solicited-node multicast group.
84    pub fn add_ip_addr_subnet_with_config(
85        &mut self,
86        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
87        addr_subnet: AddrSubnet<I::Addr>,
88        addr_config: I::ManualAddressConfig<<C::BindingsContext as InstantBindingsTypes>::Instant>,
89    ) -> Result<(), AddIpAddrSubnetError> {
90        let addr_subnet = addr_subnet
91            .replace_witness::<I::AssignedWitness>()
92            .ok_or(AddIpAddrSubnetError::InvalidAddr)?;
93        if !device.is_loopback() && I::LOOPBACK_SUBNET.contains(&addr_subnet.addr().get()) {
94            return Err(AddIpAddrSubnetError::InvalidAddr);
95        }
96        let (core_ctx, bindings_ctx) = self.contexts();
97        core_ctx.with_ip_device_configuration(device, |config, mut core_ctx| {
98            device::add_ip_addr_subnet_with_config(
99                &mut core_ctx,
100                bindings_ctx,
101                device,
102                addr_subnet,
103                addr_config.into(),
104                config,
105            )
106            .map(|_address_id| ())
107            .map_err(|ExistsError| AddIpAddrSubnetError::Exists)
108        })
109    }
110
111    /// Delete an IP address on a device.
112    pub fn del_ip_addr(
113        &mut self,
114        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
115        addr: SpecifiedAddr<I::Addr>,
116    ) -> Result<
117        RemoveResourceResultWithContext<AddrSubnet<I::Addr>, C::BindingsContext>,
118        NotFoundError,
119    > {
120        let (core_ctx, bindings_ctx) = self.contexts();
121        device::del_ip_addr(
122            core_ctx,
123            bindings_ctx,
124            device,
125            DelIpAddr::SpecifiedAddr(addr),
126            AddressRemovedReason::Manual,
127        )
128    }
129
130    /// Updates the IP configuration for a device.
131    ///
132    /// Each field in [`Ipv4DeviceConfigurationUpdate`] or
133    /// [`Ipv6DeviceConfigurationUpdate`] represents an optionally updateable
134    /// configuration. If the field has a `Some(_)` value, then an attempt will
135    /// be made to update that configuration on the device. A `None` value
136    /// indicates that an update for the configuration is not requested.
137    ///
138    /// Note that some fields have the type `Option<Option<T>>`. In this case,
139    /// as long as the outer `Option` is `Some`, then an attempt will be made to
140    /// update the configuration.
141    ///
142    /// This function returns a [`PendingDeviceConfigurationUpdate`] which is
143    /// validated and [`DeviceIpApi::apply`] can be called to apply the
144    /// configuration.
145    pub fn new_configuration_update<'a>(
146        &mut self,
147        device_id: &'a <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
148        config: I::ConfigurationUpdate,
149    ) -> Result<
150        PendingIpDeviceConfigurationUpdate<
151            'a,
152            I,
153            <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
154        >,
155        UpdateIpConfigurationError,
156    > {
157        PendingIpDeviceConfigurationUpdate::new(config, device_id)
158    }
159
160    /// Applies a pre-validated pending configuration to the device.
161    ///
162    /// Returns a configuration update with the previous value for all the
163    /// requested fields in `config`.
164    pub fn apply_configuration(
165        &mut self,
166        config: PendingIpDeviceConfigurationUpdate<
167            '_,
168            I,
169            <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
170        >,
171    ) -> I::ConfigurationUpdate {
172        let (core_ctx, bindings_ctx) = self.contexts();
173        IpDeviceConfigurationHandler::apply_configuration(core_ctx, bindings_ctx, config)
174    }
175
176    /// A shortcut for [`DeviceIpApi::new_configuration_update`] followed by
177    /// [`DeviceIpApi::apply_configuration`].
178    pub fn update_configuration(
179        &mut self,
180        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
181        config: I::ConfigurationUpdate,
182    ) -> Result<I::ConfigurationUpdate, UpdateIpConfigurationError> {
183        let pending = self.new_configuration_update(device_id, config)?;
184        Ok(self.apply_configuration(pending))
185    }
186
187    /// Gets the IP configuration and flags for a `device_id`.
188    pub fn get_configuration(
189        &mut self,
190        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
191    ) -> IpDeviceConfigurationAndFlags<I> {
192        self.core_ctx().with_ip_device_configuration(device_id, |config, mut core_ctx| {
193            IpDeviceConfigurationAndFlags {
194                config: config.clone(),
195                flags: core_ctx.with_ip_device_flags(device_id, |flags| flags.clone()),
196                gmp_mode: core_ctx.gmp_get_mode(device_id),
197            }
198        })
199    }
200
201    /// Gets the routing metric for the device.
202    pub fn get_routing_metric(
203        &mut self,
204        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
205    ) -> RawMetric {
206        self.core_ctx().get_routing_metric(device_id)
207    }
208
209    /// Sets properties on an IP address.
210    pub fn set_addr_properties(
211        &mut self,
212        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
213        address: SpecifiedAddr<I::Addr>,
214        next_properties: CommonAddressProperties<
215            <C::BindingsContext as InstantBindingsTypes>::Instant,
216        >,
217    ) -> Result<(), SetIpAddressPropertiesError> {
218        trace!("set_ip_addr_properties: setting {:?} for addr={:?}", next_properties, address);
219        let (core_ctx, bindings_ctx) = self.contexts();
220        let address_id = core_ctx.get_address_id(device, address)?;
221        core_ctx.with_ip_address_data_mut(device, &address_id, |address_state| {
222            let IpAddressData { flags: _, config } = address_state;
223            let Some(config) = config else {
224                // Address is being removed, configuration has been
225                // taken out.
226                return Err(NotFoundError.into());
227            };
228
229            #[derive(GenericOverIp)]
230            #[generic_over_ip(I, Ip)]
231            struct Wrap<'a, I: IpDeviceIpExt, Inst: Instant>(&'a mut I::AddressConfig<Inst>);
232            let CommonAddressProperties { valid_until, preferred_lifetime } = I::map_ip_in(
233                Wrap(config),
234                |Wrap(Ipv4AddrConfig { config: _, properties })| Ok(properties),
235                |Wrap(config)| match config {
236                    Ipv6AddrConfig::Slaac(_) => Err(SetIpAddressPropertiesError::NotManual),
237                    Ipv6AddrConfig::Manual(Ipv6AddrManualConfig {
238                        config: _,
239                        properties,
240                        temporary: _,
241                    }) => Ok(properties),
242                },
243            )?;
244
245            let CommonAddressProperties {
246                valid_until: next_valid_until,
247                preferred_lifetime: next_preferred_lifetime,
248            } = next_properties;
249            let mut changed = core::mem::replace(valid_until, next_valid_until) != next_valid_until;
250            changed |= core::mem::replace(preferred_lifetime, next_preferred_lifetime)
251                != next_preferred_lifetime;
252
253            if changed {
254                bindings_ctx.on_event(IpDeviceEvent::AddressPropertiesChanged {
255                    device: device.clone(),
256                    addr: address,
257                    valid_until: next_valid_until,
258                    preferred_lifetime: next_preferred_lifetime,
259                });
260            }
261            Ok(())
262        })
263    }
264
265    /// Calls `f` for each assigned IP address on the device.
266    pub fn for_each_assigned_ip_addr_subnet<F: FnMut(AddrSubnet<I::Addr>)>(
267        &mut self,
268        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
269        f: F,
270    ) {
271        self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
272            addrs
273                .filter_map(|addr| {
274                    let assigned = core_ctx.with_ip_address_data(device, &addr, |addr_data| {
275                        let IpAddressData { flags: IpAddressFlags { assigned }, config: _ } =
276                            addr_data;
277                        *assigned
278                    });
279                    assigned.then(|| addr.addr_sub().to_witness())
280                })
281                .for_each(f);
282        })
283    }
284
285    /// Shorthand for [`DeviceIpApi::Collect_assigned_ip_addr_subnets`],
286    /// returning the addresses in a `Vec`.
287    pub fn get_assigned_ip_addr_subnets(
288        &mut self,
289        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
290    ) -> Vec<AddrSubnet<I::Addr>> {
291        let mut vec = Vec::new();
292        self.for_each_assigned_ip_addr_subnet(device, |a| vec.push(a));
293        vec
294    }
295
296    /// Exports IP state for `device` into `inspector`.
297    pub fn inspect<N: Inspector>(
298        &mut self,
299        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
300        inspector: &mut N,
301    ) where
302        C::CoreContext: GmpStateContext<I, C::BindingsContext>,
303    {
304        inspector.record_child("Addresses", |inspector| {
305            self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
306                for addr in addrs {
307                    inspector.record_display_child(addr.addr_sub(), |inspector| {
308                        core_ctx.with_ip_address_data(device, &addr, |addr_state| {
309                            inspector.delegate_inspectable(addr_state)
310                        })
311                    });
312                }
313            })
314        });
315        inspector.record_child("Configuration", |inspector| {
316            self.core_ctx().with_ip_device_configuration(device, |config, _core_ctx| {
317                let IpDeviceConfiguration {
318                    gmp_enabled,
319                    unicast_forwarding_enabled,
320                    multicast_forwarding_enabled,
321                    dad_transmits,
322                } = config.as_ref();
323                inspector.record_bool("GmpEnabled", *gmp_enabled);
324                inspector.record_bool("ForwardingEnabled", *unicast_forwarding_enabled);
325                inspector.record_bool("MulticastForwardingEnabled", *multicast_forwarding_enabled);
326                inspector.record_uint("DadTransmits", dad_transmits.map(|t| t.get()).unwrap_or(0));
327            })
328        });
329        inspector.record_child("GMP", |inspector| {
330            self.core_ctx().with_gmp_state(device, |groups, gmp_state| {
331                inspector.record_inspectable_value("Mode", gmp_state.mode());
332                inspector.record_inspectable_value("Groups", groups);
333            })
334        })
335    }
336}
337/// The device IP API interacting with all IP versions.
338pub struct DeviceIpAnyApi<C>(C);
339
340impl<C> DeviceIpAnyApi<C> {
341    /// Creates a new API instance.
342    pub fn new(ctx: C) -> Self {
343        Self(ctx)
344    }
345}
346
347impl<C> DeviceIpAnyApi<C>
348where
349    C: ContextPair,
350    C::CoreContext: IpDeviceConfigurationContext<Ipv4, C::BindingsContext>
351        + IpDeviceConfigurationHandler<Ipv4, C::BindingsContext>
352        + IpRoutingDeviceContext<Ipv4>
353        + IpDeviceConfigurationContext<Ipv6, C::BindingsContext>
354        + IpDeviceConfigurationHandler<Ipv6, C::BindingsContext>
355        + IpRoutingDeviceContext<Ipv6>,
356    C::BindingsContext: IpDeviceBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
357        + IpDeviceBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
358{
359    fn ip<I: Ip>(&mut self) -> DeviceIpApi<I, &mut C> {
360        let Self(pair) = self;
361        DeviceIpApi::new(pair)
362    }
363
364    /// Like [`DeviceIpApi::add_ip_addr_subnet`].
365    pub fn add_ip_addr_subnet(
366        &mut self,
367        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
368        addr_sub_and_config: impl Into<
369            AddrSubnetAndManualConfigEither<<C::BindingsContext as InstantBindingsTypes>::Instant>,
370        >,
371    ) -> Result<(), AddIpAddrSubnetError> {
372        match addr_sub_and_config.into() {
373            AddrSubnetAndManualConfigEither::V4(addr_sub, config) => {
374                self.ip::<Ipv4>().add_ip_addr_subnet_with_config(device, addr_sub, config)
375            }
376            AddrSubnetAndManualConfigEither::V6(addr_sub, config) => {
377                self.ip::<Ipv6>().add_ip_addr_subnet_with_config(device, addr_sub, config)
378            }
379        }
380    }
381
382    /// Like [`DeviceIpApi::del_ip_addr`].
383    pub fn del_ip_addr(
384        &mut self,
385        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
386        addr: impl Into<SpecifiedAddr<IpAddr>>,
387    ) -> Result<
388        RemoveResourceResult<
389            AddrSubnetEither,
390            // NB: This is a bit of a mouthful, but we can't change the type of
391            // a ReferenceReceiver once created and it comes from deep inside
392            // core. The complexity should be contained here and this is simpler
393            // than making the ReferenceNotifiers trait fancier.
394            Either<
395                <C::BindingsContext as ReferenceNotifiers>::ReferenceReceiver<AddrSubnet<Ipv4Addr>>,
396                <C::BindingsContext as ReferenceNotifiers>::ReferenceReceiver<AddrSubnet<Ipv6Addr>>,
397            >,
398        >,
399        NotFoundError,
400    > {
401        let addr = addr.into();
402        match addr.into() {
403            IpAddr::V4(addr) => self
404                .ip::<Ipv4>()
405                .del_ip_addr(device, addr)
406                .map(|r| r.map_removed(Into::into).map_deferred(Either::Left)),
407            IpAddr::V6(addr) => self
408                .ip::<Ipv6>()
409                .del_ip_addr(device, addr)
410                .map(|r| r.map_removed(Into::into).map_deferred(Either::Right)),
411        }
412    }
413
414    /// Like [`DeviceIpApi::get_routing_metric`].
415    pub fn get_routing_metric(
416        &mut self,
417        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
418    ) -> RawMetric {
419        // NB: The routing metric is kept only once for both IP versions, debug
420        // assert that this is true, but return the v4 version otherwise.
421        let metric = self.ip::<Ipv4>().get_routing_metric(device_id);
422        debug_assert_eq!(metric, self.ip::<Ipv6>().get_routing_metric(device_id));
423        metric
424    }
425
426    /// Like [`DeviceIpApi::collect_assigned_ip_addr_subnets`], collecting
427    /// addresses for both IP versions.
428    pub fn for_each_assigned_ip_addr_subnet<F: FnMut(AddrSubnetEither)>(
429        &mut self,
430        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
431        mut f: F,
432    ) {
433        self.ip::<Ipv4>().for_each_assigned_ip_addr_subnet(device, |a| f(a.into()));
434        self.ip::<Ipv6>().for_each_assigned_ip_addr_subnet(device, |a| f(a.into()));
435    }
436
437    /// Like [`DeviceIpApi::get_assigned_ip_addr_subnets`], returning addresses
438    /// for both IP versions.
439    pub fn get_assigned_ip_addr_subnets(
440        &mut self,
441        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
442    ) -> Vec<AddrSubnetEither> {
443        let mut vec = Vec::new();
444        self.for_each_assigned_ip_addr_subnet(device, |a| vec.push(a));
445        vec
446    }
447}
448
449/// An AddrSubnet together with configuration specified for it when adding it
450/// to the stack.
451#[derive(Debug)]
452pub enum AddrSubnetAndManualConfigEither<Instant> {
453    /// Variant for an Ipv4 AddrSubnet.
454    V4(AddrSubnet<Ipv4Addr>, Ipv4AddrConfig<Instant>),
455    /// Variant for an Ipv6 AddrSubnet.
456    V6(AddrSubnet<Ipv6Addr>, Ipv6AddrManualConfig<Instant>),
457}
458
459impl<Inst: Instant> AddrSubnetAndManualConfigEither<Inst> {
460    /// Constructs an `AddrSubnetAndManualConfigEither`.
461    pub(crate) fn new<I: Ip + IpDeviceIpExt>(
462        addr_subnet: AddrSubnet<I::Addr>,
463        config: I::ManualAddressConfig<Inst>,
464    ) -> Self {
465        #[derive(GenericOverIp)]
466        #[generic_over_ip(I, Ip)]
467        struct AddrSubnetAndConfig<I: IpDeviceIpExt, Inst: Instant> {
468            addr_subnet: AddrSubnet<I::Addr>,
469            config: I::ManualAddressConfig<Inst>,
470        }
471
472        let result = I::map_ip_in(
473            AddrSubnetAndConfig { addr_subnet, config },
474            |AddrSubnetAndConfig { addr_subnet, config }| {
475                AddrSubnetAndManualConfigEither::V4(addr_subnet, config)
476            },
477            |AddrSubnetAndConfig { addr_subnet, config }| {
478                AddrSubnetAndManualConfigEither::V6(addr_subnet, config)
479            },
480        );
481        result
482    }
483
484    /// Extracts the `AddrSubnetEither`.
485    pub fn addr_subnet_either(&self) -> AddrSubnetEither {
486        match self {
487            Self::V4(addr_subnet, _) => AddrSubnetEither::V4(*addr_subnet),
488            Self::V6(addr_subnet, _) => AddrSubnetEither::V6(*addr_subnet),
489        }
490    }
491}
492
493impl<Inst: Instant> From<AddrSubnetEither> for AddrSubnetAndManualConfigEither<Inst> {
494    fn from(value: AddrSubnetEither) -> Self {
495        match value {
496            AddrSubnetEither::V4(addr_subnet) => {
497                AddrSubnetAndManualConfigEither::new::<Ipv4>(addr_subnet, Default::default())
498            }
499            AddrSubnetEither::V6(addr_subnet) => {
500                AddrSubnetAndManualConfigEither::new::<Ipv6>(addr_subnet, Default::default())
501            }
502        }
503    }
504}
505
506impl<Inst: Instant, I: IpAddress> From<AddrSubnet<I>> for AddrSubnetAndManualConfigEither<Inst> {
507    fn from(value: AddrSubnet<I>) -> Self {
508        AddrSubnetEither::from(value).into()
509    }
510}
511
512/// Errors that can be returned by the [`DeviceIpApiAny::add_ip_addr_subnet`]
513/// function.
514#[derive(Debug, Eq, PartialEq)]
515pub enum AddIpAddrSubnetError {
516    /// The address is already assigned to this device.
517    Exists,
518    /// The address is invalid and cannot be assigned to any device. For
519    /// example, an IPv4-mapped-IPv6 address.
520    InvalidAddr,
521}
522
523/// Error type for setting properties on IP addresses.
524#[derive(Error, Debug, PartialEq)]
525pub enum SetIpAddressPropertiesError {
526    /// The address we tried to set properties on was not found.
527    #[error(transparent)]
528    NotFound(#[from] NotFoundError),
529
530    /// We tried to set properties on a non-manually-configured address.
531    #[error("tried to set properties on a non-manually-configured address")]
532    NotManual,
533}