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    WithIpDeviceConfigurationMutInner,
35};
36use crate::internal::gmp::{GmpHandler as _, GmpStateContext};
37use crate::internal::routing::IpRoutingDeviceContext;
38use crate::internal::types::RawMetric;
39
40/// Provides an API for dealing with devices at the IP layer, aka interfaces.
41pub struct DeviceIpApi<I: Ip, C>(C, IpVersionMarker<I>);
42
43impl<I: Ip, C> DeviceIpApi<I, C> {
44    /// Creates a new API instance.
45    pub fn new(ctx: C) -> Self {
46        Self(ctx, IpVersionMarker::new())
47    }
48}
49
50impl<I, C> DeviceIpApi<I, C>
51where
52    I: IpDeviceIpExt,
53    C: ContextPair,
54    C::CoreContext: IpDeviceConfigurationContext<I, C::BindingsContext>
55        + IpDeviceConfigurationHandler<I, C::BindingsContext>
56        + IpRoutingDeviceContext<I>,
57    C::BindingsContext:
58        IpDeviceBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
59{
60    fn core_ctx(&mut self) -> &mut C::CoreContext {
61        let Self(pair, IpVersionMarker { .. }) = self;
62        pair.core_ctx()
63    }
64
65    fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
66        let Self(pair, IpVersionMarker { .. }) = self;
67        pair.contexts()
68    }
69
70    /// Like [`DeviceIpApi::add_ip_addr_subnet_with_config`] with a default
71    /// address configuration.
72    pub fn add_ip_addr_subnet(
73        &mut self,
74        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
75        addr_subnet: AddrSubnet<I::Addr>,
76    ) -> Result<(), AddIpAddrSubnetError> {
77        self.add_ip_addr_subnet_with_config(device, addr_subnet, Default::default())
78    }
79
80    /// Adds an IP address and associated subnet to this device.
81    ///
82    /// If Duplicate Address Detection (DAD) is enabled, begins performing DAD.
83    ///
84    /// For IPv6, this function also joins the solicited-node multicast group.
85    pub fn add_ip_addr_subnet_with_config(
86        &mut self,
87        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
88        addr_subnet: AddrSubnet<I::Addr>,
89        addr_config: I::ManualAddressConfig<<C::BindingsContext as InstantBindingsTypes>::Instant>,
90    ) -> Result<(), AddIpAddrSubnetError> {
91        let addr_subnet = addr_subnet
92            .replace_witness::<I::AssignedWitness>()
93            .ok_or(AddIpAddrSubnetError::InvalidAddr)?;
94        if !device.is_loopback() && I::LOOPBACK_SUBNET.contains(&addr_subnet.addr().get()) {
95            return Err(AddIpAddrSubnetError::InvalidAddr);
96        }
97        let (core_ctx, bindings_ctx) = self.contexts();
98        core_ctx.with_ip_device_configuration_mut(device, |mut core_ctx| {
99            let (config, mut core_ctx) = core_ctx.ip_device_configuration_and_ctx();
100            device::add_ip_addr_subnet_with_config(
101                &mut core_ctx,
102                bindings_ctx,
103                device,
104                addr_subnet,
105                addr_config.into(),
106                config,
107            )
108            .map(|_address_id| ())
109            .map_err(|ExistsError| AddIpAddrSubnetError::Exists)
110        })
111    }
112
113    /// Delete an IP address on a device.
114    pub fn del_ip_addr(
115        &mut self,
116        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
117        addr: SpecifiedAddr<I::Addr>,
118    ) -> Result<
119        RemoveResourceResultWithContext<AddrSubnet<I::Addr>, C::BindingsContext>,
120        NotFoundError,
121    > {
122        let (core_ctx, bindings_ctx) = self.contexts();
123        device::del_ip_addr(
124            core_ctx,
125            bindings_ctx,
126            device,
127            DelIpAddr::SpecifiedAddr(addr),
128            AddressRemovedReason::Manual,
129        )
130    }
131
132    /// Updates the IP configuration for a device.
133    ///
134    /// Each field in [`Ipv4DeviceConfigurationUpdate`] or
135    /// [`Ipv6DeviceConfigurationUpdate`] represents an optionally updateable
136    /// configuration. If the field has a `Some(_)` value, then an attempt will
137    /// be made to update that configuration on the device. A `None` value
138    /// indicates that an update for the configuration is not requested.
139    ///
140    /// Note that some fields have the type `Option<Option<T>>`. In this case,
141    /// as long as the outer `Option` is `Some`, then an attempt will be made to
142    /// update the configuration.
143    ///
144    /// This function returns a [`PendingDeviceConfigurationUpdate`] which is
145    /// validated and [`DeviceIpApi::apply`] can be called to apply the
146    /// configuration.
147    pub fn new_configuration_update<'a>(
148        &mut self,
149        device_id: &'a <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
150        config: I::ConfigurationUpdate,
151    ) -> Result<
152        PendingIpDeviceConfigurationUpdate<
153            'a,
154            I,
155            <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
156        >,
157        UpdateIpConfigurationError,
158    > {
159        PendingIpDeviceConfigurationUpdate::new(config, device_id)
160    }
161
162    /// Applies a pre-validated pending configuration to the device.
163    ///
164    /// Returns a configuration update with the previous value for all the
165    /// requested fields in `config`.
166    pub fn apply_configuration(
167        &mut self,
168        config: PendingIpDeviceConfigurationUpdate<
169            '_,
170            I,
171            <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
172        >,
173    ) -> I::ConfigurationUpdate {
174        let (core_ctx, bindings_ctx) = self.contexts();
175        IpDeviceConfigurationHandler::apply_configuration(core_ctx, bindings_ctx, config)
176    }
177
178    /// A shortcut for [`DeviceIpApi::new_configuration_update`] followed by
179    /// [`DeviceIpApi::apply_configuration`].
180    pub fn update_configuration(
181        &mut self,
182        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
183        config: I::ConfigurationUpdate,
184    ) -> Result<I::ConfigurationUpdate, UpdateIpConfigurationError> {
185        let pending = self.new_configuration_update(device_id, config)?;
186        Ok(self.apply_configuration(pending))
187    }
188
189    /// Gets the IP configuration and flags for a `device_id`.
190    pub fn get_configuration(
191        &mut self,
192        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
193    ) -> IpDeviceConfigurationAndFlags<I> {
194        self.core_ctx().with_ip_device_configuration(device_id, |config, mut core_ctx| {
195            IpDeviceConfigurationAndFlags {
196                config: config.clone(),
197                flags: core_ctx.with_ip_device_flags(device_id, |flags| flags.clone()),
198                gmp_mode: core_ctx.gmp_get_mode(device_id),
199            }
200        })
201    }
202
203    /// Gets the routing metric for the device.
204    pub fn get_routing_metric(
205        &mut self,
206        device_id: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
207    ) -> RawMetric {
208        self.core_ctx().get_routing_metric(device_id)
209    }
210
211    /// Sets properties on an IP address.
212    pub fn set_addr_properties(
213        &mut self,
214        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
215        address: SpecifiedAddr<I::Addr>,
216        next_properties: CommonAddressProperties<
217            <C::BindingsContext as InstantBindingsTypes>::Instant,
218        >,
219    ) -> Result<(), SetIpAddressPropertiesError> {
220        trace!("set_ip_addr_properties: setting {:?} for addr={:?}", next_properties, address);
221        let (core_ctx, bindings_ctx) = self.contexts();
222        let address_id = core_ctx.get_address_id(device, address)?;
223        core_ctx.with_ip_address_data_mut(device, &address_id, |address_state| {
224            let IpAddressData { flags: _, config } = address_state;
225            let Some(config) = config else {
226                // Address is being removed, configuration has been
227                // taken out.
228                return Err(NotFoundError.into());
229            };
230
231            #[derive(GenericOverIp)]
232            #[generic_over_ip(I, Ip)]
233            struct Wrap<'a, I: IpDeviceIpExt, Inst: Instant>(&'a mut I::AddressConfig<Inst>);
234            let CommonAddressProperties { valid_until, preferred_lifetime } = I::map_ip_in(
235                Wrap(config),
236                |Wrap(Ipv4AddrConfig { config: _, properties })| Ok(properties),
237                |Wrap(config)| match config {
238                    Ipv6AddrConfig::Slaac(_) => Err(SetIpAddressPropertiesError::NotManual),
239                    Ipv6AddrConfig::Manual(Ipv6AddrManualConfig {
240                        config: _,
241                        properties,
242                        temporary: _,
243                    }) => Ok(properties),
244                },
245            )?;
246
247            let CommonAddressProperties {
248                valid_until: next_valid_until,
249                preferred_lifetime: next_preferred_lifetime,
250            } = next_properties;
251            let mut changed = core::mem::replace(valid_until, next_valid_until) != next_valid_until;
252            changed |= core::mem::replace(preferred_lifetime, next_preferred_lifetime)
253                != next_preferred_lifetime;
254
255            if changed {
256                bindings_ctx.on_event(IpDeviceEvent::AddressPropertiesChanged {
257                    device: device.clone(),
258                    addr: address,
259                    valid_until: next_valid_until,
260                    preferred_lifetime: next_preferred_lifetime,
261                });
262            }
263            Ok(())
264        })
265    }
266
267    /// Calls `f` for each assigned IP address on the device.
268    pub fn for_each_assigned_ip_addr_subnet<F: FnMut(AddrSubnet<I::Addr>)>(
269        &mut self,
270        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
271        f: F,
272    ) {
273        self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
274            addrs
275                .filter_map(|addr| {
276                    let assigned = core_ctx.with_ip_address_data(device, &addr, |addr_data| {
277                        let IpAddressData { flags: IpAddressFlags { assigned }, config: _ } =
278                            addr_data;
279                        *assigned
280                    });
281                    assigned.then(|| addr.addr_sub().to_witness())
282                })
283                .for_each(f);
284        })
285    }
286
287    /// Shorthand for [`DeviceIpApi::Collect_assigned_ip_addr_subnets`],
288    /// returning the addresses in a `Vec`.
289    pub fn get_assigned_ip_addr_subnets(
290        &mut self,
291        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
292    ) -> Vec<AddrSubnet<I::Addr>> {
293        let mut vec = Vec::new();
294        self.for_each_assigned_ip_addr_subnet(device, |a| vec.push(a));
295        vec
296    }
297
298    /// Exports IP state for `device` into `inspector`.
299    pub fn inspect<N: Inspector>(
300        &mut self,
301        device: &<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
302        inspector: &mut N,
303    ) where
304        C::CoreContext: GmpStateContext<I, C::BindingsContext>,
305    {
306        inspector.record_child("Addresses", |inspector| {
307            self.core_ctx().with_address_ids(device, |addrs, core_ctx| {
308                for addr in addrs {
309                    inspector.record_display_child(addr.addr_sub(), |inspector| {
310                        core_ctx.with_ip_address_data(device, &addr, |addr_state| {
311                            inspector.delegate_inspectable(addr_state)
312                        })
313                    });
314                }
315            })
316        });
317        inspector.record_child("Configuration", |inspector| {
318            self.core_ctx().with_ip_device_configuration(device, |config, _core_ctx| {
319                let IpDeviceConfiguration {
320                    gmp_enabled,
321                    unicast_forwarding_enabled,
322                    multicast_forwarding_enabled,
323                    dad_transmits,
324                } = config.as_ref();
325                inspector.record_bool("GmpEnabled", *gmp_enabled);
326                inspector.record_bool("ForwardingEnabled", *unicast_forwarding_enabled);
327                inspector.record_bool("MulticastForwardingEnabled", *multicast_forwarding_enabled);
328                inspector.record_uint("DadTransmits", dad_transmits.map(|t| t.get()).unwrap_or(0));
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}