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