# -*- coding: utf-8 -*-
"""PyPCAP Tools
=================
.. module:: pcapkit.toolkit.pypcap
:mod:`pcapkit.toolkit.pypcap` contains all you need for
:mod:`pcapkit` handy usage with `PyPCAP`_ engine. All reforming
functions returns with a flag to indicate if usable for
its caller.
.. _PyPCAP: https://github.com/pynetwork/pypcap
.. important::
`PyPCAP`_ is a thin :manpage:`libpcap(3)` binding: it hands back the
``(timestamp, bytes)`` pair that :c:func:`pcap_next_ex` produced and performs
**no protocol dissection whatsoever**. There is therefore no IP or TCP layer
for this module to read, and the reassembly and flow tracing adapters that
:mod:`pcapkit.toolkit.dpkt` and :mod:`pcapkit.toolkit.scapy` provide cannot
be implemented here.
They are still defined below, but only so that reaching for one fails loudly
with :exc:`~pcapkit.utilities.exceptions.UnsupportedCall` rather than with an
:exc:`ImportError` that says nothing about why. Dissecting the raw bytes with
:mod:`pcapkit`'s own parsers would defeat the point of selecting a third-party
engine, so it is deliberately not done.
.. seealso::
:mod:`pcapkit.toolkit.pcap_ct` is the same adapter for `pcap-ct`_, the
independent reimplementation of this interface that
:class:`~pcapkit.foundation.engines.pcap_ct.PCAP_CT` drives. Each engine names
its own adapter, so a change made for one cannot quietly alter the other.
.. _pcap-ct: https://pypi.org/project/pcap-ct/
"""
from typing import TYPE_CHECKING
from pcapkit.utilities.exceptions import UnsupportedCall
if TYPE_CHECKING:
from ipaddress import IPv4Address, IPv6Address
from typing import Any
from pcapkit.const.reg.linktype import LinkType as Enum_LinkType
from pcapkit.foundation.reassembly.data.ip import Packet as IP_Packet
from pcapkit.foundation.reassembly.data.tcp import Packet as TCP_Packet
from pcapkit.foundation.traceflow.data.tcp import Packet as TF_TCP_Packet
__all__ = [
'packet2chain', 'packet2dict',
'ipv4_reassembly', 'ipv6_reassembly', 'tcp_reassembly', 'tcp_traceflow',
]
#: Explanatory suffix shared by every unsupported adapter below.
_NO_DISSECTION = ("'pypcap' is a libpcap binding and performs no protocol "
"dissection, so there is no protocol layer to read")
[docs]
def packet2chain(packet: 'bytes', *, data_link: 'Enum_LinkType') -> 'str':
"""Fetch PyPCAP packet protocol chain.
Args:
packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration.
data_link: Data link type, from the capture handle.
Returns:
Colon (``:``) separated list of protocol chain.
Note:
As `PyPCAP`_ does not dissect the packet, the chain is only ever the
link layer type followed by ``Raw``, e.g. ``ETHERNET:Raw``.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
return f'{data_link.name}:Raw'
[docs]
def packet2dict(packet: 'bytes', timestamp: 'float', *,
data_link: 'Enum_LinkType') -> 'dict[str, Any]':
"""Convert PyPCAP packet into :obj:`dict`.
Args:
packet: Raw packet bytes, as returned by :class:`pcap.pcap` iteration.
timestamp: Timestamp of packet, as returned by :class:`pcap.pcap` iteration.
data_link: Data link type, from the capture handle.
Returns:
Dict[str, Any]: A :obj:`dict` mapping of packet data.
Note:
The mapping carries the captured bytes verbatim rather than a decoded
protocol tree, since `PyPCAP`_ does not decode anything.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
return {
'timestamp': timestamp,
'packet': packet,
data_link.name: {
'raw_len': len(packet),
'raw': packet,
},
}
[docs]
def ipv4_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv4Address] | None':
"""Make data for IPv4 reassembly.
Args:
packet: Raw packet bytes.
count: Packet index. If not provided, default to ``-1``.
Raises:
UnsupportedCall: Always, as `PyPCAP`_ provides no IPv4 layer to read.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
raise UnsupportedCall(f'IPv4 reassembly is not supported by the PyPCAP engine: {_NO_DISSECTION}')
[docs]
def ipv6_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'IP_Packet[IPv6Address] | None':
"""Make data for IPv6 reassembly.
Args:
packet: Raw packet bytes.
count: Packet index. If not provided, default to ``-1``.
Raises:
UnsupportedCall: Always, as `PyPCAP`_ provides no IPv6 layer to read.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
raise UnsupportedCall(f'IPv6 reassembly is not supported by the PyPCAP engine: {_NO_DISSECTION}')
[docs]
def tcp_reassembly(packet: 'bytes', *, count: 'int' = -1) -> 'TCP_Packet | None':
"""Make data for TCP reassembly.
Args:
packet: Raw packet bytes.
count: Packet index. If not provided, default to ``-1``.
Raises:
UnsupportedCall: Always, as `PyPCAP`_ provides no TCP layer to read.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
raise UnsupportedCall(f'TCP reassembly is not supported by the PyPCAP engine: {_NO_DISSECTION}')
[docs]
def tcp_traceflow(packet: 'bytes', timestamp: 'float', *, data_link: 'Enum_LinkType',
count: 'int' = -1) -> 'TF_TCP_Packet | None':
"""Trace packet flow for TCP.
Args:
packet: Raw packet bytes.
timestamp: Timestamp of the packet.
data_link: Data link layer protocol (from the capture handle).
count: Packet index. If not provided, default to ``-1``.
Raises:
UnsupportedCall: Always, as `PyPCAP`_ provides no TCP layer to read.
.. _PyPCAP: https://github.com/pynetwork/pypcap
"""
raise UnsupportedCall(f'TCP flow tracing is not supported by the PyPCAP engine: {_NO_DISSECTION}')