Skip to content
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/link/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ link layer, with detailed implementation and methods.
linux_sll2
loopback
netlink
radiotap
vlan
c_tag
s_tag
Expand Down
100 changes: 100 additions & 0 deletions docs/source/pcapkit/protocols/link/radiotap.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Radiotap - IEEE 802.11 Radiotap Header
========================================

.. module:: pcapkit.protocols.link.radiotap

:mod:`pcapkit.protocols.link.radiotap` contains
:class:`~pcapkit.protocols.link.radiotap.Radiotap`
only, which implements the extractor for the radiotap header [*]_ capture
tools prepend to an 802.11 frame, as seen on a
``LINKTYPE_LINUX_SLL``/``LINKTYPE_LINUX_SLL2`` capture whose ``arphrd_type``
is ``ARPHRD_IEEE80211_RADIOTAP``. Radiotap is not part of the 802.11
standard itself -- it is a prefix written by the capturing tool, in front of
the raw 802.11 MAC frame. Its fixed start, all little-endian, is described
as below:

.. table::

====== ==== ======================== ================================
Octets Bits Name Description
====== ==== ======================== ================================
0 0 ``radiotap.it_version`` Header Revision (always ``0``)
1 8 ``radiotap.it_pad`` Padding
2 16 ``radiotap.it_len`` Header Length (header included)
4 32 ``radiotap.it_present`` First Present-Flags Word
====== ==== ======================== ================================

followed by a chain of further present-flags words -- bit 31 of a word
signals that another follows -- and then the optional-field region those
bitmasks select, each field aligned and sized per its own type. ``it_len``
bounds the whole of it; the 802.11 MAC frame starts at that offset.

Scope
-----

Decoding every optional field radiotap.org defines, and the 802.11 MAC
frame behind them, is future work tracked on :issue:`2460` and
:issue:`2502` -- this is already the widest of that issue's five
follow-ups, and the 802.11 MAC frame is a protocol family of its own. This
first cut instead:

* **Decodes** the fixed 8-octet start -- ``it_version``, ``it_pad``,
``it_len`` -- and walks the present-flags chain far enough to know how
many words it has
(:attr:`~pcapkit.protocols.data.link.radiotap.Radiotap.present`), without
decoding what any bit selects.
* **Skips, rather than decodes,** the optional-field region those bits
select -- the octets between the present-flags chain and ``it_len`` are
kept whole and undissected, as
:attr:`~pcapkit.protocols.data.link.radiotap.Radiotap.fields`.
* **Leaves opaque** everything from ``it_len`` on: the 802.11 MAC frame
itself, which has no dissector anywhere in this package yet. It is kept
as :class:`~pcapkit.protocols.misc.raw.Raw` (or
:class:`~pcapkit.protocols.misc.null.NoPayload` when nothing follows),
unconditionally.

.. autoclass:: pcapkit.protocols.link.radiotap.Radiotap
:no-members:
:show-inheritance:

.. autoproperty:: name
.. autoproperty:: alias
.. autoproperty:: length

.. automethod:: id

.. automethod:: read
.. automethod:: make

.. automethod:: __length_hint__
.. automethod:: __index__

.. automethod:: _make_data
.. automethod:: _import_next_layer

.. autoattribute:: __proto__
:no-value:

Header Schemas
--------------

.. module:: pcapkit.protocols.schema.link.radiotap

.. autoclass:: pcapkit.protocols.schema.link.radiotap.Radiotap
:members:
:show-inheritance:

Data Models
-----------

.. module:: pcapkit.protocols.data.link.radiotap

.. autoclass:: pcapkit.protocols.data.link.radiotap.Radiotap
:members:
:show-inheritance:

.. rubric:: Footnotes

.. [*] <https://www.radiotap.org/>; the Linux kernel's
``include/net/ieee80211_radiotap.h`` and ``net/wireless/radiotap.c``; and
Wireshark's ``packet-ieee80211-radiotap.c``.
3 changes: 2 additions & 1 deletion pcapkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
'Raw', # Raw Packet

'ARP', 'C_Tag', 'Ethernet', 'FRAD', 'GRE', 'InARP', 'L2TP', # Link Layer
'L2TPv2', 'LinuxSLL', 'LinuxSLL1', 'LinuxSLL2', 'Loopback', 'Netlink', 'S_Tag', 'VLAN',
'L2TPv2', 'LinuxSLL', 'LinuxSLL1', 'LinuxSLL2', 'Loopback', 'Netlink', 'Radiotap',
'S_Tag', 'VLAN',

'AH', 'ESP', 'ICMP', 'ICMPv4', 'ICMPv6', # Internet Layer
'IGMP', 'IGMPv1', 'IGMPv2', 'IGMPv3', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX',
Expand Down
3 changes: 1 addition & 2 deletions pcapkit/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@
'NoPayload', # No Payload
'Raw', # Raw Packet
'ARP', 'C_Tag', 'Ethernet', 'FRAD', 'GRE', 'InARP', 'L2TP', 'L2TPv2', 'LinuxSLL', 'LinuxSLL1',
'LinuxSLL2', 'Loopback', 'Netlink', 'S_Tag', 'VLAN', # Link Layer
'LinuxSLL2', 'Loopback', 'Netlink', 'Radiotap', 'S_Tag', 'VLAN', # Link Layer
'AH', 'ESP', 'ICMP', 'ICMPv4', 'ICMPv6', 'IGMP', 'IGMPv1', 'IGMPv2', 'IGMPv3',
'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX', # Internet Layer

'HIP', 'HOPOPT', 'IPv6_Ext', 'IPv6_Frag', 'IPv6_Opts', 'IPv6_Route', 'MH',
# IPv6 Extension Header
'IP_Pseudo', # IP Pseudo-Header
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

# Link Layer
'ARP', 'C_Tag', 'Ethernet', 'FRAD', 'GRE', 'InARP', 'L2TP', 'L2TPv2', 'LinuxSLL', 'LinuxSLL1',
'LinuxSLL2', 'Loopback', 'Netlink', 'S_Tag', 'VLAN',
'LinuxSLL2', 'Loopback', 'Netlink', 'Radiotap', 'S_Tag', 'VLAN',

# Internet Layer
'AH', 'ESP', 'ICMP', 'ICMPv4', 'ICMPv6', 'IGMP', 'IGMPv1', 'IGMPv2', 'IGMPv3',
Expand Down
3 changes: 3 additions & 0 deletions pcapkit/protocols/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
# Netlink Socket Framing
'Netlink',

# IEEE 802.11 Radiotap Header
'Radiotap',

# 802.1Q/802.1ad VLAN Tag Types
'VLAN', 'VLAN_TCI',

Expand Down
6 changes: 6 additions & 0 deletions pcapkit/protocols/data/link/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
# Netlink Socket Framing
from pcapkit.protocols.data.link.netlink import Netlink

# IEEE 802.11 Radiotap Header
from pcapkit.protocols.data.link.radiotap import Radiotap

# 802.1Q/802.1ad VLAN Tag Types
from pcapkit.protocols.data.link.vlan import TCI as VLAN_TCI
from pcapkit.protocols.data.link.vlan import VLAN
Expand Down Expand Up @@ -58,6 +61,9 @@
# Netlink Socket Framing
'Netlink',

# IEEE 802.11 Radiotap Header
'Radiotap',

# 802.1Q/802.1ad VLAN Tag Types
'VLAN', 'VLAN_TCI',
]
48 changes: 48 additions & 0 deletions pcapkit/protocols/data/link/radiotap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
"""data models for IEEE 802.11 radiotap headers"""

from typing import TYPE_CHECKING

from pcapkit.corekit.infoclass import info_final
from pcapkit.protocols.data.protocol import Protocol

if TYPE_CHECKING:
pass

__all__ = ['Radiotap']


@info_final
class Radiotap(Protocol):
"""Data model for an IEEE 802.11 radiotap header.

Only the fixed 8-octet start is decoded field by field; the rest is kept
whole, see :attr:`present` and :attr:`fields` and the module docstring's
*Scope* section on :mod:`pcapkit.protocols.link.radiotap`.

"""

#: Header revision; ``0`` is the only version radiotap.org defines, and
#: the only one this package decodes.
it_version: 'int'
#: Padding octet, kept for alignment; not interpreted.
it_pad: 'int'
#: Total length of the radiotap header -- the fixed 8-octet start,
#: every extended present-flags word, and the optional-field region
#: they select -- in octets; the 802.11 MAC frame starts here. Kept
#: exactly as captured, even where it disagrees with :attr:`present`
#: and :attr:`fields`.
it_len: 'int'
#: Every present-flags word in the chain that was actually read, in
#: order; index ``0`` is the mandatory first word (radiotap.org's own
#: ``it_present``). Bit 31 of a word signals that another word
#: follows; it is clear on the last word read, unless the capture ran
#: out before a word it claimed would follow.
present: 'tuple[int, ...]'
#: The optional-field region :attr:`present` selects, kept whole and
#: undissected.
fields: 'bytes'

if TYPE_CHECKING:
def __init__(self, it_version: 'int', it_pad: 'int', it_len: 'int', # pylint: disable=unused-argument,super-init-not-called,multiple-statements
present: 'tuple[int, ...]', fields: 'bytes') -> 'None': ... # pylint: disable=unused-argument
3 changes: 2 additions & 1 deletion pcapkit/protocols/link/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pcapkit.protocols.link.gre import GRE
from pcapkit.protocols.link.loopback import Loopback
from pcapkit.protocols.link.netlink import Netlink
from pcapkit.protocols.link.radiotap import Radiotap

# Linux "Cooked" Capture Encapsulation Family
from pcapkit.protocols.link.linux_sll import LinuxSLL
Expand All @@ -45,5 +46,5 @@

# Link Layer Protocols
'ARP', 'C_Tag', 'Ethernet', 'FRAD', 'GRE', 'InARP', 'L2TP', 'L2TPv2', 'LinuxSLL', 'LinuxSLL1',
'LinuxSLL2', 'Loopback', 'Netlink', 'S_Tag', 'VLAN',
'LinuxSLL2', 'Loopback', 'Netlink', 'Radiotap', 'S_Tag', 'VLAN',
]
3 changes: 2 additions & 1 deletion pcapkit/protocols/link/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
:class:`~pcapkit.protocols.link.ethernet.Ethernet`,
:class:`~pcapkit.protocols.link.frad.FRAD`,
:class:`~pcapkit.protocols.link.l2tp.L2TP`,
:class:`~pcapkit.protocols.link.loopback.Loopback` and
:class:`~pcapkit.protocols.link.loopback.Loopback`,
:class:`~pcapkit.protocols.link.radiotap.Radiotap` and
:class:`~pcapkit.protocols.link.vlan.VLAN`.

"""
Expand Down
22 changes: 13 additions & 9 deletions pcapkit/protocols/link/linux_sll.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,12 @@ def _import_next_layer(self, proto: 'int', length: 'Optional[int]' = None, *, #
:class:`~pcapkit.protocols.link.gre.GRE` when ``arphrd_type`` is
:attr:`ARPHRD.IPGRE <pcapkit.const.linux.arphrd.ARPHRD.IPGRE>` or
:attr:`ARPHRD.IP6GRE <pcapkit.const.linux.arphrd.ARPHRD.IP6GRE>`;
otherwise :class:`~pcapkit.protocols.misc.raw.Raw`, recording
``proto``, for every other ``arphrd_type`` in
:data:`_ARPHRD_NOT_ETHERTYPE`.
:class:`~pcapkit.protocols.link.radiotap.Radiotap` when ``arphrd_type``
is :attr:`ARPHRD.IEEE80211_RADIOTAP
<pcapkit.const.linux.arphrd.ARPHRD.IEEE80211_RADIOTAP>`;
otherwise the dissector :attr:`Link.__proto__
<pcapkit.protocols.link.link.Link.__proto__>` holds for ``proto``,
an EtherType.

"""
file_ = self._get_payload()
Expand Down Expand Up @@ -189,10 +192,11 @@ def _import_next_layer(self, proto: 'int', length: 'Optional[int]' = None, *, #
layer=self._exlayer, protocol=self._exproto,
__context__=self._exctx)

if arphrd_type not in _ARPHRD_NOT_ETHERTYPE:
return super()._import_next_layer(proto, length, packet=packet) # type: ignore[misc,call-arg]
if arphrd_type == ARPHRD.IEEE80211_RADIOTAP:
from pcapkit.protocols.link.radiotap import \
Radiotap # pylint: disable=import-outside-toplevel # isort: skip
return self._parse_next_layer(Radiotap, file_, length, alias=proto, packet=packet,
layer=self._exlayer, protocol=self._exproto,
__context__=self._exctx)

from pcapkit.protocols.misc.raw import Raw # pylint: disable=import-outside-toplevel # isort: skip
return self._parse_next_layer(Raw, file_, length, alias=proto, packet=packet,
layer=self._exlayer, protocol=self._exproto,
__context__=self._exctx)
return super()._import_next_layer(proto, length, packet=packet) # type: ignore[misc,call-arg]
Loading
Loading