Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/pcapkit/protocols/internet/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ internet layer, with detailed implementation and methods.
ip
ipv4
ipv6
ipv6_ext
ipv6_frag
ipv6_generic_ext
ipv6_opts
ipv6_route
hopopt
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
IPv6_GenericExt - Generic IPv6 Extension Header
================================================

.. module:: pcapkit.protocols.internet.ipv6_generic_ext

:mod:`pcapkit.protocols.internet.ipv6_generic_ext` contains
:class:`~pcapkit.protocols.internet.ipv6_generic_ext.IPv6_GenericExt`
only, which implements a **generic** extractor for IPv6 extension
IPv6_Ext - IPv6 Extension Header
================================

.. module:: pcapkit.protocols.internet.ipv6_ext

:mod:`pcapkit.protocols.internet.ipv6_ext` contains
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
only, which serves two roles at once (GitHub issue #917): it is the
shared **base class** of all eight IPv6 extension headers this package
implements -- supplying them the ``extension``-mode contract, i.e. the
guards that make :attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.payload`,
:attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protocol` and
:attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protochain`
unavailable on a header parsed as part of an IPv6 chain -- and it
implements a **generic** extractor for IPv6 extension
headers [*]_, standing in for one whenever the header's own dedicated
parser is unavailable or has failed. :rfc:`6564#section-4` guarantees,
with an RFC 2119 **MUST**, that any IPv6 extension header defined
Expand All @@ -29,7 +36,7 @@ parser at all -- none of the four ever reaches this class), the two ways
this class is dispatched to, and why an overrun stops the walk instead of
clipping it.

.. autoclass:: pcapkit.protocols.internet.ipv6_generic_ext.IPv6_GenericExt
.. autoclass:: pcapkit.protocols.internet.ipv6_ext.IPv6_Ext
:no-members:
:show-inheritance:

Expand All @@ -52,18 +59,18 @@ clipping it.
Header Schemas
--------------

.. module:: pcapkit.protocols.schema.internet.ipv6_generic_ext
.. module:: pcapkit.protocols.schema.internet.ipv6_ext

.. autoclass:: pcapkit.protocols.schema.internet.ipv6_generic_ext.IPv6_GenericExt
.. autoclass:: pcapkit.protocols.schema.internet.ipv6_ext.IPv6_Ext
:members:
:show-inheritance:

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

.. module:: pcapkit.protocols.data.internet.ipv6_generic_ext
.. module:: pcapkit.protocols.data.internet.ipv6_ext

.. autoclass:: pcapkit.protocols.data.internet.ipv6_generic_ext.IPv6_GenericExt
.. autoclass:: pcapkit.protocols.data.internet.ipv6_ext.IPv6_Ext
:members:
:show-inheritance:

Expand Down
6 changes: 3 additions & 3 deletions examples/generators/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ def _internet_payload(code: 'int') -> 'bytes':
from pcapkit.protocols.link.ospf import OSPF
return bytes(OSPF())
if code == TransType.Shim6:
# #904: no dedicated dissector exists for Shim6 -- IPv6_GenericExt
# #904: no dedicated dissector exists for Shim6 -- IPv6_Ext
# parses only the two octets RFC 6564 §4 guarantees (next header,
# Hdr Ext Len), so this is hand-built rather than constructed
# through a class: next=TCP(6), Hdr Ext Len=0 -> an 8-octet header,
Expand Down Expand Up @@ -630,10 +630,10 @@ def _internet_enum() -> 'Any':
'internet/OSPFIGP': ('pcapkit.protocols.link.ospf', 'OSPF'),
# #904: Shim6 (140) previously had no entry at all, and the default
# factory made it resolve to Raw. It is now registered directly at
# IPv6_GenericExt, which parses the RFC 6564 §4 generic layout it has
# IPv6_Ext, which parses the RFC 6564 §4 generic layout it has
# never had a dedicated dissector for -- a deliberate addition, not a
# regression.
'internet/Shim6': ('pcapkit.protocols.internet.ipv6_generic_ext', 'IPv6_GenericExt'),
'internet/Shim6': ('pcapkit.protocols.internet.ipv6_ext', 'IPv6_Ext'),

# -- TCP.__proto__ (port) --------------------------------------------------
'tcp/20': ('pcapkit.protocols.application.ftp', 'FTP_DATA'),
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
'L2TPv2', 'OSPF', 'RARP', 'S_Tag', 'VLAN',

'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX', # Internet Layer
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_GenericExt', 'IPv6_Opts', 'IPv6_Route', 'MH',
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_Ext', 'IPv6_Opts', 'IPv6_Route', 'MH',
# IPv6 Extension Header

'TCP', 'UDP', 'SCTP', # Transport Layer
Expand Down
2 changes: 1 addition & 1 deletion pcapkit/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX',

# IPv6 Extension Header
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_GenericExt', 'IPv6_Opts',
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_Ext', 'IPv6_Opts',
'IPv6_Route', 'MH',

# Transport Layer
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/protocols/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@
# IPv6 Fragment Header
'IPv6_Frag',

# Generic IPv6 Extension Header
'IPv6_GenericExt',
# IPv6 Extension Header (base + generic fallback)
'IPv6_Ext',

# IPv6 Destination Options Header
'IPv6_Opts',
Expand Down
8 changes: 4 additions & 4 deletions pcapkit/protocols/data/internet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@
# IPv6 Fragment Header
from pcapkit.protocols.data.internet.ipv6_frag import IPv6_Frag

# Generic IPv6 Extension Header
from pcapkit.protocols.data.internet.ipv6_generic_ext import IPv6_GenericExt
# IPv6 Extension Header (base + generic fallback)
from pcapkit.protocols.data.internet.ipv6_ext import IPv6_Ext

# IPv6 Destination Options
from pcapkit.protocols.data.internet.ipv6_opts import CALIPSOOption as IPv6_Opts_CALIPSOOption
Expand Down Expand Up @@ -269,8 +269,8 @@
# IPv6 Fragment Header
'IPv6_Frag',

# Generic IPv6 Extension Header
'IPv6_GenericExt',
# IPv6 Extension Header (base + generic fallback)
'IPv6_Ext',

# IPv6 Destination Options Header
'IPv6_Opts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
from pcapkit.const.ipv6.extension_header import ExtensionHeader
from pcapkit.const.reg.transtype import TransType

__all__ = ['IPv6_GenericExt']
__all__ = ['IPv6_Ext']


@info_final
class IPv6_GenericExt(Protocol):
class IPv6_Ext(Protocol):
"""Data model for a generically-parsed IPv6 extension header.

See :class:`pcapkit.protocols.internet.ipv6_generic_ext.IPv6_GenericExt`
See :class:`pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
for how each field below is derived.

"""
Expand All @@ -28,9 +28,9 @@ class IPv6_GenericExt(Protocol):
#: the caller dispatched on, resolved to its
#: :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` member.
#: :data:`None` when ``alias`` named no such member (:meth:`read
#: <pcapkit.protocols.internet.ipv6_generic_ext.IPv6_GenericExt.read>`
#: <pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.read>`
#: sets it so on a lookup miss, and the class property at
#: :attr:`~pcapkit.protocols.internet.ipv6_generic_ext.IPv6_GenericExt.protocol`
#: :attr:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.protocol`
#: is typed to match).
protocol: 'Optional[ExtensionHeader]'
#: Next header, parsed off the wire. :data:`None` when the declared
Expand Down
4 changes: 2 additions & 2 deletions pcapkit/protocols/internet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pcapkit.protocols.internet.hip import HIP
from pcapkit.protocols.internet.hopopt import HOPOPT
from pcapkit.protocols.internet.ipv6_frag import IPv6_Frag
from pcapkit.protocols.internet.ipv6_generic_ext import IPv6_GenericExt
from pcapkit.protocols.internet.ipv6_ext import IPv6_Ext
from pcapkit.protocols.internet.ipv6_opts import IPv6_Opts
from pcapkit.protocols.internet.ipv6_route import IPv6_Route
from pcapkit.protocols.internet.mh import MH
Expand All @@ -40,6 +40,6 @@
__all__ = [
'ETHERTYPE', # Protocol Numbers
'AH', 'ESP', 'IP', 'IPsec', 'IPv4', 'IPv6', 'IPX', # Internet Layer
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_GenericExt',
'HIP', 'HOPOPT', 'IPv6_Frag', 'IPv6_Ext',
'IPv6_Opts', 'IPv6_Route', 'MH', # IPv6 Extension Header
]
35 changes: 33 additions & 2 deletions pcapkit/protocols/internet/ah.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from pcapkit.const.reg.transtype import TransType as Enum_TransType
from pcapkit.protocols.data.internet.ah import AH as Data_AH
from pcapkit.protocols.internet.ipsec import IPsec
from pcapkit.protocols.internet.ipv6_ext import IPv6_Ext
from pcapkit.protocols.schema.internet.ah import AH as Schema_AH
from pcapkit.utilities.exceptions import UnsupportedCall

Expand All @@ -46,9 +47,20 @@
__all__ = ['AH']


class AH(IPsec[Data_AH, Schema_AH],
class AH(IPsec[Data_AH, Schema_AH], IPv6_Ext[Data_AH, Schema_AH],
schema=Schema_AH, data=Data_AH):
"""This class implements Authentication Header."""
"""This class implements Authentication Header.

Double-inherited (GitHub issue #917): ``AH`` is both a member of the
IPsec family and an IPv6 extension header -- IANA's
``protocol-numbers-1.csv`` marks it ``Y`` in the *IPv6 Extension Header*
column (:rfc:`4302`), and this package's own
:class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` registry
agrees. :class:`~pcapkit.protocols.internet.ipsec.IPsec` is first in the
bases so that its :meth:`~pcapkit.protocols.internet.ipsec.IPsec.id`
keeps precedence.

"""

##########################################################################
# Properties.
Expand All @@ -59,6 +71,25 @@ def name(self) -> 'Literal["Authentication Header"]':
"""Name of corresponding protocol."""
return 'Authentication Header'

@property
def alias(self) -> 'Literal["AH"]':
"""Acronym of corresponding protocol.

Spelled out rather than left to
:attr:`ProtocolBase.alias <pcapkit.protocols.protocol.ProtocolBase.alias>`'s
class-name default, because
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` now sits
between this class and that default in the MRO and carries a concrete
``'IPv6-Ext'`` of its own (GitHub issue #917). Inheriting it would
rename this header in every
:class:`~pcapkit.corekit.protochain.ProtoChain` string and in
:meth:`IPv6._decode_next_layer
<pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer>`'s packet
dict key. The value is exactly what the default produced before.

"""
return 'AH'

@property
def length(self) -> 'int':
"""Header length of current protocol."""
Expand Down
56 changes: 52 additions & 4 deletions pcapkit/protocols/internet/esp.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,10 @@
from pcapkit.corekit.infoclass import Info, info_final
from pcapkit.protocols.data.internet.esp import ESP as Data_ESP
from pcapkit.protocols.internet.ipsec import IPsec
from pcapkit.protocols.internet.ipv6_ext import IPv6_Ext
from pcapkit.protocols.schema.internet.esp import ESP as Schema_ESP
from pcapkit.protocols.schema.schema import Schema
from pcapkit.utilities.exceptions import ProtocolError, ProtocolUnbound
from pcapkit.utilities.exceptions import ProtocolError, ProtocolUnbound, UnsupportedCall
from pcapkit.utilities.warnings import ProtocolWarning, warn

__all__ = ['ESP', 'ESPStatus', 'Cipher', 'Integrity', 'CipherSuite', 'IntegritySuite',
Expand All @@ -187,7 +188,7 @@
if TYPE_CHECKING:
from enum import IntEnum as StdlibEnum
from ipaddress import IPv4Address, IPv6Address
from typing import IO, Any, Optional, Type
from typing import IO, Any, NoReturn, Optional, Type

from aenum import IntEnum as AenumEnum
from typing_extensions import Literal
Expand Down Expand Up @@ -962,9 +963,26 @@ def __repr__(self) -> 'str':
##############################################################################


class ESP(IPsec[Data_ESP, Schema_ESP],
class ESP(IPsec[Data_ESP, Schema_ESP], IPv6_Ext[Data_ESP, Schema_ESP],
schema=Schema_ESP, data=Data_ESP):
"""This class implements Encapsulating Security Payload."""
"""This class implements Encapsulating Security Payload.

Double-inherited (GitHub issue #917), mirroring
:class:`~pcapkit.protocols.internet.ah.AH`: IANA's
``protocol-numbers-1.csv`` marks ``ESP`` ``Y`` in the *IPv6 Extension
Header* column (:rfc:`4303`), and this package's own
:class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader` registry
agrees (``ESP = 50``), so it must honour the same extension-mode contract
as its siblings. :attr:`payload` and :attr:`protochain` come from
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`; :attr:`protocol`
is spelled out below for the reason given there.

Note:
:rfc:`8200#section-4.5` says outright that ESP "is not considered an
extension header". The library follows IANA's registry rather than
that sentence, on the owner's ruling for GitHub issue #895.

"""

##########################################################################
# Properties.
Expand All @@ -975,6 +993,36 @@ def name(self) -> 'Literal["Encapsulating Security Payload"]':
"""Name of corresponding protocol."""
return 'Encapsulating Security Payload'

@property
def alias(self) -> 'Literal["ESP"]':
"""Acronym of corresponding protocol.

Spelled out rather than left to
:attr:`ProtocolBase.alias <pcapkit.protocols.protocol.ProtocolBase.alias>`'s
class-name default, because
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` now sits
between this class and that default in the MRO and carries a concrete
``'IPv6-Ext'`` of its own. Inheriting it would rename this header in
every :class:`~pcapkit.corekit.protochain.ProtoChain` string and in
:meth:`IPv6._decode_next_layer
<pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer>`'s packet
dict key. The value is exactly what the default produced before.

"""
return 'ESP'

@property
def protocol(self) -> 'Optional[str] | NoReturn':
"""Name of next layer protocol (if any).

Raises:
UnsupportedCall: if the protocol is used as an IPv6 extension header

"""
if self._extf:
raise UnsupportedCall(f"'{self.__class__.__name__}' object has no attribute 'protocol'")
return super().protocol

@property
def length(self) -> 'int':
"""Length of the ESP header, payload, trailer and ICV.
Expand Down
31 changes: 30 additions & 1 deletion pcapkit/protocols/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
from pcapkit.protocols.data.internet.hip import UnassignedParameter as Data_UnassignedParameter
from pcapkit.protocols.data.internet.hip import ViaRVSParameter as Data_ViaRVSParameter
from pcapkit.protocols.internet.internet import Internet
from pcapkit.protocols.internet.ipv6_ext import IPv6_Ext
from pcapkit.protocols.schema.internet.hip import HIP as Schema_HIP
from pcapkit.protocols.schema.internet.hip import AckDataParameter as Schema_AckDataParameter
from pcapkit.protocols.schema.internet.hip import ACKParameter as Schema_ACKParameter
Expand Down Expand Up @@ -254,10 +255,38 @@ class Locator(TypedDict):
spi: 'NotRequired[int]'


class HIP(Internet[Data_HIP, Schema_HIP],
class HIP(IPv6_Ext[Data_HIP, Schema_HIP], Internet[Data_HIP, Schema_HIP],
schema=Schema_HIP, data=Data_HIP):
"""This class implements Host Identity Protocol.

Double-inherited, per the maintainer's convention on GitHub pull request
#924: a header that is *only* usable as an extension header inherits
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` alone, while one
that is also usable as a standalone protocol names
:class:`~pcapkit.protocols.internet.internet.Internet` as well. HIP is
both, on two independent grounds:

* :rfc:`7401#section-5.1` states that "the HIP header is logically an
IPv6 extension header", and IANA lists protocol 139 in its *IPv6
Extension Header Types* registry -- 11 entries, HIP among them.
* :rfc:`7401#appendix-C.2`, "IPv4 HIP Packet (I1 Packet)", works a
checksum for an **IPv4** header carrying ``Next Header: 139`` with
``Payload Protocol: 59``. HIP therefore travels directly as an IPv4
payload, exactly as :class:`~pcapkit.protocols.internet.ah.AH` and
:class:`~pcapkit.protocols.internet.esp.ESP` do.

The second ground is what separates HIP from
:class:`~pcapkit.protocols.internet.mh.MH` and Shim6, which are protocols
in their own right but cannot appear under IPv4:
:rfc:`6275#section-6.1.1` defines the Mobility Header checksum over a
pseudo-header of IPv6 header fields with no IPv4 variant, and Mobile IPv4
carries its equivalent messages over UDP port 434 (:rfc:`5944`) rather
than as protocol 135.

``Internet`` is already reached transitively through ``IPv6_Ext``; naming
it is what records the classification, so a future reader can tell a
deliberate standalone protocol from a header that merely inherits one.

This class currently supports parsing of the following HIP parameters,
which are registered in the :attr:`self.__parameter__ <pcapkit.protocols.internet.hip.HIP.__parameter__>`
attribute:
Expand Down
Loading
Loading