Extension-Header Base Classes

Every IPv6 extension header in this package subclasses IPv6_Ext. Some name a second base as well, and which ones do is a ruling rather than an accident. The owner ruled, in review of the rename that made IPv6_Ext the shared base (#917), that a header usable only as an extension header inherits IPv6_Ext and nothing else – IPv6_Frag being the example – while one that is usable as a standalone protocol in its own right inherits both IPv6_Ext and Internet (or IPsec), as ESP does.

The family:

Header

Bases

Classification

HOPOPT, IPv6_Route, IPv6_Frag, IPv6_Opts, MH

IPv6_Ext

extension-header only

AH, ESP

IPsec, IPv6_Ext

also standalone

HIP

IPv6_Ext, Internet

also standalone

IPsec is itself an Internet subclass, which is the parenthetical IPsec alternative in the ruling: naming it satisfies the convention, and it is the right second base for a header whose standalone form is an IPsec one.

The Shared-Registry Trap

This is the part most often got wrong. The obvious way to decide whether a header is “usable as a standalone protocol” is to ask what the library’s own dispatch already allows. That answer is useless, and measurably so:

>>> from pcapkit.protocols.internet.ipv4.protocol import IPv4
>>> from pcapkit.protocols.internet.internet import Internet
>>> IPv4.__proto__ is Internet.__proto__
True

The protocol-number registry is one shared object, so every extension header is reachable as an IPv4 payload in this library – HOPOPT and IPv6_Frag included. Registry membership therefore says nothing at all about standalone-ness, and a classification derived from it would make every one of them standalone.

Nor does IANA’s IPv6 Extension Header Types registry discriminate: it lists every one of the implemented headers, plus Shim6 (140) and 253/254. Being in that registry is what makes something an extension header; it is not evidence about whether the same header is also a protocol in its own right.

The IPv4-Payload Test

So each header is classified from its RFCs, on the owner’s instruction, and the test that decides is whether a primary source shows the header carried directly as an IPv4 payload:

  • AH – RFC 4302 Section 3.1.1, “In the context of IPv4, this calls for placing AH after the IP header”, with a before-and-after IPv4 diagram.

  • ESP – RFC 4303 Section 3.1.1, the same sentence and the same diagram for ESP.

  • HIP – RFC 7401 Appendix C.2, “IPv4 HIP Packet (I1 Packet)”, whose worked checksum is over an IPv4 header carrying Next Header: 139. RFC 7401 Section 5.1 also calls the HIP header “logically an IPv6 extension header”, so HIP is genuinely both.

MH is the instructive failure, because it is a protocol in its own right and still does not qualify: RFC 6275 Section 6.1.1 defines its checksum over a pseudo-header of “IPv6 header fields” whose addresses are “the addresses that appear in the Source and Destination Address fields in the IPv6 packet carrying the Mobility Header” – there is no IPv4 variant of that computation – and the IPv4 equivalent function is not protocol 135 at all, since RFC 5944 carries Mobile IPv4 over UDP port 434. Shim6 (140) has the same shape and the same verdict; this package carries no parser class for it, so nothing implements the classification, but a future one inherits IPv6_Ext alone.

Important

Own-protocolhood on its own is not sufficient, and MH is the case that settles it: the alternative reading – that a protocol in its own right qualifies whether or not it can appear under IPv4 – was put to the owner explicitly in review of #917 and not taken, so MH and Shim6 stay extension-only. A header that is a protocol in its own right but structurally cannot be an IPv4 payload names IPv6_Ext alone.

Explicit Base Declarations

Name the second base explicitly, even where it is already in the MRO. IPv6_Ext derives Internet, so every extension header reaches Internet transitively and an __mro__ check cannot tell the two groups apart. The declaration is the only place the classification exists, which is why tests/protocols/internet/test_ipv6_ext_unit.py pins it against __bases__:

STANDALONE_MEMBERS = frozenset({'AH', 'ESP', 'HIP'})

Add a header to that set in the same change that adds its second base, and keep the RFC ground in the #: comment beside it. The test walks IPv6_Ext.__subclasses__() rather than a hard-coded list, so any further header is held to the convention whether or not anyone remembers this page.

ESP’s Two Facts

Two facts about ESP coexist, and mistaking one for a refutation of the other either drops ESP from the extension-header contract or walks the chain past it.

It is an extension header. IANA’s IPv6 Extension Header Types registry lists protocol number 50, and this package’s ExtensionHeader agrees (ESP = 50). RFC 8200 Section 4.5 appears to say otherwise – “the Encapsulating Security Payload (ESP) is not considered an extension header” – but that sentence opens “For this purpose,”, scoping it to the fragmentation discussion it sits in, and the sentence after it lists ESP among “examples of upper-layer headers”. The library follows the registry, on the owner’s ruling for #895, which is why ESP carries the same extension-mode contract as its siblings.

And it terminates the chain walk. RFC 4303 places ESP’s Next Header byte inside the encrypted trailer, so with no key material there is nothing to continue on: ESP’s own data model reports next as None, and IPv6._decode_next_layer ends the ordinary way one iteration later – not because ESP lacks a parser, which it has, but because it cannot hand the walk a successor.

The full reasoning, including how ESP’s terminal case differs from 253/254’s, lives with the code: see IPv6._import_next_layer and the module docstring of pcapkit.protocols.internet.ipv6_ext.protocol.