Skip to main content

netstack3_device/
gro.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//! Constructs that support Generic Receive Offload (GRO) at the device layer.
6
7use alloc::vec::Vec;
8use core::num::NonZeroU16;
9
10use assert_matches::assert_matches;
11use derivative::Derivative;
12use net_types::ethernet::Mac;
13use net_types::for_any_ip_version;
14use net_types::ip::{IpAddress, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
15use packet::ParsablePacket;
16use packet_formats::ethernet::{EtherType, EthernetFrame, EthernetFrameLengthCheck};
17use packet_formats::ip::{DscpAndEcn, IpExt, IpProto, Ipv4Proto, Ipv6Proto};
18use packet_formats::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketRaw};
19use packet_formats::ipv6::{IPV6_FIXED_HDR_LEN, Ipv6Header, Ipv6Packet, Ipv6PacketRaw};
20use packet_formats::tcp::{MAX_OPTIONS_LEN, TcpParseArgs, TcpSegment, TcpSegmentRaw};
21
22use netstack3_base::{ChecksumRxOffloading, GsoInfo, Ipv4IdMode, NetworkParsingContext};
23
24/// The maximum length of a coalesced frame.
25///
26/// Chosen so that the lengths derived from a coalesced frame (IPv4 total
27/// length, IPv6 payload length, and the transport packet length that's part of
28/// the IP pseudo-header used in transport-layer checksum calculation) always
29/// fit in 16 bits (the size allotted for each of these values, with the
30/// exception of the transport length in the IPv6 pseudo-header which is 32
31/// bits). This is a conservative bound: the frame length also includes the
32/// link-layer and IP headers, so each of those lengths is strictly smaller than
33/// the frame length.
34const MAX_GRO_FRAME_LEN: u16 = u16::MAX;
35
36/// Returns the number of bytes that can still be coalesced into a frame whose
37/// current length is `frame_len`.
38fn gro_headroom(frame_len: usize) -> usize {
39    usize::from(MAX_GRO_FRAME_LEN).saturating_sub(frame_len)
40}
41
42/// A slice view of a buffer, which is either a contiguous slice or linearized
43/// into scratch storage.
44#[derive(Debug)]
45pub enum BufferSlice<'a, 'b> {
46    /// A slice view directly into a contiguous buffer.
47    Contiguous(&'a mut [u8]),
48    /// A slice view into scratch storage after linearizing a non-contiguous
49    /// buffer.
50    Linearized(&'b mut [u8]),
51}
52
53impl BufferSlice<'_, '_> {
54    /// Returns an immutable slice view of the buffer.
55    pub fn as_slice(&self) -> &[u8] {
56        match self {
57            Self::Contiguous(s) => s,
58            Self::Linearized(s) => s,
59        }
60    }
61
62    /// Returns a mutable slice view of the buffer.
63    pub fn as_slice_mut(&mut self) -> &mut [u8] {
64        match self {
65            Self::Contiguous(s) => s,
66            Self::Linearized(s) => s,
67        }
68    }
69}
70
71/// A buffer that may be backed by a contiguous memory slice.
72pub trait MaybeContiguousBuffer {
73    /// Obtains a slice view into the buffer, linearizing into `storage` if
74    /// necessary, or returning `None` if linearization was required and
75    /// `storage` was `None`.
76    fn linearized<'a, 'b>(
77        &'a mut self,
78        storage: Option<&'b mut Vec<u8>>,
79    ) -> Option<BufferSlice<'a, 'b>>;
80
81    /// Obtains a mutable slice into the buffer without linearizing. Panics if
82    /// called on a buffer that's not contiguous.
83    fn unwrap_contiguous<'a>(&'a mut self) -> &'a mut [u8] {
84        assert_matches!(
85            self.linearized(None).expect("must be `Some` if contiguous"),
86            BufferSlice::Contiguous(slice) => slice
87        )
88    }
89}
90
91/// Frame type for GRO packet parsing.
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum GroFrameType {
94    /// Ethernet frame.
95    Ethernet,
96    /// Pure IP frame (IPv4 or IPv6).
97    PureIp(IpVersion),
98}
99
100/// The destination for a buffer involved in GRO (e.g., a device ID). Buffers
101/// with different destinations are not coalesced.
102pub trait GroBufferDestination: Eq {
103    /// Returns the frame type for this destination.
104    fn frame_type(&self) -> GroFrameType;
105}
106
107/// Ethernet flow identifier for GRO matching.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109struct EthernetFlowId {
110    src_mac: Mac,
111    dst_mac: Mac,
112    tag: Option<u32>,
113}
114
115/// Link layer flow identifier for GRO matching.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117enum LinkLayerFlowId {
118    Ethernet(EthernetFlowId),
119    PureIp,
120}
121
122/// Link-layer framing information used to derive GRO eligibility and flow ID.
123enum LinkLayerFrame {
124    Ethernet { flow_id: EthernetFlowId, ethertype: Option<EtherType> },
125    PureIp(IpVersion),
126}
127
128impl LinkLayerFrame {
129    fn parse<T: GroBufferDestination>(slice: &mut &[u8], target: &T) -> Option<LinkLayerFrame> {
130        match target.frame_type() {
131            GroFrameType::Ethernet => {
132                let frame = EthernetFrame::parse(slice, EthernetFrameLengthCheck::NoCheck).ok()?;
133                let flow_id = EthernetFlowId {
134                    src_mac: frame.src_mac(),
135                    dst_mac: frame.dst_mac(),
136                    tag: frame.tag(),
137                };
138                Some(LinkLayerFrame::Ethernet { flow_id, ethertype: frame.ethertype() })
139            }
140            GroFrameType::PureIp(ip_version) => Some(LinkLayerFrame::PureIp(ip_version)),
141        }
142    }
143
144    fn is_eligible_for_gro(&self) -> bool {
145        match self {
146            Self::Ethernet { .. } => true,
147            Self::PureIp(_) => true,
148        }
149    }
150
151    fn ethertype(&self) -> Option<EtherType> {
152        match self {
153            Self::Ethernet { ethertype, .. } => *ethertype,
154            Self::PureIp(ip_version) => Some(EtherType::from_ip_version(*ip_version)),
155        }
156    }
157
158    fn flow_id(&self) -> LinkLayerFlowId {
159        match self {
160            Self::Ethernet { flow_id, .. } => LinkLayerFlowId::Ethernet(*flow_id),
161            Self::PureIp(_) => LinkLayerFlowId::PureIp,
162        }
163    }
164}
165
166/// IPv4 flow identifier for GRO matching.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168struct Ipv4FlowId {
169    src_ip: Ipv4Addr,
170    dst_ip: Ipv4Addr,
171}
172
173/// IPv6 flow identifier for GRO matching.
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175struct Ipv6FlowId {
176    src_ip: Ipv6Addr,
177    dst_ip: Ipv6Addr,
178    flowlabel: u32,
179}
180
181/// IP layer flow identifier for GRO matching.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183enum IpFlowId {
184    Ipv4(Ipv4FlowId),
185    Ipv6(Ipv6FlowId),
186}
187
188impl IpFlowId {
189    /// Returns the partial sum of `segment`'s payload, computed from
190    /// `segment`'s checksum and this flow's pseudo header addresses.
191    fn recover_payload_partial_sum(&self, segment: &TcpSegment<&'_ [u8]>) -> [u8; 2] {
192        fn recover<A: IpAddress>(src_ip: A, dst_ip: A, segment: &TcpSegment<&'_ [u8]>) -> [u8; 2] {
193            segment
194                .recover_payload_partial_sum::<A::Version>(src_ip, dst_ip)
195                .expect("transport len fits in IP total length")
196        }
197
198        match self {
199            IpFlowId::Ipv4(Ipv4FlowId { src_ip, dst_ip }) => recover(*src_ip, *dst_ip, segment),
200            IpFlowId::Ipv6(Ipv6FlowId { src_ip, dst_ip, flowlabel: _ }) => {
201                recover(*src_ip, *dst_ip, segment)
202            }
203        }
204    }
205}
206
207enum IpPacket<'a> {
208    V4(Ipv4Packet<&'a [u8]>),
209    V6(Ipv6Packet<&'a [u8]>),
210}
211
212impl<'a> From<Ipv4Packet<&'a [u8]>> for IpPacket<'a> {
213    fn from(packet: Ipv4Packet<&'a [u8]>) -> IpPacket<'a> {
214        IpPacket::V4(packet)
215    }
216}
217
218impl<'a> From<Ipv6Packet<&'a [u8]>> for IpPacket<'a> {
219    fn from(packet: Ipv6Packet<&'a [u8]>) -> IpPacket<'a> {
220        IpPacket::V6(packet)
221    }
222}
223
224impl IpPacket<'_> {
225    fn total_len(&self) -> usize {
226        match self {
227            IpPacket::V4(packet) => packet.header_len() + packet.body().len(),
228            IpPacket::V6(packet) => packet.header_len() + packet.body().len(),
229        }
230    }
231}
232
233trait GroIpPacket<I: IpExt> {
234    fn is_eligible_for_gro(&self) -> bool;
235    fn ip_proto(&self) -> Option<IpProto>;
236    fn flow_id(&self) -> IpFlowId;
237}
238
239impl GroIpPacket<Ipv4> for Ipv4Packet<&[u8]> {
240    fn is_eligible_for_gro(&self) -> bool {
241        // Must not be fragmented: the transport headers are only
242        // available for flow matching in the first fragment.
243        self.fragment_offset() == packet_formats::ip::FragmentOffset::ZERO
244            && !self.mf_flag()
245            // Must not have options: this lets us avoid the need to
246            // copy them out of the buffer or arbitrarily index into the
247            // coalescing buffer to find them.
248            && self.header_len() == packet_formats::ipv4::HDR_PREFIX_LEN
249    }
250
251    fn ip_proto(&self) -> Option<IpProto> {
252        match self.proto() {
253            Ipv4Proto::Proto(proto) => Some(proto),
254            Ipv4Proto::Icmp | Ipv4Proto::Igmp | Ipv4Proto::Other(_) => None,
255        }
256    }
257
258    fn flow_id(&self) -> IpFlowId {
259        IpFlowId::Ipv4(Ipv4FlowId { src_ip: self.src_ip(), dst_ip: self.dst_ip() })
260    }
261}
262
263impl GroIpPacket<Ipv6> for Ipv6Packet<&[u8]> {
264    fn is_eligible_for_gro(&self) -> bool {
265        // Must not have extension headers. Note that this differs from
266        // Linux, which allows extension headers as long as they're
267        // equal. We omit these for the same reason as the IPv4 case
268        // above: to avoid needing to copy them or arbitrarily index
269        // into the coalescing buffer.
270        self.iter_extension_hdrs().next().is_none()
271    }
272
273    fn ip_proto(&self) -> Option<IpProto> {
274        match self.proto() {
275            Ipv6Proto::Proto(proto) => Some(proto),
276            Ipv6Proto::Icmpv6 | Ipv6Proto::NoNextHeader | Ipv6Proto::Other(_) => None,
277        }
278    }
279
280    fn flow_id(&self) -> IpFlowId {
281        IpFlowId::Ipv6(Ipv6FlowId {
282            src_ip: self.src_ip(),
283            dst_ip: self.dst_ip(),
284            flowlabel: self.flowlabel(),
285        })
286    }
287}
288
289/// TCP flow identifier for GRO matching.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291struct TcpFlowId {
292    src_port: NonZeroU16,
293    dst_port: NonZeroU16,
294}
295
296/// Transport layer flow identifier for GRO matching.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298enum TransportFlowId {
299    Tcp(TcpFlowId),
300}
301
302fn flag_requires_flush(segment: &TcpSegment<&'_ [u8]>) -> bool {
303    segment.syn() || segment.fin() || segment.rst() || segment.urg() || segment.psh()
304}
305
306enum TransportPacket<'a> {
307    Tcp(TcpSegment<&'a [u8]>),
308}
309
310impl<'a> TransportPacket<'a> {
311    fn parse<A: IpAddress>(
312        transport_view: &mut &'a [u8],
313        proto: IpProto,
314        src_ip: A,
315        dst_ip: A,
316        context: &mut NetworkParsingContext,
317    ) -> Option<TransportPacket<'a>> {
318        match proto {
319            IpProto::Tcp => TcpSegment::parse(
320                transport_view,
321                TcpParseArgs::with_context(src_ip, dst_ip, context),
322            )
323            .ok()
324            .map(TransportPacket::Tcp),
325            // TODO(https://fxbug.dev/555942793): Implement GRO for UDP.
326            IpProto::Udp | IpProto::Reserved => None,
327        }
328    }
329
330    fn is_eligible_for_gro(&self) -> bool {
331        match self {
332            Self::Tcp(_) => true,
333        }
334    }
335
336    fn flow_id(&self) -> TransportFlowId {
337        match self {
338            Self::Tcp(tcp) => TransportFlowId::Tcp(TcpFlowId {
339                src_port: tcp.src_port(),
340                dst_port: tcp.dst_port(),
341            }),
342        }
343    }
344
345    /// Checks if this packet must be flushed to the stack given that it was not
346    /// merged into a flow, either because it didn't match one or because it
347    /// failed the merge checks.
348    ///
349    /// If it need not be flushed, it's eligible to become a new flow.
350    fn flush_if_not_merged(&self) -> bool {
351        match self {
352            Self::Tcp(tcp) => {
353                let is_payload_segment = !tcp.body().is_empty();
354                !is_payload_segment || flag_requires_flush(tcp)
355            }
356        }
357    }
358}
359
360/// Flow identifier for GRO matching.
361///
362/// Note: if two packets have matching flow identifiers, it's not necessarily
363/// true that they'll be coalesced, but it is true that their fates (coalesced,
364/// flushed, or both) are shared.
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366struct GroFlowId {
367    link_layer: LinkLayerFlowId,
368    ip: IpFlowId,
369    transport: TransportFlowId,
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373struct HeaderOffsets {
374    pub ip_offset: usize,
375    pub transport_offset: usize,
376}
377
378/// Constructs the checksum offloading indication to include in the GRO output
379/// given the input offloading indication and the parsing context used to parse
380/// the GRO packet.
381fn build_csum_offload_output(
382    input: ChecksumRxOffloading,
383    context_post_parse: &NetworkParsingContext,
384) -> ChecksumRxOffloading {
385    let verified = context_post_parse.verified_checksum_count();
386    match input {
387        ChecksumRxOffloading::FullyOffloaded => ChecksumRxOffloading::FullyOffloaded,
388        ChecksumRxOffloading::Offloaded(Some(n)) => {
389            ChecksumRxOffloading::Offloaded(Some(n.saturating_add(verified)))
390        }
391        ChecksumRxOffloading::Offloaded(None) => {
392            ChecksumRxOffloading::Offloaded(NonZeroU16::new(verified))
393        }
394    }
395}
396
397/// Parsed packet containing all metadata needed for GRO matching and
398/// accumulation.
399struct GroPacket<'a> {
400    pub flow_id: GroFlowId,
401    pub offsets: HeaderOffsets,
402    pub ip: IpPacket<'a>,
403    pub transport: TransportPacket<'a>,
404}
405
406impl<'a> GroPacket<'a> {
407    fn parse<T: GroBufferDestination>(
408        slice: &'a [u8],
409        target: &T,
410        context: &mut NetworkParsingContext,
411    ) -> Option<GroPacket<'a>> {
412        let total_len = slice.len();
413        let mut view = slice;
414
415        let ll_frame =
416            LinkLayerFrame::parse(&mut view, target).filter(|f| f.is_eligible_for_gro())?;
417        let ethertype = ll_frame.ethertype()?;
418
419        let ip_offset = total_len - view.len();
420        let ip_version = ethertype.to_ip_version()?;
421        let (ip, ip_flow_id, transport_offset, transport) = for_any_ip_version!(ip_version, I, {
422            let ip = <I as IpExt>::Packet::parse(&mut view, ())
423                .ok()
424                .filter(|p| p.is_eligible_for_gro())?;
425            let flow_id = ip.flow_id();
426            let proto = ip.ip_proto()?;
427            let src = ip.src_ip();
428            let dst = ip.dst_ip();
429
430            // NB: the IP parser trims any bytes past the end of the IP payload
431            // (e.g. link layer padding) off of `view`, so `total_len -
432            // view.len()` would overshoot the start of the transport header by
433            // the number of trailing bytes. Derive the offset from the IP
434            // header length instead.
435            let transport_offset = ip_offset + ip.header_len();
436            let transport = TransportPacket::parse(&mut view, proto, src, dst, context)
437                .filter(|p| p.is_eligible_for_gro())?;
438
439            (ip.into(), flow_id, transport_offset, transport)
440        });
441
442        let offsets = HeaderOffsets { ip_offset, transport_offset };
443        let flow_id = GroFlowId {
444            link_layer: ll_frame.flow_id(),
445            ip: ip_flow_id,
446            transport: transport.flow_id(),
447        };
448
449        Some(GroPacket { flow_id, offsets, ip, transport })
450    }
451
452    /// The offset of the end of the IP payload in the frame this packet was
453    /// parsed from, as indicated by the length field in its IP header.
454    ///
455    /// Anything past this point is a trailing byte that is not part of the
456    /// packet, typically link layer padding on a frame smaller than the link
457    /// layer minimum.
458    ///
459    /// Note that this can never be past the end of the frame: the IP parsers
460    /// reject a frame whose body is shorter than its header claims, so such a
461    /// frame never makes it this far.
462    fn payload_end(&self) -> usize {
463        self.offsets.ip_offset + self.ip.total_len()
464    }
465
466    /// Checks if this packet must be flushed to the stack given that it was
467    /// not merged into a flow.
468    fn flush_if_not_merged(&self, frame_len: usize) -> bool {
469        // A frame that is already at the maximum coalesced length can never
470        // accept another segment, so don't let it establish a flow. This also
471        // guarantees that every flow's accumulated transport packet length can
472        // be represented in the IP pseudo-header.
473        gro_headroom(frame_len) == 0 || self.transport.flush_if_not_merged()
474    }
475}
476
477enum CoalesceResult {
478    Flush,
479    Continue,
480}
481
482/// Encapsulates raw TCP options bytes up to maximum supported options length.
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484struct TcpOptions {
485    bytes: [u8; MAX_OPTIONS_LEN],
486    len: usize,
487}
488
489impl TcpOptions {
490    fn from_slice(slice: &[u8]) -> Self {
491        let mut bytes = [0u8; MAX_OPTIONS_LEN];
492        // Note: we're depending on TCP segment parsing (specifically
493        // `TcpSegmentRaw::parse`) to uphold the invariant that this length is
494        // <= `MAX_OPTIONS_LEN`.
495        let len = slice.len();
496        bytes[..len].copy_from_slice(slice);
497        Self { bytes, len }
498    }
499
500    fn as_bytes(&self) -> &[u8] {
501        &self.bytes[..self.len]
502    }
503}
504
505/// The flags that coalesced segments are allowed to disagree on.
506const PSH_OR_FIN: u8 = packet_formats::tcp::flags::PSH | packet_formats::tcp::flags::FIN;
507
508/// TCP flow matching criteria and accumulation state.
509#[derive(Debug, Clone, PartialEq, Eq)]
510struct TcpFlow {
511    flags: u8,
512    reserved_bits: u8,
513    ack: Option<u32>,
514    win: u16,
515    options: TcpOptions,
516    /// The sequence number that the next coalesced segment must start at.
517    ///
518    /// This only counts payload bytes; it ignores the virtual sequence number
519    /// length consumed by a `FIN`. That's fine because anything received after
520    /// a `FIN` is discarded by the state machine, so a segment that would only
521    /// match the `FIN`-inclusive value can't be usefully coalesced anyways.
522    next_seq: u32,
523    checksum: [u8; 2],
524    /// The total length of the first segment in the flow (header, options, and
525    /// payload), i.e. the pseudo header length that is covered by the
526    /// accumulated `checksum`.
527    ///
528    /// The difference between this and the final coalesced length is applied to
529    /// the checksum once in `finalize`.
530    orig_tcp_len: usize,
531    /// Whether the accumulated length of the coalesced TCP segment (header,
532    /// options, and payload so far) is odd.
533    ///
534    /// Determines whether the next coalesced payload starts on an odd byte
535    /// offset; see [`TcpFlow::update_checksum`].
536    tcp_len_is_odd: bool,
537    /// The maximum size of the individual segment payloads that comprise the
538    /// coalesced segment. These are all required to have the same length, with
539    /// the possible exception of the last which is permitted to be shorter.
540    ///
541    /// This is tracked so that the segment can be faithfully resegmented in the
542    /// event that it needs to be forwarded.
543    gso_size: NonZeroU16,
544}
545
546impl TcpFlow {
547    fn new(segment: &TcpSegment<&'_ [u8]>) -> Self {
548        let payload_len = u16::try_from(segment.body().len()).expect("payload len fits in u16");
549        let next_seq = segment.seq_num().wrapping_add(u32::from(payload_len));
550        let options = TcpOptions::from_slice(segment.options().bytes());
551        // `TransportFlow::flush_if_not_merged` ensures that only payload
552        // segments can start GRO flows, so it's safe to assume here that
553        // payload length is nonzero.
554        let gso_size = NonZeroU16::new(payload_len).expect("payload len is non-zero");
555        let tcp_len = segment.total_segment_len();
556        Self {
557            flags: segment.flags(),
558            reserved_bits: segment.reserved_bits(),
559            ack: segment.ack_num(),
560            win: segment.window_size(),
561            options,
562            next_seq,
563            checksum: segment.checksum(),
564            orig_tcp_len: tcp_len,
565            tcp_len_is_odd: tcp_len % 2 == 1,
566            gso_size,
567        }
568    }
569
570    /// Determines whether a TCP segment already matched to this flow is
571    /// permitted to coalesce with it.
572    fn can_coalesce(&self, current_frame_len: usize, segment: &TcpSegment<&'_ [u8]>) -> bool {
573        let Self {
574            flags,
575            reserved_bits,
576            ack,
577            win,
578            options,
579            next_seq,
580            checksum: _,
581            orig_tcp_len: _,
582            tcp_len_is_odd: _,
583            gso_size,
584        } = self;
585        let payload_len = segment.body().len();
586        // All coalesced segments must have size `gso_size` save for the last,
587        // which may be smaller but cannot be larger.
588        if payload_len == 0 || payload_len > usize::from(gso_size.get()) {
589            return false;
590        }
591        payload_len <= gro_headroom(current_frame_len)
592            // Coalesced segments' flags may only disagree on `PSH` or `FIN`.
593            // The coalesced segment will receive the union of the flags.
594            && (*flags & !PSH_OR_FIN) == (segment.flags() & !PSH_OR_FIN)
595            // The reserved bits must match exactly; they may carry semantics
596            // that we don't know about.
597            && *reserved_bits == segment.reserved_bits()
598            && *ack == segment.ack_num()
599            && *win == segment.window_size()
600            && options.as_bytes() == segment.options().bytes()
601            // Sequence numbers must be contiguous.
602            && *next_seq == segment.seq_num()
603    }
604
605    /// Updates the accumulated checksum to cover `segment`'s payload.
606    ///
607    /// The pseudo header length is not updated here; it is applied once in
608    /// `finalize`.
609    fn update_checksum(&mut self, ip_flow: &IpFlowId, segment: &TcpSegment<&'_ [u8]>) {
610        let mut partial_sum = ip_flow.recover_payload_partial_sum(segment);
611        // The TCP checksum is computed by summing the pseudo-header and segment
612        // two bytes at a time. If the accumulated segment length is odd, then
613        // the newly added payload would start in the middle of a two-byte pair.
614        // Because the Internet Checksum is byte-order independent, we can
615        // correct for this by swapping the bytes of the new payload's partial
616        // sum before adding it to our checksum.
617        if self.tcp_len_is_odd {
618            partial_sum = [partial_sum[1], partial_sum[0]];
619        }
620        self.checksum = internet_checksum::add(self.checksum, &partial_sum);
621    }
622
623    /// Coalesces a TCP segment into this flow.
624    ///
625    /// Note that this operation does not modify the header of the coalesced TCP
626    /// segment. Instead it performs internal bookkeeping and performs the
627    /// modifications once in `finalize`.
628    fn coalesce(
629        &mut self,
630        ip_flow: &IpFlowId,
631        segment: &TcpSegment<&'_ [u8]>,
632        coalesce_into: &mut Vec<u8>,
633    ) -> CoalesceResult {
634        coalesce_into.extend_from_slice(segment.body());
635        let added_payload_len =
636            u16::try_from(segment.body().len()).expect("payload len fits in u16");
637        self.update_checksum(ip_flow, segment);
638
639        self.tcp_len_is_odd ^= added_payload_len % 2 == 1;
640        self.next_seq = segment.seq_num().wrapping_add(u32::from(added_payload_len));
641        self.flags |= segment.flags() & PSH_OR_FIN;
642
643        // All GRO'd segments must be the same length, save for the last one,
644        // which may be shorter. This allows us to pass a single value up the
645        // stack to indicate the original segment size and ensure that we can
646        // resegment along the original segment boundaries if we end up
647        // forwarding the frame.
648        let is_smaller_than_gso = added_payload_len < self.gso_size.get();
649        if is_smaller_than_gso || flag_requires_flush(segment) {
650            CoalesceResult::Flush
651        } else {
652            CoalesceResult::Continue
653        }
654    }
655
656    /// Finalizes the coalesced TCP segment by updating the header with the
657    /// correct checksum and the merged flags (which may only set `FIN` and/or
658    /// `PSH`).
659    ///
660    /// Returns the GSO segment size (the payload length of each coalesced
661    /// segment).
662    fn finalize(&self, mut transport_view: &mut [u8]) -> NonZeroU16 {
663        // By the time the flow is finalized, the view holds exactly the
664        // coalesced segment: any trailing bytes on the seed frame were trimmed
665        // before the first payload was appended.
666        let tcp_len = transport_view.len();
667        let mut tcp =
668            TcpSegmentRaw::parse_mut(&mut transport_view, ()).expect("valid TCP segment header");
669
670        // NB: the IPv6 pseudo header carries a 32-bit upper layer length where
671        // the IPv4 pseudo header carries a 16-bit TCP length.
672        // `MAX_GRO_FRAME_LENGTH` ensures that our TCP segment length can be
673        // represented by the latter, but we don't bother casting from `usize`
674        // because the upper bits must all be zero anyways and thus have no
675        // effect on the checksum.
676        let checksum = internet_checksum::update(
677            self.checksum,
678            &self.orig_tcp_len.to_be_bytes(),
679            &tcp_len.to_be_bytes(),
680        );
681
682        tcp.set_checksum(checksum);
683        tcp.set_flags(self.flags);
684        self.gso_size
685    }
686}
687
688/// Transport-specific flow state.
689#[derive(Debug, Clone, PartialEq, Eq)]
690enum TransportFlow {
691    Tcp(TcpFlow),
692}
693
694impl TransportFlow {
695    fn new(packet: &TransportPacket<'_>) -> Self {
696        match packet {
697            TransportPacket::Tcp(tcp) => Self::Tcp(TcpFlow::new(tcp)),
698        }
699    }
700
701    fn can_coalesce(&self, current_frame_len: usize, packet: &TransportPacket<'_>) -> bool {
702        match (self, packet) {
703            (Self::Tcp(flow), TransportPacket::Tcp(tcp)) => {
704                flow.can_coalesce(current_frame_len, tcp)
705            }
706        }
707    }
708
709    fn coalesce(
710        &mut self,
711        ip_flow: &IpFlowId,
712        packet: &TransportPacket<'_>,
713        coalesce_into: &mut Vec<u8>,
714    ) -> CoalesceResult {
715        match (self, packet) {
716            (Self::Tcp(flow), TransportPacket::Tcp(tcp)) => {
717                flow.coalesce(ip_flow, tcp, coalesce_into)
718            }
719        }
720    }
721
722    fn finalize(&self, transport_view: &mut [u8]) -> NonZeroU16 {
723        match self {
724            Self::Tcp(tcp) => tcp.finalize(transport_view),
725        }
726    }
727}
728
729#[derive(Debug, Clone, Copy, PartialEq, Eq)]
730enum Ipv4IdState {
731    /// Only one packet seen so far with this ID.
732    Initial(u16),
733    /// IDs are consistent across packets.
734    Consistent(u16),
735    /// IDs are increasing by 1 across packets; stores the last seen ID.
736    Increasing(u16),
737}
738
739impl Ipv4IdState {
740    fn can_coalesce(&self, next_id: u16) -> bool {
741        match self {
742            Self::Initial(id) => next_id == *id || next_id == id.wrapping_add(1),
743            Self::Consistent(id) => next_id == *id,
744            Self::Increasing(prev_id) => next_id == prev_id.wrapping_add(1),
745        }
746    }
747
748    fn coalesce(&mut self, next_id: u16) {
749        *self = match self {
750            Self::Initial(id) => {
751                if next_id == *id {
752                    Self::Consistent(next_id)
753                } else if next_id == id.wrapping_add(1) {
754                    Self::Increasing(next_id)
755                } else {
756                    unreachable!("cannot coalesce non-matching ID");
757                }
758            }
759            Self::Consistent(id) => {
760                debug_assert_eq!(next_id, *id);
761                Self::Consistent(next_id)
762            }
763            Self::Increasing(_) => Self::Increasing(next_id),
764        };
765    }
766}
767
768/// IPv4 flow post-match fields and header finalization.
769#[derive(Debug, Clone, Copy, PartialEq, Eq)]
770struct Ipv4Flow {
771    ttl: u8,
772    dscp_and_ecn: DscpAndEcn,
773    df_flag: bool,
774    id_state: Ipv4IdState,
775}
776
777impl Ipv4Flow {
778    fn new(packet: &impl Ipv4Header) -> Ipv4Flow {
779        Self {
780            ttl: packet.ttl(),
781            dscp_and_ecn: packet.dscp_and_ecn(),
782            df_flag: packet.df_flag(),
783            id_state: Ipv4IdState::Initial(packet.id()),
784        }
785    }
786
787    /// Determines whether an IPv4 packet already matched to this flow is
788    /// permitted to coalesce with it.
789    fn can_coalesce(&self, packet: &impl Ipv4Header) -> bool {
790        let Self { ttl, dscp_and_ecn, df_flag, id_state } = self;
791        *ttl == packet.ttl()
792            && *dscp_and_ecn == packet.dscp_and_ecn()
793            && *df_flag == packet.df_flag()
794            && id_state.can_coalesce(packet.id())
795    }
796
797    fn coalesce(&mut self, packet: &impl Ipv4Header) {
798        self.id_state.coalesce(packet.id());
799    }
800
801    /// Finalizes the coalesced IPv4 packet by updating the header with the new
802    /// length and checksum.
803    fn finalize(&self, mut ip_view: &mut [u8]) {
804        let total_len = ip_view.len();
805        let mut ip = Ipv4PacketRaw::parse_mut(&mut ip_view, ()).expect("valid IPv4 header");
806        let new_len_u16 = u16::try_from(total_len).expect("IP total length fits in u16");
807        ip.set_total_len_and_update_checksum(new_len_u16);
808    }
809
810    fn ipv4_id_mode(&self) -> Ipv4IdMode {
811        match self.id_state {
812            Ipv4IdState::Consistent(_) => Ipv4IdMode::Fixed,
813            Ipv4IdState::Increasing(_) => Ipv4IdMode::Incrementing,
814            Ipv4IdState::Initial(_) => {
815                unreachable!("coalesced flow with num_coalesced > 1 cannot be Initial")
816            }
817        }
818    }
819}
820
821/// IPv6 flow post-match fields and header finalization.
822#[derive(Debug, Clone, Copy, PartialEq, Eq)]
823struct Ipv6Flow {
824    hop_limit: u8,
825    dscp_and_ecn: DscpAndEcn,
826}
827
828impl Ipv6Flow {
829    fn new(packet: &impl Ipv6Header) -> Ipv6Flow {
830        Self { hop_limit: packet.hop_limit(), dscp_and_ecn: packet.dscp_and_ecn() }
831    }
832
833    /// Determines whether an IPv6 packet already matched to this flow is
834    /// permitted to coalesce with it.
835    fn can_coalesce(&self, packet: &impl Ipv6Header) -> bool {
836        let Self { hop_limit, dscp_and_ecn } = self;
837        *hop_limit == packet.hop_limit() && *dscp_and_ecn == packet.dscp_and_ecn()
838    }
839
840    fn coalesce(&mut self, _packet: &impl Ipv6Header) {
841        // This is a no-op: unlike IPv4, which must track the identification
842        // field across coalesced packets, no IPv6 header field needs to be
843        // accumulated. The header is updated once in `finalize`.
844    }
845
846    /// Finalizes the coalesced IPv6 packet by updating the header with the new
847    /// payload length.
848    fn finalize(&self, mut ip_view: &mut [u8]) {
849        let payload_len = ip_view.len() - IPV6_FIXED_HDR_LEN;
850        let mut ip = Ipv6PacketRaw::parse_mut(&mut ip_view, ()).expect("valid IPv6 header");
851        let payload_len_u16 = u16::try_from(payload_len).expect("IPv6 payload len fits in u16");
852        ip.set_payload_len(payload_len_u16);
853    }
854}
855
856/// IP-specific flow state.
857#[derive(Debug, Clone, Copy, PartialEq, Eq)]
858enum IpFlow {
859    Ipv4(Ipv4Flow),
860    Ipv6(Ipv6Flow),
861}
862
863impl IpFlow {
864    fn new(ip: &IpPacket<'_>) -> Self {
865        match ip {
866            IpPacket::V4(packet) => IpFlow::Ipv4(Ipv4Flow::new(packet)),
867            IpPacket::V6(packet) => IpFlow::Ipv6(Ipv6Flow::new(packet)),
868        }
869    }
870
871    fn can_coalesce(&self, ip: &IpPacket<'_>) -> bool {
872        match (self, ip) {
873            (Self::Ipv4(flow), IpPacket::V4(packet)) => flow.can_coalesce(packet),
874            (Self::Ipv6(flow), IpPacket::V6(packet)) => flow.can_coalesce(packet),
875            (Self::Ipv4(_), IpPacket::V6(_)) | (Self::Ipv6(_), IpPacket::V4(_)) => false,
876        }
877    }
878
879    fn coalesce(&mut self, ip: &IpPacket<'_>) {
880        match (self, ip) {
881            (Self::Ipv4(flow), IpPacket::V4(packet)) => flow.coalesce(packet),
882            (Self::Ipv6(flow), IpPacket::V6(packet)) => flow.coalesce(packet),
883            (Self::Ipv4(_), IpPacket::V6(_)) | (Self::Ipv6(_), IpPacket::V4(_)) => {
884                unreachable!("mismatched IP versions can't be coalesced")
885            }
886        }
887    }
888
889    fn finalize(&self, ip_view: &mut [u8]) {
890        match self {
891            Self::Ipv4(flow) => flow.finalize(ip_view),
892            Self::Ipv6(flow) => flow.finalize(ip_view),
893        }
894    }
895
896    fn ipv4_id_mode(&self) -> Option<Ipv4IdMode> {
897        match self {
898            Self::Ipv4(flow) => Some(flow.ipv4_id_mode()),
899            Self::Ipv6(_) => None,
900        }
901    }
902}
903
904struct GroFlow<T> {
905    target: T,
906    flow_id: GroFlowId,
907    offsets: HeaderOffsets,
908    ip: IpFlow,
909    transport: TransportFlow,
910    checksum_offload: ChecksumRxOffloading,
911    num_coalesced: usize,
912}
913
914impl<T: Eq> GroFlow<T> {
915    fn new(target: T, parsed: GroPacket<'_>, checksum_offload: ChecksumRxOffloading) -> Self {
916        let GroPacket { flow_id, offsets, ip, transport } = parsed;
917        Self {
918            target,
919            flow_id,
920            offsets,
921            ip: IpFlow::new(&ip),
922            transport: TransportFlow::new(&transport),
923            checksum_offload,
924            num_coalesced: 1,
925        }
926    }
927
928    fn matches(
929        &self,
930        target: &T,
931        flow_id: &GroFlowId,
932        checksum_offload: ChecksumRxOffloading,
933    ) -> bool {
934        self.target == *target
935            && self.flow_id == *flow_id
936            && self.checksum_offload == checksum_offload
937    }
938
939    fn can_coalesce(&self, current_frame_len: usize, parsed: &GroPacket<'_>) -> bool {
940        self.ip.can_coalesce(&parsed.ip)
941            && self.transport.can_coalesce(current_frame_len, &parsed.transport)
942    }
943
944    fn coalesce(&mut self, parsed: GroPacket<'_>, coalesce_into: &mut Vec<u8>) -> CoalesceResult {
945        self.num_coalesced += 1;
946        self.ip.coalesce(&parsed.ip);
947        self.transport.coalesce(&self.flow_id.ip, &parsed.transport, coalesce_into)
948    }
949
950    fn finalize(&self, view: &mut [u8]) -> Option<GsoInfo> {
951        let HeaderOffsets { ip_offset, transport_offset } = self.offsets;
952        if self.num_coalesced > 1 {
953            self.ip.finalize(&mut view[ip_offset..]);
954            let gso_size = self.transport.finalize(&mut view[transport_offset..]);
955            let ipv4_id_mode = self.ip.ipv4_id_mode();
956            Some(GsoInfo { gso_size, ipv4_id_mode })
957        } else {
958            None
959        }
960    }
961}
962
963/// An input buffer item for GRO processing.
964#[derive(Debug, PartialEq, Eq)]
965pub struct GroInputItem<B, T> {
966    /// The buffer.
967    pub buffer: B,
968    /// Target for the incoming frame.
969    pub target: T,
970    /// Checksum offload state for the incoming frame.
971    pub checksum_offload: ChecksumRxOffloading,
972}
973
974/// Buffers associated with a GRO output item.
975#[derive(Debug)]
976pub enum GroOutputBuffers<'a, B, O> {
977    /// A single contiguous buffer.
978    Contiguous(B),
979    /// A single buffer that was linearized into temporary scratch space.
980    Linearized {
981        /// The linearized slice view into scratch storage.
982        slice: &'a mut [u8],
983        /// The original buffer.
984        buffer: B,
985    },
986    /// A coalesced set of buffers.
987    Coalesced {
988        /// The coalesced slice view into coalescing storage.
989        slice: &'a mut [u8],
990        /// The original buffers that formed this frame.
991        buffers: O,
992    },
993}
994
995impl<'a, B: MaybeContiguousBuffer, O> GroOutputBuffers<'a, B, O> {
996    /// Returns a mutable slice view of the frame buffer.
997    pub fn slice_mut(&mut self) -> &mut [u8] {
998        match self {
999            Self::Contiguous(b) => b.unwrap_contiguous(),
1000            Self::Linearized { slice, .. } | Self::Coalesced { slice, .. } => slice,
1001        }
1002    }
1003}
1004
1005/// An item yielded by GRO processing.
1006#[derive(Debug)]
1007pub struct GroOutputItem<'a, B, T, O> {
1008    /// Target for the frame.
1009    pub target: T,
1010    /// Checksum offload state for the frame.
1011    pub checksum_offload: ChecksumRxOffloading,
1012    /// GSO metadata if the frame was coalesced from multiple segments.
1013    pub gso_info: Option<GsoInfo>,
1014    /// The buffer(s) associated with this frame.
1015    pub buffers: GroOutputBuffers<'a, B, O>,
1016}
1017
1018/// Persistent reusable buffer storage for GRO to save on per-batch allocations.
1019#[derive(Debug, Derivative)]
1020#[derivative(Default(bound = ""))]
1021pub struct GroBufferStorage<B> {
1022    /// Buffer for GRO coalescing.
1023    coalescing_vec: Vec<u8>,
1024    /// Holds onto the original buffers while building a coalesced frame before
1025    /// it's passed to the stack.
1026    coalesced_buffers: Vec<B>,
1027    /// Buffer for linearization of fragmented buffers.
1028    linearization_vec: Vec<u8>,
1029}
1030
1031impl<B> GroBufferStorage<B> {
1032    /// Creates a new `GroBufferStorage`.
1033    pub fn new() -> Self {
1034        Self::default()
1035    }
1036
1037    fn clear(&mut self) {
1038        self.coalescing_vec.clear();
1039        self.linearization_vec.clear();
1040        self.coalesced_buffers.clear();
1041    }
1042}
1043
1044impl<B: MaybeContiguousBuffer> GroBufferStorage<B> {
1045    /// Adapts the provided iterator of packet buffers into a GRO iterator.
1046    pub fn coalesce<I, T>(&mut self, iter: I, enable_tcp_gro: bool) -> GroIter<'_, I, B, T>
1047    where
1048        I: Iterator<Item = GroInputItem<B, T>>,
1049        T: GroBufferDestination,
1050    {
1051        let enable_tcp_gro = match iter.size_hint() {
1052            (_, Some(1)) => false,
1053            _ => enable_tcp_gro,
1054        };
1055        GroIter::new(iter, self, enable_tcp_gro)
1056    }
1057
1058    fn build_output<'a, T: Eq>(
1059        &'a mut self,
1060        ActiveFlow { flow, buffers }: ActiveFlow<B, T>,
1061    ) -> GroOutputItem<'a, B, T, alloc::vec::Drain<'a, B>> {
1062        match buffers {
1063            // Nothing was ever merged into this flow, so its buffer was never
1064            // copied into the coalescing buffer; hand it back untouched.
1065            ActiveFlowBuffers::Single { buffer, payload_end: _ } => GroOutputItem {
1066                target: flow.target,
1067                checksum_offload: flow.checksum_offload,
1068                gso_info: None,
1069                buffers: GroOutputBuffers::Contiguous(buffer),
1070            },
1071            ActiveFlowBuffers::Coalesced => {
1072                let GroBufferStorage { coalescing_vec, coalesced_buffers, .. } = self;
1073
1074                let gso_info = flow.finalize(&mut coalescing_vec[..]);
1075
1076                let GroFlow { target, checksum_offload, num_coalesced, .. } = flow;
1077                let buffers = GroOutputBuffers::Coalesced {
1078                    slice: &mut coalescing_vec[..],
1079                    buffers: coalesced_buffers.drain(..num_coalesced),
1080                };
1081                GroOutputItem { target, checksum_offload, gso_info, buffers }
1082            }
1083        }
1084    }
1085}
1086
1087/// The buffers held by an [`ActiveFlow`].
1088enum ActiveFlowBuffers<B> {
1089    /// Nothing has been merged into the flow yet, so its seed buffer is still
1090    /// held verbatim and has not been copied into the coalescing buffer.
1091    Single {
1092        buffer: B,
1093        /// The seed frame's [`GroPacket::payload_end`]; only the bytes before
1094        /// this point are copied into the coalescing buffer, since any trailing
1095        /// bytes would be stranded in the middle of the coalesced payload.
1096        payload_end: usize,
1097    },
1098    /// The flow's frame lives in the coalescing buffer and the buffers that
1099    /// formed it are held by [`GroBufferStorage`].
1100    Coalesced,
1101}
1102
1103/// A [`GroFlow`] along with the buffers it currently holds.
1104struct ActiveFlow<B, T> {
1105    flow: GroFlow<T>,
1106    buffers: ActiveFlowBuffers<B>,
1107}
1108
1109impl<B, T> ActiveFlow<B, T> {
1110    /// The length the frame for this flow would have if a payload were merged
1111    /// into it right now.
1112    fn frame_len(&self, coalescing_vec_len: usize) -> usize {
1113        match &self.buffers {
1114            // Nothing has been merged in yet, so the buffer still holds the
1115            // seed frame verbatim. Any trailing bytes it carries are dropped on
1116            // the first merge, so they don't count towards the frame length.
1117            ActiveFlowBuffers::Single { payload_end, .. } => *payload_end,
1118            ActiveFlowBuffers::Coalesced => coalescing_vec_len,
1119        }
1120    }
1121}
1122
1123impl<B: MaybeContiguousBuffer, T: Eq> ActiveFlow<B, T> {
1124    fn matches(
1125        &self,
1126        target: &T,
1127        flow_id: &GroFlowId,
1128        checksum_offload: ChecksumRxOffloading,
1129    ) -> bool {
1130        self.flow.matches(target, flow_id, checksum_offload)
1131    }
1132
1133    fn can_coalesce(&self, coalescing_vec_len: usize, parsed: &GroPacket<'_>) -> bool {
1134        self.flow.can_coalesce(self.frame_len(coalescing_vec_len), parsed)
1135    }
1136
1137    /// Merges `parsed` into the flow's frame in `coalesce_into`.
1138    ///
1139    /// If the flow is still holding its seed buffer verbatim, the seed is first
1140    /// copied into `coalesce_into` and the buffer is moved into `hold_buffers`,
1141    /// which must keep the buffers alive until the frame is handed to the
1142    /// stack.
1143    fn coalesce(
1144        &mut self,
1145        parsed: GroPacket<'_>,
1146        coalesce_into: &mut Vec<u8>,
1147        hold_buffers: &mut Vec<B>,
1148    ) -> CoalesceResult {
1149        match core::mem::replace(&mut self.buffers, ActiveFlowBuffers::Coalesced) {
1150            ActiveFlowBuffers::Single { mut buffer, payload_end } => {
1151                coalesce_into.clear();
1152                // Unwrapping is okay here because `ActiveFlowBuffers::Single`
1153                // is only ever constructed for a buffer that was found to be
1154                // contiguous; a fragmented buffer is linearized into the
1155                // coalescing buffer up front and recorded as
1156                // `ActiveFlowBuffers::Coalesced`.
1157                //
1158                // Any trailing bytes (e.g. link layer padding) are dropped;
1159                // only the packet itself may be coalesced against.
1160                coalesce_into.extend_from_slice(&buffer.unwrap_contiguous()[..payload_end]);
1161                hold_buffers.push(buffer);
1162            }
1163            ActiveFlowBuffers::Coalesced => {}
1164        }
1165        self.flow.coalesce(parsed, coalesce_into)
1166    }
1167}
1168
1169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1170enum BufferLinearization {
1171    Contiguous,
1172    Linearized,
1173}
1174
1175/// The subsequent call to `GroIter::next()` must establish this as the active
1176/// flow.
1177///
1178/// To avoid double-linearizing a fragmented buffer, `linearization` indicates
1179/// whether `buffer` is contiguous or its linearization is still held in the
1180/// linearization buffer.
1181struct PendingFlow<B, T> {
1182    buffer: B,
1183    flow: GroFlow<T>,
1184    /// The seed frame's [`GroPacket::payload_end`]; only the bytes before this
1185    /// point are copied into the coalescing buffer, since any trailing bytes
1186    /// would be stranded in the middle of the coalesced payload.
1187    seed_payload_end: usize,
1188    linearization: BufferLinearization,
1189}
1190
1191/// The subsequent call to `GroIter::next()` must flush this buffer.
1192///
1193/// To avoid double-linearizing a fragmented buffer, `linearization` indicates
1194/// whether `buffer` is contiguous or its linearization is still held in the
1195/// linearization buffer.
1196struct PendingFlush<B, T> {
1197    buffer: B,
1198    target: T,
1199    linearization: BufferLinearization,
1200    checksum_offload: ChecksumRxOffloading,
1201}
1202
1203enum PendingItem<B, T> {
1204    NewFlow(PendingFlow<B, T>),
1205    Flush(PendingFlush<B, T>),
1206}
1207
1208/// An iterator adapter for GRO processing.
1209pub struct GroIter<'a, I, B, T> {
1210    iter: I,
1211    storage: &'a mut GroBufferStorage<B>,
1212    /// The active GRO flow that GRO is attempting to match.
1213    active_flow: Option<ActiveFlow<B, T>>,
1214    /// Item pending processing on next iteration.
1215    pending_item: Option<PendingItem<B, T>>,
1216    enable_tcp_gro: bool,
1217}
1218
1219impl<'a, I, B, T> GroIter<'a, I, B, T>
1220where
1221    B: MaybeContiguousBuffer,
1222{
1223    fn new(iter: I, storage: &'a mut GroBufferStorage<B>, enable_tcp_gro: bool) -> Self {
1224        Self { iter, storage, active_flow: None, pending_item: None, enable_tcp_gro }
1225    }
1226}
1227
1228impl<'a, I, B, T> Drop for GroIter<'a, I, B, T> {
1229    fn drop(&mut self) {
1230        self.storage.clear();
1231    }
1232}
1233
1234enum ProcessingResult<'a, B, T, O> {
1235    Continue,
1236    Return(GroOutputItem<'a, B, T, O>),
1237}
1238
1239impl<'a, I, B, T> GroIter<'a, I, B, T>
1240where
1241    B: MaybeContiguousBuffer,
1242    T: GroBufferDestination,
1243    I: Iterator<Item = GroInputItem<B, T>>,
1244{
1245    /// Advances the iterator and returns the next GRO output item.
1246    pub fn next<'b>(&'b mut self) -> Option<GroOutputItem<'b, B, T, alloc::vec::Drain<'b, B>>> {
1247        loop {
1248            if let Some(i) = self.pending_item.take() {
1249                match self.process_pending(i) {
1250                    ProcessingResult::Continue => continue,
1251                    ProcessingResult::Return(out) => return Some(out),
1252                };
1253            }
1254            if let Some(i) = self.iter.next() {
1255                match self.process_input(i) {
1256                    ProcessingResult::Continue => continue,
1257                    ProcessingResult::Return(out) => return Some(out),
1258                };
1259            }
1260            if let Some(f) = self.active_flow.take() {
1261                return Some(self.storage.build_output(f));
1262            }
1263            return None;
1264        }
1265    }
1266
1267    fn process_pending<'b>(
1268        &'b mut self,
1269        pending: PendingItem<B, T>,
1270    ) -> ProcessingResult<'b, B, T, alloc::vec::Drain<'b, B>> {
1271        let Self { storage, active_flow, .. } = self;
1272        match pending {
1273            PendingItem::NewFlow(pending) => {
1274                let PendingFlow { buffer, flow, seed_payload_end, linearization } = pending;
1275                let buffers = match linearization {
1276                    // The buffer is contiguous, so the flow can hold onto it
1277                    // verbatim and defer copying it into the coalescing buffer
1278                    // until something merges into the flow.
1279                    BufferLinearization::Contiguous => {
1280                        ActiveFlowBuffers::Single { buffer, payload_end: seed_payload_end }
1281                    }
1282                    BufferLinearization::Linearized => {
1283                        // The buffer is fragmented and its linearization is
1284                        // already in the linearization buffer; move it into the
1285                        // coalescing buffer, which the previous active flow has
1286                        // now released.
1287                        //
1288                        // Drop any trailing bytes (e.g. link layer padding)
1289                        // from the seed frame; only the packet itself may be
1290                        // coalesced against.
1291                        let GroBufferStorage {
1292                            coalescing_vec,
1293                            coalesced_buffers,
1294                            linearization_vec,
1295                        } = storage;
1296                        coalescing_vec.clear();
1297                        coalescing_vec.extend_from_slice(&linearization_vec[..seed_payload_end]);
1298                        coalesced_buffers.push(buffer);
1299                        ActiveFlowBuffers::Coalesced
1300                    }
1301                };
1302
1303                *active_flow = Some(ActiveFlow { flow, buffers });
1304                ProcessingResult::Continue
1305            }
1306            PendingItem::Flush(pending) => {
1307                let PendingFlush { buffer, target, linearization, checksum_offload } = pending;
1308
1309                let buffers = match linearization {
1310                    BufferLinearization::Contiguous => GroOutputBuffers::Contiguous(buffer),
1311                    BufferLinearization::Linearized => GroOutputBuffers::Linearized {
1312                        buffer,
1313                        slice: &mut storage.linearization_vec,
1314                    },
1315                };
1316                ProcessingResult::Return(GroOutputItem {
1317                    target,
1318                    checksum_offload,
1319                    gso_info: None,
1320                    buffers,
1321                })
1322            }
1323        }
1324    }
1325
1326    /// Processes a GRO input item and returns the action to be taken as a
1327    /// result of the processing.
1328    fn process_input<'b>(
1329        &'b mut self,
1330        item: GroInputItem<B, T>,
1331    ) -> ProcessingResult<'b, B, T, alloc::vec::Drain<'b, B>> {
1332        let Self { storage, active_flow, pending_item, enable_tcp_gro, .. } = self;
1333        let GroInputItem { mut buffer, target, checksum_offload } = item;
1334
1335        storage.linearization_vec.clear();
1336        let buffer_slice = buffer
1337            .linearized(Some(&mut storage.linearization_vec))
1338            .expect("must be `Some` if linearization vec is provided");
1339        let linearization = match &buffer_slice {
1340            BufferSlice::Contiguous(_) => BufferLinearization::Contiguous,
1341            BufferSlice::Linearized(_) => BufferLinearization::Linearized,
1342        };
1343
1344        // Implemented as a macro rather than a function or closure because
1345        // passing `buffer` and `buffer_slice` across a call boundary causes the
1346        // borrow checker to reject moving `buffer` while it is borrowed by
1347        // `buffer_slice`. Expanding the pattern match inline allows the borrow
1348        // to be dropped before moving `buffer` in the `Contiguous` arm.
1349        macro_rules! return_single_buffer {
1350            ($csum_offload:expr) => {{
1351                let buffers = match buffer_slice {
1352                    BufferSlice::Contiguous(_) => GroOutputBuffers::Contiguous(buffer),
1353                    BufferSlice::Linearized(slice) => {
1354                        GroOutputBuffers::Linearized { buffer, slice }
1355                    }
1356                };
1357                return ProcessingResult::Return(GroOutputItem {
1358                    target,
1359                    checksum_offload: $csum_offload,
1360                    gso_info: None,
1361                    buffers,
1362                });
1363            }};
1364        }
1365
1366        if !*enable_tcp_gro {
1367            return_single_buffer!(checksum_offload);
1368        }
1369
1370        let mut context = NetworkParsingContext::new(checksum_offload);
1371        let parsed = GroPacket::parse(buffer_slice.as_slice(), &target, &mut context);
1372        // Note: even if `parse` failed to produce a GRO-eligible packet, it may
1373        // still have verified the transport checksum so we build the output
1374        // offloading indication prior to checking the parse result.
1375        let checksum_offload = build_csum_offload_output(checksum_offload, &context);
1376        let parsed = match parsed {
1377            Some(p) => p,
1378            None => return_single_buffer!(checksum_offload),
1379        };
1380
1381        let flush_if_not_merged = parsed.flush_if_not_merged(buffer_slice.as_slice().len());
1382        let Some(mut active) = active_flow.take() else {
1383            // There's no active flow, so the buffer was not merged. Establish
1384            // it as the active flow unless it must be flushed immediately.
1385
1386            if flush_if_not_merged {
1387                return_single_buffer!(checksum_offload);
1388            }
1389
1390            let seed_payload_end = parsed.payload_end();
1391            let flow = GroFlow::new(target, parsed, checksum_offload);
1392            let buffers = match linearization {
1393                // The buffer is contiguous, so the flow can hold onto it
1394                // verbatim and defer copying it into the coalescing buffer
1395                // until something merges into the flow.
1396                BufferLinearization::Contiguous => {
1397                    ActiveFlowBuffers::Single { buffer, payload_end: seed_payload_end }
1398                }
1399                BufferLinearization::Linearized => {
1400                    storage.coalescing_vec.clear();
1401                    // Drop any trailing bytes (e.g. link layer padding) from
1402                    // the seed frame; only the packet itself may be coalesced
1403                    // against.
1404                    storage
1405                        .coalescing_vec
1406                        .extend_from_slice(&buffer_slice.as_slice()[..seed_payload_end]);
1407                    storage.coalesced_buffers.push(buffer);
1408                    ActiveFlowBuffers::Coalesced
1409                }
1410            };
1411            *active_flow = Some(ActiveFlow { flow, buffers });
1412            return ProcessingResult::Continue;
1413        };
1414
1415        if !active.matches(&target, &parsed.flow_id, checksum_offload) {
1416            // The buffer didn't match the active flow, so it was not merged and
1417            // the active flow is not flushed. Replace the active flow with a
1418            // new one unless the buffer must be flushed immediately, in which
1419            // case we just keep the active flow as-is.
1420            //
1421            // Note that while this may result in packet re-ordering across
1422            // separate flows, it will still preserve ordering within any given
1423            // flow. This is consistent with Linux's implementation of GRO
1424            // (albeit with a single tracked flow, where Linux tracks up to 8).
1425
1426            if flush_if_not_merged {
1427                *active_flow = Some(active);
1428                return_single_buffer!(checksum_offload);
1429            }
1430
1431            let seed_payload_end = parsed.payload_end();
1432            let flow = GroFlow::new(target, parsed, checksum_offload);
1433            let pending =
1434                PendingItem::NewFlow(PendingFlow { buffer, flow, seed_payload_end, linearization });
1435            *pending_item = Some(pending);
1436            return ProcessingResult::Return(storage.build_output(active));
1437        }
1438
1439        if active.can_coalesce(storage.coalescing_vec.len(), &parsed) {
1440            let coalesce_result = active.coalesce(
1441                parsed,
1442                &mut storage.coalescing_vec,
1443                &mut storage.coalesced_buffers,
1444            );
1445
1446            storage.coalesced_buffers.push(buffer);
1447
1448            match coalesce_result {
1449                CoalesceResult::Flush => ProcessingResult::Return(storage.build_output(active)),
1450                CoalesceResult::Continue => {
1451                    *active_flow = Some(active);
1452                    ProcessingResult::Continue
1453                }
1454            }
1455        } else {
1456            // The buffer matched the active flow but could not be merged into
1457            // it, so the active flow is flushed. The buffer is flushed as well
1458            // if it must be; otherwise it becomes the new active flow.
1459
1460            let pending = if flush_if_not_merged {
1461                PendingItem::Flush(PendingFlush { buffer, target, linearization, checksum_offload })
1462            } else {
1463                let seed_payload_end = parsed.payload_end();
1464                let flow = GroFlow::new(target, parsed, checksum_offload);
1465                PendingItem::NewFlow(PendingFlow { buffer, flow, seed_payload_end, linearization })
1466            };
1467            *pending_item = Some(pending);
1468            ProcessingResult::Return(storage.build_output(active))
1469        }
1470    }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    use super::*;
1476    use alloc::sync::Arc;
1477    use alloc::vec;
1478    use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1479
1480    use net_declare::{net_ip_v4, net_ip_v6, net_mac};
1481    use net_types::ip::{Ipv4, Ipv6};
1482    use netstack3_base::NetworkSerializationContext;
1483    use packet::{Buf, InnerPacketBuilder, NestableSerializer as _, Serializer};
1484    use packet_formats::arp::{ArpOp, ArpPacketBuilder};
1485    use packet_formats::ethernet::{EtherType, EthernetFrameBuilder};
1486    use packet_formats::ip::{FragmentOffset, IpExt, IpProto, Ipv4Proto};
1487    use packet_formats::ipv4::options::Ipv4Option;
1488    use packet_formats::ipv4::{Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
1489    use packet_formats::ipv6::ext_hdrs::{
1490        ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
1491    };
1492    use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
1493    use packet_formats::tcp::options::{TcpOptionsBuilder, TimestampOption};
1494    use packet_formats::tcp::{TcpSegmentBuilder, TcpSegmentBuilderWithOptions};
1495    use packet_formats::udp::UdpPacketBuilder;
1496    use test_case::test_case;
1497
1498    impl GroBufferDestination for GroFrameType {
1499        fn frame_type(&self) -> GroFrameType {
1500            *self
1501        }
1502    }
1503
1504    /// A buffer that records how GRO used it.
1505    #[derive(Debug)]
1506    struct TrackedBuffer {
1507        buf: Vec<u8>,
1508        contiguous: bool,
1509        dropped: Arc<AtomicBool>,
1510        linearized_count: Arc<AtomicUsize>,
1511    }
1512
1513    impl TrackedBuffer {
1514        fn new(buf: Vec<u8>, contiguous: bool) -> Self {
1515            Self {
1516                buf,
1517                contiguous,
1518                dropped: Arc::new(AtomicBool::new(false)),
1519                linearized_count: Arc::new(AtomicUsize::new(0)),
1520            }
1521        }
1522
1523        /// Returns a handle reporting whether this buffer has been dropped.
1524        fn dropped(&self) -> Arc<AtomicBool> {
1525            self.dropped.clone()
1526        }
1527
1528        /// Returns a handle to the number of times this buffer has been
1529        /// linearized.
1530        fn linearized_count(&self) -> Arc<AtomicUsize> {
1531            self.linearized_count.clone()
1532        }
1533    }
1534
1535    impl Drop for TrackedBuffer {
1536        fn drop(&mut self) {
1537            self.dropped.store(true, Ordering::SeqCst);
1538        }
1539    }
1540
1541    impl MaybeContiguousBuffer for TrackedBuffer {
1542        fn linearized<'a, 'b>(
1543            &'a mut self,
1544            storage: Option<&'b mut Vec<u8>>,
1545        ) -> Option<BufferSlice<'a, 'b>> {
1546            if self.contiguous {
1547                Some(BufferSlice::Contiguous(&mut self.buf[..]))
1548            } else {
1549                let storage = storage?;
1550                let _: usize = self.linearized_count.fetch_add(1, Ordering::SeqCst);
1551                let frame_length = self.buf.len();
1552                if storage.len() < frame_length {
1553                    storage.resize(frame_length, 0);
1554                }
1555                let slice = &mut storage[..frame_length];
1556                slice.copy_from_slice(&self.buf);
1557                Some(BufferSlice::Linearized(slice))
1558            }
1559        }
1560    }
1561
1562    /// Wraps `buffer` in an Ethernet GRO input item.
1563    fn input_item(buffer: TrackedBuffer) -> GroInputItem<TrackedBuffer, GroFrameType> {
1564        GroInputItem {
1565            buffer,
1566            target: GroFrameType::Ethernet,
1567            checksum_offload: ChecksumRxOffloading::FullyOffloaded,
1568        }
1569    }
1570
1571    #[test]
1572    fn process_gro_handles_fragmented() {
1573        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = vec![
1574            GroInputItem {
1575                buffer: TrackedBuffer::new(vec![1, 2, 3], true),
1576                target: GroFrameType::Ethernet,
1577                checksum_offload: ChecksumRxOffloading::FullyOffloaded,
1578            },
1579            GroInputItem {
1580                buffer: TrackedBuffer::new(vec![4, 5, 6], false),
1581                target: GroFrameType::Ethernet,
1582                checksum_offload: ChecksumRxOffloading::FullyOffloaded,
1583            },
1584        ];
1585
1586        let mut storage = GroBufferStorage::new();
1587        let mut output = Vec::new();
1588        let mut gro = storage.coalesce(items.into_iter(), false);
1589        while let Some(mut item) = gro.next() {
1590            output.push(item.buffers.slice_mut().to_vec());
1591        }
1592
1593        assert_eq!(output, vec![vec![1, 2, 3], vec![4, 5, 6]]);
1594    }
1595
1596    const SRC_MAC: Mac = net_mac!("00:11:22:33:44:55");
1597    const DST_MAC: Mac = net_mac!("66:77:88:99:aa:bb");
1598    const SRC_IP_V4: Ipv4Addr = net_ip_v4!("192.168.0.1");
1599    const DST_IP_V4: Ipv4Addr = net_ip_v4!("192.168.0.2");
1600    const SRC_IP_V6: Ipv6Addr = net_ip_v6!("2001:db8::1");
1601    const DST_IP_V6: Ipv6Addr = net_ip_v6!("2001:db8::2");
1602
1603    /// A TCP-over-IP frame to feed to GRO.
1604    ///
1605    /// Construct with [`v4`] or [`v6`] and override the fields of interest with
1606    /// struct update syntax, e.g. `FrameSpec { psh: true, ..v4(100, b"x") }`.
1607    #[derive(Clone, Debug)]
1608    struct FrameSpec {
1609        /// The IP version to emit, along with its version-specific fields.
1610        ip: IpSpec,
1611        seq: u32,
1612        ack: u32,
1613        win: u16,
1614        psh: bool,
1615        fin: bool,
1616        syn: bool,
1617        rst: bool,
1618        urg: bool,
1619        /// The TTL for IPv4, or the hop limit for IPv6.
1620        ttl: u8,
1621        dscp_and_ecn: DscpAndEcn,
1622        /// Emits a TCP timestamp option with this TSval when set.
1623        timestamp: Option<u32>,
1624        /// The minimum Ethernet body length, used to force link layer padding.
1625        min_body_len: usize,
1626        payload: &'static [u8],
1627        /// Whether the frame is presented to GRO as a contiguous buffer.
1628        contiguous: bool,
1629    }
1630
1631    /// The IP-version-specific fields of a [`FrameSpec`].
1632    #[derive(Clone, Copy, Debug)]
1633    enum IpSpec {
1634        V4 { df: bool, id: u16 },
1635        V6 { flowlabel: u32 },
1636    }
1637
1638    /// Returns a default IPv4 frame carrying `payload` at `seq`.
1639    fn v4(seq: u32, payload: &'static [u8]) -> FrameSpec {
1640        FrameSpec {
1641            ip: IpSpec::V4 { df: false, id: 0 },
1642            seq,
1643            ack: 1000,
1644            win: 64240,
1645            psh: false,
1646            fin: false,
1647            syn: false,
1648            rst: false,
1649            urg: false,
1650            ttl: 64,
1651            dscp_and_ecn: DscpAndEcn::default(),
1652            timestamp: None,
1653            min_body_len: 0,
1654            payload,
1655            contiguous: true,
1656        }
1657    }
1658
1659    /// Returns a default IPv6 frame carrying `payload` at `seq`.
1660    fn v6(seq: u32, payload: &'static [u8]) -> FrameSpec {
1661        FrameSpec { ip: IpSpec::V6 { flowlabel: 0 }, ..v4(seq, payload) }
1662    }
1663
1664    impl FrameSpec {
1665        /// Serializes this spec into an Ethernet frame.
1666        fn build(&self) -> Vec<u8> {
1667            let FrameSpec {
1668                ip: ip_spec,
1669                seq,
1670                ack,
1671                win,
1672                psh,
1673                fin,
1674                syn,
1675                rst,
1676                urg,
1677                ttl,
1678                dscp_and_ecn,
1679                timestamp,
1680                min_body_len,
1681                payload,
1682                contiguous: _,
1683            } = *self;
1684
1685            let mut body = payload.to_vec();
1686
1687            macro_rules! tcp_builder {
1688                ($src:expr, $dst:expr) => {{
1689                    let mut tcp = TcpSegmentBuilder::new(
1690                        $src,
1691                        $dst,
1692                        TEST_SRC_PORT,
1693                        TEST_DST_PORT,
1694                        seq,
1695                        Some(ack),
1696                        win,
1697                    );
1698                    tcp.psh(psh);
1699                    tcp.fin(fin);
1700                    tcp.syn(syn);
1701                    tcp.rst(rst);
1702                    tcp.urg(urg);
1703                    tcp
1704                }};
1705            }
1706
1707            macro_rules! serialize {
1708                ($tcp:expr, $ip:expr, $ethertype:expr) => {
1709                    Buf::new(&mut body[..], ..)
1710                        .wrap_in($tcp)
1711                        .wrap_in($ip)
1712                        .wrap_in(EthernetFrameBuilder::new(
1713                            SRC_MAC,
1714                            DST_MAC,
1715                            $ethertype,
1716                            min_body_len,
1717                        ))
1718                        .serialize_vec_outer(&mut NetworkSerializationContext::default())
1719                        .unwrap()
1720                        .unwrap_b()
1721                        .as_ref()
1722                        .to_vec()
1723                };
1724            }
1725
1726            // NB: the TCP builder type differs depending on whether options are
1727            // present, so each combination needs its own serialization.
1728            macro_rules! serialize_with_options {
1729                ($tcp:expr, $ip:expr, $ethertype:expr) => {
1730                    match timestamp {
1731                        None => serialize!($tcp, $ip, $ethertype),
1732                        Some(ts_val) => {
1733                            let options = TcpOptionsBuilder {
1734                                timestamp: Some(TimestampOption::new(ts_val, 0)),
1735                                ..Default::default()
1736                            };
1737                            let tcp = TcpSegmentBuilderWithOptions::new($tcp, options).unwrap();
1738                            serialize!(tcp, $ip, $ethertype)
1739                        }
1740                    }
1741                };
1742            }
1743
1744            match ip_spec {
1745                IpSpec::V4 { df, id } => {
1746                    let mut ip =
1747                        Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, ttl, IpProto::Tcp.into());
1748                    ip.dscp_and_ecn(dscp_and_ecn);
1749                    ip.df_flag(df);
1750                    ip.id(id);
1751                    serialize_with_options!(tcp_builder!(SRC_IP_V4, DST_IP_V4), ip, EtherType::Ipv4)
1752                }
1753                IpSpec::V6 { flowlabel } => {
1754                    let mut ip =
1755                        Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, ttl, IpProto::Tcp.into());
1756                    ip.dscp_and_ecn(dscp_and_ecn);
1757                    ip.flowlabel(flowlabel);
1758                    serialize_with_options!(tcp_builder!(SRC_IP_V6, DST_IP_V6), ip, EtherType::Ipv6)
1759                }
1760            }
1761        }
1762    }
1763
1764    /// Runs GRO over `frames` and returns the frames it emits.
1765    fn run_gro(frames: &[FrameSpec], enable_tcp_gro: bool) -> Vec<Vec<u8>> {
1766        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = frames
1767            .iter()
1768            .map(|spec| input_item(TrackedBuffer::new(spec.build(), spec.contiguous)))
1769            .collect();
1770
1771        let mut storage = GroBufferStorage::new();
1772        let mut output = Vec::new();
1773        let mut gro = storage.coalesce(items.into_iter(), enable_tcp_gro);
1774        while let Some(mut item) = gro.next() {
1775            output.push(item.buffers.slice_mut().to_vec());
1776        }
1777        output
1778    }
1779
1780    /// Parses `frame` as a GRO packet, verifying its checksum.
1781    #[track_caller]
1782    fn parse_frame(frame: &[u8]) -> GroPacket<'_> {
1783        GroPacket::parse(
1784            frame,
1785            &GroFrameType::Ethernet,
1786            &mut NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(None)),
1787        )
1788        .expect("frame is a valid GRO packet")
1789    }
1790
1791    #[test_case(
1792        vec![v4(100, b"hello "), v4(106, b"world")],
1793        vec![b"hello world"]; "coalesces_contiguous")]
1794    #[test_case(
1795        vec![
1796            FrameSpec { contiguous: false, ..v4(100, b"foo ") },
1797            FrameSpec { contiguous: false, ..v4(104, b"bar") },
1798        ],
1799        vec![b"foo bar"]; "coalesces_fragmented")]
1800    #[test_case(
1801        vec![v4(100, b"contig "), FrameSpec { contiguous: false, ..v4(107, b"frag") }],
1802        vec![b"contig frag"]; "coalesces_contiguous_then_fragmented")]
1803    #[test_case(
1804        vec![FrameSpec { contiguous: false, ..v4(100, b"single") }],
1805        vec![b"single"]; "lone_fragmented_frame_is_emitted")]
1806    #[test_case(
1807        vec![v4(100, b"first "), v4(106, b"second"), v4(112, b"third")],
1808        vec![b"first secondthird"]; "coalesces_three_frames")]
1809    #[test_case(
1810        vec![v6(200, b"ipv6_1"), FrameSpec { psh: true, ..v6(206, b"ipv6_2") }],
1811        vec![b"ipv6_1ipv6_2"]; "coalesces_ipv6")]
1812    #[test_case(
1813        vec![
1814            FrameSpec { ack: 1000, ..v4(100, b"ack1000 ") },
1815            FrameSpec { ack: 2000, ..v4(108, b"ack2000") },
1816        ],
1817        vec![b"ack1000 ", b"ack2000"]; "ack_mismatch_flushes")]
1818    #[test_case(
1819        vec![
1820            FrameSpec { win: 64240, ..v4(100, b"win64k ") },
1821            FrameSpec { win: 32120, ..v4(107, b"win32k") },
1822        ],
1823        vec![b"win64k ", b"win32k"]; "window_mismatch_flushes")]
1824    #[test_case(
1825        vec![
1826            FrameSpec { timestamp: Some(1), ..v4(100, b"opt1") },
1827            FrameSpec { timestamp: Some(1), ..v4(104, b"opt2") },
1828        ],
1829        vec![b"opt1opt2"]; "matching_options_coalesce")]
1830    #[test_case(
1831        vec![
1832            FrameSpec { timestamp: Some(1), ..v4(100, b"opt1") },
1833            FrameSpec { timestamp: Some(2), ..v4(104, b"mismatch") },
1834        ],
1835        vec![b"opt1", b"mismatch"]; "options_mismatch_flushes")]
1836    #[test_case(
1837        vec![
1838            FrameSpec { ttl: 64, ..v4(100, b"ttl64 ") },
1839            FrameSpec { ttl: 63, ..v4(106, b"ttl63") },
1840        ],
1841        vec![b"ttl64 ", b"ttl63"]; "ttl_mismatch_flushes")]
1842    #[test_case(
1843        vec![
1844            FrameSpec { ip: IpSpec::V4 { df: true, id: 0 }, ..v4(100, b"df1 ") },
1845            FrameSpec { ip: IpSpec::V4 { df: false, id: 0 }, ..v4(104, b"df0") },
1846        ],
1847        vec![b"df1 ", b"df0"]; "df_flag_mismatch_flushes")]
1848    #[test_case(
1849        vec![
1850            FrameSpec { dscp_and_ecn: DscpAndEcn::new(1, 0), ..v4(100, b"ecn1 ") },
1851            FrameSpec { dscp_and_ecn: DscpAndEcn::new(2, 0), ..v4(105, b"ecn2") },
1852        ],
1853        vec![b"ecn1 ", b"ecn2"]; "dscp_ecn_mismatch_flushes")]
1854    #[test_case(
1855        vec![
1856            FrameSpec { ttl: 64, ..v6(200, b"hop64 ") },
1857            FrameSpec { ttl: 63, ..v6(206, b"hop63") },
1858        ],
1859        vec![b"hop64 ", b"hop63"]; "hop_limit_mismatch_flushes")]
1860    #[test_case(
1861        vec![
1862            FrameSpec { ip: IpSpec::V6 { flowlabel: 123 }, ..v6(200, b"lbl1 ") },
1863            FrameSpec { ip: IpSpec::V6 { flowlabel: 456 }, ..v6(205, b"lbl2") },
1864        ],
1865        vec![b"lbl1 ", b"lbl2"]; "flowlabel_mismatch_flushes")]
1866    #[test_case(
1867        vec![v4(100, b"seq100 "), v4(200, b"seq200")],
1868        vec![b"seq100 ", b"seq200"]; "out_of_order_seq_flushes")]
1869    #[test_case(
1870        vec![v4(100, b"seq100 "), v4(200, b"seq200"), v4(206, b"seq206")],
1871        vec![b"seq100 ", b"seq200seq206"]; "merge_failure_starts_new_flow")]
1872    #[test_case(
1873        vec![
1874            FrameSpec { urg: true, ..v4(100, b"urg ") },
1875            FrameSpec { urg: false, ..v4(104, b"normal") },
1876        ],
1877        vec![b"urg ", b"normal"]; "urg_flag_flushes")]
1878    #[test_case(
1879        vec![
1880            FrameSpec { urg: true, ..v4(100, b"urg1") },
1881            FrameSpec { urg: true, ..v4(104, b"urg2") },
1882        ],
1883        vec![b"urg1", b"urg2"]; "consecutive_urg_flags_flush")]
1884    #[test_case(
1885        vec![
1886            FrameSpec { syn: true, ..v4(100, b"syn1") },
1887            FrameSpec { syn: true, ..v4(104, b"syn2") },
1888        ],
1889        vec![b"syn1", b"syn2"]; "syn_flag_flushes")]
1890    #[test_case(
1891        vec![
1892            FrameSpec { rst: true, ..v4(100, b"rst1") },
1893            FrameSpec { rst: true, ..v4(104, b"rst2") },
1894        ],
1895        vec![b"rst1", b"rst2"]; "rst_flag_flushes")]
1896    #[test_case(
1897        vec![
1898            FrameSpec { ip: IpSpec::V4 { df: false, id: 42 }, ..v4(100, b"hello ") },
1899            FrameSpec { ip: IpSpec::V4 { df: false, id: 42 }, ..v4(106, b"world ") },
1900            FrameSpec { ip: IpSpec::V4 { df: false, id: 42 }, ..v4(112, b"again") },
1901        ],
1902        vec![b"hello world again"]; "consistent_ipv4_ids_coalesce")]
1903    #[test_case(
1904        vec![
1905            FrameSpec { ip: IpSpec::V4 { df: false, id: 100 }, ..v4(100, b"hello ") },
1906            FrameSpec { ip: IpSpec::V4 { df: false, id: 101 }, ..v4(106, b"world ") },
1907            FrameSpec { ip: IpSpec::V4 { df: false, id: 102 }, ..v4(112, b"again") },
1908        ],
1909        vec![b"hello world again"]; "increasing_ipv4_ids_coalesce")]
1910    #[test_case(
1911        vec![
1912            FrameSpec { ip: IpSpec::V4 { df: false, id: u16::MAX }, ..v4(100, b"hello ") },
1913            FrameSpec { ip: IpSpec::V4 { df: false, id: 0 }, ..v4(106, b"world") },
1914        ],
1915        vec![b"hello world"]; "wrapping_ipv4_ids_coalesce")]
1916    #[test_case(
1917        vec![
1918            FrameSpec { ip: IpSpec::V4 { df: false, id: 100 }, ..v4(100, b"hello ") },
1919            FrameSpec { ip: IpSpec::V4 { df: false, id: 105 }, ..v4(106, b"world") },
1920        ],
1921        vec![b"hello ", b"world"]; "ipv4_id_mismatch_flushes")]
1922    #[test_case(
1923        vec![
1924            FrameSpec { ip: IpSpec::V4 { df: false, id: 100 }, ..v4(100, b"first ") },
1925            FrameSpec { ip: IpSpec::V4 { df: false, id: 100 }, ..v4(106, b"second") },
1926            FrameSpec { ip: IpSpec::V4 { df: false, id: 101 }, ..v4(112, b"third") },
1927        ],
1928        vec![b"first second", b"third"]; "ipv4_id_mode_switch_flushes")]
1929    #[test_case(
1930        vec![FrameSpec { min_body_len: 46, ..v4(100, b"abcd") }, v4(104, b"e")],
1931        vec![b"abcde"]; "padded_seed_frame_is_trimmed")]
1932    #[test_case(
1933        vec![
1934            FrameSpec { min_body_len: 46, contiguous: false, ..v4(100, b"abcd") },
1935            v4(104, b"e"),
1936        ],
1937        vec![b"abcde"]; "linearized_padded_seed_frame_is_trimmed")]
1938    #[test_case(
1939        vec![FrameSpec { min_body_len: 46, ..v4(100, b"a") }, v4(101, b"normal")],
1940        vec![b"a", b"normal"]; "unmerged_padded_frame_is_emitted_verbatim")]
1941    #[test_case(
1942        vec![v4(100, b"first "), FrameSpec { min_body_len: 46, ..v4(106, b"b") }],
1943        vec![b"first b"]; "padded_second_frame_is_trimmed")]
1944    #[test_case(
1945        vec![FrameSpec { min_body_len: 70, ..v6(200, b"c") }, v6(201, b"ipv6_normal")],
1946        vec![b"c", b"ipv6_normal"]; "unmerged_padded_ipv6_frame_is_emitted_verbatim")]
1947    #[test_case(
1948        vec![v4(100, b"short"), v4(105, b"longer_payload")],
1949        vec![b"short", b"longer_payload"]; "payload_larger_than_gso_size_flushes")]
1950    #[test_case(
1951        vec![v4(100, b"data"), v4(104, b"")],
1952        vec![b"data", b""]; "empty_payload_in_active_flow_flushes")]
1953    #[test_case(
1954        vec![v4(100, b"odd"), v4(103, b"len"), v4(106, b"tcp")],
1955        vec![b"oddlentcp"]; "coalesces_odd_length_payloads")]
1956    #[test_case(
1957        vec![
1958            FrameSpec { timestamp: Some(1), ..v4(100, b"odd") },
1959            FrameSpec { timestamp: Some(1), ..v4(103, b"opt") },
1960        ],
1961        vec![b"oddopt"]; "coalesces_odd_length_payloads_with_options")]
1962    #[test_case(
1963        vec![v4(100, b"first "), v4(106, b"sub"), v4(109, b"more")],
1964        vec![b"first sub", b"more"]; "payload_smaller_than_gso_size_terminates_flow")]
1965    fn gro_coalescing(frames: Vec<FrameSpec>, expected: Vec<&'static [u8]>) {
1966        let output = run_gro(&frames, true);
1967        assert_eq!(output.len(), expected.len(), "wrong number of frames emitted");
1968
1969        for (index, (frame, payload)) in output.iter().zip(expected.iter()).enumerate() {
1970            let parsed = parse_frame(&frame[..]);
1971            let TransportPacket::Tcp(tcp) = parsed.transport;
1972            assert_eq!(tcp.body(), *payload, "frame {index}");
1973        }
1974    }
1975
1976    #[test]
1977    fn gro_disabled_emits_every_frame_unchanged() {
1978        let frames = vec![v4(100, b"hello "), v4(106, b"world")];
1979        let output = run_gro(&frames, false);
1980        assert_eq!(output, vec![frames[0].build(), frames[1].build()]);
1981    }
1982
1983    #[test_case(true; "contiguous")]
1984    #[test_case(false; "fragmented")]
1985    fn gro_emits_unmergeable_frames_unmodified(contiguous: bool) {
1986        // `URG` segments are never coalesced, so every frame is emitted on its
1987        // own with its headers and payload untouched.
1988        let frames = vec![
1989            FrameSpec { urg: true, contiguous, ..v4(100, b"hello ") },
1990            FrameSpec { urg: true, contiguous, ..v4(106, b"world") },
1991        ];
1992        let output = run_gro(&frames, true);
1993        assert_eq!(output.len(), frames.len(), "wrong number of frames emitted");
1994
1995        for (index, (frame, spec)) in output.iter().zip(frames.iter()).enumerate() {
1996            let expected = spec.build();
1997            let parsed = parse_frame(&expected);
1998            // Trailing bytes (e.g. link layer padding) may or may not be
1999            // trimmed, so only compare up to the end of the IP packet.
2000            let packet_end = parsed.payload_end();
2001            assert!(frame.len() >= packet_end, "frame {index} is truncated");
2002            assert_eq!(&frame[..packet_end], &expected[..packet_end], "frame {index}");
2003        }
2004    }
2005
2006    #[test]
2007    fn gro_merges_psh_into_coalesced_frame() {
2008        let frames = vec![v4(100, b"hello "), FrameSpec { psh: true, ..v4(106, b"world") }];
2009        let output = run_gro(&frames, true);
2010        let output = assert_matches!(&output[..], [output] => output);
2011
2012        let parsed = parse_frame(output);
2013        let TransportPacket::Tcp(tcp) = parsed.transport;
2014        assert_eq!(tcp.body(), b"hello world");
2015        assert_eq!(tcp.seq_num(), 100);
2016        assert_eq!(tcp.ack_num(), Some(1000));
2017        assert_eq!(tcp.window_size(), 64240);
2018        assert!(tcp.psh());
2019    }
2020
2021    #[test]
2022    fn gro_merges_fin_into_coalesced_frame() {
2023        let frames = vec![v4(100, b"data "), FrameSpec { fin: true, ..v4(105, b"fin") }];
2024        let output = run_gro(&frames, true);
2025        let output = assert_matches!(&output[..], [output] => output);
2026
2027        let parsed = parse_frame(output);
2028        let TransportPacket::Tcp(tcp) = parsed.transport;
2029        assert_eq!(tcp.body(), b"data fin");
2030        assert!(tcp.fin());
2031    }
2032
2033    #[test]
2034    fn gro_gso_info_metadata() {
2035        let mut storage = GroBufferStorage::new();
2036
2037        // Consistent IPv4 IDs across the flow yield a fixed IP ID.
2038        let items = vec![
2039            input_item(TrackedBuffer::new(
2040                FrameSpec { ip: IpSpec::V4 { df: false, id: 1 }, ..v4(100, b"first ") }.build(),
2041                true,
2042            )),
2043            input_item(TrackedBuffer::new(
2044                FrameSpec { ip: IpSpec::V4 { df: false, id: 1 }, ..v4(106, b"second") }.build(),
2045                true,
2046            )),
2047        ];
2048        let mut gro = storage.coalesce(items.into_iter(), true);
2049        let item = gro.next().unwrap();
2050        assert_eq!(
2051            item.gso_info,
2052            Some(GsoInfo {
2053                gso_size: NonZeroU16::new(6).unwrap(),
2054                ipv4_id_mode: Some(Ipv4IdMode::Fixed),
2055            })
2056        );
2057        drop(item);
2058        assert!(gro.next().is_none());
2059        drop(gro);
2060
2061        // Increasing IPv4 IDs yield an incrementing IP ID mode.
2062        let items = vec![
2063            input_item(TrackedBuffer::new(
2064                FrameSpec { ip: IpSpec::V4 { df: false, id: 1 }, ..v4(100, b"first ") }.build(),
2065                true,
2066            )),
2067            input_item(TrackedBuffer::new(
2068                FrameSpec { ip: IpSpec::V4 { df: false, id: 2 }, ..v4(106, b"second") }.build(),
2069                true,
2070            )),
2071        ];
2072        let mut gro = storage.coalesce(items.into_iter(), true);
2073        let item = gro.next().unwrap();
2074        assert_eq!(
2075            item.gso_info,
2076            Some(GsoInfo {
2077                gso_size: NonZeroU16::new(6).unwrap(),
2078                ipv4_id_mode: Some(Ipv4IdMode::Incrementing),
2079            })
2080        );
2081        drop(item);
2082        assert!(gro.next().is_none());
2083        drop(gro);
2084
2085        // IPv6 flows have no IPv4 ID mode.
2086        let items = vec![
2087            input_item(TrackedBuffer::new(v6(100, b"first ").build(), true)),
2088            input_item(TrackedBuffer::new(v6(106, b"second").build(), true)),
2089        ];
2090        let mut gro = storage.coalesce(items.into_iter(), true);
2091        let item = gro.next().unwrap();
2092        assert_eq!(
2093            item.gso_info,
2094            Some(GsoInfo { gso_size: NonZeroU16::new(6).unwrap(), ipv4_id_mode: None })
2095        );
2096        drop(item);
2097        assert!(gro.next().is_none());
2098    }
2099
2100    #[test]
2101    fn gro_buffers_not_dropped_until_frame_processed() {
2102        let pkt1 = v4(100, b"hello ").build();
2103        let pkt2 = v4(106, b"world").build();
2104
2105        let buffer1 = TrackedBuffer::new(pkt1, true);
2106        let buffer2 = TrackedBuffer::new(pkt2, true);
2107        let dropped1 = buffer1.dropped();
2108        let dropped2 = buffer2.dropped();
2109        let items = vec![input_item(buffer1), input_item(buffer2)];
2110
2111        let mut gro_state = GroBufferStorage::new();
2112        let mut gro = gro_state.coalesce(items.into_iter(), true);
2113
2114        let item = gro.next();
2115        assert!(item.is_some());
2116        let mut item = item.unwrap();
2117        assert!(item.buffers.slice_mut().ends_with(b"hello world"));
2118
2119        // While the yielded frame is in use, neither buffer should be dropped!
2120        assert!(!dropped1.load(Ordering::SeqCst));
2121        assert!(!dropped2.load(Ordering::SeqCst));
2122
2123        // When the returned item is dropped, its buffers are dropped.
2124        drop(item);
2125        assert!(dropped1.load(Ordering::SeqCst));
2126        assert!(dropped2.load(Ordering::SeqCst));
2127
2128        //`GroIter` is a lending iterator so `item` (and by extension any
2129        // associated buffers) must be dropped before the next call to `next()`.
2130        let next_item = gro.next();
2131        assert!(next_item.is_none());
2132    }
2133
2134    #[test]
2135    fn gro_unmerged_flows_are_not_copied() {
2136        // Nothing merges into either flow, so each hands its buffer back
2137        // verbatim rather than copying it into the coalescing buffer.
2138        let first = v4(100, b"one").build();
2139        // Not sequential with `first`, so it starts a new flow rather than
2140        // merging into it.
2141        let second = v4(500, b"two").build();
2142        let items = vec![
2143            input_item(TrackedBuffer::new(first.clone(), true)),
2144            input_item(TrackedBuffer::new(second.clone(), true)),
2145        ];
2146
2147        let mut gro_state = GroBufferStorage::new();
2148        let mut gro = gro_state.coalesce(items.into_iter(), true);
2149
2150        for expected in [first, second] {
2151            let mut item = gro.next().expect("emits a frame");
2152            assert_matches!(&mut item.buffers, GroOutputBuffers::Contiguous(buffer) => {
2153                assert_eq!(buffer.unwrap_contiguous(), &expected[..]);
2154            });
2155        }
2156        assert!(gro.next().is_none());
2157    }
2158
2159    #[test]
2160    fn gro_iter_drop_releases_held_buffers() {
2161        let pkt1 = v4(100, b"hello ").build();
2162        let buffer1 = TrackedBuffer::new(pkt1, true);
2163        let dropped1 = buffer1.dropped();
2164        let items = vec![input_item(buffer1)];
2165
2166        let mut gro_state = GroBufferStorage::new();
2167        {
2168            let mut gro = gro_state.coalesce(items.into_iter(), true);
2169            let item = gro.next();
2170            assert!(item.is_some());
2171            assert!(!dropped1.load(Ordering::SeqCst));
2172        }
2173        // Dropping `gro` releases all held buffers.
2174        assert!(dropped1.load(Ordering::SeqCst));
2175    }
2176
2177    #[test]
2178    fn gro_interleaved_non_gro_packet() {
2179        let pkt1 = v4(100, b"tcp1 ").build();
2180        let non_gro_pkt = vec![0u8; 30];
2181        let pkt2 = v4(105, b"tcp2").build();
2182
2183        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = vec![
2184            GroInputItem {
2185                buffer: TrackedBuffer::new(pkt1, true),
2186                target: GroFrameType::Ethernet,
2187                checksum_offload: ChecksumRxOffloading::FullyOffloaded,
2188            },
2189            GroInputItem {
2190                buffer: TrackedBuffer::new(non_gro_pkt.clone(), true),
2191                target: GroFrameType::Ethernet,
2192                checksum_offload: ChecksumRxOffloading::FullyOffloaded,
2193            },
2194            GroInputItem {
2195                buffer: TrackedBuffer::new(pkt2, true),
2196                target: GroFrameType::Ethernet,
2197                checksum_offload: ChecksumRxOffloading::FullyOffloaded,
2198            },
2199        ];
2200
2201        let mut gro_state = GroBufferStorage::new();
2202        let mut output_frames = Vec::new();
2203        let mut gro = gro_state.coalesce(items.into_iter(), true);
2204        while let Some(mut item) = gro.next() {
2205            output_frames.push(item.buffers.slice_mut().to_vec());
2206        }
2207
2208        // Non-GRO frame is emitted immediately without interrupting the TCP GRO flow.
2209        assert_eq!(output_frames.len(), 2);
2210        assert_eq!(output_frames[0], non_gro_pkt);
2211        assert!(output_frames[1].ends_with(b"tcp1 tcp2"));
2212    }
2213
2214    #[test]
2215    fn gro_buffers_linearized_only_once() {
2216        let buffer1 = TrackedBuffer::new(v4(100, b"first ").build(), false);
2217        let buffer2 = TrackedBuffer::new(v4(106, b"second").build(), false);
2218        let buffer3 = TrackedBuffer::new(v4(112, b"third").build(), false);
2219        let count1 = buffer1.linearized_count();
2220        let count2 = buffer2.linearized_count();
2221        let count3 = buffer3.linearized_count();
2222        let items = vec![input_item(buffer1), input_item(buffer2), input_item(buffer3)];
2223
2224        let mut gro_state = GroBufferStorage::new();
2225        let mut output_frames = Vec::new();
2226        let mut gro = gro_state.coalesce(items.into_iter(), true);
2227        while let Some(mut item) = gro.next() {
2228            output_frames.push(item.buffers.slice_mut().to_vec());
2229        }
2230
2231        assert_eq!(output_frames.len(), 1);
2232        assert!(output_frames[0].ends_with(b"first secondthird"));
2233
2234        assert_eq!(count1.load(Ordering::SeqCst), 1);
2235        assert_eq!(count2.load(Ordering::SeqCst), 1);
2236        assert_eq!(count3.load(Ordering::SeqCst), 1);
2237    }
2238
2239    #[test]
2240    fn gro_transport_offset_ignores_padding() {
2241        // The transport offset must point at the transport header regardless of
2242        // any trailing link layer padding, which the IP parser strips from the
2243        // buffer before the transport header is parsed.
2244        let pkt_padded = FrameSpec { min_body_len: 46, ..v4(100, b"a") }.build();
2245        let pkt = v4(100, b"a").build();
2246        assert!(pkt_padded.len() > pkt.len(), "packet should have been padded");
2247
2248        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2249        let parsed = GroPacket::parse(&pkt_padded, &GroFrameType::Ethernet, &mut context)
2250            .expect("should parse padded packet");
2251        // The IP packet ends where the unpadded frame ends; the padding beyond
2252        // it is not part of the packet.
2253        assert_eq!(parsed.offsets.ip_offset + parsed.ip.total_len(), pkt.len());
2254        let padded_offset = parsed.offsets.transport_offset;
2255
2256        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2257        let parsed = GroPacket::parse(&pkt, &GroFrameType::Ethernet, &mut context)
2258            .expect("should parse unpadded packet");
2259        assert_eq!(parsed.offsets.ip_offset + parsed.ip.total_len(), pkt.len());
2260        assert_eq!(padded_offset, parsed.offsets.transport_offset);
2261
2262        // The offset points at the TCP source and destination ports.
2263        assert_eq!(&pkt_padded[padded_offset..padded_offset + 2], &1234u16.to_be_bytes());
2264        assert_eq!(&pkt_padded[padded_offset + 2..padded_offset + 4], &5678u16.to_be_bytes());
2265    }
2266
2267    #[test]
2268    fn gro_buffers_dropped_when_item_dropped() {
2269        let buffer1 = TrackedBuffer::new(vec![1, 2, 3], true);
2270        let buffer2 = TrackedBuffer::new(vec![4, 5, 6], false);
2271        let dropped1 = buffer1.dropped();
2272        let dropped2 = buffer2.dropped();
2273        let items = vec![input_item(buffer1), input_item(buffer2)];
2274
2275        let mut storage = GroBufferStorage::new();
2276        let mut gro = storage.coalesce(items.into_iter(), false);
2277
2278        let mut item1 = gro.next().unwrap();
2279        assert_eq!(item1.buffers.slice_mut(), &[1, 2, 3]);
2280        assert!(!dropped1.load(Ordering::SeqCst));
2281        assert!(!dropped2.load(Ordering::SeqCst));
2282        drop(item1);
2283        assert!(dropped1.load(Ordering::SeqCst));
2284        assert!(!dropped2.load(Ordering::SeqCst));
2285
2286        let mut item2 = gro.next().unwrap();
2287        assert_eq!(item2.buffers.slice_mut(), &[4, 5, 6]);
2288        assert!(dropped1.load(Ordering::SeqCst));
2289        assert!(!dropped2.load(Ordering::SeqCst));
2290        drop(item2);
2291        assert!(dropped2.load(Ordering::SeqCst));
2292    }
2293
2294    const TEST_SRC_PORT: NonZeroU16 = NonZeroU16::new(1234).unwrap();
2295    const TEST_DST_PORT: NonZeroU16 = NonZeroU16::new(5678).unwrap();
2296    const TEST_FLOWLABEL: u32 = 0x12345;
2297    const TEST_PAYLOAD: [u8; 12] = *b"hello world!";
2298
2299    trait TestIpExt: IpExt {
2300        const SRC_IP: Self::Addr;
2301        const DST_IP: Self::Addr;
2302        fn ip_builder(proto: IpProto) -> Self::PacketBuilder<NetworkSerializationContext>;
2303    }
2304
2305    impl TestIpExt for Ipv4 {
2306        const SRC_IP: Ipv4Addr = SRC_IP_V4;
2307        const DST_IP: Ipv4Addr = DST_IP_V4;
2308        fn ip_builder(proto: IpProto) -> Ipv4PacketBuilder {
2309            Ipv4PacketBuilder::new(Self::SRC_IP, Self::DST_IP, 64, Ipv4Proto::Proto(proto))
2310        }
2311    }
2312
2313    impl TestIpExt for Ipv6 {
2314        const SRC_IP: Ipv6Addr = SRC_IP_V6;
2315        const DST_IP: Ipv6Addr = DST_IP_V6;
2316        fn ip_builder(proto: IpProto) -> Ipv6PacketBuilder {
2317            let mut ip = Ipv6PacketBuilder::new(Self::SRC_IP, Self::DST_IP, 64, proto.into());
2318            ip.flowlabel(TEST_FLOWLABEL);
2319            ip
2320        }
2321    }
2322
2323    fn ethernet_builder<I: TestIpExt>() -> EthernetFrameBuilder {
2324        EthernetFrameBuilder::new(SRC_MAC, DST_MAC, I::ETHER_TYPE, 0)
2325    }
2326
2327    fn tcp_builder<I: TestIpExt>() -> TcpSegmentBuilder<I::Addr> {
2328        TcpSegmentBuilder::new(
2329            I::SRC_IP,
2330            I::DST_IP,
2331            TEST_SRC_PORT,
2332            TEST_DST_PORT,
2333            100,
2334            Some(200),
2335            1024,
2336        )
2337    }
2338
2339    fn build_ethernet_tcp_packet<I: TestIpExt>() -> Vec<u8> {
2340        Buf::new(TEST_PAYLOAD.to_vec(), ..)
2341            .wrap_in(tcp_builder::<I>())
2342            .wrap_in(I::ip_builder(IpProto::Tcp))
2343            .wrap_in(ethernet_builder::<I>())
2344            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2345            .unwrap()
2346            .into_inner()
2347            .as_ref()
2348            .to_vec()
2349    }
2350
2351    fn build_pure_ip_tcp_packet<I: TestIpExt>() -> Vec<u8> {
2352        Buf::new(TEST_PAYLOAD.to_vec(), ..)
2353            .wrap_in(tcp_builder::<I>())
2354            .wrap_in(I::ip_builder(IpProto::Tcp))
2355            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2356            .unwrap()
2357            .into_inner()
2358            .as_ref()
2359            .to_vec()
2360    }
2361
2362    #[test_case(
2363        build_ethernet_tcp_packet::<Ipv4>(),
2364        GroFrameType::Ethernet,
2365        HeaderOffsets { ip_offset: 14, transport_offset: 34 },
2366        GroFlowId {
2367            link_layer: LinkLayerFlowId::Ethernet(EthernetFlowId {
2368                src_mac: SRC_MAC,
2369                dst_mac: DST_MAC,
2370                tag: None,
2371            }),
2372            ip: IpFlowId::Ipv4(Ipv4FlowId { src_ip: Ipv4::SRC_IP, dst_ip: Ipv4::DST_IP }),
2373            transport: TransportFlowId::Tcp(TcpFlowId {
2374                src_port: TEST_SRC_PORT,
2375                dst_port: TEST_DST_PORT,
2376            }),
2377        };
2378        "ethernet_ipv4_tcp"
2379    )]
2380    #[test_case(
2381        build_ethernet_tcp_packet::<Ipv6>(),
2382        GroFrameType::Ethernet,
2383        HeaderOffsets { ip_offset: 14, transport_offset: 54 },
2384        GroFlowId {
2385            link_layer: LinkLayerFlowId::Ethernet(EthernetFlowId {
2386                src_mac: SRC_MAC,
2387                dst_mac: DST_MAC,
2388                tag: None,
2389            }),
2390            ip: IpFlowId::Ipv6(Ipv6FlowId {
2391                src_ip: Ipv6::SRC_IP,
2392                dst_ip: Ipv6::DST_IP,
2393                flowlabel: TEST_FLOWLABEL,
2394            }),
2395            transport: TransportFlowId::Tcp(TcpFlowId {
2396                src_port: TEST_SRC_PORT,
2397                dst_port: TEST_DST_PORT,
2398            }),
2399        };
2400        "ethernet_ipv6_tcp"
2401    )]
2402    #[test_case(
2403        build_pure_ip_tcp_packet::<Ipv4>(),
2404        GroFrameType::PureIp(IpVersion::V4),
2405        HeaderOffsets { ip_offset: 0, transport_offset: 20 },
2406        GroFlowId {
2407            link_layer: LinkLayerFlowId::PureIp,
2408            ip: IpFlowId::Ipv4(Ipv4FlowId { src_ip: Ipv4::SRC_IP, dst_ip: Ipv4::DST_IP }),
2409            transport: TransportFlowId::Tcp(TcpFlowId {
2410                src_port: TEST_SRC_PORT,
2411                dst_port: TEST_DST_PORT,
2412            }),
2413        };
2414        "pure_ip_v4_tcp"
2415    )]
2416    #[test_case(
2417        build_pure_ip_tcp_packet::<Ipv6>(),
2418        GroFrameType::PureIp(IpVersion::V6),
2419        HeaderOffsets { ip_offset: 0, transport_offset: 40 },
2420        GroFlowId {
2421            link_layer: LinkLayerFlowId::PureIp,
2422            ip: IpFlowId::Ipv6(Ipv6FlowId {
2423                src_ip: Ipv6::SRC_IP,
2424                dst_ip: Ipv6::DST_IP,
2425                flowlabel: TEST_FLOWLABEL,
2426            }),
2427            transport: TransportFlowId::Tcp(TcpFlowId {
2428                src_port: TEST_SRC_PORT,
2429                dst_port: TEST_DST_PORT,
2430            }),
2431        };
2432        "pure_ip_v6_tcp"
2433    )]
2434    fn gro_packet_parse_success(
2435        packet_bytes: Vec<u8>,
2436        target: GroFrameType,
2437        expected_offsets: HeaderOffsets,
2438        expected_flow_id: GroFlowId,
2439    ) {
2440        let mut context = NetworkParsingContext::default();
2441        let parsed = GroPacket::parse(&packet_bytes, &target, &mut context)
2442            .expect("GroPacket::parse should succeed");
2443        assert_eq!(parsed.offsets, expected_offsets);
2444        assert_eq!(parsed.flow_id, expected_flow_id);
2445    }
2446
2447    #[test]
2448    fn gro_ineligible_non_ip_ethertype() {
2449        let arp =
2450            ArpPacketBuilder::new(ArpOp::Request, SRC_MAC, Ipv4::SRC_IP, DST_MAC, Ipv4::DST_IP);
2451        let arp_bytes = arp
2452            .into_serializer()
2453            .wrap_in(EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Arp, 0))
2454            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2455            .unwrap()
2456            .unwrap_b()
2457            .as_ref()
2458            .to_vec();
2459        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2460        assert!(GroPacket::parse(&arp_bytes, &GroFrameType::Ethernet, &mut context).is_none());
2461    }
2462
2463    #[test]
2464    fn gro_ineligible_ipv4_options() {
2465        let ip = Ipv4PacketBuilderWithOptions::new(
2466            Ipv4::ip_builder(IpProto::Tcp),
2467            [Ipv4Option::RouterAlert { data: 0 }],
2468        )
2469        .unwrap();
2470        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2471            .wrap_in(tcp_builder::<Ipv4>())
2472            .wrap_in(ip)
2473            .wrap_in(ethernet_builder::<Ipv4>())
2474            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2475            .unwrap()
2476            .into_inner()
2477            .as_ref()
2478            .to_vec();
2479        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2480        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2481    }
2482
2483    #[test]
2484    fn gro_ineligible_ipv4_mf_flag() {
2485        let mut ip = Ipv4::ip_builder(IpProto::Tcp);
2486        ip.mf_flag(true);
2487        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2488            .wrap_in(tcp_builder::<Ipv4>())
2489            .wrap_in(ip)
2490            .wrap_in(ethernet_builder::<Ipv4>())
2491            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2492            .unwrap()
2493            .into_inner()
2494            .as_ref()
2495            .to_vec();
2496        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2497        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2498    }
2499
2500    #[test]
2501    fn gro_ineligible_ipv4_fragment_offset() {
2502        let mut ip = Ipv4::ip_builder(IpProto::Tcp);
2503        ip.fragment_offset(FragmentOffset::new(1).unwrap());
2504        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2505            .wrap_in(tcp_builder::<Ipv4>())
2506            .wrap_in(ip)
2507            .wrap_in(ethernet_builder::<Ipv4>())
2508            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2509            .unwrap()
2510            .into_inner()
2511            .as_ref()
2512            .to_vec();
2513        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2514        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2515    }
2516
2517    #[test]
2518    fn gro_ineligible_ipv6_extension_headers() {
2519        let hbh_opt = [HopByHopOption {
2520            action: ExtensionHeaderOptionAction::SkipAndContinue,
2521            mutable: false,
2522            data: HopByHopOptionData::RouterAlert { data: 0 },
2523        }];
2524        let ip =
2525            Ipv6PacketBuilderWithHbhOptions::new(Ipv6::ip_builder(IpProto::Tcp), &hbh_opt).unwrap();
2526        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2527            .wrap_in(tcp_builder::<Ipv6>())
2528            .wrap_in(ip)
2529            .wrap_in(ethernet_builder::<Ipv6>())
2530            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2531            .unwrap()
2532            .into_inner()
2533            .as_ref()
2534            .to_vec();
2535        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2536        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2537    }
2538
2539    #[test]
2540    fn gro_ineligible_non_tcp_transport_proto() {
2541        let udp =
2542            UdpPacketBuilder::new(Ipv4::SRC_IP, Ipv4::DST_IP, Some(TEST_SRC_PORT), TEST_DST_PORT);
2543        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2544            .wrap_in(udp)
2545            .wrap_in(Ipv4::ip_builder(IpProto::Udp))
2546            .wrap_in(ethernet_builder::<Ipv4>())
2547            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2548            .unwrap()
2549            .into_inner()
2550            .as_ref()
2551            .to_vec();
2552        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2553        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2554    }
2555
2556    #[test]
2557    fn gro_corrupt_tcp_checksum() {
2558        let mut packet = build_ethernet_tcp_packet::<Ipv4>();
2559        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2560        let parsed = GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context)
2561            .expect("should parse valid packet");
2562        let checksum_offset =
2563            parsed.offsets.transport_offset + packet_formats::tcp::CHECKSUM_OFFSET;
2564        packet[checksum_offset] ^= 0xff;
2565
2566        // Fails when checksum verification is not offloaded.
2567        let mut context = NetworkParsingContext::default();
2568        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_none());
2569
2570        // Succeeds when checksum verification is offloaded.
2571        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2572        assert!(GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context).is_some());
2573    }
2574
2575    #[test]
2576    fn gro_ineligible_pure_ip_version_mismatch() {
2577        let pure_v4 = build_pure_ip_tcp_packet::<Ipv4>();
2578        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2579        assert!(
2580            GroPacket::parse(&pure_v4, &GroFrameType::PureIp(IpVersion::V6), &mut context)
2581                .is_none()
2582        );
2583
2584        let pure_v6 = build_pure_ip_tcp_packet::<Ipv6>();
2585        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2586        assert!(
2587            GroPacket::parse(&pure_v6, &GroFrameType::PureIp(IpVersion::V4), &mut context)
2588                .is_none()
2589        );
2590    }
2591
2592    #[test]
2593    fn upgrades_csum_offload_on_verified_tcp() {
2594        let packet = build_ethernet_tcp_packet::<Ipv4>();
2595        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = vec![GroInputItem {
2596            buffer: TrackedBuffer::new(packet, true),
2597            target: GroFrameType::Ethernet,
2598            checksum_offload: ChecksumRxOffloading::default(),
2599        }];
2600
2601        let mut storage = GroBufferStorage::new();
2602        let mut gro = GroIter::new(items.into_iter(), &mut storage, true);
2603        let item = gro.next().unwrap();
2604        assert_eq!(
2605            item.checksum_offload,
2606            ChecksumRxOffloading::Offloaded(Some(NonZeroU16::new(1).unwrap()))
2607        );
2608    }
2609
2610    #[test]
2611    fn preserves_input_csum_offload_on_unparsed_tcp() {
2612        // Build a packet with IPv4 options: ineligible for GRO, so transport
2613        // checksum verification is never reached.
2614        let ip = Ipv4PacketBuilderWithOptions::new(
2615            Ipv4::ip_builder(IpProto::Tcp),
2616            [Ipv4Option::RouterAlert { data: 0 }],
2617        )
2618        .unwrap();
2619        let packet = Buf::new(TEST_PAYLOAD.to_vec(), ..)
2620            .wrap_in(tcp_builder::<Ipv4>())
2621            .wrap_in(ip)
2622            .wrap_in(ethernet_builder::<Ipv4>())
2623            .serialize_vec_outer(&mut NetworkSerializationContext::default())
2624            .unwrap()
2625            .into_inner()
2626            .as_ref()
2627            .to_vec();
2628
2629        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = vec![GroInputItem {
2630            buffer: TrackedBuffer::new(packet, true),
2631            target: GroFrameType::Ethernet,
2632            checksum_offload: ChecksumRxOffloading::default(),
2633        }];
2634
2635        let mut storage = GroBufferStorage::new();
2636        let mut gro = GroIter::new(items.into_iter(), &mut storage, true);
2637        let item = gro.next().unwrap();
2638        assert_eq!(item.checksum_offload, ChecksumRxOffloading::default());
2639    }
2640
2641    #[test]
2642    fn preserves_input_csum_offload_on_corrupt_csum() {
2643        let mut packet = build_ethernet_tcp_packet::<Ipv4>();
2644        let mut context = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
2645        let parsed = GroPacket::parse(&packet, &GroFrameType::Ethernet, &mut context)
2646            .expect("should parse valid packet");
2647        let checksum_offset =
2648            parsed.offsets.transport_offset + packet_formats::tcp::CHECKSUM_OFFSET;
2649        packet[checksum_offset] ^= 0xff;
2650
2651        let items: Vec<GroInputItem<TrackedBuffer, GroFrameType>> = vec![GroInputItem {
2652            buffer: TrackedBuffer::new(packet, true),
2653            target: GroFrameType::Ethernet,
2654            checksum_offload: ChecksumRxOffloading::default(),
2655        }];
2656
2657        let mut storage = GroBufferStorage::new();
2658        let mut gro = GroIter::new(items.into_iter(), &mut storage, true);
2659        let item = gro.next().unwrap();
2660        assert_eq!(item.checksum_offload, ChecksumRxOffloading::default());
2661    }
2662}