Protocol Layer Placement

Which subpackage of pcapkit.protocols a new dissector belongs in, and which base classes it names. Settled by the owner on #719.

The Rule

  1. Layer is decided by designed function, not by encapsulation. What carries a protocol on the wire is a separate question from what the protocol is for.

  2. The IETF is the single source of truth – RFC 1122 Section 1.1.3 and RFC 1812 Section 7. Where they place a protocol, so does this package.

  3. The operative question for a new protocol is: is this protocol a user of the stack, or part of its forwarding path? A user is application-layer; the forwarding path is link, internet or transport by its own function.

  4. In the inheritance chain the layer base comes first, then the protocol family.

  5. Encapsulation is the last tie-break, never the first.

The subpackages, functionally rather than by carrier:

Subpackage

What it holds

link

frames, and link-address or tagging protocols

internet

layer-3 addressing and the forwarding path itself – RFC 1812 Section 4.1 confines it to IP, ICMP and IGMP

transport

IANA transport protocols

application

protocols that are users of the stack rather than part of its forwarding path

misc

file formats and layerless sentinels

Important

application is not “payloads reached by port or SCTP PPID”. OSPF is reached by IANA protocol number and RARP by EtherType, and neither has a port.

Why Function and Not Encapsulation

RFC 1122 settles this inside one document. It lists RARP in the application layer at RFC 1122 Section 1.1.3, among the “support protocols, used for host name mapping, booting, and management”, while ARP sits in the Link Layer chapter at RFC 1122 Section 2.3.2. Two sibling protocols, one frame format, one EtherType, two layers – decided on function alone.

OSI says the same by construction. ITU-T X.200 §9.2.4.4 has a protocol declare its own layer in terms of “functions which pertain to a particular layer”, and clause 7 defines every layer by its purpose rather than by what encapsulates it.

IANA Assignments Carry No Layer Claim

All three registries this package dispatches on – Protocol Numbers, Ether Types, and Service Name and Transport Protocol Port Number – have no layer field. Protocol Numbers frames itself as identifying “the next level protocol”, i.e. the encapsulated one. A registry assignment is evidence of encapsulation and of nothing else.

Caution

This retires the registration grid at docs/source/ext.rst (the Protocol Type → Registry Function table) as a placement rule. It is a guide to which registrar to call when extending the library, and it was never a layer taxonomy. Read literally as one it makes ARP, RARP and both VLAN tags internet-layer and all eight IPv6 extension headers transport-layer. A contributor looking for a placement rule will find that grid first, which is why this page says so explicitly.

Routing Protocols: Control Plane, Not Data Plane

RFC 1812 is Standards Track and organises itself by layer. Its §7 is titled “APPLICATION LAYER - ROUTING PROTOCOLS”, with OSPF at §7.2.2, while RFC 1812 Section 4.1 reads “This chapter and chapter 5 discuss the protocols used at the Internet Layer: IP, ICMP, and IGMP.” So a routing protocol is application-layer and the internet layer holds three protocols.

X.200 does not contradict this, and must not be cited as though it did. X.200 §7.5.2.1 says the Network Layer provides transport “independence of routing and relay considerations” – that is the network layer performing forwarding so transport need not care, which is the data plane. X.200 treats routing as a generic function parameterised by layer: §5.4.1.4 defines it as “a function within a layer”, and §5.9 as “a routing function within the (N)-layer enables communication to be relayed by a chain of (N)-entities.” An (N)-layer function is not a layer assignment, and X.200 assigns none.

The distinction that dissolves the apparent conflict: computing a forwarding table is the control plane and makes the protocol a user of the stack; forwarding packets is the data plane and is internet-layer. OSPF does the former.

Tunnelling Protocols Follow Their Payload

A tunnelling protocol is placed by what it carries, not by what carries it. So L2TP and L2TPv2 stay in link and are not application-layer, even though L2TP is commonly seen over UDP.

RFC 4949’s $ tunnel entry is the citation: a tunnel is “a logical point-to-point link – i.e., an OSIRM Layer 2 connection”, and a tunnelling protocol “e.g., L2TP” is “layered below the tunneled Layer 2 protocol and above the encapsulating protocol.” RFC 2473 Section 3 and RFC 4213 Section 3.4 (“the tunnel is a link”) agree.

The carrier was never single-valued for L2TP anyway: RFC 3931 Section 4.1 makes L2TP-over-IP (protocol 115) a MUST and UDP/1701 only a SHOULD.

The Inheritance Convention

Layer base first, protocol family second. Where a protocol’s layer and its parsing family disagree, it names both, in that order.

The order is load-bearing, and the mechanism is checkable. __layer__ is resolved by the MRO, so whichever base owns it earliest wins:

>>> from pcapkit.protocols.link.link import Link
>>> from pcapkit.protocols.misc.raw import Raw
>>> '__layer__' in vars(Link), '__layer__' in vars(Raw)
(True, False)

Raw does not own __layer__ – it inherits None from ProtocolBase – so FTP_DATA would report 'Application' in either base order. ARP’s chain reaches Link, which does own it, so class RARP(ARP, Application) would report 'Link' and be wrong.

Ordering layer-first is correct in both cases, which is the point: a contributor does not have to work out which case they are in.

Two Things That Will Look Like Mistakes

Both are deliberate. Do not “fix” either.

A parsing base may live in another subpackage. pcapkit.protocols.application.rarp imports ARP from pcapkit.protocols.link.arp. This is the first application/ → link/ dependency in the package, and it is the honest consequence of letting layer and parser disagree.

The dispatch tier is decoupled from the subpackage. A protocol’s subpackage says nothing about which registry reaches it, and the move did not touch any dispatch key:

Class

Dispatched from

Subpackage

OSPF

Internet.__proto__ by OSPFIGP

application

RARP

Link.__proto__ by EtherType

application

Nothing else needs overriding. Application accepts the -1 sentinel (the undissected remainder) and resolves it to Raw, so OSPF’s body and the padding Ethernet adds to a short RARP frame attach without either class touching __post_init__, _decode_next_layer or _import_next_layer. Only a real protocol number is refused.

Swapping Link for Application is not name-for-name. Counting the names each class body sets, whether new or overriding ProtocolBase’s, Link has seven – __data__, __layer__, __proto__, __schema__, _read_protos, register and layer – and Application eight: the four they share (__data__, __layer__, __schema__, layer) plus __index__, __post_init__, _decode_next_layer and _import_next_layer. Link overrides three of those that Application does not – __proto__, register and _read_protos – but all three also exist on ProtocolBase, so the strict difference Link minus Application minus ProtocolBase is empty and every one of them still resolves on OSPF. What changes is which registry it resolves to: OSPF.__proto__ is now ProtocolBase.__proto__ rather than Link.__proto__, so OSPF no longer sees Link’s EtherType entries. That is inert – nothing reaches OSPF by EtherType, and a -1 lookup misses in both registries alike and resolves to Raw, inserting nothing on the miss. RARP keeps Link’s through ARP.

Tie-Break Order

For a genuinely ambiguous protocol, in this order:

  1. the protocol’s own RFC’s functional self-description;

  2. the placement of its closest functional peers already in this package;

  3. encapsulation, last.

Why Not OSI’s Seven Layers

Asked and declined on #719. The four-layer Internet model is what the public API already declares – Layers = Literal['link', 'internet', 'transport', 'application', 'none'] in pcapkit.foundation.extraction – and the session and presentation layers have no header on the wire and no IANA registry to dispatch on, so as subpackages they would be structurally empty rather than merely sparse. OSI also resolves neither of the cases that prompted this page: routing’s control/data-plane split and L2TP’s carrier/payload split exist identically in both models.