Skip to content

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

Open
JarryShaw wants to merge 7 commits into
mainfrom
feat/2438-linux-sll
Open

JarryShaw wants to merge 7 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: LinuxSLL/LinuxSLL2 dissectors for LINKTYPE_LINUX_SLL (113) and LINKTYPE_LINUX_SLL2 (276), the Linux "cooked capture" header that tcpdump -i any captures use. Schema/data/protocol split per discussion #251, registered onto Frame.__proto__ and PCAPNG.__proto__.

Shared base and selectors. Both classes subclass a plain LINUX_SLL base (linux_sll_base.py). LINUX_SLL.id() is ('LINUX_SLL',), LinuxSLL.id() is ('LINUX_SLL1', 'LINUX_SLL') and LinuxSLL2.id() is ('LINUX_SLL2', 'LINUX_SLL'), so 'LINUX_SLL' selects either version. 'LINUX_SLL1'/'LINUX_SLL2' select only their own version, but only because they are not protocol-registry keys; the registry names 'LinuxSLL'/'LinuxSLL2', like any class, select both.

Next-layer dispatch matches Ethernet. protocol_type is an EtherType for every arphrd_type except the hardware types tcpdump.org names (NETLINK, IPGRE, IP6GRE, IEEE80211_RADIOTAP, FRAD), which are forced to Raw with the raw protocol_type recorded. Otherwise protocol_type goes through the same EnumField as Ethernet's type, so the dispatch value, next layer and protochain match Ethernet for all 65,536 values (<unassigned> included); the header's own protocol_type field stays a plain int, and Link.register() overrides apply.

Round trips. link_addr_length may exceed the 8 octets kept inline (e.g. InfiniBand's 20-octet addresses), so make() allows each format's own field width.

Tests. tests/protocols/link/ (incl. test_linux_sll_unit.py, test_linux_sll2_unit.py, test_linux_sll_selector_unit.py) and the dispatch-registry test: 157 passed, 992 subtests. Also adds the SLL payload builders and pinned targets to examples/generators/dispatch.py for the dispatch-registry test.

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.46% (unit tier, Python 3.15, 632721648, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 2 95.16%
pcapkit/const 19609 133 2620 120 98.84%
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 17783 194 4694 180 98.33%
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.

Wave 2 of the roadmap. Neither `LINKTYPE_LINUX_SLL` (113) nor `LINKTYPE_LINUX_SLL2`
(276) -- the Linux "cooked capture" headers every `tcpdump -i any` capture uses --
had a dissector, despite both DLTs already being registered.

- Add `LinuxSLL`/`LinuxSLL2` (schema/data/protocol split per discussion #251),
  registered onto `Link`, `Frame.__proto__` and `PCAPNG.__proto__`.
- Next-layer dispatch is an EXCLUSION list: per tcpdump.org's own docs,
  `protocol_type` is an EtherType for every `arphrd_type` except five
  (`NETLINK`, `IPGRE`, `IP6GRE`, `IEEE80211_RADIOTAP`, `FRAD`, named constants
  in `linux_sll.py`), not an inclusion list of `Hardware.Ethernet` alone. An
  unregistered `protocol_type` falls back to `Raw` at the payload level
  instead of raising and losing the whole header.
- `make()` allows `link_addr_length` up to each format's own field width
  (0xFFFF for v1, 0xFF for v2), not capped at 8, so a round trip survives a
  hardware address (e.g. InfiniBand) longer than the 8 octets kept inline.
- The two headers disagree on field order and two fields narrow in v2, so
  they don't share a base class; the two routines they do share (the
  `arphrd_type`-gated dispatch, and link-layer address validation) live once
  in `linux_sll.py` rather than twice.
- Export the new classes from every aggregator and document them under
  `docs/source/pcapkit/protocols/link/`; sync `pep.rst`'s roadmap and record
  why the pair didn't want the shared-base treatment.
- `NOT_A_COPY`/`UNRELATED` entries in `test_rfc_constants_one_home_1739_unit.py`
  for the headers' fixed lengths and `link_addr_length`'s own 0xFFFF bound.

Tests: the real five-value exclusion list staying Raw, non-excluded
non-Ethernet hardware types (loopback, PPP, SLIP/CSLIP) dispatching by
EtherType, an unregistered EtherType not breaking the header,
`link_addr_length > 8` round trips, padding/reserved round trip, make()
validation against each format's real field width, registry wiring, and
full-capture extraction. 100% coverage on every new file;
`tests/protocols/link/` (136 tests/868 subtests) and `tests/project/` (2107
tests) pass with these changes in place.

Closes #2438
@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 JarryShaw added the review: running A cross-review is in flight against the current head - no verdict yet label Oct 30, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

NEEDS CHANGES (Sonnet, 4b75f94b4). Selectors, __index__ and round trips are all correct.

  • An unassigned EtherType (e.g. 0x1234) stores a bare int and chains as LINUX_SLL1:Raw. Ethernet stores <unassigned> and chains as Ethernet:<unassigned>. This holds for every unassigned value across all 65,536 protocol_type values; the cause is _resolve_next_layer_proto returning protocol_type on a miss. Return EnumField(namespace=EtherType).post_process(...)'s value instead. The tests compare via int(...), which hides the mismatch.
  • The five excluded hardware types still put the -1 sentinel in Raw.info.protocol. Replace it, or state it in the PR body.
  • Review-history narration in the test docstrings; unqualified :class: roles in linux_sll_base.py.
  • I'll fix the PR body ("registered onto Link" is wrong: only Frame/PCAPNG.__proto__ hold SLL).

@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
#2438)

- An unassigned EtherType now stores Ethernet's <unassigned> pseudo-member,
  via the same EnumField post-processing, so the chain reads
  LINUX_SLL1:<unassigned> like Ethernet:<unassigned>.
- The excluded hardware types keep forcing Raw from LINUX_SLL's
  _import_next_layer, recording the real protocol_type instead of -1.
- Tests compare the stored value's repr against Ethernet's; review-history
  wording dropped; LinuxSLL/LinuxSLL2 roles qualified.
- List the LINUX_SLL family in the ext.rst and protocols index tables.

Link tests, dispatch registry and edge round trips pass; a sweep of all
65,536 protocol_type values matches Ethernet on value, class, chain and JSON.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verified (d21359d95): protocol_type now goes through the same EnumField as Ethernet's type (_ETHERTYPE_FIELD); the -1 sentinel is gone from the excluded-type Raw. Body updated. Sonnet review dispatched.

@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

NEEDS CHANGES (Sonnet, d21359d95). Ethernet parity holds for the next layer across all 65,536 values; excluded types carry the real int; round trips pass 1,248 cases.

  • Two new Sphinx warnings: the packet_type comment "Packet type (direction): …" parses as a type. Drop the colon.
  • Unqualified :meth:/:attr: roles in linux_sll.rst, linux_sll2.rst and linux_sll_base.rst.
  • Prose: a 5-line #: NOTE:; narration in pep.rst and the test docstrings; the 55-line LinuxSLL docstring; the narrow-label rule repeated in four places.
  • Body overstated parity: the header's own protocol_type is a bare int, unlike Ethernet's type. Fixed in the body.

@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
- Reword the packet_type comments so Sphinx no longer reads a type.
- Qualify the id/alias roles in the three LINUX_SLL pages.
- Move the excluded-arphrd rule, its constants and the shared helpers into
  linux_sll_base, so both versions read one set and v2 no longer imports
  private names from v1.
- State the narrow-label rule once, as linux-sll-labels, and reference it.
- Cut narration from the class docstrings, the NOTE, pep.rst and the tests.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Pushed 8d24a268b (held until the 1.5.1 release cleared): addresses the previous review. Sonnet review dispatched.

@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

NEEDS CHANGES (Sonnet, 8d24a268b), prose only. The docs build matches main (1 warning), 806,144 parity checks against Ethernet show 0 mismatches, excluded types stay Raw with the real int, and the consolidation changes no behaviour.

  • test_class_and_registry_key_selectors_are_family_wide re-explains the narrow-label rule. Point it at linux-sll-labels instead.
  • Nits: the InfiniBand "may exceed 8 inline" note is repeated 7 times; there's a "done" status line in pep.rst; and v1 == 'LinuxSLL' is False while in matches, the same string-== rule as ICMPv4. State that once.

@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
)

- Selector test points at the linux-sll-labels section instead of
  restating it.
- Keep the InfiniBand note in _validate_link_addr only; make() docs
  shrink to two lines per link_addr_length.
- Drop the SLL status line and base-class paragraph from pep.rst.
- Document that string == compares against (cls, *id()); add a test.
@JarryShaw

Copy link
Copy Markdown
Owner Author

Verified (632721648): prose-only round; narrow-label rule stated once, InfiniBand note kept in one place, string == rule documented and tested. Sonnet review dispatched.

@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

GOOD TO GO (Sonnet, 632721648). Prose-only round (AST-identical code); the string-== rule is measured true and pinned; docs warnings equal main's. Nit: "as for every protocol" overreaches, since the False/True split only shows where a class name differs from its id().

@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 added a commit that referenced this pull request Oct 30, 2026
- id() criterion: members-only and own-name-first tuples; ARP alone.
- Drop the Shim6 narration and the SSL/TLS/DTLS grouping.
- Versioned class names "may"; add #2443's selector ruling.
- ProtoChain also matches aliases; ARP row names RARP/DRARP on 0x8035.
- Cite pull requests with :pr:; no count.

This branch has not been deployed

No deployments
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) review: good-to-go Cross-review at the current head says ready; CI state is separate

Projects

Status: In review

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