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 pcapkit/protocols/internet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
# Ethertype IEEE 802 Numbers
from pcapkit.const.reg.ethertype import EtherType as ETHERTYPE

# Deprecated / Base Classes
# Base Classes
from pcapkit.protocols.internet.ip import IP
from pcapkit.protocols.internet.ipsec import IPsec

Expand Down
44 changes: 20 additions & 24 deletions pcapkit/protocols/internet/esp.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
? ? ``esp.icv`` Integrity Check Value (ICV, variable)
======= ========= ===================== ==============================================

Unlike every other protocol in :mod:`pcapkit`, ESP is **not** self
describing. :rfc:`4303` places the ``Pad Length`` and ``Next Header``
ESP is **not** self describing. :rfc:`4303` places the ``Pad Length`` and ``Next Header``
fields *inside* the ciphertext, and leaves the length of the ``Integrity
Check Value`` to be determined by the Security Association (SA), which is
negotiated out of band. Therefore:
Expand Down Expand Up @@ -56,7 +55,7 @@
)
extraction = pcapkit.extract('esp.pcap', context=ESPContext(sa))

Registered algorithms, and supported ones
Registered Algorithms, and Supported Ones
-----------------------------------------

ESP has no algorithm registry of its own -- an SA's algorithms are negotiated
Expand Down Expand Up @@ -139,7 +138,7 @@
:attr:`Cipher.ENCR_AES_CBC <pcapkit.const.esp.cipher.Cipher.ENCR_AES_CBC>`
are the same member.

Known limitations
Known Limitations
-----------------

* **Extended Sequence Numbers (ESN,** :rfc:`4303` **§2.2.1) are not
Expand Down Expand Up @@ -458,10 +457,9 @@ def get(cls, value: 'Integrity | str | int') -> 'IntegritySuite':
class ESPStatus(EnumLookup, enum.IntEnum):
"""Outcome of ESP payload processing.

Re-parented onto :class:`~pcapkit.corekit.enum.EnumLookup` per GitHub
issue :issue:`930`, finishing :issue:`877`'s phase 2 -- pure re-parenting, since this
class defines neither ``get`` nor ``_missing_`` of its own to reconcile
with the base.
``get`` / ``get_all`` come from :class:`~pcapkit.corekit.enum.EnumLookup`;
the class defines no ``_missing_``, so an unknown value raises
:exc:`ValueError`.

"""

Expand Down Expand Up @@ -525,7 +523,7 @@ class SecurityAssociation:

Raises:
ProtocolError: If the algorithms or key lengths are inconsistent, or
``destination`` is a :obj:`bool` (c.f. :issue:`491`) -- :obj:`bool` is an
``destination`` is a :obj:`bool` -- :obj:`bool` is an
:class:`int` subclass, and :func:`ipaddress.ip_address` treats
any :class:`int` below ``2**32`` as IPv4, so without this check
``destination=True`` would silently become
Expand Down Expand Up @@ -563,7 +561,7 @@ def __init__(self, spi: 'Optional[int]' = None, *,
# subclass, and ``ipaddress.ip_address()`` treats any ``int`` below
# ``2**32`` as IPv4 -- so without this check, ``destination=True``
# would silently become ``IPv4Address('0.0.0.1')``, with no
# exception and no warning (c.f. #491).
# exception and no warning.
raise ProtocolError(
f'invalid destination: must not be a bool, not {destination!r} -- '
f'pass int({destination!r}) if the numeric value is what is wanted')
Expand Down Expand Up @@ -975,18 +973,16 @@ 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.

Double-inherited (GitHub issue :issue:`917`), mirroring
:class:`~pcapkit.protocols.internet.ah.AH`: IANA's *IPv6 Extension
Header Types* registry lists ``ESP`` at 50 (:rfc:`4303#section-3.1.1`
has it appear after the hop-by-hop, routing and fragmentation
extension headers in the IPv6 header chain), and this package's own
Double-inherited, mirroring :class:`~pcapkit.protocols.internet.ah.AH`:
IANA's *IPv6 Extension Header Types* registry lists ``ESP`` at 50
(:rfc:`4303#section-3.1.1` has it follow the hop-by-hop, routing and
fragmentation extension headers in the IPv6 header chain), 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. The same section separately states that,
in the context of IPv4, ESP is placed after the IP header and
before the next-layer protocol -- the primary-source evidence that
it also travels directly as an IPv4 payload, which is what
qualifies it for a base besides
agrees, so it must honour the same extension-mode contract as its
siblings. The same section places ESP after the IP header and before the
next-layer protocol in IPv4, so it also travels directly as an IPv4
payload, which is what qualifies it for a base besides
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`.
:attr:`payload` and :attr:`protochain` come from
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`; :attr:`protocol`
Expand All @@ -995,7 +991,7 @@ class ESP(IPsec[Data_ESP, Schema_ESP], IPv6_Ext[Data_ESP, Schema_ESP],
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 :issue:`895`.
that sentence (decided on :issue:`895`).

"""

Expand All @@ -1015,13 +1011,13 @@ def alias(self) -> 'Literal["ESP"]':
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
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` 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.
dict key. The value equals the class-name default.

"""
return 'ESP'
Expand Down
13 changes: 7 additions & 6 deletions pcapkit/protocols/internet/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
:class:`~pcapkit.protocols.internet.ip.IP` only,
which is a base class for Internet Protocol (IP)
protocol family [*]_, eg.
:class:`~pcapkit.protocols.internet.ipv4.IPv4`,
:class:`~pcapkit.protocols.internet.ipv6.IPv6`, and
:class:`~pcapkit.protocols.internet.ipsec.IPsec`.
:class:`~pcapkit.protocols.internet.ipv4.IPv4` and
:class:`~pcapkit.protocols.internet.ipv6.IPv6`.

.. [*] https://en.wikipedia.org/wiki/Internet_Protocol

Expand All @@ -27,12 +26,14 @@


class IP(Internet[_PT, _ST], Generic[_PT, _ST]): # pylint: disable=abstract-method
"""This class implements all protocols in IP family.
"""Base class for the IP protocol family.

- Internet Protocol version 4 (:class:`~pcapkit.protocols.internet.ipv4.IPv4`) [:rfc:`791`]
- Internet Protocol version 6 (:class:`~pcapkit.protocols.internet.ipv6.IPv6`) [:rfc:`2460`]
- Authentication Header (:class:`~pcapkit.protocols.internet.ah.AH`) [:rfc:`4302`]
- Encapsulating Security Payload (:class:`~pcapkit.protocols.internet.esp.ESP`) [:rfc:`4303`]

The IPsec protocols (:class:`~pcapkit.protocols.internet.ah.AH` and
:class:`~pcapkit.protocols.internet.esp.ESP`) derive from
:class:`~pcapkit.protocols.internet.ipsec.IPsec` instead.

"""

Expand Down
149 changes: 63 additions & 86 deletions pcapkit/protocols/internet/ipv6.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,59 +60,47 @@ class IPv6(IP[Data_IPv6, Schema_IPv6],
# Defaults.
##########################################################################

#: Extension header codes that have a *dedicated* parser class in this
#: package whose own layout follows :rfc:`6564#section-4`'s generic
#: ``next`` + ``Hdr Ext Len`` format (see the module docstring of
#: :mod:`pcapkit.protocols.internet.ipv6_ext` for the exception
#: table in full). When that dedicated parser raises,
#: :meth:`_import_next_layer` substitutes
#: :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
#: Extension header codes with a *dedicated* parser class in this
#: package whose layout follows :rfc:`6564#section-4`'s generic ``next`` +
#: ``Hdr Ext Len`` format (see the module docstring of
#: :mod:`pcapkit.protocols.internet.ipv6_ext` for the exception table).
#: When that dedicated parser raises, :meth:`_import_next_layer`
#: substitutes :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
#: instead of letting the generic
#: :func:`~pcapkit.utilities.decorators.beholder` fall back to plain
#: :class:`~pcapkit.protocols.misc.raw.Raw`, which has no ``next`` field
#: and used to crash the whole packet at :meth:`_decode_next_layer`'s
#: ``proto = info.next`` (GitHub issue :issue:`891`).
#: and so cannot continue the walk in :meth:`_decode_next_layer`.
#:
#: :attr:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6`
#: is deliberately absent, even though its wire format also conforms:
#: this package has never had a dedicated parser for it to begin with
#: (``pcapkit/protocols/internet/NotImplemented/shim6.py`` is a 0-byte
#: placeholder, excluded from the wheel by ``MANIFEST.in``), so there is
#: no "own parser" here for it to raise from -- ``Shim6`` reaches
#: :class:`IPv6_Ext` by *direct* registration instead (see the
#: bottom of :mod:`pcapkit.protocols.internet.ipv6_ext`), which
#: already produces exactly this class without needing this set to name
#: it. ``ESP``, ``253`` and ``254`` are absent, but not for the same
#: reason as each other, and not because a generic fallback would help
#: them:
#: Three groups are deliberately absent:
#:
#: * :attr:`~pcapkit.const.ipv6.extension_header.ExtensionHeader.Shim6`
#: has no dedicated parser (``pcapkit/protocols/internet/NotImplemented/shim6.py``
#: is a 0-byte placeholder), so there is no "own parser" for it to raise
#: from. It reaches :class:`IPv6_Ext` by *direct* registration (see the
#: bottom of :mod:`pcapkit.protocols.internet.ipv6_ext`), which already
#: produces exactly this class.
#: * ``ESP`` *does* have a dedicated, registered parser
#: (:class:`~pcapkit.protocols.internet.esp.ESP`) -- it is excluded
#: (:class:`~pcapkit.protocols.internet.esp.ESP`). It is excluded
#: because :rfc:`4303` places the real Next Header byte inside the
#: encrypted trailer, so its own info always *carries* a ``next``
#: attribute (unlike ``253``/``254`` below), just one that is
#: :data:`None` whenever the payload could not be decrypted -- which,
#: with no key material available to a generic parse, is always. The
#: :meth:`_decode_next_layer` walk below still ends there, one iteration
#: later, because :data:`None` fails
#: attribute, just one that is :data:`None` whenever the payload could not
#: be decrypted -- which, with no key material available to a generic
#: parse, is always. The :meth:`_decode_next_layer` walk still ends
#: there, one iteration later, because :data:`None` fails
#: :class:`~pcapkit.const.ipv6.extension_header.ExtensionHeader`'s
#: constructor at the top of the loop -- the *existing* end-of-chain
#: path, unrelated to the structural check this set exists for.
#: constructor at the top of the loop -- the ordinary end-of-chain path,
#: unrelated to the structural check this set exists for.
#: * ``253`` and ``254`` have no dedicated parser at all, so they resolve
#: to plain :class:`~pcapkit.protocols.misc.raw.Raw`, whose info has no
#: ``next`` *attribute* -- this is what the structural check catches.
#: (``BIT-EMU``/147 used to sit here too, until GitHub issue :issue:`925` found
#: it was never in IANA's authoritative extension-header registry to
#: begin with; :class:`~pcapkit.const.ipv6.extension_header
#: .ExtensionHeader` no longer carries it, so a next-header byte of 147
#: now fails that same constructor at the *top* of the loop instead --
#: the ordinary end-of-chain path any unrecognised upper-layer protocol
#: code already takes, one step earlier than it used to.)
#: ``next`` *attribute*; the structural check in
#: :meth:`_decode_next_layer` catches these. A next-header byte that is
#: not in the extension-header registry at all (e.g. ``147``) fails the
#: same constructor at the top of the loop, like any unrecognised
#: upper-layer protocol code.
#:
#: :meth:`_decode_next_layer`'s walk stops cleanly at whichever of these
#: (or any other IANA code this package has not implemented) it meets,
#: and keeps this layer's own header intact instead of losing the whole
#: packet as it used to.
#: (or any other IANA code this package has not implemented) it meets, and
#: keeps this layer's own header intact.
__generic_ext_codes__ = frozenset({
Enum_ExtensionHeader.HOPOPT,
Enum_ExtensionHeader.IPv6_Route,
Expand Down Expand Up @@ -416,30 +404,26 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None,
# is what gets handed to ``super()._decode_next_layer`` below
payload = payload[next_.length:]

# GitHub issue #891: a layer with no ``next`` field cannot
# safely continue the walk. This is a *structural* check --
# does the parsed info even carry a ``next`` attribute? -- not
# a fixed set of codes, and deliberately so: HOPOPT, IPv6-Route,
# IPv6-Opts, MH, HIP, IPv6-Frag and AH all have dedicated
# parsers whose data carries ``next``, and Shim6 and any
# recognised header whose own parser raised are both handled by
# :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`,
# which also carries ``next`` (possibly :data:`None`, on an
# overrun -- see its module docstring). Every IANA extension
# header code this package has not implemented a dedicated
# parser for -- today that is ``253`` and ``254``, and tomorrow
# it is whatever IANA assigns next -- has no
# generic fallback either (see :attr:`__generic_ext_codes__`'s
# docstring for why), so :meth:`_import_next_layer` returns a
# plain :class:`~pcapkit.protocols.misc.raw.Raw`, whose info
# carries no ``next`` at all. Reading ``info.next`` on that
# unconditionally is what used to raise ``AttributeError``
# here and let a further-out :func:`~pcapkit.utilities.decorators.beholder`
# catch it and degrade the *whole* packet -- the actual #891
# defect, for every code nobody has implemented. Stopping here
# instead keeps this layer's own fields (still recorded above,
# in ``self._exthdr`` and in the packet dict) and reports no
# further next header, exactly like the overrun case.
# A layer with no ``next`` field cannot safely continue the walk.
# This is a *structural* check -- does the parsed info even carry a
# ``next`` attribute? -- not a fixed set of codes, and deliberately
# so: HOPOPT, IPv6-Route, IPv6-Opts, MH, HIP, IPv6-Frag and AH all
# have dedicated parsers whose data carries ``next``, and Shim6 and
# any recognised header whose own parser raised are both handled by
# :class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`, which also
# carries ``next`` (possibly :data:`None`, on an overrun -- see its
# module docstring). Every IANA extension header code without a
# dedicated parser -- ``253`` and ``254`` today, and whatever IANA
# assigns next -- has no generic fallback either (see
# :attr:`__generic_ext_codes__`'s docstring for why), so
# :meth:`_import_next_layer` returns a plain
# :class:`~pcapkit.protocols.misc.raw.Raw`, whose info carries no
# ``next`` at all. Reading ``info.next`` on that unconditionally
# would raise ``AttributeError`` and let a further-out
# :func:`~pcapkit.utilities.decorators.beholder` degrade the *whole*
# packet. Stopping here instead keeps this layer's own fields (still
# recorded above, in ``self._exthdr`` and in the packet dict) and
# reports no further next header, exactly like the overrun case.
#
# This has to run -- and, on a hit, has to set ``proto`` --
# *before* the fragment-header special case below: IPv6-Frag
Expand Down Expand Up @@ -495,31 +479,24 @@ def _import_next_layer(self, proto: 'int', length: 'Optional[int]' = None, *, #
Notes:
If the dedicated parser for a code in :attr:`__generic_ext_codes__`
raises, this substitutes
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext`
for it rather than letting the exception reach the
:class:`~pcapkit.protocols.internet.ipv6_ext.IPv6_Ext` for it
rather than letting the exception reach the
:func:`~pcapkit.utilities.decorators.beholder` decorating this
method, which would otherwise substitute plain
method, which would substitute plain
:class:`~pcapkit.protocols.misc.raw.Raw` -- and ``Raw`` has no
``next`` field, which is what used to crash the whole packet at
:meth:`_decode_next_layer`'s ``proto = info.next`` (GitHub issue
:issue:`891`). Every other exception -- including one raised by
``IPv6_Ext`` itself, or by ``ESP``'s own dedicated
parser -- still reaches ``beholder`` unchanged, so *this
method's own* behaviour for anything outside that closed set is
exactly what it was before this method learned the
substitution: a plain ``Raw`` for that one layer. What changed
for ``253`` and ``254`` -- which have no dedicated parser at
all, so they were *already* reaching plain ``Raw`` with no
exception involved -- is one level up:
:meth:`_decode_next_layer` now stops its walk structurally on
``next`` field, so it could not continue
:meth:`_decode_next_layer`'s walk. Every other exception --
including one raised by ``IPv6_Ext`` itself, or by ``ESP``'s own
dedicated parser -- still reaches ``beholder`` unchanged, giving a
plain ``Raw`` for that one layer. ``253`` and ``254`` have no
dedicated parser, so they reach plain ``Raw`` with no exception
involved; :meth:`_decode_next_layer` stops its walk structurally on
any layer whose info carries no ``next`` attribute, ``Raw``
included, instead of reading ``info.next`` unconditionally and
crashing the whole packet. ``ESP`` is unaffected either way: its
info always carries a ``next`` (:data:`None`, since :rfc:`4303`
encrypts the real value), so neither this substitution nor that
structural check ever engages for it, and the walk ends after it
exactly as it always has -- see :attr:`__generic_ext_codes__`'s
docstring for the full distinction.
included. ``ESP``'s info always carries a ``next`` (:data:`None`,
since :rfc:`4303` encrypts the real value), so neither this
substitution nor that structural check engages for it, and the walk
ends after it -- see :attr:`__generic_ext_codes__`'s docstring for
the full distinction.

"""
if TYPE_CHECKING:
Expand Down
Loading
Loading