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 Header |
1 |
8 |
|
|
2 |
16 |
|
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:
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,aliasand theschema=/data=above implement.The base class of the eight 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 – exactly like
IPsec, the other 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. 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 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. Nothing in the language enforces the override, sotests/protocols/internet/test_ipv6_ext_unit.pyenforces it instead, over every subclass discovered at runtime.- property name: str¶
Name of current protocol.
Annotated
strrather than as theLiteralevery leaf protocol in this package uses, because this one is also a base: aLiteralhere makes each subclass’s ownLiteralan 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
strrather thanLiteral, for the reason given onname.Hyphenated, like
IPv6_Frag.aliasandIPv6_Opts.alias, and for the same reason:IPv6._decode_next_layerbuilds the packet-dict key byself.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, 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_Fragdoes not – must override it. All eight implemented headers do, 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 – i.e. when this instance parsed this module’s own schema – this is not the base
Protocol.protocolmeaning (“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.HOPOPTorShim6. That 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 (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 passesextension=Truefor 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 ordinaryProtocolBasemeaning. That fall-through is load-bearing rather than tidy: all eight subclasses implement their own_extf-guardedprotocolasreturn super().protocol, and this class sits between them andProtocolBasein the MRO – so without the discriminator below, every one of those eight would end up readingself._info.protocoloff a data model that has no such field. Measured:AttributeErroron all eight.The discriminator is the class-level
__data__rather than anisinstance()test onself._info, because amake-only instance has no_infoat all – reading it here to decide which role we are in turnsProtocolBase.protocol, which only ever neededself._protos, into anAttributeError(measured: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 – see
_NextHeaderDatafor why that is sound. Note this is an addition for the eight implemented headers: none of them declared anextproperty of its own before GitHub issue #917, so reading one raisedAttributeError, and nothing could have depended on a value it never returned.Nonewhen 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:
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.0forHOPOPTor140forShim6.error– the parsing error, when this instance was reached as abeholder()fallback rather than by direct dispatch.
- Return type:
TypeVar(_PT, bound= Data)- Returns:
Parsed packet data.
Note
Those two are read out of
**kwargsrather than declared as parameters, andversionis not declared either, because this class is also the base of eight subclasses whose ownreadaccepts 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 eightSignature of "read" incompatible with supertype[override]errors, pylintarguments-differ.versioncosts nothing to drop in any case – this method never read it (it carried# pylint: disable=unused-argumentfor 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:
len (
int) – RawHdr Ext Lenoctet 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 frominspect.signature()ofmake. Hiding them in**kwargswas tried and breaks construction: measured asUnsupportedCall: IPv6_Ext: unexpected keyword(s): 'len' (did you mean 'length'?), 'next', 'payload'on a plainIPv6_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
makeputs different names in the same positions. As positional parameters they drew 18 pylintarguments-renamedwarnings (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_keywordscollectsKEYWORD_ONLYparameters too. Nothing calls a protocol’smakepositionally –__init__spreads**kwargsinto it (protocol.py:607).readneeds none of this: itsalias/erroronly ever arrive on the parse path, which__init__exempts from the construction check outright.
- classmethod _make_data(data)[source]¶
Create key-value pairs from
datafor protocol construction.Inverts whichever per-protocol length rule
read()applied, usingdata.protocol– the extension header this instance stood in for – to pick the same rule back. Round-tripping adata.nextofNone(the overrun case) is not supported: there is no octet value that both satisfies the rule and still fits, which is exactly whyread()stopped rather than clipped.
- __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:
- Raises:
ProtocolError – If this is the fallback parser (see the class docstring’s “two roles”) and
versionis not6.
See also
For construction argument, please refer to
make().Note
The version gate below is scoped to the fallback role, by the same
__data__discriminatorprotocoluses. Applying it to subclasses would break the two that are not IPv6-only:AHandESPboth default toversion=4and are perfectly valid under IPv4, so an unconditional gate here rejects them outright (measured:ProtocolError: ESP: only valid for IPv6, got version=4fromtests/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 everyInternetsubclass,IPv4included – so thatShim6resolves to a working parser instead of defaulting toRaw. 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 readIPv4:IPv6-Ext:...instead of theIPv4:Shim6a plain, non-continuingRawgives today. Rejecting here sends construction back throughbeholder()at the caller’s layer, which substitutes that sameRaw– 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 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:
Header Schemas¶
- class pcapkit.protocols.schema.internet.ipv6_ext.IPv6_Ext(*args: _VT, **kwargs: _VT)[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(*args: VT, **kwargs: VT)[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.Nonewhenaliasnamed no such member (readsets it so on a lookup miss, 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.
- error: Exception | None¶
Original parsing error, if this instance was reached as a
beholder()fallback rather than by direct dispatch.
Footnotes