Source code for pcapkit.protocols.link.arp

# -*- coding: utf-8 -*-
"""ARP/InARP - (Inverse) Address Resolution Protocol
=======================================================

:mod:`pcapkit.protocols.link.arp` contains
:class:`~pcapkit.protocols.link.arp.ARP` only,
which implements extractor for (Inverse) Address Resolution
Protocol (ARP/InARP) [*]_, whose structure is described as
below:

+========+=======+===============+=========================+
| Octets | Bits  | Name          | Description             |
+========+=======+===============+=========================+
| 0      |     0 | ``arp.htype`` | Hardware Type           |
+--------+-------+---------------+-------------------------+
| 2      |    16 | ``arp.ptype`` | Protocol Type           |
+--------+-------+---------------+-------------------------+
| 4      |    32 | ``arp.hlen``  | Hardware Address Length |
+--------+-------+---------------+-------------------------+
| 5      |    40 | ``arp.plen``  | Protocol Address Length |
+--------+-------+---------------+-------------------------+
| 6      |    48 | ``arp.oper``  | Operation               |
+--------+-------+---------------+-------------------------+
| 8      |    64 | ``arp.sha``   | Sender Hardware Address |
+--------+-------+---------------+-------------------------+
| 14     |   112 | ``arp.spa``   | Sender Protocol Address |
+--------+-------+---------------+-------------------------+
| 18     |   144 | ``arp.tha``   | Target Hardware Address |
+--------+-------+---------------+-------------------------+
| 24     |   192 | ``arp.tpa``   | Target Protocol Address |
+--------+-------+---------------+-------------------------+

.. [*] http://en.wikipedia.org/wiki/Address_Resolution_Protocol

"""
import ipaddress
import sys
import textwrap
from typing import TYPE_CHECKING

from pcapkit.const.arp.hardware import Hardware as RegType_Hardware
from pcapkit.const.arp.operation import Operation as RegType_Operation
from pcapkit.const.reg.ethertype import EtherType as RegType_EtherType
from pcapkit.protocols.data.link.arp import ARP as DataType_ARP
from pcapkit.protocols.data.link.arp import Address as DataType_Address
from pcapkit.protocols.data.link.arp import Type as DataType_Type
from pcapkit.protocols.link.link import Link
from pcapkit.utilities.compat import cached_property

if TYPE_CHECKING:
    from ipaddress import IPv4Address, IPv6Address
    from typing import Any, NoReturn, Optional

    from typing_extensions import Literal

__all__ = ['ARP']

# check Python version
py38 = ((version_info := sys.version_info).major >= 3 and version_info.minor >= 8)


[docs]class ARP(Link[DataType_ARP]): """This class implements all protocols in ARP family. - Address Resolution Protocol (:class:`~pcapkit.protocols.link.arp.ARP`) [:rfc:`826`] - Reverse Address Resolution Protocol (:class:`~pcapkit.protocols.link.rarp.RARP`) [:rfc:`903`] - Dynamic Reverse Address Resolution Protocol (:class:`~pcapkit.protocols.link.DRARP`) [:rfc:`1931`] - Inverse Address Resolution Protocol (:class:`~pcapkit.protocols.link.InARP`) [:rfc:`2390`] """ #: Name of corresponding protocol. _name: 'Literal["Address Resolution Protocol", "Inverse Address Resolution Protocol", "Reverse Address Resolution Protocol", "Dynamic Reverse Address Resolution Protocol"]' # pylint: disable=line-too-long #: Acronym of corresponding protocol. _acnm: 'Literal["ARP", "InARP", "RARP", "DRARP"]' ########################################################################## # Properties. ########################################################################## @property def name(self) -> 'Literal["Dynamic Reverse Address Resolution Protocol", "Inverse Address Resolution Protocol", "Reverse Address Resolution Protocol", "Address Resolution Protocol"]': # pylint: disable=line-too-long """Name of current protocol.""" return self._name @property def alias(self) -> 'Literal["ARP", "InARP", "RARP", "DRARP"]': """Acronym of corresponding protocol.""" return self._acnm @property def length(self) -> 'int': """Header length of current protocol.""" return self._info.len @cached_property def src(self) -> 'DataType_Address': """Sender hardware & protocol address.""" return DataType_Address(self._info.sha, self._info.spa) @cached_property def dst(self) -> 'DataType_Address': """Target hardware & protocol address.""" return DataType_Address(self._info.tha, self._info.tpa) @cached_property def type(self) -> 'DataType_Type': """Hardware & protocol type.""" return DataType_Type(self._info.htype, self._info.ptype) ########################################################################## # Methods. ##########################################################################
[docs] @classmethod def id(cls) -> 'tuple[Literal["ARP"], Literal["InARP"]]': """Index ID of the protocol.""" return ('ARP', 'InARP')
[docs] def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'DataType_ARP': # pylint: disable=unused-argument r"""Read Address Resolution Protocol. Data structure of ARP Request header [:rfc:`826`]: .. code-block:: text 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Hdr Type | Proto Type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Hdr Len | Proto Len | Operation | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ Sender Hdr Addr \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ Sender Proto Addr \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ Target Hdr Addr \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ Target Proto Addr \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Args: length: Length of packet data. **kwargs: Arbitrary keyword arguments. Returns: Parsed packet data. """ if length is None: length = self.__len__() _hwty = self._read_unpack(2) _ptty = self._read_unpack(2) _hlen = self._read_unpack(1) _plen = self._read_unpack(1) _oper = self._read_unpack(2) _shwa = self._read_addr_resolve(_hlen, _hwty) _spta = self._read_proto_resolve(_plen, _ptty) _thwa = self._read_addr_resolve(_hlen, _hwty) _tpta = self._read_proto_resolve(_plen, _ptty) if _oper in (5, 6, 7): self._acnm = 'DRARP' self._name = 'Dynamic Reverse Address Resolution Protocol' elif _oper in (8, 9): self._acnm = 'InARP' self._name = 'Inverse Address Resolution Protocol' elif _oper in (3, 4): self._acnm = 'RARP' self._name = 'Reverse Address Resolution Protocol' else: self._acnm = 'ARP' self._name = 'Address Resolution Protocol' _htype = RegType_Hardware.get(_hwty) _ptype = RegType_EtherType.get(_ptty) arp = DataType_ARP( htype=_htype, ptype=_ptype, hlen=_hlen, plen=_plen, oper=RegType_Operation.get(_oper), sha=_shwa, spa=_spta, tha=_thwa, tpa=_tpta, len=8 + _hlen * 2 + _plen * 2, ) return self._decode_next_layer(arp, -1, length - arp.len)
[docs] def make(self, **kwargs: 'Any') -> 'NoReturn': """Make (construct) packet data. Args: **kwargs: Arbitrary keyword arguments. Returns: Constructed packet data. """ raise NotImplementedError
########################################################################## # Data models. ########################################################################## def __length_hint__(self) -> 'Literal[28]': """Return an estimated length for the object.""" return 28
[docs] @classmethod def __index__(cls) -> 'RegType_EtherType': # pylint: disable=invalid-index-returned """Numeral registry index of the protocol. Returns: Numeral registry index of the protocol in `IANA`_. .. _IANA: https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml """ return RegType_EtherType.Address_Resolution_Protocol # type: ignore[return-value]
########################################################################## # Utilities. ########################################################################## def _read_addr_resolve(self, length: 'int', htype: 'int') -> 'str': """Resolve headware address according to protocol. Arguments: length: Hardware address length. htype: Hardware type. Returns: Hardware address. If ``htype`` is ``1``, i.e. MAC address, returns ``:`` seperated *hex* encoded MAC address. """ if htype == 1: # Ethernet _byte = self._read_fileng(length) if py38: _addr = _byte.hex(':') else: _addr = ':'.join(textwrap.wrap(_byte.hex(), 2)) else: _addr = self._read_fileng(length).hex() return _addr def _read_proto_resolve(self, length: 'int', ptype: 'int') -> 'str | IPv4Address | IPv6Address': """Resolve protocol address according to protocol. Arguments: length: Protocol address length. ptype: Protocol type. Returns: Protocol address. If ``ptype`` is ``0x0800``, i.e. IPv4 adddress, returns an :class:`~ipaddress.IPv4Address` object; if ``ptype`` is ``0x86dd``, i.e. IPv6 address, returns an :class:`~ipaddress.IPv6Address` object; otherwise, returns a raw :data:`str` representing the protocol address. """ if ptype == RegType_EtherType.Internet_Protocol_version_4: # IPv4 return ipaddress.ip_address(self._read_fileng(length)) if ptype == RegType_EtherType.Internet_Protocol_version_6: # IPv6 return ipaddress.ip_address(self._read_fileng(length)) return self._read_fileng(length).hex()