Skip to main content

netstack3_device/
loopback.rs

1// Copyright 2022 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//! The loopback device.
6
7use alloc::vec::Vec;
8use core::fmt::Debug;
9use derivative::Derivative;
10
11use lock_order::lock::{OrderedLockAccess, OrderedLockRef};
12use log::trace;
13use net_types::ethernet::Mac;
14use net_types::ip::{Ipv4, Ipv6, Mtu};
15use netstack3_base::sync::{BindingsSyncTypes, Mutex};
16use netstack3_base::{
17    AnyDevice, BroadcastIpExt, ChecksumOffloadSpec, ChecksumRxOffloading, CoreTimerContext, Device,
18    DeviceIdAnyCompatContext, DeviceIdContext, FrameDestination, NetworkParsingContext,
19    NetworkSerializer, PacketType, RecvFrameContext, RecvIpFrameMeta, ResourceCounterContext,
20    SendFrameError, SendFrameErrorReason, SendableFrameMeta, StrongDeviceIdentifier, TimerContext,
21    TxMetadataBindingsTypes, WeakDeviceIdentifier,
22};
23use netstack3_ip::{DeviceIpLayerMetadata, IpCounters, IpPacketDestination};
24use packet::{Buf, Buffer as _, BufferMut, FragmentedBuffer as _, NestablePacketBuilder as _};
25use packet_formats::ethernet::{
26    EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt,
27};
28
29use crate::internal::base::{
30    DeviceCounters, DeviceLayerTypes, DeviceReceiveFrameSpec, EthernetDeviceCounters,
31};
32use crate::internal::id::{BaseDeviceId, BasePrimaryDeviceId, BaseWeakDeviceId, WeakDeviceId};
33use crate::internal::queue::rx::{
34    ReceiveDequeFrameContext, ReceiveQueue, ReceiveQueueState, ReceiveQueueTypes,
35};
36use crate::internal::queue::tx::{
37    BufVecU8Allocator, TransmitQueue, TransmitQueueHandler, TransmitQueueState,
38    TxQueuePacketMetadataCommon,
39};
40use crate::internal::queue::{DequeueState, DeviceBufferSpec, TransmitQueueFrameError};
41use crate::internal::socket::{
42    DeviceSocketHandler, DeviceSocketMetadata, DeviceSocketSendTypes, EthernetHeaderParams,
43    ReceivedFrame,
44};
45use crate::internal::state::{DeviceStateSpec, IpLinkDeviceState};
46
47/// The MAC address corresponding to the loopback interface.
48const LOOPBACK_MAC: Mac = Mac::UNSPECIFIED;
49
50/// A weak device ID identifying a loopback device.
51///
52/// This device ID is like [`WeakDeviceId`] but specifically for loopback
53/// devices.
54///
55/// [`WeakDeviceId`]: crate::device::WeakDeviceId
56pub type LoopbackWeakDeviceId<BT> = BaseWeakDeviceId<LoopbackDevice, BT>;
57
58/// A strong device ID identifying a loopback device.
59///
60/// This device ID is like [`DeviceId`] but specifically for loopback devices.
61///
62/// [`DeviceId`]: crate::device::DeviceId
63pub type LoopbackDeviceId<BT> = BaseDeviceId<LoopbackDevice, BT>;
64
65/// The primary reference for a loopback device.
66pub type LoopbackPrimaryDeviceId<BT> = BasePrimaryDeviceId<LoopbackDevice, BT>;
67
68/// Loopback device domain.
69#[derive(Copy, Clone)]
70pub enum LoopbackDevice {}
71
72impl Device for LoopbackDevice {}
73
74impl<BT> DeviceBufferSpec<BT> for LoopbackDevice {
75    type TxBuffer = Buf<Vec<u8>>;
76    type TxAllocator = BufVecU8Allocator;
77}
78
79impl DeviceStateSpec for LoopbackDevice {
80    type State<BT: DeviceLayerTypes> = LoopbackDeviceState<WeakDeviceId<BT>, BT>;
81    type External<BT: DeviceLayerTypes> = BT::LoopbackDeviceState;
82    type CreationProperties = LoopbackCreationProperties;
83    type Counters = EthernetDeviceCounters;
84    type TimerId<D: WeakDeviceIdentifier> = !;
85
86    fn new_device_state<
87        CC: CoreTimerContext<Self::TimerId<CC::WeakDeviceId>, BC> + DeviceIdContext<Self>,
88        BC: DeviceLayerTypes + TimerContext,
89    >(
90        _bindings_ctx: &mut BC,
91        _self_id: CC::WeakDeviceId,
92        LoopbackCreationProperties { mtu }: Self::CreationProperties,
93        tx_allocator: <Self as DeviceBufferSpec<BC>>::TxAllocator,
94    ) -> Self::State<BC>
95    where
96        Self: DeviceBufferSpec<BC>,
97    {
98        LoopbackDeviceState {
99            counters: Default::default(),
100            mtu,
101            rx_queue: Default::default(),
102            tx_queue: TransmitQueue::new(tx_allocator, ChecksumOffloadSpec::generic()),
103        }
104    }
105
106    const IS_LOOPBACK: bool = true;
107    const DEBUG_TYPE: &'static str = "Loopback";
108
109    fn tx_offload_spec<BT: DeviceLayerTypes>(
110        state: &Self::State<BT>,
111    ) -> Option<ChecksumOffloadSpec> {
112        Some(state.tx_queue.tx_offload_spec())
113    }
114}
115
116/// Properties used to create a loopback device.
117#[derive(Debug)]
118pub struct LoopbackCreationProperties {
119    /// The device's MTU.
120    pub mtu: Mtu,
121}
122
123/// State for a loopback device.
124pub struct LoopbackDeviceState<
125    D: WeakDeviceIdentifier,
126    BT: TxMetadataBindingsTypes + BindingsSyncTypes,
127> {
128    /// Loopback device counters.
129    pub counters: EthernetDeviceCounters,
130    /// The MTU this device was created with (immutable).
131    pub mtu: Mtu,
132    /// Loopback device receive queue.
133    pub rx_queue: ReceiveQueue<LoopbackRxQueueMeta<D, BT>, Buf<Vec<u8>>, BT>,
134    /// Loopback device transmit queue.
135    pub tx_queue: TransmitQueue<
136        LoopbackTxQueueMeta<D, BT>,
137        <LoopbackDevice as DeviceBufferSpec<BT>>::TxBuffer,
138        <LoopbackDevice as DeviceBufferSpec<BT>>::TxAllocator,
139        BT,
140    >,
141}
142
143/// Metadata associated with a frame in the Loopback TX queue.
144pub struct LoopbackTxQueueMeta<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> {
145    /// Device that should be used to deliver the packet. If not set then the
146    /// packet delivered as if it came from the loopback device.
147    target_device: Option<D>,
148    /// Metadata that is produced and consumed by the IP layer but which traverses
149    /// the device layer through the loopback device.
150    ip_layer_metadata: DeviceIpLayerMetadata<BT>,
151}
152
153impl<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> TxQueuePacketMetadataCommon
154    for LoopbackTxQueueMeta<D, BT>
155{
156    fn set_checksum_offload_result(
157        &mut self,
158        _result: Option<netstack3_base::ChecksumOffloadResult>,
159    ) {
160        // Loopback doesn't need checksum offload result because it skips
161        // verification on receive.
162    }
163}
164
165/// Metadata associated with a frame in the Loopback RX queue.
166#[derive(Derivative)]
167#[derivative(Debug(bound = ""))]
168pub struct LoopbackRxQueueMeta<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> {
169    /// Device that should be used to deliver the packet. If not set then the
170    /// packet delivered as if it came from the loopback device.
171    target_device: Option<D>,
172    /// Metadata that is produced and consumed by the IP layer but which traverses
173    /// the device layer through the loopback device.
174    ip_layer_metadata: DeviceIpLayerMetadata<BT>,
175}
176
177impl<D: WeakDeviceIdentifier, BT: TxMetadataBindingsTypes> From<LoopbackTxQueueMeta<D, BT>>
178    for LoopbackRxQueueMeta<D, BT>
179{
180    fn from(
181        LoopbackTxQueueMeta { target_device, ip_layer_metadata }: LoopbackTxQueueMeta<D, BT>,
182    ) -> Self {
183        Self { target_device, ip_layer_metadata }
184    }
185}
186
187impl<BT: DeviceLayerTypes>
188    OrderedLockAccess<ReceiveQueueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
189    for IpLinkDeviceState<LoopbackDevice, BT>
190{
191    type Lock = Mutex<
192        ReceiveQueueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>,
193        BT::Mutex,
194    >;
195    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
196        OrderedLockRef::new(&self.link.rx_queue.queue)
197    }
198}
199
200impl<BT: DeviceLayerTypes>
201    OrderedLockAccess<DequeueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
202    for IpLinkDeviceState<LoopbackDevice, BT>
203{
204    type Lock =
205        Mutex<DequeueState<LoopbackRxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>, BT::Mutex>;
206    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
207        OrderedLockRef::new(&self.link.rx_queue.deque)
208    }
209}
210
211impl<BT: DeviceLayerTypes>
212    OrderedLockAccess<
213        TransmitQueueState<
214            LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>,
215            Buf<Vec<u8>>,
216            BufVecU8Allocator,
217        >,
218    > for IpLinkDeviceState<LoopbackDevice, BT>
219{
220    type Lock = Mutex<
221        TransmitQueueState<
222            LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>,
223            Buf<Vec<u8>>,
224            BufVecU8Allocator,
225        >,
226        BT::Mutex,
227    >;
228    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
229        OrderedLockRef::new(&self.link.tx_queue.queue)
230    }
231}
232
233impl<BT: DeviceLayerTypes>
234    OrderedLockAccess<DequeueState<LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>>
235    for IpLinkDeviceState<LoopbackDevice, BT>
236{
237    type Lock =
238        Mutex<DequeueState<LoopbackTxQueueMeta<WeakDeviceId<BT>, BT>, Buf<Vec<u8>>>, BT::Mutex>;
239    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
240        OrderedLockRef::new(&self.link.tx_queue.deque)
241    }
242}
243
244impl DeviceSocketSendTypes for LoopbackDevice {
245    /// When `None`, data will be sent as a raw Ethernet frame without any
246    /// system-applied headers.
247    type Metadata = Option<EthernetHeaderParams>;
248}
249
250impl<CC, BC> ReceiveDequeFrameContext<LoopbackDevice, BC> for CC
251where
252    CC: DeviceIdContext<LoopbackDevice>
253        + ResourceCounterContext<Self::DeviceId, EthernetDeviceCounters>
254        + ReceiveQueueTypes<
255            LoopbackDevice,
256            BC,
257            Meta = LoopbackRxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
258        >,
259    // Loopback needs to deliver messages to `AnyDevice`.
260    CC: DeviceIdAnyCompatContext<LoopbackDevice>
261        + RecvFrameContext<
262            RecvIpFrameMeta<
263                <CC as DeviceIdContext<AnyDevice>>::DeviceId,
264                DeviceIpLayerMetadata<BC>,
265                Ipv4,
266            >,
267            BC,
268        > + RecvFrameContext<
269            RecvIpFrameMeta<
270                <CC as DeviceIdContext<AnyDevice>>::DeviceId,
271                DeviceIpLayerMetadata<BC>,
272                Ipv6,
273            >,
274            BC,
275        > + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, DeviceCounters>
276        + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, IpCounters<Ipv4>>
277        + ResourceCounterContext<<CC as DeviceIdContext<AnyDevice>>::DeviceId, IpCounters<Ipv6>>
278        + DeviceSocketHandler<AnyDevice, BC>,
279    CC::Buffer: BufferMut + Debug,
280    BC: DeviceLayerTypes,
281{
282    fn handle_frame(
283        &mut self,
284        bindings_ctx: &mut BC,
285        device_id: &Self::DeviceId,
286        rx_meta: Self::Meta,
287        mut buf: Self::Buffer,
288    ) {
289        // NOTE: At the time of writing, netdevice_worker in bindings uses a
290        // Buf<&mut [u8]> to feed receive frames into core. Matching the same
291        // type as bindings gives us the benefit of not generating rx path code
292        // twice.
293        //
294        // TODO(https://fxbug.dev/42051635): This might no longer be true when
295        // we revisit owned netdevice buffers, at which point we must consider
296        // the binary size tradeoff here.
297        let mut buf = Buf::new(buf.as_mut(), ..);
298        let buflen = buf.len();
299
300        let (frame, whole_body) =
301            match buf.parse_with_view::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck) {
302                Err(e) => {
303                    self.increment_both(&device_id.clone().into(), |counters: &DeviceCounters| {
304                        &counters.recv_parse_error
305                    });
306                    trace!("dropping invalid ethernet frame over loopback: {:?}", e);
307                    return;
308                }
309                Ok(e) => e,
310            };
311
312        let LoopbackRxQueueMeta { target_device, mut ip_layer_metadata } = rx_meta;
313        let target_device: <CC as DeviceIdContext<AnyDevice>>::DeviceId =
314            match target_device.map(|d| d.upgrade()) {
315                // This is a packet that should be delivered on `target_device`.
316                Some(Some(dev)) => dev,
317
318                // `target_device` is gone. Drop the packet.
319                Some(None) => return,
320
321                // This is a packet sent to the loopback device.
322                None => device_id.clone().into(),
323            };
324
325        self.add_both_usize(&target_device, buflen, |counters: &DeviceCounters| {
326            &counters.recv_bytes
327        });
328        self.increment_both(&target_device, |counters: &DeviceCounters| &counters.recv_frame);
329
330        let frame_dest = FrameDestination::from_dest(frame.dst_mac(), Mac::UNSPECIFIED);
331        let ethertype = frame.ethertype();
332
333        let packet_type = match ip_layer_metadata.packet_type() {
334            PacketType::Loopback => PacketType::Loopback,
335            _ => PacketType::from(frame_dest),
336        };
337        ip_layer_metadata.set_packet_type(packet_type);
338
339        DeviceSocketHandler::<AnyDevice, _>::handle_frame(
340            self,
341            bindings_ctx,
342            &target_device,
343            ReceivedFrame::from_ethernet(frame, frame_dest, packet_type, /*gso_info=*/ None).into(),
344            whole_body,
345        );
346
347        match ethertype {
348            Some(EtherType::Ipv4) => {
349                let local_frame_dst = match frame_dest.check_local() {
350                    Some(dst) => dst,
351                    None => {
352                        self.increment_both(&target_device, |counters: &IpCounters<Ipv4>| {
353                            &counters.drop_ip_packet_other_host
354                        });
355                        return;
356                    }
357                };
358                self.increment_both(&target_device, |counters: &DeviceCounters| {
359                    &counters.recv_ipv4_delivered
360                });
361                self.receive_frame(
362                    bindings_ctx,
363                    RecvIpFrameMeta::<_, _, Ipv4>::new(
364                        target_device,
365                        Some(local_frame_dst),
366                        ip_layer_metadata,
367                        NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded),
368                        None,
369                    ),
370                    buf,
371                );
372            }
373            Some(EtherType::Ipv6) => {
374                let local_frame_dst = match frame_dest.check_local() {
375                    Some(dst) => dst,
376                    None => {
377                        self.increment_both(&target_device, |counters: &IpCounters<Ipv6>| {
378                            &counters.drop_ip_packet_other_host
379                        });
380                        return;
381                    }
382                };
383                self.increment_both(&target_device, |counters: &DeviceCounters| {
384                    &counters.recv_ipv6_delivered
385                });
386                self.receive_frame(
387                    bindings_ctx,
388                    RecvIpFrameMeta::<_, _, Ipv6>::new(
389                        target_device,
390                        Some(local_frame_dst),
391                        ip_layer_metadata,
392                        NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded),
393                        None,
394                    ),
395                    buf,
396                );
397            }
398            Some(ethertype @ (EtherType::Arp | EtherType::Other(_))) => {
399                self.increment_both(device_id, |counters: &EthernetDeviceCounters| {
400                    &counters.recv_unsupported_ethertype
401                });
402                trace!("not handling loopback frame of type {:?}", ethertype)
403            }
404            None => {
405                self.increment_both(device_id, |counters: &EthernetDeviceCounters| {
406                    &counters.recv_no_ethertype
407                });
408                trace!("dropping ethernet frame without ethertype");
409            }
410        }
411    }
412}
413
414impl<CC, BC> SendableFrameMeta<CC, BC> for DeviceSocketMetadata<LoopbackDevice, CC, BC>
415where
416    CC: TransmitQueueHandler<
417            LoopbackDevice,
418            BC,
419            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
420        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
421        + DeviceIdContext<AnyDevice>,
422    BC: DeviceLayerTypes,
423{
424    fn send_meta<S>(
425        self,
426        core_ctx: &mut CC,
427        bindings_ctx: &mut BC,
428        body: S,
429    ) -> Result<(), SendFrameError<S>>
430    where
431        S: NetworkSerializer,
432        S::Buffer: BufferMut,
433    {
434        let Self { device_id, metadata, ip_layer_metadata } = self;
435        let tx_meta = LoopbackTxQueueMeta { target_device: None, ip_layer_metadata };
436        match metadata {
437            Some(EthernetHeaderParams { dest_addr, protocol }) => send_as_ethernet_frame_to_dst(
438                core_ctx,
439                bindings_ctx,
440                &device_id,
441                body,
442                protocol,
443                dest_addr,
444                tx_meta,
445            ),
446            None => send_ethernet_frame(core_ctx, bindings_ctx, &device_id, body, tx_meta),
447        }
448    }
449}
450
451/// Sends an IP frame `packet` over `device_id`.
452pub fn send_ip_frame<CC, BC, I, S>(
453    core_ctx: &mut CC,
454    bindings_ctx: &mut BC,
455    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
456    destination: IpPacketDestination<I, &<CC as DeviceIdContext<AnyDevice>>::DeviceId>,
457    ip_layer_metadata: DeviceIpLayerMetadata<BC>,
458    packet: S,
459) -> Result<(), SendFrameError<S>>
460where
461    CC: TransmitQueueHandler<
462            LoopbackDevice,
463            BC,
464            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
465        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
466        + DeviceIdContext<AnyDevice>,
467    BC: DeviceLayerTypes,
468    I: EthernetIpExt + BroadcastIpExt,
469    S: NetworkSerializer,
470    S::Buffer: BufferMut,
471{
472    core_ctx.increment_both(device_id, DeviceCounters::send_frame::<I>);
473
474    let (target_device, dst_mac) = match destination {
475        IpPacketDestination::Loopback(device) => (Some(device.downgrade()), LOOPBACK_MAC),
476        IpPacketDestination::Broadcast(_marker) => (None, Mac::BROADCAST),
477        IpPacketDestination::Multicast(multicast_ip) => (None, Mac::from(&multicast_ip)),
478        IpPacketDestination::Neighbor(_) => (None, LOOPBACK_MAC),
479    };
480    send_as_ethernet_frame_to_dst(
481        core_ctx,
482        bindings_ctx,
483        device_id,
484        packet,
485        I::ETHER_TYPE,
486        dst_mac,
487        LoopbackTxQueueMeta { target_device, ip_layer_metadata },
488    )
489}
490
491fn send_as_ethernet_frame_to_dst<CC, BC, S>(
492    core_ctx: &mut CC,
493    bindings_ctx: &mut BC,
494    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
495    packet: S,
496    protocol: EtherType,
497    dst_mac: Mac,
498    meta: LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
499) -> Result<(), SendFrameError<S>>
500where
501    CC: TransmitQueueHandler<
502            LoopbackDevice,
503            BC,
504            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
505        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
506        + DeviceIdContext<AnyDevice>,
507    BC: DeviceLayerTypes,
508    S: NetworkSerializer,
509    S::Buffer: BufferMut,
510{
511    /// The minimum length of bodies of Ethernet frames sent over the loopback
512    /// device.
513    ///
514    /// Use zero since the frames are never sent out a physical device, so it
515    /// doesn't matter if they are shorter than would be required.
516    const MIN_BODY_LEN: usize = 0;
517
518    let frame =
519        EthernetFrameBuilder::new(LOOPBACK_MAC, dst_mac, protocol, MIN_BODY_LEN).wrap_body(packet);
520
521    send_ethernet_frame(core_ctx, bindings_ctx, device_id, frame, meta)
522        .map_err(|err| err.into_inner())
523}
524
525fn send_ethernet_frame<CC, BC, S>(
526    core_ctx: &mut CC,
527    bindings_ctx: &mut BC,
528    device_id: &<CC as DeviceIdContext<LoopbackDevice>>::DeviceId,
529    frame: S,
530    meta: LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
531) -> Result<(), SendFrameError<S>>
532where
533    CC: TransmitQueueHandler<
534            LoopbackDevice,
535            BC,
536            Meta = LoopbackTxQueueMeta<<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId, BC>,
537        > + ResourceCounterContext<<CC as DeviceIdContext<LoopbackDevice>>::DeviceId, DeviceCounters>
538        + DeviceIdContext<AnyDevice>,
539    S: NetworkSerializer,
540    S::Buffer: BufferMut,
541    BC: DeviceLayerTypes,
542{
543    core_ctx.increment_both(device_id, |counters: &DeviceCounters| &counters.send_total_frames);
544    match TransmitQueueHandler::<LoopbackDevice, _>::queue_tx_frame(
545        core_ctx,
546        bindings_ctx,
547        device_id,
548        meta,
549        frame,
550    ) {
551        Ok(len) => {
552            core_ctx.add_both_usize(device_id, len, |counters| &counters.send_bytes);
553            core_ctx.increment_both(device_id, |counters: &DeviceCounters| &counters.send_frame);
554            Ok(())
555        }
556        Err(TransmitQueueFrameError::NoQueue(err)) => {
557            unreachable!("loopback never fails to send a frame: {err:?}")
558        }
559        Err(TransmitQueueFrameError::QueueFull(serializer)) => {
560            core_ctx
561                .increment_both(device_id, |counters: &DeviceCounters| &counters.send_queue_full);
562            Err(SendFrameError { serializer, error: SendFrameErrorReason::QueueFull })
563        }
564        Err(TransmitQueueFrameError::SerializeError(err)) => {
565            core_ctx.increment_both(device_id, |counters: &DeviceCounters| {
566                &counters.send_serialize_error
567            });
568            Err(err.err_into())
569        }
570    }
571}
572
573impl DeviceReceiveFrameSpec for LoopbackDevice {
574    // Loopback never receives frames from bindings, so make it impossible to
575    // instantiate it.
576    type FrameMetadata<D> = !;
577}