Skip to content

Commit 5dacd83

Browse files
committed
feat(link): implement LINUX_SLL and LINUX_SLL2 dissectors
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
1 parent e1efee7 commit 5dacd83

23 files changed

Lines changed: 1603 additions & 42 deletions

‎docs/source/contributing/pep.rst‎

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ step-by-step guide for adding a protocol, and is worth reading before starting
4040
one. Grouped by each TCP/IP layer and ordered by protocol name alphabetically,
4141
the ones outstanding are,
4242

43-
* Link Layer: DSL, EAPOL, FDDI, ISDN, LINUX_SLL, LINUX_SLL2, NDP, PPP
43+
* Link Layer: DSL, EAPOL, FDDI, ISDN, NDP, PPP
4444
* Internet Layer: ECN, ICMP, ICMPv6, IGMP, Shim6
4545
* Transport Layer: DCCP, RSVP
4646
* Application Layer: BGP, DHCP, DHCPv6, DNS, DTLS, IMAP, LDAP, MQTT, NNTP,
@@ -49,21 +49,30 @@ the ones outstanding are,
4949

5050
Several of these come in pairs that want a shared abstract base rather than two
5151
independent implementations, in the way
52-
:class:`~pcapkit.protocols.internet.ip.IP` already covers its family: ICMP with
53-
ICMPv6, TLS/SSL with DTLS, and ``LINUX_SLL`` with ``LINUX_SLL2``.
54-
:class:`~pcapkit.protocols.link.vlan.VLAN` is the closer precedent for a pair
55-
whose *layout* is identical -- it holds the whole of the tag, and
56-
:class:`~pcapkit.protocols.link.c_tag.C_Tag` and
52+
:class:`~pcapkit.protocols.internet.ip.IP` already covers its family: ICMP
53+
with ICMPv6, and TLS/SSL with DTLS. :class:`~pcapkit.protocols.link.vlan.VLAN`
54+
is the closer precedent for a pair whose *layout* is identical -- it holds the
55+
whole of the tag, and :class:`~pcapkit.protocols.link.c_tag.C_Tag` and
5756
:class:`~pcapkit.protocols.link.s_tag.S_Tag` add only how each names itself.
57+
``LINUX_SLL`` and ``LINUX_SLL2`` were a third such pair -- see `Delivery
58+
Sequence`_ below -- but turned out not to want the full shared-base treatment:
59+
their wire layouts disagree on field *order* -- v2 moves ``protocol_type`` to
60+
the front and adds ``interface_index`` -- and ``packet_type``/``link_addr_length``
61+
narrow from 2 octets to 1, while ``arphrd_type``, ``link_addr`` and
62+
``protocol_type`` itself stay the same width in both. What they actually
63+
share is two routines (the ``arphrd_type``-gated next-layer rule, and
64+
link-layer address validation), which
65+
:func:`~pcapkit.protocols.link.linux_sll._resolve_next_layer_proto` and
66+
:func:`~pcapkit.protocols.link.linux_sll._validate_link_addr` hold once
67+
rather than twice.
5868

5969
.. note::
6070

6171
Some of these have an empty, protocol-named file under a
6272
``NotImplemented`` folder. **Those files are not a roadmap.** They are
6373
scratch reminders, so a stub's presence does not mean a protocol is planned or
6474
claimed, and its absence does not mean the protocol is unwanted:
65-
``LINUX_SLL``, ``LINUX_SLL2``, ``QUIC`` and **DTLS** have no stub and are on the
66-
list above,
75+
``QUIC`` and **DTLS** have no stub and are on the list above,
6776
while **NGAP** has none either and is implemented. Take this page as the
6877
record and ignore the folder.
6978

@@ -350,9 +359,11 @@ Follow-ups to the bindings above:
350359
instead of guessing, since a guess path is wrong on non-HTTP input.
351360

352361
Beyond those, the gaps most likely to be met in a real capture are ICMP (1),
353-
ICMPv6 (58) and IGMP (2) on the internet layer, each of which has a stub;
354-
and ``LINUX_SLL`` and ``LINUX_SLL2`` at the link layer, which every
355-
``tcpdump -i any`` capture uses and which have no stub.
362+
ICMPv6 (58) and IGMP (2) on the internet layer, each of which has a stub.
363+
``LINUX_SLL`` and ``LINUX_SLL2`` at the link layer -- the header every
364+
``tcpdump -i any`` capture uses -- are **done**; see
365+
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL` and
366+
:class:`~pcapkit.protocols.link.linux_sll2.LinuxSLL2`.
356367

357368
PCAPNG Support
358369
--------------
@@ -1071,24 +1082,29 @@ post-wave-1 consistency sweep described under `Release Plan — 1.5.0 in Two
10711082
Steps`_ above.
10721083

10731084
**Wave 2 — the protocol pairs that want a shared abstract base**, per `More
1074-
Protocols, More!!!`_ above: **ICMP with ICMPv6**, **TLS/SSL with DTLS**, and
1075-
``LINUX_SLL`` with ``LINUX_SLL2``, plus **IGMP** on its own. These are grouped
1076-
because the shared-base question is the design work and doing either half of a
1077-
pair alone would answer it twice. ``LINUX_SLL`` matters out of proportion to its
1078-
size: every ``tcpdump -i any`` capture uses it, and neither variant has even a
1079-
stub.
1080-
1081-
**Done ahead of wave 2 — handlers for the** ``DLT_NULL`` **and** ``DLT_RAW``
1082-
**link types.** Both are genuine DLTs that a capture can legitimately be rooted
1083-
in — ``DLT_NULL`` (0) is BSD's loopback encapsulation, ``DLT_RAW`` (101) an IP
1084-
packet with no link header at all.
1085+
Protocols, More!!!`_ above: **ICMP with ICMPv6** and **TLS/SSL with DTLS**,
1086+
plus **IGMP** on its own. These are grouped because the shared-base question
1087+
is the design work and doing either half of a pair alone would answer it
1088+
twice.
1089+
1090+
**Done ahead of wave 2 — handlers for the** ``DLT_NULL``, ``DLT_RAW``,
1091+
``DLT_LINUX_SLL`` **and** ``DLT_LINUX_SLL2`` **link types.** All four are
1092+
genuine DLTs that a capture can legitimately be rooted in — ``DLT_NULL`` (0)
1093+
is BSD's loopback encapsulation, ``DLT_RAW`` (101) an IP packet with no link
1094+
header at all, and ``DLT_LINUX_SLL`` (113) with ``DLT_LINUX_SLL2`` (276) the
1095+
Linux "cooked" capture header every ``tcpdump -i any`` capture uses.
10851096
:class:`~pcapkit.protocols.link.loopback.Loopback` dissects ``DLT_NULL`` and
1086-
``DLT_LOOP`` (:issue:`1574`), and the frame dispatcher reads ``DLT_RAW`` as IPv4
1087-
or IPv6 by its version nibble (:issue:`1502`), so of the link types this
1088-
section names only ``LINUX_SLL`` and ``LINUX_SLL2`` remain enumerated but
1089-
unparsed. Neither ``NULL`` nor ``RAW`` is an honest stand-in for "unknown link
1090-
type", which is why the toolkit raises for an unresolvable link-layer name
1091-
instead of defaulting to :attr:`LinkType.NULL <pcapkit.const.reg.linktype.LinkType.NULL>`.
1097+
``DLT_LOOP`` (:issue:`1574`), the frame dispatcher reads ``DLT_RAW`` as IPv4
1098+
or IPv6 by its version nibble (:issue:`1502`), and
1099+
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL` with
1100+
:class:`~pcapkit.protocols.link.linux_sll2.LinuxSLL2` dissect the two Linux
1101+
cooked-capture variants (:issue:`2438`) -- turning out not to want the
1102+
shared-base treatment after all, since their wire layouts disagree on field
1103+
order and two fields narrow in v2; see `More Protocols, More!!!`_ above for
1104+
what they share instead. Neither ``NULL`` nor ``RAW`` is an honest stand-in for "unknown
1105+
link type", which is why the toolkit raises for an unresolvable link-layer
1106+
name instead of defaulting to :attr:`LinkType.NULL
1107+
<pcapkit.const.reg.linktype.LinkType.NULL>`.
10921108

10931109
**Wave 3 — the remaining protocols** from the same list, taken three or four at a
10941110
time. QUIC, DSL, FDDI and ISDN come last: QUIC because it is really HTTP/3 over a

‎docs/source/pcapkit/protocols/link/index.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ link layer, with detailed implementation and methods.
2626
arp
2727
l2tp
2828
l2tpv2
29+
linux_sll
30+
linux_sll2
2931
loopback
3032
vlan
3133
c_tag
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
Linux "Cooked" Capture Encapsulation
2+
=====================================
3+
4+
.. module:: pcapkit.protocols.link.linux_sll
5+
6+
:mod:`pcapkit.protocols.link.linux_sll` contains
7+
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL`
8+
only, which implements the extractor for the Linux "cooked" capture
9+
encapsulation [*]_ of ``LINKTYPE_LINUX_SLL`` -- the header every
10+
``tcpdump -i any`` capture on Linux uses -- whose structure is described as
11+
below:
12+
13+
.. table::
14+
15+
====== ===== ============================== ================================
16+
Octets Bits Name Description
17+
====== ===== ============================== ================================
18+
0 0 ``linux_sll.packet_type`` Packet Type
19+
------ ----- ------------------------------ --------------------------------
20+
2 16 ``linux_sll.arphrd_type`` ARP Hardware Type
21+
------ ----- ------------------------------ --------------------------------
22+
4 32 ``linux_sll.link_addr_length`` Link-Layer Address Length
23+
------ ----- ------------------------------ --------------------------------
24+
6 48 ``linux_sll.link_addr`` Link-Layer Address (padded)
25+
------ ----- ------------------------------ --------------------------------
26+
14 112 ``linux_sll.protocol_type`` Protocol (Internet Layer)
27+
====== ===== ============================== ================================
28+
29+
``protocol_type`` is an EtherType for every ``arphrd_type`` except five; see
30+
the class docstring for the next-layer dispatch rule this shares with
31+
:class:`~pcapkit.protocols.link.linux_sll2.LinuxSLL2`.
32+
33+
.. autoclass:: pcapkit.protocols.link.linux_sll.LinuxSLL
34+
:no-members:
35+
:show-inheritance:
36+
37+
.. autoproperty:: name
38+
.. autoproperty:: length
39+
.. autoproperty:: protocol
40+
41+
.. automethod:: read
42+
.. automethod:: make
43+
44+
.. automethod:: _make_data
45+
46+
.. automethod:: __index__
47+
48+
Auxiliary Functions
49+
~~~~~~~~~~~~~~~~~~~~
50+
51+
.. autofunction:: pcapkit.protocols.link.linux_sll._resolve_next_layer_proto
52+
.. autofunction:: pcapkit.protocols.link.linux_sll._validate_link_addr
53+
54+
Auxiliary Constants
55+
~~~~~~~~~~~~~~~~~~~~
56+
57+
.. autodata:: pcapkit.protocols.link.linux_sll.ARPHRD_FRAD
58+
.. autodata:: pcapkit.protocols.link.linux_sll.ARPHRD_IPGRE
59+
.. autodata:: pcapkit.protocols.link.linux_sll.ARPHRD_IEEE80211_RADIOTAP
60+
.. autodata:: pcapkit.protocols.link.linux_sll.ARPHRD_IP6GRE
61+
.. autodata:: pcapkit.protocols.link.linux_sll.ARPHRD_NETLINK
62+
.. autodata:: pcapkit.protocols.link.linux_sll._ARPHRD_NOT_ETHERTYPE
63+
64+
Header Schemas
65+
--------------
66+
67+
.. module:: pcapkit.protocols.schema.link.linux_sll
68+
69+
.. autoclass:: pcapkit.protocols.schema.link.linux_sll.LinuxSLL
70+
:members:
71+
:show-inheritance:
72+
73+
Data Models
74+
-----------
75+
76+
.. module:: pcapkit.protocols.data.link.linux_sll
77+
78+
.. autoclass:: pcapkit.protocols.data.link.linux_sll.LinuxSLL
79+
:members:
80+
:show-inheritance:
81+
82+
.. rubric:: Footnotes
83+
84+
.. [*] https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
Linux "Cooked" Capture Encapsulation v2
2+
=========================================
3+
4+
.. module:: pcapkit.protocols.link.linux_sll2
5+
6+
:mod:`pcapkit.protocols.link.linux_sll2` contains
7+
:class:`~pcapkit.protocols.link.linux_sll2.LinuxSLL2`
8+
only, which implements the extractor for the Linux "cooked" capture
9+
encapsulation v2 [*]_ of ``LINKTYPE_LINUX_SLL2``, whose structure is described
10+
as below:
11+
12+
.. table::
13+
14+
====== ===== ================================ ================================
15+
Octets Bits Name Description
16+
====== ===== ================================ ================================
17+
0 0 ``linux_sll2.protocol_type`` Protocol (Internet Layer)
18+
------ ----- -------------------------------- --------------------------------
19+
2 16 ``linux_sll2.reserved`` Reserved
20+
------ ----- -------------------------------- --------------------------------
21+
4 32 ``linux_sll2.interface_index`` Interface Index
22+
------ ----- -------------------------------- --------------------------------
23+
8 64 ``linux_sll2.arphrd_type`` ARP Hardware Type
24+
------ ----- -------------------------------- --------------------------------
25+
10 80 ``linux_sll2.packet_type`` Packet Type
26+
------ ----- -------------------------------- --------------------------------
27+
11 88 ``linux_sll2.link_addr_length`` Link-Layer Address Length
28+
------ ----- -------------------------------- --------------------------------
29+
12 96 ``linux_sll2.link_addr`` Link-Layer Address (padded)
30+
====== ===== ================================ ================================
31+
32+
The field *order* differs from v1 -- ``protocol_type`` moves to the front and
33+
``interface_index`` is new -- and ``packet_type`` / ``link_addr_length``
34+
narrow from 2 octets to 1; ``arphrd_type``, ``link_addr`` and
35+
``protocol_type`` itself are the same width in both. See
36+
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL` for v1. Next-layer
37+
dispatch is the same rule, shared through
38+
:func:`~pcapkit.protocols.link.linux_sll._resolve_next_layer_proto`.
39+
40+
.. autoclass:: pcapkit.protocols.link.linux_sll2.LinuxSLL2
41+
:no-members:
42+
:show-inheritance:
43+
44+
.. autoproperty:: name
45+
.. autoproperty:: length
46+
.. autoproperty:: protocol
47+
48+
.. automethod:: read
49+
.. automethod:: make
50+
51+
.. automethod:: _make_data
52+
53+
.. automethod:: __index__
54+
55+
Header Schemas
56+
--------------
57+
58+
.. module:: pcapkit.protocols.schema.link.linux_sll2
59+
60+
.. autoclass:: pcapkit.protocols.schema.link.linux_sll2.LinuxSLL2
61+
:members:
62+
:show-inheritance:
63+
64+
Data Models
65+
-----------
66+
67+
.. module:: pcapkit.protocols.data.link.linux_sll2
68+
69+
.. autoclass:: pcapkit.protocols.data.link.linux_sll2.LinuxSLL2
70+
:members:
71+
:show-inheritance:
72+
73+
.. rubric:: Footnotes
74+
75+
.. [*] https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL2.html

‎pcapkit/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
'Raw', # Raw Packet
113113

114114
'ARP', 'C_Tag', 'Ethernet', 'InARP', 'L2TP', # Link Layer
115-
'L2TPv2', 'Loopback', 'S_Tag', 'VLAN',
115+
'L2TPv2', 'LinuxSLL', 'LinuxSLL2', 'Loopback', 'S_Tag', 'VLAN',
116116

117117
'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX', # Internet Layer
118118
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_Ext', 'IPv6_Opts', 'IPv6_Route', 'MH',

‎pcapkit/all.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
'PCAPNG', # PCAPNG Format
120120
'NoPayload', # No Payload
121121
'Raw', # Raw Packet
122-
'ARP', 'C_Tag', 'Ethernet', 'InARP', 'L2TP', 'L2TPv2',
122+
'ARP', 'C_Tag', 'Ethernet', 'InARP', 'L2TP', 'L2TPv2', 'LinuxSLL', 'LinuxSLL2',
123123
'Loopback', 'S_Tag', 'VLAN', # Link Layer
124124
'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX', # Internet Layer
125125
'HIP', 'HOPOPT', 'IPv6_Ext', 'IPv6_Frag', 'IPv6_Opts', 'IPv6_Route', 'MH',

‎pcapkit/protocols/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
'Raw',
5151

5252
# Link Layer
53-
'ARP', 'C_Tag', 'Ethernet', 'InARP', 'L2TP', 'L2TPv2', 'Loopback', 'S_Tag', 'VLAN',
53+
'ARP', 'C_Tag', 'Ethernet', 'InARP', 'L2TP', 'L2TPv2', 'LinuxSLL', 'LinuxSLL2',
54+
'Loopback', 'S_Tag', 'VLAN',
5455

5556
# Internet Layer
5657
'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX',

‎pcapkit/protocols/data/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@
6565
# Layer Two Tunnelling Protocol
6666
'L2TP', 'L2TP_Flags',
6767

68+
# Linux "Cooked" Capture Encapsulation
69+
'LinuxSLL', 'LinuxSLL2',
70+
6871
# BSD Loopback Encapsulation
6972
'Loopback',
7073

‎pcapkit/protocols/data/link/__init__.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
from pcapkit.protocols.data.link.l2tp import L2TP
1414
from pcapkit.protocols.data.link.l2tp import Flags as L2TP_Flags
1515

16+
# Linux "Cooked" Capture Encapsulation
17+
from pcapkit.protocols.data.link.linux_sll import LinuxSLL
18+
from pcapkit.protocols.data.link.linux_sll2 import LinuxSLL2
19+
1620
# BSD Loopback Encapsulation
1721
from pcapkit.protocols.data.link.loopback import Loopback
1822

@@ -30,6 +34,9 @@
3034
# Layer Two Tunnelling Protocol
3135
'L2TP', 'L2TP_Flags',
3236

37+
# Linux "Cooked" Capture Encapsulation
38+
'LinuxSLL', 'LinuxSLL2',
39+
3340
# BSD Loopback Encapsulation
3441
'Loopback',
3542

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- coding: utf-8 -*-
2+
"""data models for Linux "cooked" capture encapsulation (``LINUX_SLL``, v1)"""
3+
4+
from typing import TYPE_CHECKING
5+
6+
from pcapkit.corekit.infoclass import info_final
7+
from pcapkit.protocols.data.protocol import Protocol
8+
9+
if TYPE_CHECKING:
10+
from pcapkit.const.arp.hardware import Hardware
11+
12+
__all__ = ['LinuxSLL']
13+
14+
15+
@info_final
16+
class LinuxSLL(Protocol):
17+
"""Data model for Linux "cooked" capture encapsulation (``LINUX_SLL``, v1).
18+
19+
``protocol_type`` is an :class:`~pcapkit.const.reg.ethertype.EtherType`
20+
for every :attr:`arphrd_type` except five -- see
21+
:class:`~pcapkit.protocols.link.linux_sll.LinuxSLL`; it is kept as its
22+
raw wire value here regardless.
23+
24+
"""
25+
26+
#: Packet type (direction): ``0`` to us, ``1`` broadcast, ``2`` multicast,
27+
#: ``3`` sent to another host promiscuously, ``4`` sent by us.
28+
packet_type: 'int'
29+
#: ARP hardware type of the interface the packet was seen on.
30+
arphrd_type: 'Hardware'
31+
#: Link-layer address length actually in use; may exceed the 8 octets
32+
#: :attr:`link_addr` keeps inline (e.g. InfiniBand's 20-octet addresses).
33+
link_addr_length: 'int'
34+
#: Link-layer address, padded to 8 octets as captured; only the first
35+
#: :attr:`link_addr_length` of them are meaningful.
36+
link_addr: 'bytes'
37+
#: Protocol (internet layer), kept as its raw wire value.
38+
protocol_type: 'int'
39+
40+
if TYPE_CHECKING:
41+
def __init__(self, packet_type: 'int', arphrd_type: 'Hardware', link_addr_length: 'int', # pylint: disable=unused-argument,super-init-not-called
42+
link_addr: 'bytes', protocol_type: 'int') -> 'None': ... # pylint: disable=unused-argument,multiple-statements

0 commit comments

Comments
 (0)