Skip to content

fix(toolkit): dpkt walks IP layers off the octets as the default engine does (#1609, #1626, #1651, #1654) - #1662

Merged
JarryShaw merged 4 commits into
mainfrom
fix/1609-1626-1651-1654-dpkt-walkers
Oct 13, 2026
Merged

JarryShaw merged 4 commits into
mainfrom
fix/1609-1626-1651-1654-dpkt-walkers

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

The dpkt adapters walked what dpkt decoded. They now read every IP layer, extension header and TCP segment off the record octets, by the default engine's rules (_ip_layers, _ipv6_chain, _tcp_carrier in pcapkit/toolkit/dpkt.py):

Measured on 3.14 (dpkt 1.9.8, scapy 2.7.0) and 3.11 (dpkt 1.9.8, scapy 2.8.0, pypcapfile 0.12.0), over 294 built cases (every issue row plus controls) and the 23 fixture captures. dpkt now agrees with default field for field on all 294 cases (159 on main). The 135 dpkt cells that changed are exactly the ones that disagreed. No default, scapy or pypcapfile cell changed, and no fixture changed.

New tests/toolkit/test_dpkt_ip_walk_1609_1626_1651_1654_unit.py: all 6 tests fail on main (83 subtest failures) and pass here (115 subtests), on both interpreters. Over the dpkt test files, coverage (lines and branches) of pcapkit/toolkit/dpkt.py goes from 98% to 99%, and of the dpkt engine from 89% to 91%.

A packet built by hand has no record octets, so it is now read from dpkt's serialisation of its network layer. Its TCP header therefore carries the checksum dpkt fills in, and that pin is updated. The duck-typed fakes in test_dpkt_unit.py are replaced with real frames. The engine and two shared tests (the #1604 gap, and the #1606 _decode_ip assert) each get a separate commit.

Closes #1651
Closes #1654
Part of #1609, #1626

…ne does (#1609, #1626, #1651, #1654)

The dpkt adapters walked what dpkt decoded, which is not what the default
engine dissects. They now read every IP layer, extension header and TCP
segment off the record octets, by the default engine's rules:

- #1651: an IPv6 payload ends at its Payload Length, or at a jumbogram's
  Jumbo Payload Length; a zero one with no Jumbo Payload option has none.
- #1654: IP behind a VLAN tag stack dpkt leaves undecoded is read too.
- #1609: TCP and IP inputs stop at 32 extension headers in a row and at
  the 64-layer frame bound.
- #1626: Mobility, HIP and Shim6 are walked, and so is a Fragment header
  with another header behind it, in a frame dpkt cannot decode as well,
  which is read by its link type (attach_linktype).
- packet2dict maps a frame kept raw to {} rather than raising.

Closes #1651
Closes #1654
Part of #1609, #1626
A frame dpkt cannot decode is kept as raw octets, which do not say what
link layer they start with, so the adapters could hand over nothing for
it. The engine now stashes the link type beside the timestamp and the
record octets, and the adapters read the frame by it.

Part of #1626
dpkt's adapters now stop the chain at EXTENSION_HEADER_LIMIT, so the
ipv6-33 and ipv6-1000 dpkt cases agree with default, and a gap that no
longer fails is an error in the harness.

Part of #1609
…d it (#1626)

The test asserted _decode_ip, which the dpkt walk no longer needs. It now
asserts dpkt's own decode raises, and that the TCP in the frame is read,
as the default engine reads it.

Part of #1626
@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) review: good-to-go Cross-review at the current head says ready; CI state is separate labels Oct 13, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verdict: GOOD TO GO at c874bfb73. A Sonnet cross-review checked the Opus worker's change. I re-ran its five test files: 49 passed on 3.14 and 49 on 3.11, with no failures.

The reviewer re-measured dpkt against the default engine:

  • 294 of 294 cases agree, against 159 on main.
  • An independent fuzz of about 25,000 frames over six link types found no disagreement except a timestamp-rounding one that predates this PR. That one is being filed separately.
  • No new exceptions, and no default-engine cell changed.

Disclosed behaviour change, for you to accept: the dpkt adapters now read dpkt's serialisation of the network layer. A hand-built packet therefore reports dpkt's recomputed TCP checksum, and a duck-typed non-dpkt object returns None.

Three commits sit outside the toolkit:

  • pcapkit/foundation/engines/dpkt.py (+3), which attaches the link type to a frame that dpkt fails to parse
  • two test files, updated for the gaps this closes

This conflicts with the scapy PR only in tests/foundation/engines/test_ext_chain_limit_agreement_1604_unit.py. Whichever lands second gets rebased.

@JarryShaw JarryShaw moved this to In review in PyPCAPKit Oct 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Coverage: 89.68% (unit tier, Python 3.14, c874bfb73, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 3 20 3 95.16%
pcapkit/const 18858 954 2366 868 91.06%
pcapkit/corekit 2366 67 760 35 96.48%
pcapkit/dumpkit 275 3 100 3 98.40%
pcapkit/foundation 3031 152 1074 35 94.23%
pcapkit/interface 138 7 52 5 93.68%
pcapkit/protocols 17166 226 4580 190 98.07%
pcapkit/toolkit 1255 188 496 6 83.67%
pcapkit/utilities 494 4 146 4 98.75%
pcapkit/vendor 4409 2342 1006 157 43.25%

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

@JarryShaw
JarryShaw merged commit 6c17c7e into main Oct 13, 2026
47 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in PyPCAPKit Oct 13, 2026
@JarryShaw
JarryShaw deleted the fix/1609-1626-1651-1654-dpkt-walkers branch October 13, 2026 18:29
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 13, 2026
JarryShaw added a commit that referenced this pull request Oct 13, 2026
#1662 made the dpkt adapters stop at EXTENSION_HEADER_LIMIT and at
FRAME_LAYER_LIMIT, but both sections still listed dpkt among the engines
whose own parsers walk past them. Only pyshark, and pypcapfile for the
frame bound, are left there.

Part of #1609
JarryShaw added a commit that referenced this pull request Oct 13, 2026
… headers as the default engine does (#1609, #1626, #1650) (#1663)

* fix(toolkit): scapy walks link types, layer bounds and IPv6 extension headers as the default engine does (#1609, #1626, #1650)

- #1650: the first layer was whatever Scapy dissected for the link type, so
  TCP and IP fragments at LINUX_SLL, LINUX_SLL2, 12 and PPP were handed on.
  _network now starts the walk only at NULL, ETHERNET, LOOP, RAW, IPV4 and
  IPV6, and reads a loopback family by the default engine's rules (2 is
  IPv4; 24, 28, 30 are IPv6) in its byte order, re-dissecting the packet
  where Scapy's host-dependent bindings differ. A packet built by hand
  takes its link type from its first layer.
- #1609: _dissected now counts each layer, and stops at FRAME_LAYER_LIMIT,
  so nothing is read past layer 64, behind VLAN tags included.
- #1626: _ipv6_chain walks an IPv6 extension header chain over the octets,
  up to EXTENSION_HEADER_LIMIT and the frame bound, keeping Scapy's layers
  while they match. TCP, IPv4 and IPv6 behind AH, Mobility, HIP, Shim6 and
  AH with Next Header 0 are now read, and so is the Fragment header behind
  them; IPv6 reassembly takes it from the octets.

New tests/toolkit/test_scapy_linktype_and_layer_bounds_1609_1626_1650_unit.py
compares scapy with the default engine over 90 cases; all 6 tests fail
against main and pass with the fix, on 3.14 and 3.11.

Closes #1650
Part of #1609, #1626

* test(engines): drop the scapy gap the extension header bound now closes (#1609)

scapy now stops an IPv6 extension header chain at EXTENSION_HEADER_LIMIT,
so ipv6-33/scapy/* come back OK and the recorded Gap fails the module. The
Gap is removed and the docstring says why scapy agrees; the dpkt and tshark
gaps stay.

Part of #1609

* docs(protocols): the scapy toolkit stops at the extension header bound (#1609)

The Extension Header Chains section listed scapy among the engines whose
own parsers walk past EXTENSION_HEADER_LIMIT; it now stops there, as the
pypcapfile toolkit does.

Part of #1609

* docs(protocols): the scapy toolkit stops at the frame layer bound (#1609)

The FRAME_LAYER_LIMIT section listed scapy among the engines that do not
apply the bound; it now counts the layers it walks and stops at it.

Part of #1609

* docs(protocols): the dpkt toolkit stops at both bounds too (#1609)

#1662 made the dpkt adapters stop at EXTENSION_HEADER_LIMIT and at
FRAME_LAYER_LIMIT, but both sections still listed dpkt among the engines
whose own parsers walk past them. Only pyshark, and pypcapfile for the
frame bound, are left there.

Part of #1609
JarryShaw added a commit that referenced this pull request Oct 14, 2026
Third-party engines are best effort (#1687), so the dpkt code that exists
only to match the default engine comes out:

- revert #1662, #1655, #1624, #1622, #1614 and the dpkt hunks of #1605:
  the octet walk and its link-type stash, IPv6 in IPv4 and IPv4 in IPv6,
  refusing MPLS and ISL, the tunnel and extension-header walks, the IPv4
  header checks and the layer bounds
- keep #1690's timestamps, #1594's parser choice, #1621 and #1653
- re-land as guards: a layer dpkt's own lengths misplace (#1646) is
  re-serialised, not refused with ProtocolError; a Total Length below the
  IHL keeps the header whole (#1616), and a header the capture cuts is no
  input; packet2dict and packet2chain take a str payload and a raw frame
- delete the modules #1662 and #1655 added; the 1615/1616 module keeps
  only what its #1616 tests use, as no scapy test imports it now; dpkt's
  comparisons with the default engine assert constants or the capture's
  octets, or are dropped
- drop pypcapfile's VLAN_TAGS, VLAN_TAG_LEN and IPV6_EXTENSION_HEADERS,
  which only dpkt imported, and the 1668 module's _Agreement base, which
  nothing subclasses now
- the docs drop the depth claims and name what dpkt leaves out silently

The dpkt modules, the shared modules that run dpkt, tests/foundation/engines
and the project guards pass on 3.14 and 3.11 (dpkt 1.9.8, pypcapfile 0.12.0).
JarryShaw added a commit that referenced this pull request Oct 14, 2026
Third-party engines are best effort (#1687), so the dpkt code that exists
only to match the default engine comes out:

- revert #1662, #1655, #1624, #1622, #1614 and the dpkt hunks of #1605:
  the octet walk and its link-type stash, IPv6 in IPv4 and IPv4 in IPv6,
  refusing MPLS and ISL, the tunnel and extension-header walks, the IPv4
  header checks and the layer bounds
- keep #1690's timestamps, #1594's parser choice, #1621 and #1653
- re-land as guards: a layer dpkt's own lengths misplace (#1646) is
  re-serialised, not refused with ProtocolError; a Total Length below the
  IHL keeps the header whole (#1616), and a header the capture cuts is no
  input; packet2dict and packet2chain take a str payload and a raw frame
- delete the modules #1662 and #1655 added; the 1615/1616 module keeps
  only what its #1616 tests use, as no scapy test imports it now; dpkt's
  comparisons with the default engine assert constants or the capture's
  octets, or are dropped
- drop pypcapfile's VLAN_TAGS, VLAN_TAG_LEN and IPV6_EXTENSION_HEADERS,
  which only dpkt imported, and the 1668 module's _Agreement base, which
  nothing subclasses now
- the docs drop the depth claims and name what dpkt leaves out silently

The dpkt modules, the shared modules that run dpkt, tests/foundation/engines
and the project guards pass on 3.14 and 3.11 (dpkt 1.9.8, pypcapfile 0.12.0).
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

1 participant