Skip to content

fix(protocols): a capture cut inside the IPv4 header rebuilds with a fabricated UDP header - #1169

Merged
JarryShaw merged 1 commit into
mainfrom
fix/1167-ipv4-header-truncated-payload
Oct 7, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/1167-ipv4-header-truncated-payload

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner
  • Searched for similar pull requests
  • Followed the coding style (make pylint, make mypy, make isort) — pylint and isort run on ipv4.py with nothing new; mypy not run
  • make test passes, and a test case covers the change — the new module, test_ipv4_unit, test_ip_runtime, test_internet_unit, test_declared_length_roundtrip_runtime, test_tcp_udp_unit, test_option_roundtrip_unit, tests/project
  • Added a changelog entry under docs/source/changelog/ and regenerated CHANGELOG.md, if the change is user-visible — N/A, added centrally after the wave

What is the purpose of your pull request?

  • fix — corrects a defect

Description of your pull request and other information

Closes #1167.

Root cause: IPv4.read, not the rebuild. It passed the declared len - hdr_len (280) to _decode_next_layer while 0 payload octets were captured. _import_next_layer picks NoPayload only when length == 0, so UDP was parsed from an empty stream into an all-zero header, and from_data rebuilt those 8 octets faithfully. Now, when nothing was captured past the header, the length is 0 and the payload is a NoPayload.

test.pcapng frame 3, IPv4.from_data(ip.info).data: before …ffff + 0000 + 8 × 00 (28 octets, chain IPv4:UDP:Raw), after …ffff0000 (20 octets, chain IPv4). The last 2 octets are the zeros the parse already put into dst (255.255.0.0).

Sweep over examples/captures/ (from_data vs parsed, header-isolated / full): IPv4 1/1 → 1/1, UDP 1/1 → 0/0 (104 → 103 layers), IPv6 0/183 → 0/183. Nothing new mismatches.

Reverting the fix: 2 failed, 1 passed. With it: 3 passed.

…fabricated UDP header

IPv4.read handed the next layer the declared payload length even when the
capture held no payload octets, so the next layer was parsed from an empty
stream into an all-zero header that from_data then rebuilt. With nothing
captured past the header the length is now 0, which makes the payload a
NoPayload, as for any other empty payload.

Closes #1167
@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Oct 7, 2026
@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Oct 7, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Cross-review verdict on 645f07c53: GOOD TO GO (ran on Sonnet; author Opus)

  • Root cause confirmed: IPv4.read passed the declared length to _decode_next_layer with zero captured payload octets, so UDP parsed from an empty stream. test.pcapng frame 3 now gives Ethernet:IPv4 instead of Ethernet:IPv4:UDP:Raw.

  • len(self) > hdr_len is the right predicate. It is the captured length of this layer's stream, set before read. It holds for real parses and from_data, with options (IHL 6), with a zero declared length plus Ethernet padding, and with a full UDP header but a cut payload. No false positive was found.

  • Sweep, 1612 frames: exactly one frame changes, and UDP mismatches go from 1 to 0. Nothing newly mismatches.

  • Fails without the fix: 2 failed.

  • Not fixed here, as the PR body says:

    • IPv6 header-only still fabricates UDP:Raw (re-ran: 40 captured octets rebuild to 48);
    • a partial UDP header is default-filled;
    • frame 3 rebuilds to 20 octets, not 18.

    All three are filed as fix(protocols): a payload layer parsed from a short stream rebuilds default-filled octets #1170.

@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 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Coverage: 88.88% (unit tier, Python 3.14, 645f07c53, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 4 93.55%
pcapkit/const 18757 1034 2342 845 90.24%
pcapkit/corekit 1878 91 580 22 94.34%
pcapkit/dumpkit 142 0 42 0 100.00%
pcapkit/foundation 2442 102 852 39 94.38%
pcapkit/interface 112 7 40 5 92.11%
pcapkit/protocols 15699 188 3972 165 98.17%
pcapkit/toolkit 487 65 144 1 85.42%
pcapkit/utilities 429 4 122 4 98.55%
pcapkit/vendor 4409 2359 1006 158 42.84%

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

@JarryShaw
JarryShaw merged commit ecb681d into main Oct 7, 2026
65 of 67 checks passed
@JarryShaw
JarryShaw deleted the fix/1167-ipv4-header-truncated-payload branch October 7, 2026 02:25
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 7, 2026
@JarryShaw JarryShaw added this to the PyPCAPKit v1.5 milestone Oct 25, 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 capture cut inside the IPv4 header rebuilds with a fabricated UDP header

1 participant