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
30 changes: 21 additions & 9 deletions docs/source/contributing/pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -852,16 +852,16 @@ These protocols parse a checksum or CRC field —
:class:`~pcapkit.protocols.transport.udp.UDP` — and SCTP was the first to
check whether the value is *right*, through
:attr:`SCTP.checksum_valid <pcapkit.protocols.transport.sctp.SCTP.checksum_valid>`.
IPv4, OSPF, TCP, UDP and DCCP check it too, as do ICMP and ICMPv6, as the
one's-complement half below describes. For GRE, HIP, HOPOPT, IGMP, IPv6-Opts, IPX
and MH the field is recorded and never questioned, so a corrupted capture parses
as cleanly as an intact one; verifying GRE's and IGMP's is :issue:`2618`.
IPv4, OSPF, TCP, UDP and DCCP check it too, as do ICMP, ICMPv6, IGMP and GRE, as
the one's-complement half below describes. For HIP, HOPOPT, IPv6-Opts, IPX and MH
the field is recorded and never questioned, so a corrupted capture parses as
cleanly as an intact one.

**These are different problems and they want separating**, because only one
of them is cryptographic and the distinction decides what is actually hard.

**The one's-complement Internet checksum**, which covers IPv4's header, TCP, UDP,
DCCP, ICMP/ICMPv6 and OSPF, needs no cryptography at all — it is sixteen-bit addition
DCCP, ICMP/ICMPv6, IGMP, GRE and OSPF, needs no cryptography at all — it is sixteen-bit addition
with end-around carry, and implementing it is an afternoon. What blocks it is the
**IP pseudo-header**: the TCP, UDP, DCCP and ICMPv6 checksums are computed over source
and destination addresses, the protocol number and the payload length, all of
Expand Down Expand Up @@ -922,8 +922,9 @@ from *wrong*, or it will cry wolf on the commonest capture there is —
:class:`~pcapkit.protocols.internet.esp.ESPStatus`'s ``NO_SA`` member is the
model for how to spell "expected, not an error".

**The one's-complement half is done** (:issue:`2470`, :issue:`2472` to
:issue:`2475`), on :class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`
**The one's-complement half is done** for every protocol named above
(:issue:`2470`, :issue:`2472` to :issue:`2475`, :issue:`2489`, :issue:`2618`), on
:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo`
(:issue:`2471`) and :mod:`pcapkit.corekit.checksum.internet`, settled as
below: the toggles by the owner on :issue:`2471`, the rest at implementation,
as the offload question above asked; the owner accepted the toggles and the
Expand All @@ -948,11 +949,21 @@ offload heuristic on :issue:`2470`.
mandatory (:rfc:`8200#section-8.1`).
* **A fragment is**
:attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NO_PSEUDO_HEADER`,
atomic ones aside: TCP, UDP, ICMP and ICMPv6 checksums cover all of the
atomic ones aside: TCP, UDP, ICMP, ICMPv6 and IGMP checksums cover all of the
datagram's data, which it carries part of. OSPF's Packet Length says what its
checksum covers, so it is verified when the fragment holds that much, and
:attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.TRUNCATED`
otherwise.
* **IGMP covers the whole IP payload in every version, and GRE its header and
payload only when** ``C`` **is set** (:rfc:`2236#section-2.5`,
:rfc:`3376#section-4.1.2`, :rfc:`2784#section-2.5`). The IGMPv1 rule follows
:rfc:`2236`, which updates :rfc:`1112`'s 8-octet one. A GRE header with ``C``
clear is :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NOT_COMPUTED`,
and ``make()`` computes one when ``checksum_present`` asks without giving it.
Neither GRE nor the Linux cooked-capture header carries a length, so a GRE frame
the capture cut short is
:attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.INVALID`. Settled at
implementation (:issue:`2618`).
* **DCCP covers what its Checksum Coverage gives** (:rfc:`4340#section-9.2`), over
a pseudo-header that carries the whole packet's length, as TCP's does, so a
fragment is
Expand Down Expand Up @@ -1361,7 +1372,8 @@ and its signature parameters could start whenever, since
:class:`~pcapkit.protocols.internet.esp.ESP` has already established both the
keying-context channel and the report-rather-than-raise policy. The
one's-complement half (IPv4, TCP, UDP, ICMP/ICMPv6, OSPF) is **done**
(:issue:`2470`, :issue:`2472` to :issue:`2475`), and DCCP's with it (:issue:`2489`).
(:issue:`2470`, :issue:`2472` to :issue:`2475`), and DCCP's with it (:issue:`2489`),
as are IGMP's and GRE's (:issue:`2618`).

**IP pseudo-header — done** (:issue:`2471`).
:class:`~pcapkit.protocols.misc.checksum.ip_pseudo.IP_Pseudo` is the
Expand Down
12 changes: 7 additions & 5 deletions docs/source/pcapkit/corekit/checksum/internet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Internet Checksum
.. module:: pcapkit.corekit.checksum.internet

:mod:`pcapkit.corekit.checksum.internet` contains the one's-complement
Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6
and OSPF. Parsing verifies a checksum only if
Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6,
IGMP, GRE and OSPF. Parsing verifies a checksum only if
:data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on, and records the
outcome as ``checksum_status``; construction computes one left out unless
:data:`~pcapkit.utilities.config.CHECKSUM_COMPUTE` is off (:issue:`2471`).
Expand All @@ -14,9 +14,10 @@ which a filter can silence alone (:issue:`2470`).

It imports nothing from :mod:`pcapkit.protocols`, which builds on it
(:issue:`2580`). A checksum over the IP pseudo-header is left to the enclosing
IP layer, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. ICMP's
checksum, over the message alone, is verified as it is parsed, unless the IP
layer it is parsed from is a fragment, see :func:`fragment_payload`.
IP layer, see :mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. ICMP's, IGMP's
and GRE's checksums, over the message alone, are verified as it is parsed,
unless the IP layer it is parsed from is a fragment, see :func:`verify_message`
(:issue:`2618`).

.. versionadded:: 1.6.0

Expand All @@ -28,4 +29,5 @@ layer it is parsed from is a fragment, see :func:`fragment_payload`.
.. autofunction:: pcapkit.corekit.checksum.internet.compute_checksum
.. autofunction:: pcapkit.corekit.checksum.internet.partial_checksum
.. autofunction:: pcapkit.corekit.checksum.internet.verify_checksum
.. autofunction:: pcapkit.corekit.checksum.internet.verify_message
.. autofunction:: pcapkit.corekit.checksum.internet.fragment_payload
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/internet/igmp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Octets Bits Name Description

.. automethod:: _make_data
.. automethod:: _classify
.. automethod:: _verify_checksum
.. automethod:: _dispatch
.. automethod:: _member_of_version

Expand Down
3 changes: 0 additions & 3 deletions docs/source/pcapkit/utilities/contextvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,5 @@ a module at any layer may import it; see :ref:`utilities-or-corekit`.
.. autodata:: pcapkit.utilities.contextvars.frame_depth
:no-value:

.. autodata:: pcapkit.utilities.contextvars.in_fragment
:no-value:

.. autodata:: pcapkit.utilities.contextvars.rebuilding
:no-value:
5 changes: 3 additions & 2 deletions pcapkit/corekit/checksum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@

"""
from pcapkit.corekit.checksum.internet import (ChecksumStatus, compute_checksum, fragment_payload,
internet_checksum, partial_checksum, verify_checksum)
internet_checksum, partial_checksum, verify_checksum,
verify_message)

__all__ = [
'ChecksumStatus',
'internet_checksum', 'compute_checksum', 'partial_checksum', 'verify_checksum',
'fragment_payload',
'verify_message', 'fragment_payload',
]
66 changes: 52 additions & 14 deletions pcapkit/corekit/checksum/internet.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
.. module:: pcapkit.corekit.checksum.internet

:mod:`pcapkit.corekit.checksum.internet` contains the one's-complement
Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6
and OSPF: :class:`ChecksumStatus`, the outcome a parse records, and the
functions that compute and verify the checksum. It imports nothing from
Internet checksum (:rfc:`1071`) of the IPv4 header, TCP, UDP, ICMP, ICMPv6,
IGMP, GRE and OSPF: :class:`ChecksumStatus`, the outcome a parse records, and
the functions that compute and verify the checksum. It imports nothing from
:mod:`pcapkit.protocols`, which builds on it (:issue:`2580`).

Verification is opt-in and computation opt-out, by
Expand All @@ -17,20 +17,21 @@
which a filter can silence alone (:issue:`2470`).

A checksum over the IP pseudo-header is left to the enclosing IP layer, see
:mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. A checksum over the upper
layer alone, as ICMP's, is verified as it is parsed, unless the IP layer it is
parsed from is a fragment, see :func:`fragment_payload`.
:mod:`pcapkit.protocols.misc.checksum.ip_pseudo`. A checksum over the message
alone, as ICMP's, IGMP's and GRE's, is verified as it is parsed, unless the IP
layer it is parsed from is a fragment, see :func:`verify_message`.

.. versionadded:: 1.6.0

"""
import contextlib
import contextvars
import enum
import struct
from typing import TYPE_CHECKING

from pcapkit.corekit.enum import EnumLookup
from pcapkit.utilities.contextvars import in_fragment, rebuilding
from pcapkit.utilities.contextvars import rebuilding
from pcapkit.utilities.exceptions import stacklevel
from pcapkit.utilities.warnings import ChecksumWarning, warn

Expand All @@ -40,9 +41,15 @@
__all__ = [
'ChecksumStatus',
'internet_checksum', 'compute_checksum', 'partial_checksum', 'verify_checksum',
'fragment_payload',
'verify_message', 'fragment_payload',
]

#: contextvars.ContextVar[bool]: Whether the layer being parsed is the payload
#: of an IP fragment, as :func:`fragment_payload` sets it and
#: :func:`verify_message` reads it. Only this module uses it, so it stays here
#: (:issue:`2616`).
_in_fragment = contextvars.ContextVar('pcapkit.corekit.checksum.internet._in_fragment', default=False)


class ChecksumStatus(EnumLookup, enum.IntEnum):
"""Outcome of verifying a checksum, as recorded in ``checksum_status``.
Expand Down Expand Up @@ -187,21 +194,52 @@ def verify_checksum(alias: 'str', data: 'bytes', field: 'bytes', *, length: 'Opt
return ChecksumStatus.INVALID


def verify_message(alias: 'str', data: 'bytes', field: 'bytes', *,
length: 'Optional[int]' = None) -> 'ChecksumStatus':
"""Verify the checksum ``field`` of a message that covers itself alone.

ICMP's (:rfc:`792`), IGMP's (:rfc:`2236#section-2.3`,
:rfc:`3376#section-4.1.2`) and GRE's (:rfc:`2784#section-2.5`) checksums
cover the octets of their own message, with no pseudo-header, so they are
verified as the message is parsed, unless an IP fragment carries it; see
:func:`fragment_payload`.

Args:
alias: Protocol alias, to name in the warning.
data: Octets the checksum covers, the checksum field included.
field: The checksum field, as on the wire.
length: Octets the checksum covers, as the headers declare it; the
length of ``data`` by default.

Returns:
:attr:`~ChecksumStatus.NO_PSEUDO_HEADER` for the payload of an IP
fragment, otherwise as :func:`verify_checksum`.

Warns:
ChecksumWarning: As :func:`verify_checksum` does.

"""
if _in_fragment.get():
return ChecksumStatus.NO_PSEUDO_HEADER
return verify_checksum(alias, data, field, length=length)


@contextlib.contextmanager
def fragment_payload(fragment: 'bool') -> 'Iterator[None]':
"""Mark the payload an IP layer parses as a fragment's, or not.

A checksum over the upper layer alone, as ICMP's (:rfc:`792`), covers all
of the datagram's data, which a fragment carries part of, so the upper
layer of one records :attr:`~ChecksumStatus.NO_PSEUDO_HEADER` rather than
being verified (:issue:`2470`). The nearest enclosing IP layer decides.
A checksum over the message alone, as ICMP's (:rfc:`792`), covers all
of the datagram's data, which a fragment carries part of, so
:func:`verify_message` records :attr:`~ChecksumStatus.NO_PSEUDO_HEADER` for
the upper layer of one rather than verifying it (:issue:`2470`). The
nearest enclosing IP layer decides.

Args:
fragment: Whether the IP layer is a fragment; an atomic one is not.

"""
token = in_fragment.set(fragment)
token = _in_fragment.set(fragment)
try:
yield
finally:
in_fragment.reset(token)
_in_fragment.reset(token)
5 changes: 5 additions & 0 deletions pcapkit/protocols/data/internet/igmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing_extensions import Literal

from pcapkit.const.igmp.type import Type as Enum_Type
from pcapkit.corekit.checksum.internet import ChecksumStatus

__all__ = ['IGMP']

Expand Down Expand Up @@ -50,5 +51,9 @@ class IGMP(Protocol):
group: 'IPv4Address'

if TYPE_CHECKING:
#: Outcome of verifying :attr:`checksum`, present only when
#: :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on.
checksum_status: 'ChecksumStatus'

def __init__(self, version: 'Literal[1, 2]', type: 'Enum_Type', max_resp_time: 'int', # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin
checksum: 'bytes', group: 'IPv4Address') -> 'None': ...
6 changes: 6 additions & 0 deletions pcapkit/protocols/data/internet/igmpv3.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from pcapkit.const.igmp.record_type import RecordType as Enum_RecordType
from pcapkit.const.igmp.type import Type as Enum_Type
from pcapkit.corekit.checksum.internet import ChecksumStatus

__all__ = ['IGMPv3', 'Query', 'Report', 'GroupRecord']

Expand Down Expand Up @@ -43,6 +44,11 @@ class IGMPv3(Protocol):
#: same way :attr:`~pcapkit.protocols.data.internet.esp.ESP.length` is.
length: 'int'

if TYPE_CHECKING:
#: Outcome of verifying the message's ``checksum``, present only when
#: :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on.
checksum_status: 'ChecksumStatus'


@info_final
class Query(IGMPv3):
Expand Down
7 changes: 7 additions & 0 deletions pcapkit/protocols/data/link/gre.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Optional

from pcapkit.const.reg.ethertype import EtherType
from pcapkit.corekit.checksum.internet import ChecksumStatus

__all__ = ['GRE']

Expand Down Expand Up @@ -50,6 +51,12 @@ class GRE(Protocol):
len: 'int'

if TYPE_CHECKING:
#: Outcome of verifying :attr:`checksum`, present only when
#: :data:`~pcapkit.utilities.config.CHECKSUM_VERIFY` is on;
#: :attr:`~pcapkit.corekit.checksum.internet.ChecksumStatus.NOT_COMPUTED`
#: when :attr:`checksum_present` is clear.
checksum_status: 'ChecksumStatus'

def __init__(self, checksum_present: 'bool', key_present: 'bool', # pylint: disable=unused-argument,super-init-not-called,multiple-statements
sequence_present: 'bool', reserved: 'int', version: 'int', # pylint: disable=unused-argument
protocol_type: 'EtherType', checksum: 'Optional[int]', # pylint: disable=unused-argument
Expand Down
10 changes: 2 additions & 8 deletions pcapkit/protocols/internet/icmpv4.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,9 @@ def read(self, length: 'Optional[int]' = None, *,
# NOTE: The checksum covers the whole message, with no pseudo-header (RFC 792);
# an IP fragment carries only part of one.
if config.CHECKSUM_VERIFY:
from pcapkit.corekit.checksum.internet import ( # isort: skip # pylint: disable=import-outside-toplevel
ChecksumStatus, verify_checksum)
from pcapkit.utilities.contextvars import in_fragment # isort: skip # pylint: disable=import-outside-toplevel
if in_fragment.get():
status = ChecksumStatus.NO_PSEUDO_HEADER
else:
status = verify_checksum(self.alias, self._data, schema.checksum, length=length)
from pcapkit.corekit.checksum.internet import verify_message # isort: skip # pylint: disable=import-outside-toplevel
icmp.__update__([
('checksum_status', status),
('checksum_status', verify_message(self.alias, self._data, schema.checksum, length=length)),
])

# NOTE: The quoted original datagram stays raw in the message body,
Expand Down
Loading
Loading