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. Sibling protocols sharing one frame format, each under its own EtherType, in different 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

Every registry this package dispatches on – Protocol Numbers, Ether Types, and Service Name and Transport Protocol Port Number – has 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 on Customisation & Extensions (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 every IPv6 extension header 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 only IP, ICMP and IGMP.

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 a class naming both Application and Raw reports 'Application' in either base order. So FTP_DATA, declared class FTP_DATA(Application, Raw), reports 'Application'. 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.

Things That Will Look Like Mistakes

Each is deliberate. Do not “fix” any of them.

A parsing base may live in another subpackage. pcapkit.protocols.application.rarp imports ARP from pcapkit.protocols.link.arp. That application/ → link/ dependency 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 no dispatch key depends on it:

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 in each class’s own namespace, whether new or overriding ProtocolBase’s, and including those ProtocolBase.__init_subclass__ sets there, Link has __data__, __layer__, __post_init__, __proto__, __proto_key__, __schema__, _read_protos, _shipped_proto, from_data, register and layer, and Application has what they share (__data__, __layer__, __post_init__, __schema__, from_data, layer) plus __index__, _decode_next_layer and _import_next_layer. Link overrides names that Application does not – __proto__, register, _read_protos and _shipped_proto, the last set because Link defines its own __proto__ – and each of those also exists on ProtocolBase, so each still resolves on OSPF. The strict difference Link minus Application minus ProtocolBase is __proto_key__ alone (#2343), which Link declares and neither of the others does, so it does not resolve on OSPF. Nothing turns on that: register_protocol_code() reads the pairing out of the registry owner’s own namespace rather than off the class, and reads its absence as declares no pairing rather than as an error. What changes is which registry it resolves to: OSPF.__proto__ is ProtocolBase.__proto__ rather than Link.__proto__, so OSPF does not see 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.