Skip to main content

netstack3_base/
packet.rs

1// Copyright 2026 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//! Contexts for packet parsing and serialization in netstack3.
6
7use bitflags::bitflags;
8use core::num::NonZeroU16;
9use net_types::ip::IpInvariant;
10use packet::{
11    DynamicSerializer, PacketBuilder, PacketConstraints, PartialSerializer, SerializationContext,
12    Serializer,
13};
14use packet_formats::TransportChecksumAction;
15use packet_formats::ethernet::{EthernetEnvelope, EthernetSerializationContext};
16use packet_formats::icmp::{IcmpEnvelope, IcmpSerializationContext};
17use packet_formats::ip::{IpEnvelope, IpExt, IpSerializationContext};
18use packet_formats::tcp::{TcpEnvelope, TcpParseContext, TcpSerializationContext};
19use packet_formats::udp::{UdpEnvelope, UdpParseContext, UdpSerializationContext};
20use static_assertions::const_assert;
21
22/// The specific packet `Serializer` type used within netstack3.
23pub trait NetworkSerializer: Serializer<NetworkSerializationContext> {}
24impl<S: Serializer<NetworkSerializationContext>> NetworkSerializer for S {}
25
26/// The specific packet `PartialSerializer` type used within netstack3.
27pub trait NetworkPartialSerializer: PartialSerializer<NetworkSerializationContext> {}
28impl<S: PartialSerializer<NetworkSerializationContext>> NetworkPartialSerializer for S {}
29
30/// The specific dynamic packet `Serializer` type used within netstack3.
31pub trait DynamicNetworkSerializer: DynamicSerializer<NetworkSerializationContext> {}
32impl<S: DynamicSerializer<NetworkSerializationContext>> DynamicNetworkSerializer for S {}
33
34/// Networking protocols that support checksum offloading.
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36pub enum OffloadableProtocol {
37    /// No protocol.
38    #[default]
39    None,
40    /// Protocol does not support checksum offloading.
41    NotOffloadable,
42    /// Transmission Control Protocol.
43    Tcp,
44    /// User Datagram Protocol.
45    Udp,
46    /// Internet Protocol version 4.
47    Ipv4,
48    /// Internet Protocol version 6.
49    Ipv6,
50    /// Ethernet Frame.
51    Ethernet,
52}
53
54bitflags! {
55    /// Bitmask for networking protocols that support checksum offloading.
56    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
57    pub struct OffloadableProtocols: u8 {
58        /// Not an offloadable protocol.
59        const NOT_OFFLOADABLE = 1 << 0;
60        /// Transmission Control Protocol.
61        const TCP = 1 << 1;
62        /// User Datagram Protocol.
63        const UDP = 1 << 2;
64        /// Internet Protocol version 4.
65        const IPV4 = 1 << 3;
66        /// Internet Protocol version 6.
67        const IPV6 = 1 << 4;
68        /// Ethernet Frame.
69        const ETHERNET = 1 << 5;
70    }
71}
72
73impl From<OffloadableProtocol> for OffloadableProtocols {
74    fn from(p: OffloadableProtocol) -> Self {
75        match p {
76            OffloadableProtocol::None => Self::empty(),
77            OffloadableProtocol::NotOffloadable => Self::NOT_OFFLOADABLE,
78            OffloadableProtocol::Tcp => Self::TCP,
79            OffloadableProtocol::Udp => Self::UDP,
80            OffloadableProtocol::Ipv4 => Self::IPV4,
81            OffloadableProtocol::Ipv6 => Self::IPV6,
82            OffloadableProtocol::Ethernet => Self::ETHERNET,
83        }
84    }
85}
86
87bitflags! {
88    /// Indicates that a device supports protocol-specific checksum offloading.
89    #[derive(Clone, Debug, Default, Eq, PartialEq)]
90    pub struct ProtocolSpecificOffloadSpec: u8 {
91        /// Ethernet frame with IPv4 header (without options) and UDP payload.
92        const ETH_IPV4_UDP = 1 << 0;
93        /// Ethernet frame with IPv4 header (without options) and TCP payload.
94        const ETH_IPV4_TCP = 1 << 1;
95        /// Ethernet frame with IPv6 header (without extension headers) and UDP
96        /// payload.
97        const ETH_IPV6_UDP = 1 << 2;
98        /// Ethernet frame with IPv6 header (without extension headers) and TCP
99        /// payload.
100        const ETH_IPV6_TCP = 1 << 3;
101    }
102}
103
104impl ProtocolSpecificOffloadSpec {
105    /// Creates a `ProtocolSpecificOffloadSpec` for devices that support
106    /// protocol-specific checksum offloading for UDP and TCP packets over IPv4.
107    ///
108    /// This spec does not match when the IPv4 header contains options.
109    pub fn tcp_or_udp_over_ipv4() -> Self {
110        Self::ETH_IPV4_UDP | Self::ETH_IPV4_TCP
111    }
112
113    /// Creates a `ProtocolSpecificOffloadSpec` for devices that support
114    /// protocol-specific checksum offloading for UDP and TCP packets over IPv6.
115    ///
116    /// This spec does not match when IPv6 contains extension headers.
117    pub fn tcp_or_udp_over_ipv6() -> Self {
118        Self::ETH_IPV6_UDP | Self::ETH_IPV6_TCP
119    }
120
121    /// Creates a `ProtocolSpecificOffloadSpec` that matches if either this spec
122    /// or the other spec matches.
123    fn or(self, other: Self) -> Self {
124        self | other
125    }
126
127    /// Returns true if the `current` protocols match this spec.
128    fn matches(&self, current: OffloadableProtocols) -> bool {
129        use OffloadableProtocols as P;
130        match current {
131            f if f == (P::ETHERNET | P::IPV4 | P::UDP) => self.contains(Self::ETH_IPV4_UDP),
132            f if f == (P::ETHERNET | P::IPV4 | P::TCP) => self.contains(Self::ETH_IPV4_TCP),
133            f if f == (P::ETHERNET | P::IPV6 | P::UDP) => self.contains(Self::ETH_IPV6_UDP),
134            f if f == (P::ETHERNET | P::IPV6 | P::TCP) => self.contains(Self::ETH_IPV6_TCP),
135            _ => false,
136        }
137    }
138}
139
140/// Indicates that a device supports generic checksum offloading.
141#[derive(Clone, Debug, Default, Eq, PartialEq)]
142struct GenericOffloadSpec;
143
144/// Describes the checksum offloading capabilities available during serialization.
145#[derive(Clone, Debug, Default, Eq, PartialEq)]
146pub struct ChecksumOffloadSpec {
147    /// If `Some`, the device supports protocol-specific checksum offloading.
148    protocol_specific: Option<ProtocolSpecificOffloadSpec>,
149    /// If `Some`, the device supports generic checksum offloading.
150    generic: Option<GenericOffloadSpec>,
151}
152
153impl ChecksumOffloadSpec {
154    /// Creates a `ChecksumOffloadSpec` for a device that does not support any
155    /// checksum offloading.
156    pub fn none() -> Self {
157        Self { protocol_specific: None, generic: None }
158    }
159
160    /// Creates a `ChecksumOffloadSpec` for a device that supports protocol-specific
161    /// checksum offloading for the given protocols.
162    pub fn protocol_specific(spec: ProtocolSpecificOffloadSpec) -> Self {
163        Self { protocol_specific: Some(spec), generic: None }
164    }
165
166    /// Creates a `ChecksumOffloadSpec` for a device that supports generic
167    /// checksum offloading.
168    pub fn generic() -> Self {
169        Self { protocol_specific: None, generic: Some(GenericOffloadSpec::default()) }
170    }
171
172    /// Creates a `ChecksumOffloadSpec` that matches any of the given specs.
173    pub fn any<I: IntoIterator<Item = Self>>(specs: I) -> Self {
174        specs.into_iter().fold(Self::none(), |acc, spec| Self {
175            protocol_specific: match (acc.protocol_specific, spec.protocol_specific) {
176                (Some(acc), Some(spec)) => Some(acc.or(spec)),
177                (Some(acc), None) => Some(acc),
178                (None, Some(spec)) => Some(spec),
179                (None, None) => None,
180            },
181            generic: acc.generic.or(spec.generic),
182        })
183    }
184}
185
186#[derive(Clone, Debug, Eq, PartialEq)]
187struct ProtocolStackInfo {
188    // The offset in bytes from the start of the buffer to the start of the
189    // current packet's header, if it fits in a u16.
190    header_offset: Option<u16>,
191    // The current protocol stack.
192    protocols: OffloadableProtocols,
193}
194
195impl Default for ProtocolStackInfo {
196    fn default() -> Self {
197        Self { header_offset: Some(0), protocols: OffloadableProtocols::empty() }
198    }
199}
200
201#[derive(Clone, Debug, Default, Eq, PartialEq)]
202struct ChecksumOffloadState {
203    spec: ChecksumOffloadSpec,
204    stack_info: ProtocolStackInfo,
205}
206
207impl ChecksumOffloadState {
208    fn new(spec: ChecksumOffloadSpec) -> Self {
209        Self { spec, stack_info: Default::default() }
210    }
211
212    /// Updates the checksum offload state for the given protocol and the
213    /// constraints of the encapsulating context.
214    ///
215    /// Returns a value that can be passed to `restore` to restore the previous
216    /// state.
217    fn update(
218        &mut self,
219        protocol: OffloadableProtocol,
220        constraints: &PacketConstraints,
221    ) -> ProtocolStackInfo {
222        let previous = self.stack_info.clone();
223
224        let ProtocolStackInfo { header_offset, protocols } = &mut self.stack_info;
225
226        // A `header_offset` value of `None` is sticky: it will be `None` for
227        // all subsequent inner protocols and will remain so until an earlier
228        // state is `restore`d.
229        *header_offset = header_offset.and_then(|_| constraints.header_len().try_into().ok());
230
231        if protocol == OffloadableProtocol::None {
232            return previous;
233        }
234        let protocol = protocol.into();
235        if protocols.contains(protocol) {
236            // If we encounter a duplicate protocol in the stack, then we flag
237            // that protocol-specific offloading is not available from that
238            // point on. Like a `header_offset` of `None`, this value will be
239            // retained until an earlier state is `restore`d.
240            protocols.insert(OffloadableProtocol::NotOffloadable.into());
241        } else {
242            protocols.insert(protocol);
243        }
244
245        previous
246    }
247
248    /// Restores the previous checksum offload state.
249    fn restore(&mut self, previous: ProtocolStackInfo) {
250        self.stack_info = previous;
251    }
252
253    fn try_offload(&self, csum_offset: u16) -> Option<ChecksumOffloadResult> {
254        let ProtocolStackInfo { header_offset, protocols } = self.stack_info;
255
256        // We prefer generic offloading over protocol-specific offloading where
257        // both are available to be consistent with Linux, which has been trying
258        // to move toward generic offloading.
259        if let Some(start) = self.spec.generic.as_ref().and(header_offset) {
260            Some(ChecksumOffloadResult::Generic(PartialChecksum { start, offset: csum_offset }))
261        } else if self
262            .spec
263            .protocol_specific
264            .as_ref()
265            .map(|spec| spec.matches(protocols))
266            .unwrap_or(false)
267        {
268            Some(ChecksumOffloadResult::ProtocolSpecific(protocols))
269        } else {
270            None
271        }
272    }
273}
274
275/// Describes a partial checksum whose full checksum will be offloaded. The full checksum
276/// must be computed by summing from `start` (the offset in bytes from the start of the
277/// outermost packet header) to the end of the outermost packet and then placing the result
278/// at `start + offset`.
279#[derive(Clone, Debug, Default, Eq, PartialEq)]
280pub struct PartialChecksum {
281    /// The offset in bytes from the start of the outermost packet header to the start of the
282    /// checksum.
283    pub start: u16,
284    /// The offset in bytes from the start of the checksum to the field that it replaces.
285    pub offset: u16,
286}
287
288/// Describes the checksum offloading capability used during serialization.
289#[derive(Clone, Debug, Eq, PartialEq)]
290pub enum ChecksumOffloadResult {
291    /// Protocol-specific checksum offloading was utilized.
292    ProtocolSpecific(OffloadableProtocols),
293    /// Generic checksum offloading was utilized, producing a partial checksum.
294    Generic(PartialChecksum),
295}
296
297/// A concrete serialization context for the entire network stack.
298#[derive(Clone, Debug, Default, Eq, PartialEq)]
299pub struct NetworkSerializationContext {
300    csum_offload_state: ChecksumOffloadState,
301    /// Indicates whether or not checksum offloading capabilities have been
302    /// utilized yet in the current serialization operation. Because checksum
303    /// offloading can only be performed once per packet, a value of `Some`
304    /// prevents checksum offloading from being performed multiple times.
305    csum_offload_result: Option<ChecksumOffloadResult>,
306}
307
308impl NetworkSerializationContext {
309    /// Creates a new `NetworkSerializationContext` with the given checksum offload capabilities.
310    pub fn new(csum_offload_spec: ChecksumOffloadSpec) -> Self {
311        Self {
312            csum_offload_state: ChecksumOffloadState::new(csum_offload_spec),
313            csum_offload_result: None,
314        }
315    }
316
317    fn transport_checksum_action(&mut self, csum_offset: u16) -> TransportChecksumAction {
318        if self.csum_offload_result.is_some() {
319            TransportChecksumAction::ComputeFull
320        } else {
321            self.csum_offload_result = self.csum_offload_state.try_offload(csum_offset);
322            self.csum_offload_result
323                .as_ref()
324                .map(|_| TransportChecksumAction::ComputePartial)
325                .unwrap_or(TransportChecksumAction::ComputeFull)
326        }
327    }
328
329    /// Returns the result of the checksum offloading operation for the current
330    /// packet, if any.
331    pub fn csum_offload_result(self) -> Option<ChecksumOffloadResult> {
332        self.csum_offload_result
333    }
334}
335
336impl SerializationContext for NetworkSerializationContext {
337    type ContextState = OffloadableProtocol;
338
339    fn serialize_nested<O: PacketBuilder<Self>, R>(
340        &mut self,
341        outer: &O,
342        constraints: PacketConstraints,
343        serialize_fn: impl FnOnce(&mut Self, PacketConstraints) -> R,
344    ) -> R {
345        let previous_state = self.csum_offload_state.update(outer.context_state(), &constraints);
346        let result = serialize_fn(self, constraints);
347        self.csum_offload_state.restore(previous_state);
348        result
349    }
350}
351
352impl EthernetSerializationContext for NetworkSerializationContext {
353    fn envelope_to_state(_envelope: EthernetEnvelope) -> Self::ContextState {
354        OffloadableProtocol::Ethernet
355    }
356}
357
358impl<I: IpExt> IpSerializationContext<I> for NetworkSerializationContext {
359    fn envelope_to_state(envelope: IpEnvelope<I>) -> Self::ContextState {
360        I::map_ip_in(
361            IpInvariant(envelope),
362            |IpInvariant(envelope)| {
363                if envelope.has_options {
364                    OffloadableProtocol::NotOffloadable
365                } else {
366                    OffloadableProtocol::Ipv4
367                }
368            },
369            |IpInvariant(envelope)| {
370                if envelope.has_options {
371                    OffloadableProtocol::NotOffloadable
372                } else {
373                    OffloadableProtocol::Ipv6
374                }
375            },
376        )
377    }
378}
379
380impl IcmpSerializationContext for NetworkSerializationContext {
381    fn envelope_to_state(_envelope: IcmpEnvelope) -> Self::ContextState {
382        OffloadableProtocol::NotOffloadable
383    }
384}
385
386const_assert!(packet_formats::udp::CHECKSUM_OFFSET <= u16::MAX as usize);
387const UDP_CHECKSUM_OFFSET: u16 = packet_formats::udp::CHECKSUM_OFFSET as u16;
388
389impl UdpSerializationContext for NetworkSerializationContext {
390    fn envelope_to_state(_envelope: UdpEnvelope) -> Self::ContextState {
391        OffloadableProtocol::Udp
392    }
393
394    fn checksum_action(&mut self) -> TransportChecksumAction {
395        self.transport_checksum_action(UDP_CHECKSUM_OFFSET)
396    }
397}
398
399const_assert!(packet_formats::tcp::CHECKSUM_OFFSET <= u16::MAX as usize);
400const TCP_CHECKSUM_OFFSET: u16 = packet_formats::tcp::CHECKSUM_OFFSET as u16;
401
402impl TcpSerializationContext for NetworkSerializationContext {
403    fn envelope_to_state(_envelope: TcpEnvelope) -> Self::ContextState {
404        OffloadableProtocol::Tcp
405    }
406
407    fn checksum_action(&mut self) -> TransportChecksumAction {
408        self.transport_checksum_action(TCP_CHECKSUM_OFFSET)
409    }
410}
411
412/// An indication of the checksums offloaded, if any, for a packet received from
413/// a device.
414#[derive(Clone, Copy, Debug, Eq, PartialEq)]
415pub enum ChecksumRxOffloading {
416    /// The device offloaded zero or more checksums.
417    ///
418    /// `Some(n)` can only be used to describe offloading of TCP and UDP
419    /// checksums.
420    Offloaded(Option<NonZeroU16>),
421    /// The device requires no checksum verification on packet ingress.
422    ///
423    /// NOTE: only intended to be used by the loopback interface.
424    FullyOffloaded,
425}
426
427impl Default for ChecksumRxOffloading {
428    fn default() -> Self {
429        ChecksumRxOffloading::Offloaded(None)
430    }
431}
432
433impl ChecksumRxOffloading {
434    fn skip_checksum_verification(&mut self) -> bool {
435        match self {
436            ChecksumRxOffloading::FullyOffloaded => true,
437            ChecksumRxOffloading::Offloaded(Some(n)) => {
438                *self = ChecksumRxOffloading::Offloaded(NonZeroU16::new(n.get() - 1));
439                true
440            }
441            ChecksumRxOffloading::Offloaded(None) => false,
442        }
443    }
444}
445
446/// Context for parsing network packets in netstack3.
447#[derive(Clone, Debug, Default, Eq, PartialEq)]
448pub struct NetworkParsingContext {
449    /// Hardware checksum offloading context.
450    checksum_offload: ChecksumRxOffloading,
451}
452
453impl NetworkParsingContext {
454    /// Creates a new `NetworkParsingContext`.
455    pub fn new(checksum_offload: ChecksumRxOffloading) -> Self {
456        NetworkParsingContext { checksum_offload }
457    }
458}
459
460impl UdpParseContext for &mut NetworkParsingContext {
461    fn skip_checksum_verification(&mut self) -> bool {
462        self.checksum_offload.skip_checksum_verification()
463    }
464}
465
466impl TcpParseContext for &mut NetworkParsingContext {
467    fn skip_checksum_verification(&mut self) -> bool {
468        self.checksum_offload.skip_checksum_verification()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475    use alloc::vec::Vec;
476    use assert_matches::assert_matches;
477    use core::num::NonZeroU16;
478    use net_types::ethernet::Mac;
479    use net_types::ip::{IpAddress, IpVersionMarker, Ipv4, Ipv4Addr, Ipv6Addr};
480    use packet::{
481        Buf, FragmentedBytesMut, FromRaw, NestablePacketBuilder, NestableSerializer, PacketBuilder,
482        PacketConstraints, ParseBuffer, SerializeTarget, Serializer,
483    };
484    use packet_formats::error::ParseError;
485    use packet_formats::ethernet::{
486        EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck,
487    };
488    use packet_formats::ip::{IpPacket, IpProto, Ipv4Proto, Ipv6Proto};
489    use packet_formats::ipv4::options::Ipv4Option;
490    use packet_formats::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
491    use packet_formats::ipv6::ext_hdrs::{
492        ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
493    };
494    use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
495    use packet_formats::tcp::TcpSegmentBuilder;
496    use packet_formats::udp::{
497        HEADER_BYTES as UDP_HEADER_BYTES, UdpPacket, UdpPacketBuilder, UdpPacketRaw, UdpParseArgs,
498    };
499    use test_case::test_case;
500
501    const SRC_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
502    const DST_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
503    const SRC_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 1]);
504    const DST_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 2]);
505    const SRC_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 1]);
506    const DST_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 2]);
507    const SRC_PORT: u16 = 1234;
508    const DST_PORT: u16 = 5678;
509
510    #[test_case(
511        UdpPacketBuilder::new(
512            SRC_IP_V4,
513            DST_IP_V4,
514            NonZeroU16::new(SRC_PORT),
515            NonZeroU16::new(DST_PORT).unwrap(),
516        ),
517        IpProto::Udp ; "udp"
518    )]
519    #[test_case(
520        TcpSegmentBuilder::new(
521            SRC_IP_V4,
522            DST_IP_V4,
523            NonZeroU16::new(SRC_PORT).unwrap(),
524            NonZeroU16::new(DST_PORT).unwrap(),
525            123,
526            None,
527            1000,
528        ),
529        IpProto::Tcp ; "tcp"
530    )]
531    fn ipv4_no_options_csum_offload(
532        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
533        ip_proto: IpProto,
534    ) {
535        let mut payload = [0u8; 100];
536        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
537        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
538
539        let serializer =
540            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
541
542        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
543            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
544        ));
545        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
546
547        let expected_protocol = match ip_proto {
548            IpProto::Udp => OffloadableProtocol::Udp,
549            IpProto::Tcp => OffloadableProtocol::Tcp,
550            _ => panic!("invalid proto"),
551        };
552        assert_eq!(
553            context.csum_offload_result(),
554            Some(ChecksumOffloadResult::ProtocolSpecific(
555                OffloadableProtocols::ETHERNET
556                    | OffloadableProtocols::IPV4
557                    | expected_protocol.into()
558            ))
559        );
560    }
561
562    #[test_case(
563        UdpPacketBuilder::new(
564            SRC_IP_V4,
565            DST_IP_V4,
566            NonZeroU16::new(SRC_PORT),
567            NonZeroU16::new(DST_PORT).unwrap(),
568        ),
569        IpProto::Udp ; "udp"
570    )]
571    #[test_case(
572        TcpSegmentBuilder::new(
573            SRC_IP_V4,
574            DST_IP_V4,
575            NonZeroU16::new(SRC_PORT).unwrap(),
576            NonZeroU16::new(DST_PORT).unwrap(),
577            123,
578            None,
579            1000,
580        ),
581        IpProto::Tcp ; "tcp"
582    )]
583    fn ipv4_with_options_no_csum_offload(
584        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
585        ip_proto: IpProto,
586    ) {
587        let mut payload = [0u8; 100];
588        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
589        let options = [Ipv4Option::RouterAlert { data: 0 }];
590        let ip_with_options = Ipv4PacketBuilderWithOptions::new(ip, &options).unwrap();
591        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
592
593        let serializer = Buf::new(&mut payload[..], ..)
594            .wrap_in(transport_builder)
595            .wrap_in(ip_with_options)
596            .wrap_in(ethernet);
597
598        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
599            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
600        ));
601        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
602
603        assert_eq!(context.csum_offload_result(), None);
604    }
605
606    #[test_case(
607        UdpPacketBuilder::new(
608            SRC_IP_V6,
609            DST_IP_V6,
610            NonZeroU16::new(SRC_PORT),
611            NonZeroU16::new(DST_PORT).unwrap(),
612        ),
613        IpProto::Udp ; "udp"
614    )]
615    #[test_case(
616        TcpSegmentBuilder::new(
617            SRC_IP_V6,
618            DST_IP_V6,
619            NonZeroU16::new(SRC_PORT).unwrap(),
620            NonZeroU16::new(DST_PORT).unwrap(),
621            123,
622            None,
623            1000,
624        ),
625        IpProto::Tcp ; "tcp"
626    )]
627    fn ipv6_no_extensions_csum_offload(
628        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
629        ip_proto: IpProto,
630    ) {
631        let mut payload = [0u8; 100];
632        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
633        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
634
635        let serializer =
636            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
637
638        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
639            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
640        ));
641        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
642
643        let expected_protocol = match ip_proto {
644            IpProto::Udp => OffloadableProtocol::Udp,
645            IpProto::Tcp => OffloadableProtocol::Tcp,
646            _ => panic!("invalid proto"),
647        };
648        assert_eq!(
649            context.csum_offload_result(),
650            Some(ChecksumOffloadResult::ProtocolSpecific(
651                OffloadableProtocols::ETHERNET
652                    | OffloadableProtocols::IPV6
653                    | expected_protocol.into()
654            ))
655        );
656    }
657
658    #[test_case(
659        UdpPacketBuilder::new(
660            SRC_IP_V6,
661            DST_IP_V6,
662            NonZeroU16::new(SRC_PORT),
663            NonZeroU16::new(DST_PORT).unwrap(),
664        ),
665        IpProto::Udp ; "udp"
666    )]
667    #[test_case(
668        TcpSegmentBuilder::new(
669            SRC_IP_V6,
670            DST_IP_V6,
671            NonZeroU16::new(SRC_PORT).unwrap(),
672            NonZeroU16::new(DST_PORT).unwrap(),
673            123,
674            None,
675            1000,
676        ),
677        IpProto::Tcp ; "tcp"
678    )]
679    fn ipv6_with_extension_hdrs_no_csum_offload(
680        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
681        ip_proto: IpProto,
682    ) {
683        let mut payload = [0u8; 100];
684        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
685        let options = [HopByHopOption {
686            action: ExtensionHeaderOptionAction::SkipAndContinue,
687            mutable: false,
688            data: HopByHopOptionData::RouterAlert { data: 0 },
689        }];
690        let ip_with_options = Ipv6PacketBuilderWithHbhOptions::new(ip, options).unwrap();
691        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
692
693        let serializer = Buf::new(&mut payload[..], ..)
694            .wrap_in(transport_builder)
695            .wrap_in(ip_with_options)
696            .wrap_in(ethernet);
697
698        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
699            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
700        ));
701        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
702
703        assert_eq!(context.csum_offload_result(), None);
704    }
705
706    #[test]
707    fn generic_csum_offload_preferred_over_protocol_specific() {
708        let mut payload = [0u8; 100];
709        let udp = UdpPacketBuilder::new(
710            SRC_IP_V4,
711            DST_IP_V4,
712            NonZeroU16::new(SRC_PORT),
713            NonZeroU16::new(DST_PORT).unwrap(),
714        );
715        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
716        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
717
718        let serializer = Buf::new(&mut payload[..], ..).wrap_in(udp).wrap_in(ip).wrap_in(ethernet);
719
720        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::any([
721            ChecksumOffloadSpec::generic(),
722            ChecksumOffloadSpec::protocol_specific(
723                ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
724            ),
725        ]));
726        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
727
728        // We expect generic offload to be preferred.
729        assert_matches!(context.csum_offload_result(), Some(ChecksumOffloadResult::Generic(_)));
730    }
731
732    #[derive(Debug)]
733    struct TestPacketBuilder {
734        header_len: usize,
735    }
736    impl NestablePacketBuilder for TestPacketBuilder {
737        fn constraints(&self) -> PacketConstraints {
738            PacketConstraints::new(self.header_len, 0, 0, usize::MAX)
739        }
740    }
741    impl PacketBuilder<NetworkSerializationContext> for TestPacketBuilder {
742        fn context_state(&self) -> OffloadableProtocol {
743            OffloadableProtocol::NotOffloadable
744        }
745        fn serialize(
746            &self,
747            _context: &mut NetworkSerializationContext,
748            _target: &mut SerializeTarget<'_>,
749            _body: FragmentedBytesMut<'_, '_>,
750        ) {
751            // Do nothing.
752        }
753    }
754
755    #[test_case(
756        UdpPacketBuilder::new(
757            SRC_IP_V4,
758            DST_IP_V4,
759            NonZeroU16::new(SRC_PORT),
760            NonZeroU16::new(DST_PORT).unwrap(),
761        ),
762        IpProto::Udp,
763        UDP_CHECKSUM_OFFSET ; "udp"
764    )]
765    #[test_case(
766        TcpSegmentBuilder::new(
767            SRC_IP_V4,
768            DST_IP_V4,
769            NonZeroU16::new(SRC_PORT).unwrap(),
770            NonZeroU16::new(DST_PORT).unwrap(),
771            123,
772            None,
773            1000,
774        ),
775        IpProto::Tcp,
776        TCP_CHECKSUM_OFFSET ; "tcp"
777    )]
778    fn generic_csum_offload(
779        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
780        ip_proto: IpProto,
781        expected_csum_offset: u16,
782    ) {
783        let mut payload = [0u8; 100];
784        let test_packet = TestPacketBuilder { header_len: 10 };
785        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
786        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
787
788        // Buf -> test_packet -> transport_builder -> ip -> ethernet.
789        let serializer = Buf::new(&mut payload[..], ..)
790            // We add an additional header inside the transport packet to ensure
791            // that the correct `header_offset` is restored as we walk back up
792            // the stack.
793            .wrap_in(test_packet)
794            .wrap_in(transport_builder)
795            .wrap_in(ip)
796            .wrap_in(ethernet);
797
798        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
799        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
800
801        // Ethernet header (14) + Ipv4 header (20) = 34.
802        assert_eq!(
803            context.csum_offload_result(),
804            Some(ChecksumOffloadResult::Generic(PartialChecksum {
805                start: 34,
806                offset: expected_csum_offset
807            }))
808        );
809    }
810
811    #[test]
812    fn generic_csum_offload_disabled_on_overflow() {
813        let mut payload = [0u8; 100];
814        // Use a header length that exceeds u16::MAX (65535).
815        let test_packet = TestPacketBuilder { header_len: 66000 };
816        let udp = UdpPacketBuilder::new(
817            SRC_IP_V4,
818            DST_IP_V4,
819            NonZeroU16::new(SRC_PORT),
820            NonZeroU16::new(DST_PORT).unwrap(),
821        );
822        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
823        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
824
825        // Wrap test_packet *outside* UDP to make the starting byte of the UDP
826        // header overflow a u16.
827        // Buf -> udp -> ip -> test_packet -> ethernet.
828        let serializer = Buf::new(&mut payload[..], ..)
829            .wrap_in(udp)
830            .wrap_in(ip)
831            .wrap_in(test_packet)
832            .wrap_in(ethernet);
833
834        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
835        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
836
837        // Generic offload should be disabled because of overflow.
838        assert_eq!(context.csum_offload_result(), None);
839    }
840
841    #[test]
842    fn generic_csum_offload_enabled_with_inner_overflow() {
843        let mut payload = [0u8; 100];
844        // Use a header length that exceeds u16::MAX (65535).
845        let test_packet = TestPacketBuilder { header_len: 66000 };
846        let udp = UdpPacketBuilder::new(
847            SRC_IP_V6,
848            DST_IP_V6,
849            NonZeroU16::new(SRC_PORT),
850            NonZeroU16::new(DST_PORT).unwrap(),
851        );
852        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
853
854        // Wrap test_packet *inside* UDP, bypassing IP to avoid IP size limits.
855        // Buf -> test_packet -> udp -> ethernet.
856        let serializer =
857            Buf::new(&mut payload[..], ..).wrap_in(test_packet).wrap_in(udp).wrap_in(ethernet);
858
859        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
860        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
861
862        // Generic offload should work despite the overflow inside the UDP
863        // packet.
864        assert_eq!(
865            context.csum_offload_result(),
866            Some(ChecksumOffloadResult::Generic(PartialChecksum {
867                start: 14, // Ethernet header length.
868                offset: UDP_CHECKSUM_OFFSET
869            }))
870        );
871    }
872
873    #[test]
874    fn protocol_specific_csum_offload_with_size_limit() {
875        let mut payload = [0u8; 100];
876        let udp = UdpPacketBuilder::new(
877            SRC_IP_V4,
878            DST_IP_V4,
879            NonZeroU16::new(SRC_PORT),
880            NonZeroU16::new(DST_PORT).unwrap(),
881        );
882        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
883        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
884
885        // Buf -> udp -> with_size_limit -> ip -> ethernet.
886        let serializer = Buf::new(&mut payload[..], ..)
887            .wrap_in(udp)
888            // Tests that intermediate protocol-less packet builders like
889            // `LimitedSizePacketBuilder` don't break protocol-specific
890            // offloading.
891            .with_size_limit(1000)
892            .wrap_in(ip)
893            .wrap_in(ethernet);
894
895        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
896            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
897        ));
898        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
899
900        assert_eq!(
901            context.csum_offload_result(),
902            Some(ChecksumOffloadResult::ProtocolSpecific(
903                OffloadableProtocols::ETHERNET
904                    | OffloadableProtocols::IPV4
905                    | OffloadableProtocols::UDP
906            ))
907        );
908    }
909
910    #[test]
911    fn protocol_specific_csum_offload_duplicate_protocol() {
912        let mut payload = [0u8; 100];
913        let udp_inner = UdpPacketBuilder::new(
914            SRC_IP_V4,
915            DST_IP_V4,
916            NonZeroU16::new(SRC_PORT),
917            NonZeroU16::new(DST_PORT).unwrap(),
918        );
919        let ip_inner =
920            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
921        let udp_outer = UdpPacketBuilder::new(
922            SRC_IP_V4,
923            DST_IP_V4,
924            NonZeroU16::new(SRC_PORT),
925            NonZeroU16::new(DST_PORT).unwrap(),
926        );
927        let ip_outer =
928            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
929        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
930
931        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
932        let serializer = Buf::new(&mut payload[..], ..)
933            .wrap_in(udp_inner)
934            .wrap_in(ip_inner)
935            .wrap_in(udp_outer)
936            .wrap_in(ip_outer)
937            .wrap_in(ethernet);
938
939        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
940            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
941        ));
942        let buf =
943            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
944
945        // Protocol-specific offload should work for the outer UDP packet even
946        // with the duplicate UDP packet.
947        assert_eq!(
948            context.csum_offload_result(),
949            Some(ChecksumOffloadResult::ProtocolSpecific(
950                OffloadableProtocols::ETHERNET
951                    | OffloadableProtocols::IPV4
952                    | OffloadableProtocols::UDP
953            ))
954        );
955
956        let mut buf_ref = buf.as_ref();
957        let eth = buf_ref
958            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
959            .expect("ethernet parse should succeed");
960        let mut body = eth.body();
961        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
962
963        // Parse outer UDP as raw (succeeds since it doesn't validate checksum).
964        let mut outer_udp_bytes = ip_out.body();
965        let udp_out_raw = outer_udp_bytes
966            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
967            .expect("outer udp parse should succeed");
968
969        // Try to validate outer UDP, which should fail checksum validation
970        // because the checksum was offloaded.
971        assert_eq!(
972            UdpPacket::try_from_raw_with(
973                udp_out_raw,
974                UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip())
975            )
976            .err(),
977            Some(ParseError::Checksum),
978        );
979
980        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
981        let ip_in =
982            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
983        let mut body = ip_in.body();
984
985        // This should succeed because inner UDP checksum was computed in
986        // software.
987        let _udp_in = body
988            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip()))
989            .expect("inner udp parse should succeed");
990    }
991
992    #[test]
993    fn generic_csum_offload_duplicate_protocol() {
994        let mut payload = [0u8; 100];
995        let udp_inner = UdpPacketBuilder::new(
996            SRC_IP_V4,
997            DST_IP_V4,
998            NonZeroU16::new(SRC_PORT),
999            NonZeroU16::new(DST_PORT).unwrap(),
1000        );
1001        let ip_inner =
1002            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1003        let udp_outer = UdpPacketBuilder::new(
1004            SRC_IP_V4,
1005            DST_IP_V4,
1006            NonZeroU16::new(SRC_PORT),
1007            NonZeroU16::new(DST_PORT).unwrap(),
1008        );
1009        let ip_outer =
1010            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1011        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
1012
1013        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
1014        let serializer = Buf::new(&mut payload[..], ..)
1015            .wrap_in(udp_inner)
1016            .wrap_in(ip_inner)
1017            .wrap_in(udp_outer)
1018            .wrap_in(ip_outer)
1019            .wrap_in(ethernet);
1020
1021        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
1022        let buf =
1023            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
1024
1025        // Generic offload should apply to the inner UDP packet.
1026        // Eth (14) + outer IPv4 (20) + UDP (8) + inner IPv4 (20) = 62.
1027        assert_eq!(
1028            context.csum_offload_result(),
1029            Some(ChecksumOffloadResult::Generic(PartialChecksum {
1030                start: 62,
1031                offset: UDP_CHECKSUM_OFFSET
1032            }))
1033        );
1034
1035        let mut buf_ref = buf.as_ref();
1036        let eth = buf_ref
1037            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
1038            .expect("ethernet parse should succeed");
1039        let mut body = eth.body();
1040        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
1041
1042        // Outer UDP checksum was computed in software, so parse should succeed.
1043        let mut outer_udp_bytes = ip_out.body();
1044        let _udp_out = outer_udp_bytes
1045            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip()))
1046            .expect("outer udp parse should succeed");
1047
1048        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
1049        let ip_in =
1050            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
1051        let mut body = ip_in.body();
1052
1053        // Parse inner UDP as raw (succeeds since it doesn't validate checksum).
1054        let udp_in_raw = body
1055            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
1056            .expect("inner udp parse should succeed");
1057
1058        // Try to validate inner UDP, which should fail checksum validation
1059        // because the checksum was offloaded.
1060        assert_eq!(
1061            UdpPacket::try_from_raw_with(
1062                udp_in_raw,
1063                UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip())
1064            )
1065            .err(),
1066            Some(ParseError::Checksum),
1067        );
1068    }
1069
1070    fn build_udp_packet_invalid_csum<I: IpAddress>(
1071        src_ip: I,
1072        dst_ip: I,
1073        body: &mut [u8],
1074    ) -> Vec<u8> {
1075        let mut buf = Buf::new(body, ..)
1076            .wrap_in(UdpPacketBuilder::new(
1077                src_ip,
1078                dst_ip,
1079                NonZeroU16::new(1),
1080                NonZeroU16::new(2).unwrap(),
1081            ))
1082            .serialize_vec_outer(&mut NetworkSerializationContext::default())
1083            .unwrap()
1084            .as_ref()
1085            .to_vec();
1086
1087        // Corrupt the checksum.
1088        buf[packet_formats::udp::CHECKSUM_OFFSET] ^= 0xFF;
1089        buf[packet_formats::udp::CHECKSUM_OFFSET + 1] ^= 0xFF;
1090        buf
1091    }
1092
1093    /// Builds a UDP packet containing `nesting-1` nested UDP packets, all with
1094    /// invalid checksums.
1095    fn build_nested_udp_packets_invalid_csums<I: IpAddress>(
1096        src_ip: I,
1097        dst_ip: I,
1098        nesting: usize,
1099    ) -> Vec<u8> {
1100        let mut payload = alloc::vec![0u8; 100];
1101        for _ in 0..nesting {
1102            payload = build_udp_packet_invalid_csum(src_ip, dst_ip, &mut payload);
1103        }
1104        payload
1105    }
1106
1107    #[test]
1108    fn checksum_rx_offloading_none() {
1109        let buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 1);
1110
1111        // `None` offloads no checksums so we expect to be unable to parse any
1112        // UDP packets with invalid checksums.
1113        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(None));
1114        let mut buf_ref: &[u8] = buf.as_ref();
1115        assert_eq!(
1116            buf_ref
1117                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1118                    SRC_IP_V4, DST_IP_V4, &mut ctx
1119                ))
1120                .err(),
1121            Some(ParseError::Checksum)
1122        );
1123    }
1124
1125    #[test]
1126    fn checksum_rx_offloading_fully_offloaded() {
1127        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1128
1129        // `FullyOffloaded` offloads all checksums so we expect to be able to
1130        // parse an arbitrary number of UDP packets with invalid checksums.
1131        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
1132        for _ in 0..3 {
1133            let mut buf_ref: &[u8] = buf.as_ref();
1134            buf = buf_ref
1135                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1136                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1137                ))
1138                .expect("udp parse should succeed")
1139                .body()
1140                .to_vec();
1141        }
1142    }
1143
1144    #[test]
1145    fn checksum_rx_offloading_offloaded() {
1146        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1147
1148        // `Offloaded` indicates the number checksums not to verify so we expect
1149        // to be able to parse exactly two UDP packets with invalid checksums.
1150        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(Some(
1151            NonZeroU16::new(2).unwrap(),
1152        )));
1153        for _ in 0..2 {
1154            let mut buf_ref: &[u8] = buf.as_ref();
1155            buf = buf_ref
1156                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1157                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1158                ))
1159                .expect("udp parse should succeed")
1160                .body()
1161                .to_vec();
1162        }
1163        let mut buf_ref: &[u8] = buf.as_ref();
1164        assert_eq!(
1165            buf_ref
1166                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1167                    SRC_IP_V4, DST_IP_V4, &mut ctx
1168                ))
1169                .err(),
1170            Some(ParseError::Checksum)
1171        );
1172    }
1173}