IPv6_Ext - IPv6 Extension Header

pcapkit.protocols.internet.ipv6_ext contains 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 payload, protocol and 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 since April 2012 carries the same first two octets:

Octets

Bits

Name

Description

0

0

next

Next Header

1

8

len

Hdr Ext Len (8-octet units,

excluding the first 8 octets)

2

16

payload

Header-specific content

so those two octets are parseable without knowing anything else about the header. See the module docstring below for the closed exception table (IPv6-Frag and AH each use their own length rule; ESP has a dedicated parser whose own info reports no next header rather than lacking one, and 253 and 254 have no dedicated parser at all – none of the three ever reaches this class), the two ways this class is dispatched to, and why an overrun stops the walk instead of clipping it.

class pcapkit.protocols.internet.ipv6_ext.IPv6_Ext(file=None, length=None, **kwargs)[source]

Bases: Internet[_PT, _ST], Generic[_PT, _ST]

This class implements a generic IPv6 extension header parser, and is the shared base of every IPv6 extension header in this package.

See the module docstring for the RFC citations backing the length rules below, the two ways this class gets dispatched to, and the reasoning for stopping rather than clipping on an overrun.

The two roles

This one class plays both, on the owner’s ruling for GitHub issue #917:

  1. The concrete fallback parser for an RFC 6564-conforming header this package has no dedicated class for, or whose dedicated class raised – which is what read(), make(), name, alias and the schema=/data= above implement.

  2. The base class of the eight implemented extension headers (HOPOPT, IPv6_Route, IPv6_Frag, IPv6_Opts, HIP, MH, AH and ESP), which is what the _extf guards on payload, protocol and protochain are for.

It is generic in its data and schema types – exactly like IPsec, the other base in this package – so that a subclass keeps its own _PT/_ST instead of inheriting this class’s. AH and ESP therefore double-inherit two identically-parameterised generic bases, IPsec[…] and IPv6_Ext[…].

Warning

A subclass must define name, alias, protocol, length and __index__() itself. All five carry this class’s fallback-role answers, which are wrong for a header that has an identity of its own: it would report itself as IPv6 Extension Header / IPv6-Ext, read its length off a data model that is not its own, and __index__() would raise rather than return its IANA number. Nothing in the language enforces the override, so tests/protocols/internet/test_ipv6_ext_unit.py enforces it instead, over every subclass discovered at runtime.

property name: str

Name of current protocol.

Annotated str rather than as the Literal every leaf protocol in this package uses, because this one is also a base: a Literal here makes each subclass’s own Literal an incompatible override (measured: eight [override] errors from mypy, one per subclass). The value returned is still the single fallback-role constant.

property alias: str

Acronym of corresponding protocol.

Annotated str rather than Literal, for the reason given on name.

Hyphenated, like IPv6_Frag.alias and IPv6_Opts.alias, and for the same reason: IPv6._decode_next_layer builds the packet-dict key by self.alias.lstrip('IPv6-').lower() – str.lstrip() strips a character set, not a prefix, so the default (class-name) alias 'IPv6_Ext' would strip to '_Ext' and key the dict as _ext. The hyphen makes every leading character (I, P, v, 6, -) a member of the set being stripped, same as the siblings, giving ext.

property length: int

Header length of current protocol.

Fallback-role member: it reads this module’s own data model, so a subclass whose data model records its length elsewhere – or not at all, as IPv6_Frag does not – must override it. All eight implemented headers do, and tests/protocols/internet/test_ipv6_ext_unit.py holds them to it.

property protocol: ExtensionHeader | None | str

The extension header this instance stands in for.

In the fallback role – i.e. when this instance parsed this module’s own schema – this is not the base Protocol.protocol meaning (“name of next layer protocol”); it is deliberately repointed, on the owner’s ruling for GitHub issue #891, at this instance’s own identity – which header’s format it parsed, e.g. HOPOPT or Shim6. That identity is resolved per instance from the numeric code handed to the constructor (alias), which pcapkit.protocols.internet.ipv6.IPv6._decode_next_layer() already holds before it dispatches (ipv6.py:388). See __index__() for why the class-level identity cannot be made to work the same way. It is deliberately not _extf-guarded in this role: that same call passes extension=True for every extension header in a chain, so guarding it would make the identity unreadable in precisely the case it exists for.

In the base role it falls through to super(), restoring the ordinary ProtocolBase meaning. That fall-through is load-bearing rather than tidy: all eight subclasses implement their own _extf-guarded protocol as return super().protocol, and this class sits between them and ProtocolBase in the MRO – so without the discriminator below, every one of those eight would end up reading self._info.protocol off a data model that has no such field. Measured: AttributeError on all eight.

The discriminator is the class-level __data__ rather than an isinstance() test on self._info, because a make-only instance has no _info at all – reading it here to decide which role we are in turns ProtocolBase.protocol, which only ever needed self._protos, into an AttributeError (measured: tests/protocols/internet/test_ipv6_extension_unit.py constructs exactly that instance).

property next: TransType | None

Next header, as parsed off the wire.

Shared by every subclass rather than fallback-role-only – see _NextHeaderData for why that is sound. Note this is an addition for the eight implemented headers: none of them declared a next property of its own before GitHub issue #917, so reading one raised AttributeError, and nothing could have depended on a value it never returned.

None when the declared length would have overrun what remained of the chain and the walk stopped instead of trusting it – see the module docstring’s “overrun guard” section.

property payload: ProtocolBase | NoReturn

Payload of current instance.

Raises:

UnsupportedCall – if the protocol is used as an IPv6 extension header

property protochain: ProtoChain | NoReturn

Protocol chain of current instance.

Raises:

UnsupportedCall – if the protocol is used as an IPv6 extension header

read(length=None, *, extension=False, **kwargs)[source]

Read a generically-parsed IPv6 extension header.

Parameters:
  • length (int | None) – Length of packet data.

  • extension (bool) – If the protocol is used as an IPv6 extension header.

  • **kwargs (Any) –

    Arbitrary keyword arguments, two of which are this class’s own and are supplied by IPv6._import_next_layer (ipv6.py:540,550) rather than typed by a caller:

    • alias – the numeric extension header code this instance stands in for, e.g. 0 for HOPOPT or 140 for Shim6.

    • error – the parsing error, when this instance was reached as a beholder() fallback rather than by direct dispatch.

Return type:

TypeVar(_PT, bound= Data)

Returns:

Parsed packet data.

Note

Those two are read out of **kwargs rather than declared as parameters, and version is not declared either, because this class is also the base of eight subclasses whose own read accepts none of the three. Declaring them here asserts of the whole family an interface only the fallback has, and both linters say so: mypy reports eight Signature of "read" incompatible with supertype [override] errors, pylint arguments-differ. version costs nothing to drop in any case – this method never read it (it carried # pylint: disable=unused-argument for exactly that reason); the version gate lives in __post_init__(), which still takes it explicitly.

make(*, next=<TransType.UDP: 17>, len=0, payload=b'', **kwargs)[source]

Make (construct) packet data.

Parameters:
  • next (TransType | int) – Next header type.

  • len (int) – Raw Hdr Ext Len octet to emit – the caller’s responsibility to size correctly, since this class does not know, at construction time, which per-protocol rule (see the module docstring) the octet is meant to satisfy.

  • payload (bytes | ProtocolBase | Any) – Payload of current instance.

  • **kwargs (Any) – Arbitrary keyword arguments.

Return type:

TypeVar(_ST, bound= Schema)

Returns:

Constructed packet data.

Note

Keyword-only, and that matters in two directions at once.

The three stay declared, unlike read()’s own keywords, because _check_construction_keywords() builds its allowlist from inspect.signature() of make. Hiding them in **kwargs was tried and breaks construction: measured as UnsupportedCall: IPv6_Ext: unexpected keyword(s): 'len' (did you mean 'length'?), 'next', 'payload' on a plain IPv6_Ext(next=..., len=..., payload=...). The __keywords__ escape hatch would cover that, but it is unioned down the MRO, so it would widen the allowlist – and weaken that misspelling check – for all eight subclasses too.

They are keyword-only because this class is also a base, and each subclass’s make puts different names in the same positions. As positional parameters they drew 18 pylint arguments-renamed warnings (ESP.make: next -> spi, len -> seq, payload -> next; IPv6_Route.make: next -> dst, len -> next, payload -> next_default; and two each for the other six) and eight mypy [override] errors. With no positional parameters there is no position to disagree about: both counts drop to zero, and the allowlist is unaffected because _declared_keywords collects KEYWORD_ONLY parameters too. Nothing calls a protocol’s make positionally – __init__ spreads **kwargs into it (protocol.py:607).

read needs none of this: its alias/error only ever arrive on the parse path, which __init__ exempts from the construction check outright.

classmethod _make_data(data)[source]

Create key-value pairs from data for protocol construction.

Inverts whichever per-protocol length rule read() applied, using data.protocol – the extension header this instance stood in for – to pick the same rule back. Round-tripping a data.next of None (the overrun case) is not supported: there is no octet value that both satisfies the rule and still fits, which is exactly why read() stopped rather than clipped.

Parameters:

data (IPv6_Ext) – protocol data

Return type:

dict[str, Any]

Returns:

Key-value pairs for protocol construction.

__post_init__(file=None, length=None, *, version=6, extension=False, **kwargs)[source]

Post initialisation hook.

Overloads:
  • self, file (IO[bytes] | bytes), length (Optional[int]), version (Literal[4, 6]), extension (bool), kwargs (Any) → None

  • self, kwargs (Any) → None

Parameters:
  • file (IO[bytes] | bytes | None) – Source packet stream.

  • length (int | None) – Length of packet data.

  • version (Literal[4, 6]) – IP protocol version.

  • extension (bool) – If the protocol is used as an IPv6 extension header.

  • **kwargs (Any) – Arbitrary keyword arguments.

Raises:

ProtocolError – If this is the fallback parser (see the class docstring’s “two roles”) and version is not 6.

See also

For construction argument, please refer to make().

Note

The version gate below is scoped to the fallback role, by the same __data__ discriminator protocol uses. Applying it to subclasses would break the two that are not IPv6-only: AH and ESP both default to version=4 and are perfectly valid under IPv4, so an unconditional gate here rejects them outright (measured: ProtocolError: ESP: only valid for IPv6, got version=4 from tests/protocols/internet/test_esp_unit.py).

The gate itself is unchanged in effect for the fallback, and is there because this class is registered into Internet.__proto__ – shared by every Internet subclass, IPv4 included – so that Shim6 resolves to a working parser instead of defaulting to Raw. Without this guard, an IPv4 packet whose protocol byte happens to be 140 would reach this class too and walk an IPv6-style extension-header chain out of an IPv4 payload – verified: it would read IPv4:IPv6-Ext:... instead of the IPv4:Shim6 a plain, non-continuing Raw gives today. Rejecting here sends construction back through beholder() at the caller’s layer, which substitutes that same Raw – i.e. this restores exactly the pre-existing, version-agnostic behaviour rather than inventing a new one, and needs no IPv4-specific code of its own.

classmethod __index__()[source]

Numeral registry index of the protocol.

Raises:

UnsupportedCall – This protocol has no class-level registry entry. Unlike Raw.__index__, which raises because Raw has no identity to report at all, this class does have one – see protocol – but it is resolved per instance, not per class: one IPv6_Ext stands in for HOPOPT, Shim6 and any other RFC 6564-conforming code alike, while __index__() is a @classmethod with nowhere to put a value that differs per instance.

Return type:

NoReturn

Header Schemas

class pcapkit.protocols.schema.internet.ipv6_ext.IPv6_Ext(*args: _VT, **kwargs: _VT)[source]

Bases: Schema

Header schema for a generically-parsed IPv6 extension header.

Only the two octets RFC 6564 Section 4 guarantees are parsed at this layer – next and the raw Hdr Ext Len octet. Combining them into an actual skip distance is per protocol (a constant for IPv6-Frag, 4-octet units for AH, 8-octet units for the rest), so that part is done in pcapkit.protocols.internet.ipv6_ext.IPv6_Ext.read(), which knows which protocol this instance stands in for; this schema does not.

next: TransType = <EnumField next>

Next header.

len: int = <UInt8Field len>

Raw Hdr Ext Len octet – see the class docstring for why its interpretation is not fixed here.

payload: bytes = <PayloadField payload>

Everything after the two fixed octets; opaque at this layer.

Data Models

class pcapkit.protocols.data.internet.ipv6_ext.IPv6_Ext(*args: VT, **kwargs: VT)[source]

Bases: Protocol

Data model for a generically-parsed IPv6 extension header.

See pcapkit.protocols.internet.ipv6_ext.IPv6_Ext for how each field below is derived.

protocol: ExtensionHeader | None

The extension header this instance stands in for – the numeric code the caller dispatched on, resolved to its ExtensionHeader member. None when alias named no such member (read sets it so on a lookup miss, and the class property at protocol is typed to match).

next: TransType | None

Next header, parsed off the wire. None when the declared length would have overrun what remained and the walk stopped instead of trusting it.

length: int

Length of this extension header, in octets, actually consumed.

error: Exception | None

Original parsing error, if this instance was reached as a beholder() fallback rather than by direct dispatch.

Footnotes