Skip to main content

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