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