Skip to content

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

Merged
JarryShaw merged 5 commits into
mainfrom
fix/1609-1626-1650-scapy-walkers
Oct 13, 2026
Merged

JarryShaw merged 5 commits into
mainfrom
fix/1609-1626-1650-scapy-walkers

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

Please follow the guide below

  • You will be asked some questions, please read them carefully and answer honestly

  • Put an x into all the boxes [ ] relevant to your pull request (like that [x])

  • Use Preview tab to see how your pull request will actually look like

  • Searched for similar pull requests

  • Followed the coding style (make pylint, make mypy, make isort)

  • make test passes, and a test case covers the change — tests cover it; only the scapy, engine and toolkit selections were run, not the whole suite

  • Added a changelog entry under docs/source/changelog/ and regenerated CHANGELOG.md, if the change is user-visible — N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657

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 scapy adapters now walk a frame as the default engine dissects it:

Measured over 141 cases (default / dpkt / scapy, plus pypcapfile on 3.11), on 3.14.8 (scapy 2.7.0) and 3.11.15 (scapy 2.8.0), with identical cells on both: 66 scapy cells move to the default engine's inputs, none of the other 75 changes, and no other engine's cell changes. Three of the 66 are outside the issues' rows: 6in4 fragments behind Hop-by-Hop or Mobility in IPv4, and a Fragment header behind an 8-octet AH, now give IPv6 reassembly input.

New tests/toolkit/test_scapy_linktype_and_layer_bounds_1609_1626_1650_unit.py: 6 tests, all failing on main (20 failed, 1 passed) and passing with the fix (6 passed, 15 subtests), on both interpreters. Coverage of pcapkit/toolkit/scapy.py goes from 99.37% to 99.58%. Separate commits drop the scapy Gap from the #1604 chain test and update two docs pages that listed scapy as unbounded.

Closes #1650
Part of #1609, #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 8fc314fc4. A Sonnet cross-review checked the Opus worker's change. I re-ran its four test files: 21 passed on 3.14 and 21 on 3.11.

The reviewer ran its own 394-case probe, covering link types, NULL and LOOP families in both byte orders, and depth and extension-chain sweeps:

  • 172 scapy cells moved to the default engine's inputs.
  • No new disagreements, none left, no default cell changed, and no exceptions.
  • Results are identical on scapy 2.7.0 and 2.8.0.

NULL and LOOP no longer depend on the host's AF_INET6.

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

Left open on #1609: the pypcapfile and pyshark parts, which neither PR touches.

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

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Coverage: 89.46% (unit tier, Python 3.14, e42881a10, 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 1350 275 558 6 77.20%
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

Copy link
Copy Markdown
Owner Author

resolve conflicts

… 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
@JarryShaw

Copy link
Copy Markdown
Owner Author

Recorded: resolving the conflicts. With #1662 merged, the only conflict is in tests/foundation/engines/test_ext_chain_limit_agreement_1604_unit.py, where both PRs remove neighbouring Gap entries. A worker is rebasing onto main and keeping both removals. The rebased head then gets a short Sonnet check, and runs the scapy, dpkt and 1604 agreement tests on 3.14 and 3.11, before the label goes back to good-to-go.

…es (#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
#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
)

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
#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

Copy link
Copy Markdown
Owner Author

Verdict: GOOD TO GO at e42881a10. It is rebased onto main at 6c17c7e1b, which includes #1662, and a Sonnet check confirmed the result. I re-ran the 1604 agreement, scapy link-type and dpkt walk tests: 3, 6 and 6 passed.

git range-diff shows three of the four commits unchanged. The fourth carries the conflict resolution: both the dpkt and the scapy Gap are gone from test_ext_chain_limit_agreement_1604_unit.py, leaving only the tshark ipv6-33/pyshark/trace one. Putting the scapy Gap back makes the test fail ("came back OK"), so this PR does close it.

One new commit, e42881a10, which is docs only: internet.rst and protocol.rst no longer say the dpkt toolkit walks past the bounds, since #1662 made it stop there.

@JarryShaw
JarryShaw force-pushed the fix/1609-1626-1650-scapy-walkers branch from 8fc314f to e42881a Compare October 13, 2026 18:40
@JarryShaw
JarryShaw merged commit 4673cff into main Oct 13, 2026
44 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-1650-scapy-walkers branch October 13, 2026 19:16
@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 14, 2026
Third-party engines are best effort (#1687), so the scapy code that
exists only to match the default engine comes out:

- revert #1683, #1663, #1652 and #1631, and the scapy hunks of #1614: the
  link-type and loopback-family walk, the frame and extension-header
  bounds, the IPv6 chain walk over the octets, the stops at GRE, VXLAN,
  PPPoE and the other layers the default engine leaves raw and at ICMP
  quotes, and the IPv4 header checks
- keep #1624's walk (TCP behind AH or an IPv6 extension header in IPv4,
  and the later-fragment stop), #1605's keying by the carrying IP header
  and #1589's trailer cut; the walk calls _walk_extension_headers, which
  the pypcapfile revert keeps
- re-land as guards: a zero-length frame is no IPv4 input and has no
  packet2dict octets (#1676); IPv4 in IPv6 ends at the Payload Length
  (#1623); a packet built by hand is read off its octets (#1676);
  ipv6_reassembly takes only its own IPv6 layer's Fragment header, and
  none in a later IPv4 fragment
- delete the four scapy agreement modules and test_scapy_unit's
  default-engine checks; the guards' tests assert constants
- docs: scapy applies the extension-header bound only behind IPv4 and the
  frame-layer bound nowhere, and its silent gaps are listed
  where gaps are said to be announced

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

- revert #1683, #1663, #1652 and #1631, and the scapy hunks of #1614: the
  link-type and loopback-family walk, the frame and extension-header
  bounds, the IPv6 chain walk over the octets, the stops at GRE, VXLAN,
  PPPoE and the other layers the default engine leaves raw and at ICMP
  quotes, and the IPv4 header checks
- keep #1624's walk (TCP behind AH or an IPv6 extension header in IPv4,
  and the later-fragment stop), #1605's keying by the carrying IP header
  and #1589's trailer cut; the walk calls _walk_extension_headers, which
  the pypcapfile revert keeps
- re-land as guards: a zero-length frame is no IPv4 input and has no
  packet2dict octets (#1676); IPv4 in IPv6 ends at the Payload Length
  (#1623); a packet built by hand is read off its octets (#1676);
  ipv6_reassembly takes only its own IPv6 layer's Fragment header, and
  none in a later IPv4 fragment
- delete the four scapy agreement modules and test_scapy_unit's
  default-engine checks; the guards' tests assert constants
- docs: scapy applies the extension-header bound only behind IPv4 and the
  frame-layer bound nowhere, and its silent gaps are listed
  where gaps are said to be announced

The scapy toolkit and engine modules, the shared modules that run scapy,
and the project and tier guards pass on 3.14 (scapy 2.7.0) and 3.11
(scapy 2.8.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

Development

Successfully merging this pull request may close these issues.

fix(toolkit): scapy reads link types the default engine leaves raw, and IPv6 at the wrong BSD loopback families

1 participant