Source code for pcapkit.protocols.link.netlink

# -*- coding: utf-8 -*-
"""Netlink - Linux Netlink Socket Framing
============================================

.. module:: pcapkit.protocols.link.netlink

:mod:`pcapkit.protocols.link.netlink` contains
:class:`~pcapkit.protocols.link.netlink.Netlink` only, which implements the
extractor for the ``struct nlmsghdr`` framing [*]_ of Linux's own kernel/user
IPC transport, as seen on a ``LINKTYPE_LINUX_SLL``/``LINKTYPE_LINUX_SLL2``
capture whose ``arphrd_type`` is ``ARPHRD_NETLINK`` (an ``nlmon`` interface)
-- :class:`~pcapkit.protocols.link.linux_sll.LinuxSLL` dispatches here
instead of falling back to :class:`~pcapkit.protocols.misc.raw.Raw` for that
one case; see :attr:`LinuxSLL.__proto__ <pcapkit.protocols.link.linux_sll.LinuxSLL.__proto__>`.
Netlink is the kernel's own IPC mechanism (routing table dumps, interface
notifications and the like), not a "next protocol over this link" the way
every other ``arphrd_type`` exception is -- its next-layer registry ships
empty, so every message's payload is kept as
:class:`~pcapkit.protocols.misc.raw.Raw` unless its ``nlmsg_type`` is registered.

.. table::

   ====== ==== ======================= ================================
   Octets Bits Name                    Description
   ====== ==== ======================= ================================
   0      0    ``netlink.nlmsg_len``   Message Length (header included)
   4      32   ``netlink.nlmsg_type``  Message Type
   6      48   ``netlink.nlmsg_flags`` Message Flags
   8      64   ``netlink.nlmsg_seq``   Sequence Number
   12     96   ``netlink.nlmsg_pid``   Sender Port ID
   ====== ==== ======================= ================================

Scope
-----

Only the fixed 16-octet ``nlmsghdr`` framing is dissected -- ``nlmsg_len``,
``nlmsg_type``, ``nlmsg_flags``, ``nlmsg_seq`` and ``nlmsg_pid``. Two things a
complete Netlink dissector would eventually need are deliberately left out of
this first cut:

* **Per-family message payloads.** What a given ``nlmsg_type`` means, and how
  its payload is laid out, is defined independently by each Netlink family
  (``NETLINK_ROUTE``, ``NETLINK_GENERIC``, ...) and is a large surface of its
  own -- routing table entries, link attributes (``rtattr``/``nlattr`` TLV
  chains), and so on. The payload is kept as
  :class:`~pcapkit.protocols.misc.raw.Raw` unless its ``nlmsg_type`` is registered.
* **Multiple messages packed into one read.** The kernel's own netlink(7)
  page documents that a single read from a netlink socket, and so a single
  captured frame, commonly carries more than one ``nlmsghdr`` message back to
  back (:rfc:`3549#section-2.3.2` numbers the header fields; it does not
  itself restate the "one read, several messages" framing convention, which
  is :manpage:`netlink(7)`'s). This class dissects only the *first* message's
  header; everything after it -- that message's own payload, and any sibling
  messages the frame may carry -- is kept together as the undissected
  :class:`~pcapkit.protocols.misc.raw.Raw` payload, exactly as if it were one
  larger opaque blob. Splitting the frame on every ``nlmsg_len`` boundary is
  future work, tracked on :issue:`2460`.

Byte Order
----------

Netlink is a kernel/userspace IPC mechanism, not a protocol with octets
specified for the wire -- its messages are written in the *capturing host's*
native byte order, and neither :manpage:`netlink(7)`, :rfc:`3549` nor
tcpdump.org's `LINKTYPE_NETLINK`_ page record a marker for which order that
was. :func:`nlmsg_byteorder` resolves it with the same heuristic Wireshark's
own netlink dissector uses (``packet-netlink.c``, ``dissect_netlink()``,
comment: *"We do not know the endianness of the capture host, we have to
guess."*): decode ``nlmsg_len`` both ways and keep whichever reading comes
closer to the number of octets actually available, since a genuine message
length is normally well under the size either misreading would imply. See
:func:`~pcapkit.protocols.link.loopback.family_byteorder` for the same
problem solved the same way for ``LINKTYPE_NULL``.

.. [*] :manpage:`netlink(7)`; :rfc:`3549#section-2.3.2`

.. _LINKTYPE_NETLINK: https://www.tcpdump.org/linktypes/LINKTYPE_NETLINK.html

.. versionadded:: 1.6.0

"""
import collections
import sys
from typing import TYPE_CHECKING

from pcapkit.const.linux.arphrd import ARPHRD as Enum_ARPHRD
from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.protocols.data.link.netlink import Netlink as Data_Netlink
from pcapkit.protocols.link.link import Link
from pcapkit.protocols.schema.link.netlink import Netlink as Schema_Netlink
from pcapkit.utilities.exceptions import ProtocolError
from pcapkit.utilities.warnings import RegistryWarning, warn

if TYPE_CHECKING:
    from typing import Any, DefaultDict, Optional, Type

    from typing_extensions import Literal

    from pcapkit.protocols.protocol import ProtocolBase
    from pcapkit.protocols.schema.schema import Schema

__all__ = ['Netlink', 'nlmsg_byteorder']


[docs] def nlmsg_byteorder(nlmsg_len: 'bytes', length: 'int') -> "Literal['big', 'little']": """Guess the byte order a Netlink message header was written in. Args: nlmsg_len: The 4 raw octets of ``nlmsg_len``. length: Octets available from the start of this header to the end of the frame -- ``nlmsg_len`` counts the header itself (:rfc:`3549#section-2.3.2`: *"the length of the message in bytes, including the header"*), so this is the quantity to compare against, not the payload alone. Returns: ``'big'`` or ``'little'``, whichever reading of ``nlmsg_len`` comes closer to ``length``; ties favour ``'little'``, the overwhelmingly common case (x86/ARM hosts). Note: This is the heuristic Wireshark's own netlink dissector uses (``packet-netlink.c``, ``dissect_netlink()``): a genuine message length is normally well under the size either misreading of 4 arbitrary octets would imply, so the reading closer to the octets actually on hand is overwhelmingly likely to be the real one. """ len_be = int.from_bytes(nlmsg_len, 'big') len_le = int.from_bytes(nlmsg_len, 'little') if abs(len_be - length) < abs(len_le - length): return 'big' return 'little'