# -*- coding: utf-8 -*-
"""(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 re
import textwrap
from pcapkit.const.arp.hardware import Hardware as HRD
from pcapkit.const.arp.operation import Operation as OPER
from pcapkit.const.reg.ethertype import EtherType as ETHERTYPE
from pcapkit.protocols.link.link import Link
__all__ = ['ARP']
[docs]class ARP(Link):
"""This class implements all protocols in ARP family.
- Address Resolution Protocol (ARP) [:rfc:`826`]
- Reverse Address Resolution Protocol (RARP) [:rfc:`903`]
- Dynamic Reverse Address Resolution Protocol (DRARP) [:rfc:`1931`]
- Inverse Address Resolution Protocol (InARP) [:rfc:`2390`]
"""
##########################################################################
# Properties.
##########################################################################
@property
def name(self):
"""Name of current protocol.
:rtype: Literal['Dynamic Reverse Address Resolution Protocol', 'Inverse Address Resolution Protocol',
'Reverse Address Resolution Protocol', 'Address Resolution Protocol']
"""
return self._name
@property
def alias(self):
"""Acronym of corresponding protocol.
:rtype: Literal['ARP', 'InARP', 'RARP', 'DRARP']
"""
return self._acnm
@property
def length(self):
"""Header length of current protocol.
:rtype: int
"""
return self._info.len # pylint: disable=E1101
@property
def src(self):
"""Sender hardware & protocol address.
:rtype: Tuple[str, Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str]]
"""
return (self._info.sha, self._info.spa) # pylint: disable=E1101
@property
def dst(self):
"""Target hardware & protocol address.
:rtype: Tuple[str, Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str]]
"""
return (self._info.tha, self._info.tpa) # pylint: disable=E1101
@property
def type(self):
"""Hardware & protocol type.
:rtype: Tuple[pcapkit.const.arp.hardware.Hardware, pcapkit.const.reg.ethertype.EtherType]
"""
return (self._info.htype, self._info.ptype) # pylint: disable=E1101
##########################################################################
# Methods.
##########################################################################
[docs] @classmethod
def id(cls): # pylint: disable=invalid-index-returned
"""Index ID of the protocol.
Returns:
Tuple[Literal['ARP'], Literal['InARP']]: Index ID of the protocol.
See Also:
:meth:`pcapkit.protocols.protocol.Protocol.__getitem__`
"""
return ('ARP', 'InARP')
[docs] def read(self, length=None, **kwargs): # pylint: disable=unused-argument
"""Read Address Resolution Protocol [:rfc:`826`].
Args:
length (Optional[int]): Length of packet data.
Keyword Args:
**kwargs: Arbitrary keyword arguments.
Returns:
DataType_ARP: Parsed packet data.
"""
if length is None:
length = len(self)
_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 = HRD.get(_hwty)
if re.match(r'.*Ethernet.*', _htype, re.IGNORECASE):
_ptype = ETHERTYPE.get(_ptty)
else:
_ptype = f'Unknown [{_ptty}]'
arp = dict(
htype=_htype,
ptype=_ptype,
hlen=_hlen,
plen=_plen,
oper=OPER.get(_oper),
sha=_shwa,
spa=_spta,
tha=_thwa,
tpa=_tpta,
len=8 + _hlen * 2 + _plen * 2,
)
length -= arp['len']
arp['packet'] = self._read_packet(header=arp['len'], payload=length)
return self._decode_next_layer(arp, None, length)
[docs] def make(self, **kwargs):
"""Make (construct) packet data.
Keyword Args:
**kwargs: Arbitrary keyword arguments.
Returns:
bytes: Constructed packet data.
"""
raise NotImplementedError
##########################################################################
# Data models.
##########################################################################
[docs] def __length_hint__(self):
"""Return an estimated length for the object.
:rtype: Literal[28]
"""
return 28
[docs] @classmethod
def __index__(cls): # pylint: disable=invalid-index-returned
"""Numeral registry index of the protocol.
Returns:
pcapkit.const.reg.ethertype.EtherType: Numeral registry index of the
protocol in `IANA`_.
.. _IANA: https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
"""
return ETHERTYPE(0x0806)
##########################################################################
# Utilities.
##########################################################################
[docs] def _read_addr_resolve(self, length, htype):
"""Resolve headware address according to protocol.
Arguments:
length (int): Hardware address length.
htype (int): Hardware type.
Returns:
str: Hardware address. If ``htype`` is ``1``, i.e. MAC address,
returns ``:`` seperated *hex* encoded MAC address.
"""
if htype == 1: # Ethernet
_byte = self._read_fileng(6)
_addr = ':'.join(textwrap.wrap(_byte.hex(), 2))
else:
_addr = self._read_fileng(length)
return _addr
[docs] def _read_proto_resolve(self, length, ptype):
"""Resolve protocol address according to protocol.
Positional arguments:
length (int): Protocol address length.
ptype (int): Protocol type.
Returns:
Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str]: 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 == 0x0800: # IPv4
return ipaddress.ip_address(self._read_fileng(4))
if ptype == 0x86dd: # IPv6
return ipaddress.ip_address(self._read_fileng(16))
return self._read_fileng(length)