Skip to content

feat(link): implement LINUX_SLL and LINUX_SLL2 dissectors - #2443

Merged
JarryShaw merged 4 commits into
mainfrom
feat/2438-linux-sll
Oct 31, 2026
Merged

JarryShaw merged 4 commits into
mainfrom
feat/2438-linux-sll

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Please follow the guide below

What is the purpose of your pull request?

  • feat — adds a feature

Description of your pull request and other information

Implements #2438: LinuxSLL1 and LinuxSLL2 for LINKTYPE_LINUX_SLL (113) and LINKTYPE_LINUX_SLL2 (276), on the family base LinuxSLL (linux_sll), wired into pcap and pcapng frame dispatch.

  • id() follows IP/IPv4: 'LinuxSLL' selects either version, 'LinuxSLL1' selects v1 only.
  • protocol_type is an EtherType, except for the five ARPHRD values in pcapkit.const.linux.arphrd.ARPHRD, which keep it as Raw.

Targeted run only: tests/protocols/link/test_linux_sll{1,2,_selector}_unit.py, test_dispatch_registry_unit.py and test_rfc_constants_one_home_1739_unit.py: 59 passed, 359 subtests. make test not run.

Closes #2438

@github-actions github-actions Bot added this to the PyPCAPKit v1.5 milestone Oct 30, 2026
@JarryShaw JarryShaw moved this to In review in PyPCAPKit Oct 30, 2026
@JarryShaw JarryShaw added feat Pull requests that add a new capability (feat: subject prefix) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Review: NEEDS CHANGES at 476773076 (Opus, different model from the author).

Must fix:

  • Dispatch condition is backwards relative to the real spec. Per tcpdump.org's own LINUX_SLL/LINUX_SLL2 pages, protocol_type is the Ethernet protocol type for every arphrd_type except five: NETLINK (824), IPGRE (778), IP6GRE (823), IEEE80211_RADIOTAP (803), FRAD (770). The PR does the opposite — treats it as an EtherType only when arphrd_type == Hardware.Ethernet — so a tcpdump -i any capture's loopback traffic (ARPHRD 772), tun/wireguard (65534), PPP (512), SLIP/CSLIP (256/257), all come out as Raw even with a perfectly valid EtherType in protocol_type. Verified empirically (table of 5 hardware types × a 0x0800 protocol_type, only Ethernet resolved to IPv4). This traces back to a wrong premise in issue feat(link): implement LINUX_SLL and LINUX_SLL2 (the tcpdump -i any cooked-capture headers) #2438 itself ("only an EtherType in the common case (arphrd_type == ARPHRD_ETHER)") — that line is being corrected on the issue. The fix: invert the condition to an exclusion list of the five non-Ethernet-semantics hardware types, not an inclusion list of one.
  • Any unregistered EtherType on the Ethernet path kills the whole header. _resolve_next_layer_proto calls Enum_EtherType.get(protocol_type), which raises EnumValueError for the ~62,555 unregistered values out of 65,536 — so e.g. protocol_type=0x1234 loses the entire SLL header, coming out as Raw(protocol=113, error=...) instead of a parsed SLL header with a Raw payload (which is what Ethernet does for the same unregistered EtherType). One-line fix: pass the raw int through, or give .get() a default, so the defaultdict fallback to Raw happens at the payload level, not by blowing up the header parse.
  • Round trip breaks for link_addr_length > 8. The spec allows this (InfiniBand addresses are 20 octets; only the first 8 are kept) — parsing succeeds but from_data() raises ProtocolError: invalid link-layer address length: 20, because _validate_link_addr caps the length at 8 on rebuild. Standing rule: every parse→rebuild round trip must match. Fix: allow 0-65535 for v1, 0-255 for v2 (matching each format's own field width).

Confirmed correct, no change needed: both wire layouts byte-exact against spec (v1: 16 bytes, v2: 20 bytes, verified field-by-field); the dispatch mechanism itself works as described (only the rule it applies is wrong, above); frame.py/pcapng.py wiring is genuine, no unrelated changes; test fixtures' hand-crafted bytes are correct (verified via struct.pack cross-check); tests/project/ fully clean (2107 passed); mypy/pylint clean per the Makefile's actual flags; the no-shared-base design call holds up, though "disagree on every field's width and order" (in the PR, twice in pep.rst) is itself inaccurate — arphrd_type/link_addr/protocol_type are the same width in both, only order and two narrower fields differ, worth a wording fix while in there.

Minor, non-blocking: pcapkit.const.arp.hardware.Hardware is the IANA ARP Hardware Types registry (RFC 826/5494), not Linux's own ARPHRD_* list — they agree through ~38 but diverge above that (e.g. ARPHRD_LOOPBACK=772 isn't Hardware.Loopback, it's Hardware.Unassigned), worth a docstring note since it affects what gets logged/compared, though not the Ethernet-vs-not gate itself (Ethernet=1 does correctly equal ARPHRD_ETHER). PR body's claimed test count ("520 tests") doesn't match the directory total; the new files themselves add 16 tests/42 subtests. No changelog entry yet — add one to the newly-opened docs/source/changelog/1.6.0.rst.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Coverage: 94.52% (unit tier, Python 3.15, 3579d5631, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 2 95.16%
pcapkit/const 19700 133 2632 120 98.85%
pcapkit/corekit 2605 74 818 37 96.52%
pcapkit/dumpkit 281 3 100 3 98.43%
pcapkit/foundation 3070 24 1072 26 98.79%
pcapkit/interface 138 4 52 3 96.32%
pcapkit/protocols 18252 195 4802 181 98.36%
pcapkit/toolkit 924 36 334 10 95.55%
pcapkit/utilities 502 7 144 5 98.14%
pcapkit/vendor 4449 1854 1020 167 55.11%

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

@JarryShaw
JarryShaw force-pushed the feat/2438-linux-sll branch from 4767730 to 986e42f Compare October 30, 2026 06:22
@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

All three required fixes applied and verified directly (new head 986e42fe5): dispatch now excludes the real five non-EtherType arphrd values (NETLINK/IPGRE/IP6GRE/RADIOTAP/FRAD) instead of including only Ethernet; an unregistered EtherType now falls back to Raw at the payload level instead of killing the whole header; link_addr_length > 8 (e.g. InfiniBand) now round-trips via a max_length parameter. Should-fix items also applied: docstring/pep.rst overstatement corrected, changelog bullet added, PR body's test-count claim fixed. Spot-verified: tests/protocols/link/ 136 passed/868 subtests, matching the report exactly. Cross-review re-dispatched.

@JarryShaw JarryShaw added review: needs-changes Cross-review at the current head says changes are required; see the verdict comment and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (cross-review round 2, different model from the author).

All three round-1 fixes independently re-derived as correct against the real spec (tcpdump.org, Linux kernel if_arp.h, Wireshark's dissectors) and end-to-end through real pcapkit.extract() calls — no issues found there.

One blocker, trivial: make isort fails on two new files (linux_sll2.py wants three import lines joined that were wrapped at 79 columns instead of the Makefile's 100). This breaks tests/project/test_isort_clean.py and is the actual cause of the PR's own CI "Project tests" job failure at this head — the worker's reported "2107 passed" count buried a single failed subtest in the summary line.

Should-fix (not blocking): the -1 sentinel for an unrecognized/excluded EtherType leaks into user-visible output (info.protocol) differently than Ethernet's own <unknown>.<unassigned>: N — "matching Ethernet's own behaviour" is only true for the Raw-fallback case, not the protocol-number field; a test gap where mistakenly using the 0xFF cap on LINUX_SLL wouldn't be caught; two stale docstrings in the data modules.

Also flagging for awareness (not a defect in this PR): a predicted conflict with #2441 in CHANGELOG.md/1.6.0.rst (same-heading bullet additions, trivial to resolve — keep both) and overlapping pep.rst roadmap edits with #2441 and #2446 — whichever of the three wave-2 PRs merges last will need a hand-merge on pep.rst.

@JarryShaw
JarryShaw force-pushed the feat/2438-linux-sll branch from 986e42f to 3fb62f7 Compare October 30, 2026 06:51
@JarryShaw JarryShaw added review: running A cross-review is in flight against the current head - no verdict yet and removed review: needs-changes Cross-review at the current head says changes are required; see the verdict comment labels Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

isort fix applied and verified directly (new head 3fb62f73d): the diff is exactly the two files, import-line-joining only, no logic changes. test_isort_clean.py passes (1 passed/4 subtests). Cross-review re-dispatched to confirm CI goes green.

@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 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

Confirmed CI's red X at this head is the same inherited tests/dumpkit dictdumper regression from main (fix is #2445, already GOOD TO GO), not a new defect — re-checked the job logs, same &amp;lt;/&amp;amp; symptoms. Round 2's substantive LINUX_SLL/LINUX_SLL2 findings were already independently verified correct, and the isort fix was verified mechanical-only. Restoring GOOD TO GO at 3fb62f73d without a fresh review round — nothing left to review that hasn't already been checked.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Changelog bullet removed per #2449's new standing practice (centralized changelog, not per-PR). Verified directly: diff at new head 6df2526b1 vs. the prior head is empty outside docs/source/changelog/1.6.0.rst/CHANGELOG.md — pure removal, nothing else changed. GOOD TO GO verdict still holds.

@JarryShaw
JarryShaw force-pushed the feat/2438-linux-sll branch from 6df2526 to 5dacd83 Compare October 30, 2026 13:55
@JarryShaw

Copy link
Copy Markdown
Owner Author

Rebased onto main (which now includes #2445's dictdumper fix) — old head 6df2526b1 → new 5dacd8302. Patch-id/content outside the pulled-in main changes is unchanged (verified no dumpkit//changelog files appear in this PR's own diff vs. the new main). Targeted test suite re-run under a scratch venv with the correct dictdumper>=0.8.5: matches the already-verified pass counts exactly. GOOD TO GO verdict still holds — this should now show fully green CI.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Should we have a thin abstract layer and/or dispatcher for v1 and v2? Since they stand the same name/family.

For those not ethertype types, do we have corresponding protocol for each? If so, are we properly dispatching to decode them?

@JarryShaw

Copy link
Copy Markdown
Owner Author

I'd prefer we use linux_sll.LinuxSLL for the base class, linux_sll1.LinuxSLL1 and linux_sll2.LinuxSLL2 for the actual implementations, so it follows the standing convention in the library.

@JarryShaw

Copy link
Copy Markdown
Owner Author

My reading of your 03:34Z comment: the base class goes in linux_sll.LinuxSLL, and the two implementations are linux_sll1.LinuxSLL1 and linux_sll2.LinuxSLL2, following the library convention. This changes the module layout, so the PR goes back to needs-changes. Rework is held until the Actions queue drops below 40; I'll correct me if you meant something else.

@JarryShaw

JarryShaw commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner Author

id() values not following house convention (the docs PR landed, can now check from the docs). and we should actually create a const enum for ARPHRD_* values. in addition, if no LinkType dispatches LINUX_SLL headers, how are we gonna use them?

@JarryShaw

Copy link
Copy Markdown
Owner Author

My reading of your 03:59Z comment: bring the id() values in line with the house convention (checked against the conventions docs that have now landed), and add a const enum for the ARPHRD_* values. This is a rework, so the PR goes back to needs-changes; the fix round is held until the Actions queue is below 40.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Held: Actions queue at 50 (above the hold line). The id() and ARPHRD_* enum rework dispatches when the queue drops below 40.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Rework pushed as adb9a4be4: id() is narrow-to-self, as IPv4 and IPv6 do under IP: LinuxSLL1 gives ('LinuxSLL1',), LinuxSLL2 gives ('LinuxSLL2',), and the base gives both.

Decision needed: is that the house convention you meant from your 03:59Z comment? It makes 'LinuxSLL1' in a v2 layer False, a change from the previous revision.

@JarryShaw

Copy link
Copy Markdown
Owner Author

yes, that's the convention. now is correct.

@JarryShaw

Copy link
Copy Markdown
Owner Author

if no LinkType dispatches LINUX_SLL headers, how are we gonna use the two protocols?

@JarryShaw

Copy link
Copy Markdown
Owner Author

Answer to your 04:49Z question: the PCAP and PCAPNG frame dispatch maps LINUX_SLL to LinuxSLL1 and LINUX_SLL2 to LinuxSLL2, so both are reached from a capture. Only LinuxSLL (the base) is not dispatched directly.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Then their __index__ should return the corresponding LinkType values?

@JarryShaw

Copy link
Copy Markdown
Owner Author

My reading of your 04:51Z comment: LinuxSLL1.__index__ returns LinkType.LINUX_SLL (113) and LinuxSLL2.__index__ returns LinkType.LINUX_SLL2 (276). This is a rework, so #2443 goes back to needs-changes, held until the Actions queue is below 40.

Add LinuxSLL1 (LINKTYPE_LINUX_SLL) and LinuxSLL2 (LINKTYPE_LINUX_SLL2) on a
shared LinuxSLL base, wired into Frame and PCAPNG link-type dispatch. The
protocol_type is an EtherType except for the five ARPHRD types in
pcapkit.const.linux.arphrd.ARPHRD, which keep it as a Raw payload.
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (Sonnet, 14db67406). Owner items hold: __index__, narrow id(), and member order match the convention; the diff matches its claims; 45 tests pass.

  • misc/pcap/frame.py __proto__ (~L115-121) and misc/pcapng.py (~L774-780, docs ~L491-493): registry-map order should be by enum value. LINUX_SLL (113) belongs between LOOP (108) and IPV4 (228); LINUX_SLL2 (276) stays after IPV6.

Owner ruling: registry maps follow enum value order. LINUX_SLL (113)
moves between LOOP (108) and IPV4 (228) in the frame and pcapng
__proto__ maps and their docs tables. LINUX_SLL2 (276) stays after IPV6.
@JarryShaw

Copy link
Copy Markdown
Owner Author

GOOD TO GO (Sonnet, fa0de89d8). The commit only reorders LINUX_SLL before IPV4 in frame.py and pcapng.py, both maps and both docs tables, by enum value (113 before 228). LINUX_SLL2 (276) stays after IPV6. No other registry map or docs table lists LINUX_SLL out of order. Narrow tests on this head: link SLL1 14 passed, SLL2 14 passed, RFC constants 14 passed, raw linktype dispatch 4 passed. Not yet checked: docs/source/ext.rst groups SLL alphabetically, left as is. Merge is yours.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Local branch still holds the pre-rework commits c086bb2 and 5dacd83, which are superseded. The pushed rework is fa0de89 and is GOOD TO GO. Nothing further to push.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Red legs on fa0de89d8. Five required Python legs fail on const census tests (tests/const/test_const_enum_builtin_parity.py:380, tests/const/test_const_enum_get.py:300 and :340), off by one. The branch is behind main (eaddd9061), so the expected counts are main's. A worker is rebasing onto main and correcting the counts to this PR's real additions. Not yet pushed; the verdict stays review: good-to-go until the new head is reviewed.

- ARPHRD is the one const registry the LINUX_SLL work adds; the sweep
  expectations in the four census files are bumped by exactly its
  contribution. origin/main passes the previous values unchanged.
- ARPHRD(0) raises: pcapkit names no 0 (ARPHRD_NETROM is unnamed), so it
  joins EXPECTED_TO_REJECT_ZERO.
- ARPHRD is hand-written on EnumLookup, not the generated EnumRegistry
  shape, so it joins EXCLUDED_FROM_INTENUM_CENSUS as (d).

Build and test: tests/const, tests/protocols/link, dispatch registry
unit tests pass on the rebased tree.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Hold, not pushed. The census fix is local commit 23cf5b27b, a fast-forward of fa0de89d8. It waits for the Actions queue to drop below 20 (currently 23). Two decisions are needed before the review. (1) Add a NETROM = 0 member to ARPHRD? Options: add it, or leave it out. Recommendation: add it, since Linux defines the value and the enum should be complete. (2) Base class for ARPHRD: keep the hand-written EnumLookup, or move it to the generated EnumRegistry shape. Recommendation: keep EnumLookup for this PR and move it in a follow-up, so this PR does not grow. The current review: good-to-go label is for fa0de89d8 and stays until the push.

@JarryShaw

Copy link
Copy Markdown
Owner Author

(1) add it

(2) move it. do the work in this PR

@JarryShaw

Copy link
Copy Markdown
Owner Author

Ruling reconciled: (1) add the NETROM = 0 member to ARPHRD. (2) move ARPHRD to the generated EnumRegistry shape now, in this PR, not in a follow-up. Both go on top of the census fix 23cf5b27b. The census counts change again, so they are re-measured on the new head. Status: the fix is held locally until the queue drops below 20 (currently 20). Clearing needs: decision; the work starts now.

- ARPHRD moves from EnumLookup to EnumRegistry with the generated
  _validate_value/_missing_ pair, members and values unchanged.
- Adds NETROM = 0 (ARPHRD_NETROM), so ARPHRD(0) now resolves.
- Census: drop ARPHRD from EXPECTED_TO_REJECT_ZERO and from the
  EXCLUDED_FROM_INTENUM_CENSUS (d) entry; generated module count 109 -> 110.
- ARPHRD(999) now yields an unregistered 'Unassigned' member, not ValueError.

Tests: tests/const census files, tests/protocols/link linux_sll unit tests pass.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Done locally, held for the queue. Commit 3579d5631 (fast-forward of the census fix and of fa0de89d8) does two things. It adds NETROM = 0 to ARPHRD and moves ARPHRD to the generated EnumRegistry shape. Census tests are updated, and the new head needs review. Behaviour change to note: ARPHRD(999) now returns an unregistered member instead of raising ValueError. Please confirm that is wanted. Queue is at 29, so the push waits.

@JarryShaw

Copy link
Copy Markdown
Owner Author

GOOD TO GO (Sonnet, 3579d5631). NETROM = 0 is added, and ARPHRD now uses the EnumRegistry shape, matching pcapkit/const/arp/hardware.py. Census counts are each +1 and match. Narrow tests: 142 passed, 1559 subtests. Still needs your confirmation: ARPHRD(999) now returns an unregistered member instead of raising ValueError. Values outside 0..0xFFFF still raise. Merge is yours, after you confirm that behaviour change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat Pull requests that add a new capability (feat: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feat(link): implement LINUX_SLL and LINUX_SLL2 (the tcpdump -i any cooked-capture headers)

1 participant