IPv6-Ext - IPv6 Extension Header¶
pcapkit.protocols.internet.ipv6_ext contains
IPv6_Ext
only, which plays a double role. It is the shared base class of every
IPv6 extension header this package implements, supplying the
extension-mode contract: the guards that make
payload,
protocol and
protochain
unavailable on a header parsed as part of an IPv6 chain. It also implements a
generic extractor for IPv6 extension headers [*], standing in whenever a
header’s own dedicated parser is unavailable or has failed. RFC 6564 Section 4
requires, with an RFC 2119 MUST, that any IPv6 extension header defined since
April 2012 carry the same first two octets:
Octets |
Bits |
Name |
Description |
|---|---|---|---|
0 |
0 |
|
Next Header |
1 |
8 |
|
Hdr Ext Len (8-octet units, excluding the first 8 octets) |
2 |
16 |
|
Header-specific content |
so those two octets are parseable without knowing anything else about the header. The sections below the class give the headers that depart from that rule, the 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[_PT: Data, _ST: Schema](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.
The module documentation has the RFC citations behind the length rules, the ways this class is dispatched to, and why an overrun stops the walk rather than clipping.
Base and Fallback Roles
One class plays both, on the owner’s ruling for GitHub issue #917:
The concrete fallback parser for an RFC 6564-conforming header with no dedicated class, or whose dedicated class raised.
read(),make(),name,aliasand theschema=/data=class keywords implement it.The base class of the implemented extension headers (
HOPOPT,IPv6_Route,IPv6_Frag,IPv6_Opts,HIP,MH,AHandESP), which is what the_extfguards onpayload,protocolandprotochainare for.
It is generic in its data and schema types, like
IPsec, another base in this package, so that a subclass keeps its own_PT/_STinstead of inheriting this class’s.AHandESPtherefore double-inherit two identically-parameterised generic bases,IPsec[…]andIPv6_Ext[…].Warning
A subclass must define
name,alias,protocol,lengthand__index__()itself. Each carries this class’s fallback-role answer, which is wrong for a header with an identity of its own: it would report itself asIPv6 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. The language cannot enforce the override, sotests/protocols/internet/test_ipv6_ext_unit.pydoes, over every subclass discovered at runtime.Added in version 1.5.0.
- property name: str¶
Name of current protocol.
Annotated
strrather than theLiteralevery leaf protocol in this package uses, because this one is also a base: aLiteralhere makes each subclass’s ownLiteralan incompatible override (mypy[override]). The value is still the single fallback-role constant.
- property alias: str¶
Acronym of corresponding protocol.
Annotated
strrather thanLiteral, for the reason given onname.Hyphenated, like
IPv6_Frag.aliasandIPv6_Opts.alias, for the same reason:IPv6._decode_next_layerbuilds the packet-dict key withself.alias.lstrip('IPv6-').lower(), andstr.lstrip()strips a character set, not a prefix. 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,-) part of the stripped set, givingext.
- 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), must override it. Every implemented header does, andtests/protocols/internet/test_ipv6_ext_unit.pyholds them to it.
- property protocol: ExtensionHeader | None | str¶
The extension header this instance stands in for.
In the fallback role (this instance parsed this module’s own schema) this is not the base
Protocol.protocolmeaning (“name of next layer protocol”). On the owner’s ruling for GitHub issue #891 it is deliberately repointed at this instance’s own identity, i.e. which header’s format it parsed, such asHOPOPTorShim6. The identity is resolved per instance from the numeric code handed to the constructor (alias), whichpcapkit.protocols.internet.ipv6.IPv6._decode_next_layer()already holds before it dispatches. See__index__()for why the class-level identity cannot work the same way. It is deliberately not_extf-guarded in this role: that same call passesextension=Truefor every extension header in a chain, so guarding it would make the identity unreadable in exactly the case it exists for.In the base role it falls through to
super(), restoring the ordinaryProtocolBasemeaning. That is load-bearing: every subclass implements its own_extf-guardedprotocolasreturn super().protocol, and this class sits between them andProtocolBasein the MRO, so without the discriminator below each would readself._info.protocoloff a data model with no such field (AttributeError).The discriminator is the class-level
__data__rather than anisinstance()test onself._info, because amake-only instance has no_infoat all. Reading it would turnProtocolBase.protocol, which only needsself._protos, into anAttributeError;tests/protocols/internet/test_ipv6_extension_unit.pyconstructs exactly that instance.
- property next: TransType | None¶
Next header, as parsed off the wire.
Shared by every subclass rather than fallback-role-only, since each of their data models records the Next Header value under this name.
Nonewhen the declared length would have overrun what remained of the chain and the walk stopped instead of trusting it; see The Overrun Guard.
- 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:
extension (
bool) – If the protocol is used as an IPv6 extension header.**kwargs (
Any) –Arbitrary keyword arguments, including these, which are this class’s own and are supplied by
IPv6._import_next_layerrather than typed by a caller:alias– the numeric extension header code this instance stands in for, e.g.0forHOPOPTor140forShim6.error– the parsing error, when this instance was reached as abeholder()fallback rather than by direct dispatch.
- Return type:
_PT- Returns:
Parsed packet data.
Note
These are read out of
**kwargsrather than declared, andversionis not declared either, because this class is also the base of subclasses whose ownreadaccepts none of them. Declaring them would assert of the whole family an interface only the fallback has, which mypy (Signature of "read" incompatible with supertype,[override]) and pylint (arguments-differ) both reject. This method never readsversion; the version gate lives in__post_init__(), which still takes it explicitly.
- make(*, next=TransType.UDP, len=0, data=b'', payload=b'', **kwargs)[source]¶
Make (construct) packet data.
- Parameters:
len (
int) – RawHdr Ext Lenoctet to emit. The caller must size it, since this class cannot know at construction time which per-protocol rule (see Length Rules) it must satisfy.data (
bytes) – Header-specific content after the two fixed octets, emitted verbatim ahead ofpayload.payload (
bytes|ProtocolBase|Any) – Payload of current instance.**kwargs (
Any) – Arbitrary keyword arguments.
- Return type:
_ST- Returns:
Constructed packet data.
Note
Declared, and keyword-only, each for its own reason.
These keywords stay declared, unlike
read()’s own, because_check_construction_keywords()builds its allowlist frominspect.signature()ofmake. Hiding them in**kwargsbreaks construction: a plainIPv6_Ext(next=..., len=..., data=..., payload=...)raisesUnsupportedCall: IPv6_Ext: unexpected keyword(s). 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 every subclass too.They are keyword-only because this class is also a base and each subclass’s
makeputs different names in the same positions. As positional parameters they draw pylintarguments-renamedwarnings (ESP.makehasspi,seqandnextwhere these havenext,lenanddata) and mypy[override]errors. With no positional parameters there is no position to disagree about, and the allowlist is unaffected because_declared_keywords()collectsKEYWORD_ONLYparameters too. Nothing calls a protocol’smakepositionally;__init__spreads**kwargsinto it.readneeds none of this: itsalias/erroronly arrive on the parse path, which__init__exempts from the construction check.
- classmethod _make_data(data)[source]¶
Create key-value pairs from
datafor protocol construction.Emits the
Hdr Ext Lenoctet and the body as they were read, so the rebuild is byte-exact whichever per-protocol length ruleread()applied, and also for an overrun, wheredata.nextisNoneanddata.declared_nextkeeps the octet.
- __post_init__(file=None, length=None, *, version=6, extension=False, **kwargs)[source]¶
Post initialisation hook.
- Overloads:
- Parameters:
- Raises:
ProtocolError – If this is the fallback parser (see the class docstring’s “Base and Fallback Roles”) and
versionis not6.
See also
For construction arguments, please refer to
make().Note
The version gate is scoped to the fallback role by the same
__data__discriminatorprotocoluses. Applying it to subclasses would break those that are not IPv6-only:AHandESPboth default toversion=4and are valid under IPv4, so an unconditional gate would reject them (ProtocolError: ESP: only valid for IPv6, got version=4).The gate exists because this class is registered into
Internet.__proto__, which everyInternetsubclass shares,IPv4included, so thatShim6resolves to a working parser instead ofRaw. Without it, an IPv4 packet whose protocol byte is 140 would reach this class and walk an IPv6-style extension-header chain out of an IPv4 payload, readingIPv4:IPv6-Ext:...instead of theIPv4:Shim6that a plain, non-continuingRawgives. Rejecting here sends construction back throughbeholder()at the caller’s layer, which substitutes that sameRaw: the version-agnostic behaviour, with no IPv4-specific code.
- classmethod __index__()[source]¶
Numeral registry index of the protocol.
- Raises:
UnsupportedCall – This protocol has no class-level registry entry. Unlike
Raw.__index__, which raises becauseRawhas no identity to report at all, this class does have one – seeprotocol– but it is resolved per instance, not per class: oneIPv6_Extstands in forHOPOPT,Shim6and any other RFC 6564-conforming code alike, while__index__()is a@classmethodwith nowhere to put a value that differs per instance.- Return type:
Length Rules¶
RFC 6564 Section 5 is explicit that the generic layout is not retroactive – it applies only to newly defined extension headers – which is why the pre-existing headers need the closed exception table below rather than being assumed to conform. (RFC 8200 Section 4.8 restates the layout without a MUST and mislabels the generic length field as the length of the Destination Options header; cite RFC 6564 Section 4 instead.)
The exception table, verified against IANA’s protocol-numbers-1.csv
IPv6 Extension Header column and each cited RFC:
Header(s) |
Length rule |
|---|---|
|
|
|
constant |
|
|
|
terminal – has a dedicated, registered parser
( |
|
terminal – no dedicated parser exists, so no next header field is ever read at all, either |
This table classifies by wire format alone, over every code
ExtensionHeader enumerates,
which match IANA’s IPv6 Extension Header Types registry exactly
(#925).
Shim6 conforms to the generic rule (RFC 5533) but has no dedicated parser class; see
Entry Paths for how it reaches this class by direct dispatch.
RFC 8200 Section 4.5 sets Encapsulating Security Payload aside: ESP is not
an extension header, and the RFC lists it among upper-layer headers.
RFC 4303 puts its Next Header inside the encrypted trailer, with no length
field in the cleartext part. 253 and 254 are reserved for private
experimentation (RFC 3692) with no wire format at all. None of them is
reachable through this class, by construction; see
IPv6._import_next_layer. Each still enters
IPv6._decode_next_layer’s walk, being a real
ExtensionHeader member, but they
part ways there. ESP resolves to its own dedicated parser, which decodes
any plaintext it recovers itself and whose info carries a next of
None whenever the payload stays opaque, so the walk ends the ordinary
way with ExtensionHeader(None) failing at the top of the next iteration.
253 and 254 have no dedicated parser and resolve to plain
Raw, whose info has no next
attribute. For these two, and any future IANA code without an
implementation, the walk stops on a structural check (does the parsed layer
carry a next at all?) rather than on a list of codes.
Entry Paths¶
This class is reached in these ways:
An unrecognised protocol number.
Shim6is a real IANA extension header (RFC 5533) with no dedicated parser, soInternet._lookup_next_layerwould default it to plainRaw. That has nonextfield, so the walk inIPv6._decode_next_layerwould crash on it (#891). This module registers this class for that code instead, at the bottom of the module, so dispatch reaches a working generic parser directly. The registration is global, shared by everyInternetsubclass, so__post_init__()gates onversion == 6to keep it from also activating for an IPv4 payload that carries protocol number 140; see its docstring.A recognised header whose own parser raises.
HOPOPT,IPv6-Route,IPv6-Opts,MH,HIP,IPv6-FragandAHall have dedicated classes. When one of those raises,beholder()(Protocol._import_next_layer) would substitute plainRaw, which losesnextand crashes the walk as in path 1.IPv6._import_next_layercatches the failure itself, one layer in frombeholder, and substitutes this class: the bad header costs only itself, not the rest of the chain.
The Overrun Guard¶
The house convention for a declared length that does not fit what remains
is warn-and-clip: emit a warning naming what was declared against what is
left, then read only what is left. See
pcapkit.protocols.schema.misc.pcapng.bounded_area() and
pcapkit.protocols.misc.pcapng.PCAPNG.read().
This class follows that convention’s warning
(SchemaWarning) and deliberately diverges
on the action. Clipping is right when the declared length only governs how
much of the current object to read, since there is always a well-defined
“what is left” to fall back to. Here the declared length also decides where
the next header starts; a clipped skip distance points at whatever bytes
end the buffer, which are not a header. Continuing from there would
fabricate a layer and record a false entry in
ProtoChain, which reads as a parsed fact
rather than a guess. So read() warns in the same
wording as the sites above, then stops the walk: this instance absorbs
every remaining octet and reports None for next, which the
caller’s loop already reads as “no more extension headers”. The chain ends
honestly at the bad header instead of inventing what follows it.
Header Schemas¶
- class pcapkit.protocols.schema.internet.ipv6_ext.IPv6_Ext(next=NO_VALUE, len=NO_VALUE, payload=NO_VALUE, *, __packet__=None, **kwargs)[source]¶
Bases:
SchemaHeader schema for a generically-parsed IPv6 extension header.
Only the two octets RFC 6564 Section 4 guarantees are parsed at this layer –
nextand the rawHdr Ext Lenoctet. Combining them into an actual skip distance is per protocol (a constant forIPv6-Frag, 4-octet units forAH, 8-octet units for the rest), so that part is done inpcapkit.protocols.internet.ipv6_ext.IPv6_Ext.read(), which knows which protocol this instance stands in for; this schema does not.
Data Models¶
- class pcapkit.protocols.data.internet.ipv6_ext.IPv6_Ext(protocol, next, declared_next, len, length, data, error)[source]¶
Bases:
ProtocolData model for a generically-parsed IPv6 extension header.
See
pcapkit.protocols.internet.ipv6_ext.IPv6_Extfor 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
ExtensionHeadermember.Nonewhenaliaswas not given or named no such member (readsets it so in both cases, and the class property atprotocolis typed to match).
- next: TransType | None¶
Next header, parsed off the wire.
Nonewhen the declared length would have overrun what remained and the walk stopped instead of trusting it.
- declared_next: TransType¶
Next header as declared on the wire. Equal to
nextexcept on an overrun, wherenextisNonebut this keeps the octet so that a rebuild reproduces it.
- error: Exception | None¶
Original parsing error, if this instance was reached as a
beholder()fallback rather than by direct dispatch.
Footnotes