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, IpDeviceConfiguration, Ipv4AddrConfig, Ipv4AddressState,
29    Ipv6AddrConfig, Ipv6AddrManualConfig, Ipv6AddressState,
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_state_mut(device, &address_id, |address_state| {
222            #[derive(GenericOverIp)]
223            #[generic_over_ip(I, Ip)]
224            struct Wrap<'a, I: IpDeviceIpExt, II: Instant>(&'a mut I::AddressState<II>);
225            let CommonAddressProperties { valid_until, preferred_lifetime } = I::map_ip_in(
226                Wrap(address_state),
227                |Wrap(Ipv4AddressState { config })| {
228                    match config {
229                        Some(Ipv4AddrConfig { common }) => Ok(common),
230                        // Address is being removed, configuration has been
231                        // taken out.
232                        None => Err(NotFoundError.into()),
233                    }
234                },
235                |Wrap(Ipv6AddressState { flags: _, config })| {
236                    match config {
237                        // Address is being removed, configuration has been
238                        // taken out.
239                        None => Err(NotFoundError.into()),
240                        Some(Ipv6AddrConfig::Slaac(_)) => {
241                            Err(SetIpAddressPropertiesError::NotManual)
242                        }
243                        Some(Ipv6AddrConfig::Manual(Ipv6AddrManualConfig {
244                            common,
245                            temporary: _,
246                        })) => Ok(common),
247                    }
248                },
249            )?;
250
251            let CommonAddressProperties {
252                valid_until: next_valid_until,
253                preferred_lifetime: next_preferred_lifetime,
254            } = next_properties;
255            let mut changed = core::mem::replace(valid_until, next_valid_until) != next_valid_until;
256            changed |= core::mem::replace(preferred_lifetime, next_preferred_lifetime)
257                != next_preferred_lifetime;
258
259            if changed {
260                bindings_ctx.on_event(IpDeviceEvent::AddressPropertiesChanged {
261                    device: device.clone(),
262                    addr: address,
263                    valid_until: next_valid_until,
264                    preferred_lifetime: next_preferred_lifetime,
265                });
266            }
267            Ok(())
268        })
269    }
270
271    /// Calls `f` for each assigned IP address on the device.
272    pub fn for_each_assigned_ip_addr_subnet<F: FnMut(AddrSubnet<I::Addr>)>(
273        &mut self,
274        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
275        f: F,
276    ) {
277        self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
278            addrs
279                .filter_map(|addr| {
280                    let assigned = core_ctx.with_ip_address_state(device, &addr, |addr_state| {
281                        I::is_addr_assigned(addr_state)
282                    });
283                    assigned.then(|| addr.addr_sub().to_witness())
284                })
285                .for_each(f);
286        })
287    }
288
289    /// Shorthand for [`DeviceIpApi::Collect_assigned_ip_addr_subnets`],
290    /// returning the addresses in a `Vec`.
291    pub fn get_assigned_ip_addr_subnets(
292        &mut self,
293        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
294    ) -> Vec<AddrSubnet<I::Addr>> {
295        let mut vec = Vec::new();
296        self.for_each_assigned_ip_addr_subnet(device, |a| vec.push(a));
297        vec
298    }
299
300    /// Exports IP state for `device` into `inspector`.
301    pub fn inspect<N: Inspector>(
302        &mut self,
303        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
304        inspector: &mut N,
305    ) where
306        C::CoreContext: GmpStateContext<I, C::BindingsContext>,
307    {
308        inspector.record_child("Addresses", |inspector| {
309            self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
310                for addr in addrs {
311                    inspector.record_display_child(addr.addr_sub(), |inspector| {
312                        core_ctx.with_ip_address_state(device, &addr, |addr_state| {
313                            inspector.delegate_inspectable(addr_state)
314                        })
315                    });
316                }
317            })
318        });
319        inspector.record_child("Configuration", |inspector| {
320            self.core_ctx().with_ip_device_configuration(device, |config, _core_ctx| {
321                let IpDeviceConfiguration {
322                    gmp_enabled,
323                    unicast_forwarding_enabled,
324                    multicast_forwarding_enabled,
325                } = config.as_ref();
326                inspector.record_bool("GmpEnabled", *gmp_enabled);
327                inspector.record_bool("ForwardingEnabled", *unicast_forwarding_enabled);
328                inspector.record_bool("MulticastForwardingEnabled", *multicast_forwarding_enabled);
329            })
330        });
331        inspector.record_child("GMP", |inspector| {
332            self.core_ctx().with_gmp_state(device, |groups, gmp_state| {
333                inspector.record_inspectable_value("Mode", gmp_state.mode());
334                inspector.record_inspectable_value("Groups", groups);
335            })
336        })
337    }
338}
339/// The device IP API interacting with all IP versions.
340pub struct DeviceIpAnyApi<C>(C);
341
342impl<C> DeviceIpAnyApi<C> {
343    /// Creates a new API instance.
344    pub fn new(ctx: C) -> Self {
345        Self(ctx)
346    }
347}
348
349impl<C> DeviceIpAnyApi<C>
350where
351    C: ContextPair,
352    C::CoreContext: IpDeviceConfigurationContext<Ipv4, C::BindingsContext>
353        + IpDeviceConfigurationHandler<Ipv4, C::BindingsContext>
354        + IpRoutingDeviceContext<Ipv4>
355        + IpDeviceConfigurationContext<Ipv6, C::BindingsContext>
356        + IpDeviceConfigurationHandler<Ipv6, C::BindingsContext>
357        + IpRoutingDeviceContext<Ipv6>,
358    C::BindingsContext: IpDeviceBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
359        + IpDeviceBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
360{
361    fn ip<I: Ip>(&mut self) -> DeviceIpApi<I, &mut C> {
362        let Self(pair) = self;
363        DeviceIpApi::new(pair)
364    }
365
366    /// Like [`DeviceIpApi::add_ip_addr_subnet`].
367    pub fn add_ip_addr_subnet(
368        &mut self,
369        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
370        addr_sub_and_config: impl Into<
371            AddrSubnetAndManualConfigEither<<C::BindingsContext as InstantBindingsTypes>::Instant>,
372        >,
373    ) -> Result<(), AddIpAddrSubnetError> {
374        match addr_sub_and_config.into() {
375            AddrSubnetAndManualConfigEither::V4(addr_sub, config) => {
376                self.ip::<Ipv4>().add_ip_addr_subnet_with_config(device, addr_sub, config)
377            }
378            AddrSubnetAndManualConfigEither::V6(addr_sub, config) => {
379                self.ip::<Ipv6>().add_ip_addr_subnet_with_config(device, addr_sub, config)
380            }
381        }
382    }
383
384    /// Like [`DeviceIpApi::del_ip_addr`].
385    pub fn del_ip_addr(
386        &mut self,
387        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
388        addr: impl Into<SpecifiedAddr<IpAddr>>,
389    ) -> Result<
390        RemoveResourceResult<
391            AddrSubnetEither,
392            // NB: This is a bit of a mouthful, but we can't change the type of
393            // a ReferenceReceiver once created and it comes from deep inside
394            // core. The complexity should be contained here and this is simpler
395            // than making the ReferenceNotifiers trait fancier.
396            Either<
397                <C::BindingsContext as ReferenceNotifiers>::ReferenceReceiver<AddrSubnet<Ipv4Addr>>,
398                <C::BindingsContext as ReferenceNotifiers>::ReferenceReceiver<AddrSubnet<Ipv6Addr>>,
399            >,
400        >,
401        NotFoundError,
402    > {
403        let addr = addr.into();
404        match addr.into() {
405            IpAddr::V4(addr) => self
406                .ip::<Ipv4>()
407                .del_ip_addr(device, addr)
408                .map(|r| r.map_removed(Into::into).map_deferred(Either::Left)),
409            IpAddr::V6(addr) => self
410                .ip::<Ipv6>()
411                .del_ip_addr(device, addr)
412                .map(|r| r.map_removed(Into::into).map_deferred(Either::Right)),
413        }
414    }
415
416    /// Like [`DeviceIpApi::get_routing_metric`].
417    pub fn get_routing_metric(
418        &mut self,
419        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
420    ) -> RawMetric {
421        // NB: The routing metric is kept only once for both IP versions, debug
422        // assert that this is true, but return the v4 version otherwise.
423        let metric = self.ip::<Ipv4>().get_routing_metric(device_id);
424        debug_assert_eq!(metric, self.ip::<Ipv6>().get_routing_metric(device_id));
425        metric
426    }
427
428    /// Like [`DeviceIpApi::collect_assigned_ip_addr_subnets`], collecting
429    /// addresses for both IP versions.
430    pub fn for_each_assigned_ip_addr_subnet<F: FnMut(AddrSubnetEither)>(
431        &mut self,
432        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
433        mut f: F,
434    ) {
435        self.ip::<Ipv4>().for_each_assigned_ip_addr_subnet(device, |a| f(a.into()));
436        self.ip::<Ipv6>().for_each_assigned_ip_addr_subnet(device, |a| f(a.into()));
437    }
438
439    /// Like [`DeviceIpApi::get_assigned_ip_addr_subnets`], returning addresses
440    /// for both IP versions.
441    pub fn get_assigned_ip_addr_subnets(
442        &mut self,
443        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
444    ) -> Vec<AddrSubnetEither> {
445        let mut vec = Vec::new();
446        self.for_each_assigned_ip_addr_subnet(device, |a| vec.push(a));
447        vec
448    }
449}
450
451/// An AddrSubnet together with configuration specified for it when adding it
452/// to the stack.
453#[derive(Debug)]
454pub enum AddrSubnetAndManualConfigEither<Instant> {
455    /// Variant for an Ipv4 AddrSubnet.
456    V4(AddrSubnet<Ipv4Addr>, Ipv4AddrConfig<Instant>),
457    /// Variant for an Ipv6 AddrSubnet.
458    V6(AddrSubnet<Ipv6Addr>, Ipv6AddrManualConfig<Instant>),
459}
460
461impl<Inst: Instant> AddrSubnetAndManualConfigEither<Inst> {
462    /// Constructs an `AddrSubnetAndManualConfigEither`.
463    pub(crate) fn new<I: Ip + IpDeviceIpExt>(
464        addr_subnet: AddrSubnet<I::Addr>,
465        config: I::ManualAddressConfig<Inst>,
466    ) -> Self {
467        #[derive(GenericOverIp)]
468        #[generic_over_ip(I, Ip)]
469        struct AddrSubnetAndConfig<I: IpDeviceIpExt, Inst: Instant> {
470            addr_subnet: AddrSubnet<I::Addr>,
471            config: I::ManualAddressConfig<Inst>,
472        }
473
474        let result = I::map_ip_in(
475            AddrSubnetAndConfig { addr_subnet, config },
476            |AddrSubnetAndConfig { addr_subnet, config }| {
477                AddrSubnetAndManualConfigEither::V4(addr_subnet, config)
478            },
479            |AddrSubnetAndConfig { addr_subnet, config }| {
480                AddrSubnetAndManualConfigEither::V6(addr_subnet, config)
481            },
482        );
483        result
484    }
485
486    /// Extracts the `AddrSubnetEither`.
487    pub fn addr_subnet_either(&self) -> AddrSubnetEither {
488        match self {
489            Self::V4(addr_subnet, _) => AddrSubnetEither::V4(*addr_subnet),
490            Self::V6(addr_subnet, _) => AddrSubnetEither::V6(*addr_subnet),
491        }
492    }
493}
494
495impl<Inst: Instant> From<AddrSubnetEither> for AddrSubnetAndManualConfigEither<Inst> {
496    fn from(value: AddrSubnetEither) -> Self {
497        match value {
498            AddrSubnetEither::V4(addr_subnet) => {
499                AddrSubnetAndManualConfigEither::new::<Ipv4>(addr_subnet, Default::default())
500            }
501            AddrSubnetEither::V6(addr_subnet) => {
502                AddrSubnetAndManualConfigEither::new::<Ipv6>(addr_subnet, Default::default())
503            }
504        }
505    }
506}
507
508impl<Inst: Instant, I: IpAddress> From<AddrSubnet<I>> for AddrSubnetAndManualConfigEither<Inst> {
509    fn from(value: AddrSubnet<I>) -> Self {
510        AddrSubnetEither::from(value).into()
511    }
512}
513
514/// Errors that can be returned by the [`DeviceIpApiAny::add_ip_addr_subnet`]
515/// function.
516#[derive(Debug, Eq, PartialEq)]
517pub enum AddIpAddrSubnetError {
518    /// The address is already assigned to this device.
519    Exists,
520    /// The address is invalid and cannot be assigned to any device. For
521    /// example, an IPv4-mapped-IPv6 address.
522    InvalidAddr,
523}
524
525/// Error type for setting properties on IP addresses.
526#[derive(Error, Debug, PartialEq)]
527pub enum SetIpAddressPropertiesError {
528    /// The address we tried to set properties on was not found.
529    #[error(transparent)]
530    NotFound(#[from] NotFoundError),
531
532    /// We tried to set properties on a non-manually-configured address.
533    #[error("tried to set properties on a non-manually-configured address")]
534    NotManual,
535}