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