This is an AI generated review of your patch.  A human has looked at the
results and deemed any concerns as plausible.

Verdict reasoning summary: the change swaps "mark CT invalid and hand
the fragment back to the conntrack batch" for "free the fragment",
keeping the same return convention, so no leak or double free is
introduced.

Hunk 1 (lib/ipf.c, duplicate/overlap branch)

>     } else {
>         ipf_count(ipf, v6, IPF_NFRAGS_OVERLAP);
> -        pkt->md.ct_state = CS_INVALID;
> -        return false;
> +        dp_packet_delete(pkt);
> +        return true;

Ownership is correct. `ipf_handle_frag()`/`ipf_process_frag()` use "true
= packet consumed, false = caller re-adds", and the only caller
(`ipf_extract_frags_from_batch`, lib/ipf.c:1038-1062) does exactly that
with `dp_packet_batch_add(pb, pkt)` on false. The iteration uses
`DP_PACKET_BATCH_REFILL_FOR_EACH`, whose contract in
lib/dp-packet.h:945-952 is "caller owns the packets that are not
refilled", so deleting here rather than refilling is the intended drop
path and matches the existing drops a few lines up (the
`ipf_v4_key_extract`/`ipf_v6_key_extract` failure paths in
`ipf_handle_frag`, which already do `dp_packet_delete(pkt); return
true;`). No double free: the refill iterator truncates the batch to the
refilled prefix, so the freed pointer is never visited again.

Accounting is also consistent: `atomic_count_inc(&ipf->nfrag)` happens
only on the accept branch, so a rejected duplicate must not decrement
it, and it does not. The `IPF_NFRAGS_OVERLAP` counter is preserved,
which keeps `dpctl/ipf-get-status` output meaningful (and the system
testsuite expectations of `overlapped: 0` are unaffected). Freeing under
`ipf->ipf_lock` is slightly heavier work inside the lock but is already
the established pattern in this file.

One behavioral consequence worth stating rather than hiding: overlapping
fragments no longer reach conntrack at all, so they no longer produce a
`+inv+trk` result. Deployments whose policy relies on matching/dropping
`ct_state=+inv+trk`, or on conntrack stats/ct events to observe such
traffic, lose that visibility; the drop is silent apart from the IPF
counter. That is what the commit message intends and matches the kernel,
so it is a note, not a defect. Note also an asymmetry that this commit
does not address: once a list reaches
`IPF_LIST_STATE_COMPLETED`/`REASS_FAIL`, `ipf_handle_frag()` returns
false early and such fragments still go to conntrack instead of being
dropped — pre-existing structure, unchanged here.

Hunk 2 (tests/ofproto-dpif.at, new test)

The packet construction checks out. I verified the IPv4 header checksum
by summing the words: `4500 01a4 0020 0032 4011` plus `0a01 0101 0a01
0102` = `0x9d0c`, and `0x9d0c + 62f3 = 0xffff`, so the header is valid —
which matters, because `ipf_is_valid_v4_frag()` recomputes the checksum
and would otherwise take the `invalid_pkt` path (which sets `CS_INVALID`
and bypasses the overlap counter entirely). The MF=0 last-fragment
choice also correctly sidesteps the minimum-fragment-size check, since
that check in lib/ipf.c:672-677 is guarded by `!lf`, so the test does
not need `dpctl/ipf-set-min-frag`.

Minor concern: the zero-output assertion

> AT_CHECK([test 0 = `ovs-ofctl parse-pcap p1-tx.pcap | wc -l`])

is only meaningful if `p1-tx.pcap` actually exists. The comparable
existing test (tests/ofproto-dpif.at:13935-13943) first uses
`OVS_WAIT_UNTIL` on a positive pcap event before asserting a zero count
elsewhere. Here the preceding `ipf-get-status` check does act as a
de-facto sync point (the overlap counter is bumped before any forwarding
decision), so I do not expect flakiness, but if the pcap file were never
created the assertion would either fail on unexpected stderr or pass
vacuously — a weak rather than wrong check. Also, there is no IPv6
duplicate case and no case exercising an overlap arriving after
reassembly completed; coverage of the drop path is v4-only.

Verdict: WARN

```json
{
  "findings": [
    {
      "type": "Logic",
      "title": "Overlapping fragments are now dropped silently, bypassing 
conntrack and its observability",
      "description": "ipf_process_frag() no longer sets pkt->md.ct_state = 
CS_INVALID and returns the fragment to the batch; it frees the fragment 
instead. The packet never reaches conntrack, so no +inv+trk state is produced 
and no conntrack statistics or ct events are generated for it.",
      "reasoning": "Verified the caller contract: 
ipf_extract_frags_from_batch() (lib/ipf.c:1038-1062) re-adds the packet only 
when ipf_handle_frag() returns false, and DP_PACKET_BATCH_REFILL_FOR_EACH 
documents that non-refilled packets belong to the caller 
(lib/dp-packet.h:945-952), so freeing is correct and leak-free. The consequence 
is purely behavioral: policies or monitoring keyed on ct_state=+inv+trk for 
overlapping fragments lose their signal; only the IPF_NFRAGS_OVERLAP counter 
remains. This matches the stated intent (aligning with the kernel) but is an 
externally visible change worth documenting.",
      "severity": "low",
      "severity_explanation": "No memory or correctness breakage; triggering 
path is any overlapping/duplicate IPv4 or IPv6 fragment processed through the 
userspace datapath conntrack path. Impact limited to loss of visibility and a 
change for flows that deliberately forward invalid packets.",
      "locations": [
        {
          "file": "lib/ipf.c",
          "function_or_symbol": "ipf_process_frag",
          "line_range": "851-856",
          "why_this_location_matters": "This is where the duplicate fragment 
stops being handed back to conntrack and is freed instead, so all downstream 
ct_state-dependent behavior changes."
        }
      ],
      "preexisting": false
    },
    {
      "type": "Logic",
      "title": "New test's zero-output check can pass vacuously if p1-tx.pcap 
is absent",
      "description": "The assertion 'test 0 = `ovs-ofctl parse-pcap p1-tx.pcap 
| wc -l`' does not first establish that the pcap file exists or that any packet 
was ever written to it, unlike the similar existing test at 
tests/ofproto-dpif.at:13935-13943 which gates on OVS_WAIT_UNTIL for a positive 
event.",
      "reasoning": "Read both tests and the add_of_ports --pcap helper 
(tests/ofproto-macros.at:341-380). The preceding dpctl/ipf-get-status check 
does synchronize with packet processing because the overlap counter is 
incremented before any forwarding decision, so I expect no flakiness; but the 
assertion itself carries no positive guarantee that output would have been 
recorded had the fragment been forwarded.",
      "severity": "low",
      "severity_explanation": "Test-strength issue only. If forwarding behavior 
regressed in a way that also prevented pcap creation, or if the file naming 
changed, the test could pass without exercising the intended property.",
      "locations": [
        {
          "file": "tests/ofproto-dpif.at",
          "function_or_symbol": "AT_SETUP([ofproto-dpif - fragment handling - 
drop duplicate fragment])",
          "line_range": "5672-5723",
          "why_this_location_matters": "This is the only assertion verifying 
that the dropped duplicate is never transmitted."
        }
      ],
      "preexisting": false
    },
    {
      "type": "Logic",
      "title": "Duplicate fragments after reassembly completion still bypass 
the new drop path",
      "description": "ipf_handle_frag() returns false early when the matching 
list is in IPF_LIST_STATE_COMPLETED or IPF_LIST_STATE_REASS_FAIL, so a 
duplicate arriving at that point is returned to conntrack rather than dropped, 
inconsistent with the new overlap handling.",
      "reasoning": "Read ipf_handle_frag() (lib/ipf.c:957-1035): the early 
bail-out on those two states precedes any call to ipf_process_frag(), so it is 
unreachable from the new drop logic. This structure predates the commit and is 
not made worse by it, but the commit's stated goal of discarding duplicates in 
IPF rather than conntrack is only partially achieved.",
      "severity": "low",
      "severity_explanation": "Narrow window: requires a duplicate fragment 
arriving after the datagram already reassembled or failed. Behavior is 
unchanged from before the commit, so this is an incomplete-coverage note rather 
than a regression.",
      "locations": [
        {
          "file": "lib/ipf.c",
          "function_or_symbol": "ipf_handle_frag",
          "line_range": "1016-1020",
          "why_this_location_matters": "Early return for completed/failed lists 
skips ipf_process_frag() and therefore the new drop."
        }
      ],
      "preexisting": true
    }
  ]
}
```

[note: tool call budget exhausted]

---
findings: 3  worst severity: low  tokens: 965456 in / 23877 out


Eli Britstein <[email protected]> writes:

> When IPF detects an overlapping or duplicate fragment, drop it instead
> of marking its CT state invalid and returning it to the conntrack batch.
> This aligns with the Linux kernel, which discards such fragments during
> IP reassembly rather than forwarding them separately.
>
> Assisted-by: composer-2.5-fast, Cursor
> Fixes: 4ea96698f667 ("Userspace datapath: Add fragmentation handling.")
> Signed-off-by: Eli Britstein <[email protected]>
> ---
>  lib/ipf.c             |  8 +++----
>  tests/ofproto-dpif.at | 52 +++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 56 insertions(+), 4 deletions(-)
>
> diff --git a/lib/ipf.c b/lib/ipf.c
> index d836b8824..185d6432e 100644
> --- a/lib/ipf.c
> +++ b/lib/ipf.c
> @@ -823,8 +823,8 @@ ipf_is_frag_duped(const struct ipf_frag *frag_list, int 
> last_inuse_idx,
>  }
>  
>  /* Adds a fragment to a list of fragments, if the fragment is not a
> - * duplicate. If the fragment is a duplicate, that fragment is marked
> - * invalid to avoid the work that conntrack would do to mark the fragment
> + * duplicate. If the fragment is a duplicate, the fragment is dropped
> + * to avoid the work that conntrack would do to mark the fragment
>   * as invalid, which it will in all cases. */
>  static bool
>  ipf_process_frag(struct ipf *ipf, struct ipf_list *ipf_list,
> @@ -852,8 +852,8 @@ ipf_process_frag(struct ipf *ipf, struct ipf_list 
> *ipf_list,
>          }
>      } else {
>          ipf_count(ipf, v6, IPF_NFRAGS_OVERLAP);
> -        pkt->md.ct_state = CS_INVALID;
> -        return false;
> +        dp_packet_delete(pkt);
> +        return true;
>      }
>      return true;
>  }
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index ee6ac873d..5308b3008 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -5669,6 +5669,58 @@ CHECK_COVERAGE([dpif_netdev_output_grow_queues], [1])
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([ofproto-dpif - fragment handling - drop duplicate fragment])
> +OVS_VSWITCHD_START
> +add_of_ports --pcap br0 1 90
> +
> +AT_DATA([flows.txt], [dnl
> +table=0 in_port=90,ip actions=ct(commit),output:1
> +])
> +AT_CHECK([ovs-ofctl -O OpenFlow11 replace-flows br0 flows.txt])
> +
> +dnl Send the same fragment twice.  It is a last fragment (MF=0) at offset 
> 400,
> +dnl so it bypasses the minimum fragment size check and is admitted, yet it
> +dnl cannot complete reassembly on its own (bytes 0..399 are missing).  The
> +dnl second copy is a duplicate and must be dropped, rather than marked CT
> +dnl invalid and returned to the datapath, so it is never forwarded.
> +dnl
> +dnl Last fragment carrying bytes 400..799 (MF clear).
> +dnl   Ethernet II, Src: 50:54:00:00:00:09, Dst: 50:54:00:00:00:0a
> +dnl       Type: IPv4 (0x0800)
> +dnl   Internet Protocol Version 4, Src: 10.1.1.1, Dst: 10.1.1.2
> +dnl       0100 .... = Version: 4
> +dnl       .... 0101 = Header Length: 20 bytes (5)
> +dnl       Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
> +dnl       Total Length: 420
> +dnl       Identification: 0x0020 (32)
> +dnl       000. .... = Flags: 0x0 (Last fragment)
> +dnl       ...0 0000 0011 0010 = Fragment Offset: 400
> +dnl       Time to Live: 64
> +dnl       Protocol: UDP (17)
> +dnl       Header Checksum: 0x62f3
> +dnl   Data (400 bytes)
> +eth="50 54 00 00 00 0a 50 54 00 00 00 09 08 00"
> +ip="45 00 01 a4 00 20 00 32 40 11 62 f3"
> +addrs="0a 01 01 01 0a 01 01 02"
> +data=$(printf '%0*d' 800 0)
> +packet="${eth}${ip}${addrs}${data}"
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet"])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet"])
> +
> +AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
> +| grep -E 'v4 frags accepted:|v4 frags overlapped:'], [], [dnl
> +        v4 frags accepted: 1
> +        v4 frags overlapped: 1
> +])
> +
> +dnl The duplicate fragment is dropped, not marked invalid and forwarded, so
> +dnl nothing must be transmitted on the output port.
> +AT_CHECK([test 0 = `ovs-ofctl parse-pcap p1-tx.pcap | wc -l`])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([ofproto-dpif - handling of malformed TCP packets])
>  OVS_VSWITCHD_START
>  add_of_ports br0 1 90

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to