netstack3_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 layer api.
6
7use alloc::fmt::Debug;
8use core::marker::PhantomData;
9
10use log::debug;
11use net_types::ip::{Ipv4, Ipv6};
12use netstack3_base::{
13    AnyDevice, ContextPair, CoreTimerContext, Device, DeviceIdAnyCompatContext, DeviceIdContext,
14    Inspector, RecvFrameContext, ReferenceNotifiers, ReferenceNotifiersExt as _,
15    RemoveResourceResultWithContext, ResourceCounterContext, TimerContext,
16};
17use netstack3_ip::device::{
18    IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceTimerId,
19    Ipv6DeviceConfigurationContext,
20};
21use netstack3_ip::gmp::{IgmpCounters, MldCounters};
22use netstack3_ip::{self as ip, IpCounters, RawMetric};
23use packet::BufferMut;
24use ref_cast::RefCast;
25
26use crate::internal::base::{
27    DeviceCollectionContext, DeviceCounters, DeviceLayerStateTypes, DeviceLayerTypes,
28    DeviceReceiveFrameSpec, OriginTrackerContext,
29};
30use crate::internal::blackhole::BlackholeDevice;
31use crate::internal::config::{
32    ArpConfiguration, ArpConfigurationUpdate, DeviceConfiguration, DeviceConfigurationContext,
33    DeviceConfigurationUpdate, DeviceConfigurationUpdateError, NdpConfiguration,
34    NdpConfigurationUpdate,
35};
36use crate::internal::ethernet::EthernetLinkDevice;
37use crate::internal::id::{
38    BaseDeviceId, BasePrimaryDeviceId, BaseWeakDeviceId, DeviceId, DeviceProvider,
39    for_any_device_id,
40};
41use crate::internal::loopback::LoopbackDevice;
42use crate::internal::pure_ip::PureIpDevice;
43use crate::internal::state::{BaseDeviceState, DeviceStateSpec, IpLinkDeviceStateInner};
44
45/// Pending device configuration update.
46///
47/// This type is a witness for a valid [`DeviceConfigurationUpdate`] for some
48/// device ID `D` and is obtained through
49/// [`DeviceApi::new_configuration_update`].
50///
51/// The configuration is only applied when [`DeviceApi::apply_configuration`] is
52/// called.
53pub struct PendingDeviceConfigurationUpdate<'a, D>(DeviceConfigurationUpdate, &'a D);
54
55/// The device API.
56#[derive(RefCast)]
57#[repr(transparent)]
58pub struct DeviceApi<D, C>(C, PhantomData<D>);
59
60impl<D, C> DeviceApi<D, C> {
61    /// Creates a new [`DeviceApi`] from `ctx`.
62    pub fn new(ctx: C) -> Self {
63        Self(ctx, PhantomData)
64    }
65}
66
67impl<D, C> DeviceApi<D, C>
68where
69    D: Device + DeviceStateSpec + DeviceReceiveFrameSpec,
70    C: ContextPair,
71    C::CoreContext: DeviceApiCoreContext<D, C::BindingsContext>,
72    C::BindingsContext: DeviceApiBindingsContext,
73{
74    pub(crate) fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
75        let Self(pair, PhantomData) = self;
76        pair.contexts()
77    }
78
79    pub(crate) fn core_ctx(&mut self) -> &mut C::CoreContext {
80        let Self(pair, PhantomData) = self;
81        pair.core_ctx()
82    }
83
84    /// Adds a new device to the stack and returns its identifier.
85    ///
86    /// # Panics
87    ///
88    /// Panics if more than 1 loopback device is added to the stack.
89    pub fn add_device(
90        &mut self,
91        bindings_id: <C::BindingsContext as DeviceLayerStateTypes>::DeviceIdentifier,
92        properties: D::CreationProperties,
93        metric: RawMetric,
94        external_state: D::External<C::BindingsContext>,
95    ) -> <C::CoreContext as DeviceIdContext<D>>::DeviceId
96    where
97        C::CoreContext: DeviceApiIpLayerCoreContext<D, C::BindingsContext>,
98    {
99        debug!("adding {} device with {:?} metric:{metric}", D::DEBUG_TYPE, properties);
100        let (core_ctx, bindings_ctx) = self.contexts();
101        let origin = core_ctx.origin_tracker();
102        let primary = BasePrimaryDeviceId::new(
103            |weak_ref| {
104                let link = D::new_device_state::<C::CoreContext, _>(
105                    bindings_ctx,
106                    weak_ref.clone(),
107                    properties,
108                );
109                IpLinkDeviceStateInner::new::<_, C::CoreContext>(
110                    bindings_ctx,
111                    weak_ref.into(),
112                    link,
113                    metric,
114                    origin,
115                )
116            },
117            external_state,
118            bindings_id,
119        );
120        let id = primary.clone_strong();
121        core_ctx.insert(primary);
122        id
123    }
124
125    /// Like [`DeviceApi::add_device`] but using default values for
126    /// `bindings_id` and `external_state`.
127    ///
128    /// This is provided as a convenience method for tests with faked bindings
129    /// contexts that have simple implementations for bindings state.
130    #[cfg(any(test, feature = "testutils"))]
131    pub fn add_device_with_default_state(
132        &mut self,
133        properties: D::CreationProperties,
134        metric: RawMetric,
135    ) -> <C::CoreContext as DeviceIdContext<D>>::DeviceId
136    where
137        <C::BindingsContext as DeviceLayerStateTypes>::DeviceIdentifier: Default,
138        D::External<C::BindingsContext>: Default,
139        C::CoreContext: DeviceApiIpLayerCoreContext<D, C::BindingsContext>,
140    {
141        self.add_device(Default::default(), properties, metric, Default::default())
142    }
143
144    /// Removes `device` from the stack.
145    ///
146    /// If the return value is `RemoveDeviceResult::Removed` the device is
147    /// immediately removed from the stack, otherwise
148    /// `RemoveDeviceResult::Deferred` indicates that the device was marked for
149    /// destruction but there are still references to it. It carries a
150    /// `ReferenceReceiver` from the bindings context that can be awaited on
151    /// until removal is complete.
152    ///
153    /// # Panics
154    ///
155    /// Panics if the device is not currently in the stack.
156    pub fn remove_device(
157        &mut self,
158        device: BaseDeviceId<D, C::BindingsContext>,
159    ) -> RemoveResourceResultWithContext<D::External<C::BindingsContext>, C::BindingsContext>
160    where
161        // Required to call into IP layer for cleanup on removal:
162        BaseDeviceId<D, C::BindingsContext>: Into<DeviceId<C::BindingsContext>>,
163        C::CoreContext: IpDeviceConfigurationContext<Ipv4, C::BindingsContext>
164            + Ipv6DeviceConfigurationContext<C::BindingsContext>
165            + DeviceIdContext<AnyDevice, DeviceId = DeviceId<C::BindingsContext>>,
166        C::BindingsContext: IpDeviceBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>
167            + IpDeviceBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
168    {
169        // Start cleaning up the device by disabling IP state. This removes timers
170        // for the device that would otherwise hold references to defunct device
171        // state.
172        let (core_ctx, bindings_ctx) = self.contexts();
173        {
174            let device = device.clone().into();
175            ip::device::clear_ipv4_device_state(core_ctx, bindings_ctx, &device);
176            ip::device::clear_ipv6_device_state(core_ctx, bindings_ctx, &device);
177        };
178
179        debug!("removing {device:?}");
180        let primary = core_ctx.remove(&device).expect("tried to remove device not in stack");
181        assert_eq!(device, primary);
182        core::mem::drop(device);
183        C::BindingsContext::unwrap_or_notify_with_new_reference_notifier(
184            primary.into_inner(),
185            |state: BaseDeviceState<_, _>| state.external_state,
186        )
187    }
188
189    /// Receive a device layer frame from the network.
190    pub fn receive_frame<B: BufferMut + Debug>(
191        &mut self,
192        meta: D::FrameMetadata<BaseDeviceId<D, C::BindingsContext>>,
193        frame: B,
194    ) {
195        let (core_ctx, bindings_ctx) = self.contexts();
196        core_ctx.receive_frame(bindings_ctx, meta, frame)
197    }
198
199    /// Applies the configuration and returns a [`DeviceConfigurationUpdate`]
200    /// with the previous values for all configurations for all `Some` fields.
201    ///
202    /// Note that even if the previous value matched the requested value, it is
203    /// still populated in the returned `DeviceConfigurationUpdate`.
204    pub fn apply_configuration(
205        &mut self,
206        pending: PendingDeviceConfigurationUpdate<'_, BaseDeviceId<D, C::BindingsContext>>,
207    ) -> DeviceConfigurationUpdate {
208        let PendingDeviceConfigurationUpdate(DeviceConfigurationUpdate { arp, ndp }, device_id) =
209            pending;
210        let core_ctx = self.core_ctx();
211        let arp = core_ctx.with_nud_config_mut::<Ipv4, _, _>(device_id, move |device_config| {
212            let device_config = match device_config {
213                Some(c) => c,
214                None => {
215                    // Can't set ARP configuration if device doesn't support it,
216                    // this is validated when creating the
217                    // `PendingDeviceConfigurationUpdate`.
218                    assert!(arp.is_none());
219                    return None;
220                }
221            };
222            arp.map(|ArpConfigurationUpdate { nud }| {
223                let nud = nud.map(|config| config.apply_and_take_previous(device_config));
224                ArpConfigurationUpdate { nud }
225            })
226        });
227        let ndp = core_ctx.with_nud_config_mut::<Ipv6, _, _>(device_id, move |device_config| {
228            let device_config = match device_config {
229                Some(c) => c,
230                None => {
231                    // Can't set NDP configuration if device doesn't support it,
232                    // this is validated when creating the
233                    // `PendingDeviceConfigurationUpdate`.
234                    assert!(ndp.is_none());
235                    return None;
236                }
237            };
238            ndp.map(|NdpConfigurationUpdate { nud }| {
239                let nud = nud.map(|config| config.apply_and_take_previous(device_config));
240                NdpConfigurationUpdate { nud }
241            })
242        });
243        DeviceConfigurationUpdate { arp, ndp }
244    }
245
246    /// Creates a new device configuration update for the given device.
247    ///
248    /// This method only validates that `config` is valid for `device`.
249    /// [`DeviceApi::apply`] must be called to apply the configuration.
250    pub fn new_configuration_update<'a>(
251        &mut self,
252        device: &'a BaseDeviceId<D, C::BindingsContext>,
253        config: DeviceConfigurationUpdate,
254    ) -> Result<
255        PendingDeviceConfigurationUpdate<'a, BaseDeviceId<D, C::BindingsContext>>,
256        DeviceConfigurationUpdateError,
257    > {
258        let core_ctx = self.core_ctx();
259        let DeviceConfigurationUpdate { arp, ndp } = &config;
260        if arp.is_some() && core_ctx.with_nud_config::<Ipv4, _, _>(device, |c| c.is_none()) {
261            return Err(DeviceConfigurationUpdateError::ArpNotSupported);
262        }
263        if ndp.is_some() && core_ctx.with_nud_config::<Ipv6, _, _>(device, |c| c.is_none()) {
264            return Err(DeviceConfigurationUpdateError::NdpNotSupported);
265        }
266        Ok(PendingDeviceConfigurationUpdate(config, device))
267    }
268
269    /// Returns a snapshot of the given device's configuration.
270    pub fn get_configuration(
271        &mut self,
272        device: &BaseDeviceId<D, C::BindingsContext>,
273    ) -> DeviceConfiguration {
274        let core_ctx = self.core_ctx();
275        let arp = core_ctx
276            .with_nud_config::<Ipv4, _, _>(device, |config| config.cloned())
277            .map(|nud| ArpConfiguration { nud });
278        let ndp = core_ctx
279            .with_nud_config::<Ipv6, _, _>(device, |config| config.cloned())
280            .map(|nud| NdpConfiguration { nud });
281        DeviceConfiguration { arp, ndp }
282    }
283
284    /// Returns a borrow to the [`DeviceCounters`] structure for `device`.
285    pub fn get_counters<'a>(
286        &'a mut self,
287        device: &'a BaseDeviceId<D, C::BindingsContext>,
288    ) -> &'a DeviceCounters {
289        ResourceCounterContext::<_, DeviceCounters>::per_resource_counters(self.core_ctx(), device)
290    }
291
292    /// Exports state for `device` into `inspector`.
293    pub fn inspect<N: Inspector>(
294        &mut self,
295        device: &BaseDeviceId<D, C::BindingsContext>,
296        inspector: &mut N,
297    ) {
298        inspector.record_child("Counters", |inspector| {
299            inspector.delegate_inspectable(
300                ResourceCounterContext::<_, DeviceCounters>::per_resource_counters(
301                    self.core_ctx(),
302                    device,
303                ),
304            );
305            inspector.delegate_inspectable(
306                ResourceCounterContext::<_, D::Counters>::per_resource_counters(
307                    self.core_ctx(),
308                    device,
309                ),
310            );
311            inspector.record_child("IPv4", |inspector| {
312                inspector.delegate_inspectable(
313                    ResourceCounterContext::<_, IpCounters<Ipv4>>::per_resource_counters(
314                        self.core_ctx(),
315                        device,
316                    ),
317                )
318            });
319            inspector.record_child("IPv6", |inspector| {
320                inspector.delegate_inspectable(
321                    ResourceCounterContext::<_, IpCounters<Ipv6>>::per_resource_counters(
322                        self.core_ctx(),
323                        device,
324                    ),
325                )
326            });
327            inspector.record_child("IGMP", |inspector| {
328                inspector.delegate_inspectable(
329                    ResourceCounterContext::<_, IgmpCounters>::per_resource_counters(
330                        self.core_ctx(),
331                        device,
332                    ),
333                );
334            });
335            inspector.record_child("MLD", |inspector| {
336                inspector.delegate_inspectable(
337                    ResourceCounterContext::<_, MldCounters>::per_resource_counters(
338                        self.core_ctx(),
339                        device,
340                    ),
341                );
342            });
343        });
344    }
345}
346
347/// The device API interacting with any kind of supported device.
348#[repr(transparent)]
349pub struct DeviceAnyApi<C>(C);
350
351impl<C> DeviceAnyApi<C> {
352    /// Creates a new [`DeviceAnyApi`] from `ctx`.
353    pub fn new(ctx: C) -> Self {
354        Self(ctx)
355    }
356}
357
358impl<C> DeviceAnyApi<C>
359where
360    C: ContextPair,
361    C::CoreContext: DeviceApiCoreContext<EthernetLinkDevice, C::BindingsContext>
362        + DeviceApiCoreContext<LoopbackDevice, C::BindingsContext>
363        + DeviceApiCoreContext<PureIpDevice, C::BindingsContext>
364        + DeviceApiCoreContext<BlackholeDevice, C::BindingsContext>,
365    C::BindingsContext: DeviceApiBindingsContext,
366{
367    fn device<D>(&mut self) -> &mut DeviceApi<D, C> {
368        let Self(ctx) = self;
369        DeviceApi::ref_cast_mut(ctx)
370    }
371
372    /// Like [`DeviceApi::apply_configuration`] but for any device types.
373    pub fn apply_configuration(
374        &mut self,
375        pending: PendingDeviceConfigurationUpdate<'_, DeviceId<C::BindingsContext>>,
376    ) -> DeviceConfigurationUpdate {
377        let PendingDeviceConfigurationUpdate(config, device) = pending;
378        for_any_device_id!(DeviceId, device,
379            device => {
380                self.device().apply_configuration(PendingDeviceConfigurationUpdate(config, device))
381            }
382        )
383    }
384
385    /// Like [`DeviceApi::new_configuration_update`] but for any device
386    /// types.
387    pub fn new_configuration_update<'a>(
388        &mut self,
389        device: &'a DeviceId<C::BindingsContext>,
390        config: DeviceConfigurationUpdate,
391    ) -> Result<
392        PendingDeviceConfigurationUpdate<'a, DeviceId<C::BindingsContext>>,
393        DeviceConfigurationUpdateError,
394    > {
395        for_any_device_id!(DeviceId, device,
396            inner => {
397                self.device()
398                .new_configuration_update(inner, config)
399                .map(|PendingDeviceConfigurationUpdate(config, _)| {
400                    PendingDeviceConfigurationUpdate(config, device)
401                })
402            }
403        )
404    }
405
406    /// A shortcut for [`DeviceAnyApi::new_configuration_update`] followed by
407    /// [`DeviceAnyApi::apply_configuration`].
408    pub fn update_configuration(
409        &mut self,
410        device: &DeviceId<C::BindingsContext>,
411        config: DeviceConfigurationUpdate,
412    ) -> Result<DeviceConfigurationUpdate, DeviceConfigurationUpdateError> {
413        let pending = self.new_configuration_update(device, config)?;
414        Ok(self.apply_configuration(pending))
415    }
416
417    /// Like [`DeviceApi::get_configuration`] but for any device types.
418    pub fn get_configuration(
419        &mut self,
420        device: &DeviceId<C::BindingsContext>,
421    ) -> DeviceConfiguration {
422        for_any_device_id!(DeviceId, device,
423            device => self.device().get_configuration(device))
424    }
425
426    /// Like [`DeviceApi::get_counters`] but for any device types.
427    pub fn get_counters<'a>(
428        &'a mut self,
429        device: &'a DeviceId<C::BindingsContext>,
430    ) -> &'a DeviceCounters {
431        for_any_device_id!(DeviceId, device,
432            device => self.device().get_counters(device))
433    }
434
435    /// Like [`DeviceApi::inspect`] but for any device type.
436    pub fn inspect<N: Inspector>(
437        &mut self,
438        device: &DeviceId<C::BindingsContext>,
439        inspector: &mut N,
440    ) {
441        for_any_device_id!(DeviceId, DeviceProvider, D, device,
442            device => self.device::<D>().inspect(device, inspector))
443    }
444}
445
446/// A marker trait for all the core context traits required to fulfill the
447/// [`DeviceApi`].
448pub trait DeviceApiCoreContext<
449    D: Device + DeviceStateSpec + DeviceReceiveFrameSpec,
450    BC: DeviceApiBindingsContext,
451>:
452    DeviceIdContext<D, DeviceId = BaseDeviceId<D, BC>, WeakDeviceId = BaseWeakDeviceId<D, BC>>
453    + OriginTrackerContext
454    + DeviceCollectionContext<D, BC>
455    + DeviceConfigurationContext<D>
456    + RecvFrameContext<D::FrameMetadata<BaseDeviceId<D, BC>>, BC>
457    + ResourceCounterContext<Self::DeviceId, DeviceCounters>
458    + ResourceCounterContext<Self::DeviceId, D::Counters>
459    + ResourceCounterContext<Self::DeviceId, IpCounters<Ipv4>>
460    + ResourceCounterContext<Self::DeviceId, IpCounters<Ipv6>>
461    + ResourceCounterContext<Self::DeviceId, IgmpCounters>
462    + ResourceCounterContext<Self::DeviceId, MldCounters>
463    + CoreTimerContext<D::TimerId<Self::WeakDeviceId>, BC>
464{
465}
466
467impl<CC, D, BC> DeviceApiCoreContext<D, BC> for CC
468where
469    D: Device + DeviceStateSpec + DeviceReceiveFrameSpec,
470    BC: DeviceApiBindingsContext,
471    CC: DeviceIdContext<D, DeviceId = BaseDeviceId<D, BC>, WeakDeviceId = BaseWeakDeviceId<D, BC>>
472        + OriginTrackerContext
473        + DeviceCollectionContext<D, BC>
474        + DeviceConfigurationContext<D>
475        + RecvFrameContext<D::FrameMetadata<BaseDeviceId<D, BC>>, BC>
476        + ResourceCounterContext<Self::DeviceId, DeviceCounters>
477        + ResourceCounterContext<Self::DeviceId, D::Counters>
478        + ResourceCounterContext<Self::DeviceId, IpCounters<Ipv4>>
479        + ResourceCounterContext<Self::DeviceId, IpCounters<Ipv6>>
480        + ResourceCounterContext<Self::DeviceId, IgmpCounters>
481        + ResourceCounterContext<Self::DeviceId, MldCounters>
482        + CoreTimerContext<D::TimerId<Self::WeakDeviceId>, BC>,
483{
484}
485
486/// A marker trait for all the bindings context traits required to fulfill the
487/// [`DeviceApi`].
488pub trait DeviceApiBindingsContext: DeviceLayerTypes + ReferenceNotifiers + TimerContext {}
489
490impl<O> DeviceApiBindingsContext for O where O: DeviceLayerTypes + ReferenceNotifiers + TimerContext {}
491
492/// A marker trait with traits required to tie the device layer with the IP
493/// layer to fulfill [`DeviceApi`].
494pub trait DeviceApiIpLayerCoreContext<D: Device, BC: DeviceLayerTypes>:
495    DeviceIdAnyCompatContext<D>
496    + CoreTimerContext<
497        IpDeviceTimerId<Ipv6, <Self as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
498        BC,
499    > + CoreTimerContext<
500        IpDeviceTimerId<Ipv4, <Self as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
501        BC,
502    >
503{
504}
505
506impl<O, D, BC> DeviceApiIpLayerCoreContext<D, BC> for O
507where
508    D: Device,
509    BC: DeviceLayerTypes,
510    O: DeviceIdAnyCompatContext<D>
511        + CoreTimerContext<
512            IpDeviceTimerId<Ipv6, <Self as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
513            BC,
514        > + CoreTimerContext<
515            IpDeviceTimerId<Ipv4, <Self as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
516            BC,
517        >,
518{
519}