Skip to content

fix(protocols): a later IPv4 fragment's data is raw, not an upper-layer header (#1553) - #1562

Merged
JarryShaw merged 1 commit into
mainfrom
fix/1553-ipv4-later-fragment-raw
Oct 10, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/1553-ipv4-later-fragment-raw

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

Please follow the guide below

What is the purpose of your pull request?

Tick the commit type your subject line carries.

  • fix — corrects a defect
  • feat — adds a feature
  • perf — changes performance, not behaviour
  • refactor — changes neither behaviour nor performance
  • test — tests only
  • docs — documentation only
  • ci — workflows or build tooling
  • release — bumps the version or rolls up a distribution
  • chore — anything else

Description of your pull request and other information

Closes #1553. The IPv4 counterpart of #1552.

  • Fix: IPv4.read passes None as the next-layer code when the Fragment Offset is non-zero, so a later fragment's data is a Raw with no protocol name. info.protocol, the lengths and packet.payload are unchanged. First fragments (offset 0, MF set) and unfragmented datagrams are still dissected.
  • tshark (-o ip.defragment:FALSE): six hand-built frames, covering first, unfragmented, offset 1/185 and behind options, now match ip:data / ip:udp:data frame for frame.
  • make(): a later fragment given a UDP object still writes the UDP octets, byte for byte the same as main. It now reads back as IPv4:Raw with no UDP layer, as IPv6 has done since fix(protocols): a later IPv6 fragment's data is raw, not an upper-layer header (#1545) #1552.
  • Captures: no sample capture holds an IPv4 later fragment. Protochains and round trips (parse, from_data(info), from_data(info.to_dict())) are identical over all 1,616 frames. IPv4 reassembly is identical over 1,331 datagrams, and over a synthetic out-of-order UDP+TCP fragment train.
  • Tests: new test_ipv4_later_fragment_raw_unit.py. On main it gives 27 failed. test_ipv4_unit.py pinned the old dispatch for an offset of 8 octets: it now asserts None, and asserts the offset-0 case still dispatches.
  • No engine-agreement Gap row cites fix(protocols): a later IPv4 fragment's data is dissected as the upper-layer header #1553.

…er header (#1553)

A datagram with a non-zero Fragment Offset now passes no next-layer code to
_decode_next_layer, so its data is a Raw leaf with no protocol name, as
Wireshark shows it, instead of a bogus UDP/TCP header read from mid-datagram
octets. info.protocol, the lengths and packet.payload are unchanged, so
reassembly and every round trip are too. First fragments and unfragmented
datagrams are still dissected. Mirrors #1552's IPv6 fix for #1545.
@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 10, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verdict: GOOD TO GO at b86351c52. Cross-reviewed on Sonnet; the author ran Opus.

  • The fix: ipv4.offset is the 13-bit field in octets (ipv4.py:316), and a non-zero value now sends the data to Raw.
    • Matches tshark: all 18 hand-built frames agree with tshark -o ip.defragment:FALSE. Later fragments of UDP/TCP/ICMP/IPv6-in-IPv4, including those behind options, at offset 0x1fff, and with TL 0, show as ip:data.
    • Still dissected: first, unfragmented and atomic datagrams.
    • Malformed DF+offset: gives IPv4:Raw, as tshark does.
  • make(): 96 payload/offset/flag combinations produce bytes identical to main. Both from_data forms are byte-exact, and only the later fragments' protochain changes.
  • Reassembly: two nine-fragment trains, one in order and one scrambled, give IPv4 and TCP reassembly and TCP traces identical to main. No sample capture contains a later IPv4 fragment, so the sample sweep is unchanged.
  • Fail-before: I ran it myself. The new module gives 4 passed on this head and 27 failed with main's ipv4.py. The re-pinned assertion in test_ipv4_unit.py is stricter, not weaker: it adds a UDP-dispatch pin at offset 0.
  • Interactions: merges cleanly with fix(toolkit): trace and reassemble a TCP segment whose header the snapshot cut (#1518) #1556 and fix(reassembly): a TSO fragment's length is its captured length, and RCVBT covers the last block (#1555, #1565) #1564, and tests/protocols/internet, tests/foundation/reassembly and tests/toolkit pass on each pairing.

The 0x1fff-offset IndexError the review hit in reassembly is #1565, which is not caused by this PR.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Coverage: 89.62% (unit tier, Python 3.14, b86351c52, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 4 93.55%
pcapkit/const 18795 957 2356 871 91.00%
pcapkit/corekit 2240 67 722 34 96.32%
pcapkit/dumpkit 258 2 90 2 98.85%
pcapkit/foundation 2766 129 980 47 94.23%
pcapkit/interface 108 7 36 5 91.67%
pcapkit/protocols 16996 230 4548 191 98.02%
pcapkit/toolkit 635 98 198 5 83.31%
pcapkit/utilities 431 4 124 4 98.56%
pcapkit/vendor 4409 2342 1006 157 43.25%

Per-file detail: the coverage-html artifact of this run.

@JarryShaw
JarryShaw merged commit a3da376 into main Oct 10, 2026
40 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in PyPCAPKit Oct 10, 2026
@JarryShaw
JarryShaw deleted the fix/1553-ipv4-later-fragment-raw branch October 10, 2026 04:39
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(protocols): a later IPv4 fragment's data is dissected as the upper-layer header

1 participant