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