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