L2TP - Layer Two Tunnelling Protocol

pcapkit.protocols.link.l2tp contains L2TP only, an abstract base class for the Layer Two Tunnelling Protocol family [*]. The concrete versions live in modules of their own:

Version

Class

Specification

L2TPv2

L2TPv2

RFC 2661

Only L2TPv2 is implemented.

The base deliberately carries no header parsing at all, in the way IP carries none for its family. That is not tidiness: the versions genuinely do not share a header. All that is common across them is the first 16-bit word carrying a version nibble at bits 12-15; everything after it differs, so a base that parsed further would be assuming one version’s layout for all of them.

What the family still wants

L2TPv3 [RFC 3931] has a different session header and a different control message header from v2, and is reachable two ways – over UDP port 1701 like v2, and directly over IP as protocol number 115. That second route is why Internet.__proto__ leaves 115 unbound today: the binding waits on an L2TPv3 class, not on a different framing decision. It also means v3 is the first member of this family to have a real __index__(), and so the first that must have a module of its own under the project’s one-module-per-index rule.

L2F [RFC 2341] is reached when the version nibble reads 1. It is not an earlier version of L2TP: RFC 2661 §3.1 requires Ver to be 2 and reserves the value 1 “to permit detection of L2F packets should they arrive intermixed with L2TP packets”. L2F is a separate protocol with its own header. It is therefore to be implemented as L2F, the canonical name, carrying L2TPv1 only as an alias in its id() – the same relationship HTTP/3 has to QUIC. c.f. HTTPv1.id for how a version-flavoured alias is spelled: canonical name first, alias second, since callers take element zero as canonical.

Selecting a version

Nothing dispatches on the version nibble yet, because only one version exists. When a second lands, the mechanism it wants already has a precedent in HTTP, which reads a version and delegates to a per-version class. L2TP is the easier case: HTTP._guess_version has to trial-parse each candidate because the wire format carries no version field, whereas L2TP states its version explicitly in those four bits. So a deterministic switch on Ver is enough, and no new registry is needed.

class pcapkit.protocols.link.l2tp.L2TP(file=None, length=None, **kwargs)[source]

Bases: Link[_PT, _ST], Generic[_PT, _ST]

This class implements all protocols in L2TP family.

It is abstract for the same mechanical reason IP is: name and read() are both declared abstract by ProtocolBase and neither is defined here, so the class cannot be instantiated. Bind a version, never this class.

property info_name: Literal['l2tp']

Key name of the info dict.

classmethod id()[source]

Index ID of the protocol.

Return type:

tuple[Literal['L2TP'], Literal['L2TPv2']]

Returns:

Index ID of the protocol – the family name, then every version in it, as HTTP.id does for its own family. L2F and L2TPv3 join this tuple when they are implemented.

classmethod __index__()[source]

Numeral registry index of the protocol.

Raises:

UnsupportedCall – This protocol has no registry entry.

Note

An abstract base is reached by nothing, so it has no index of its own. That is also the project’s rule for module layout – a distinct __index__ means a distinct module, and a base carrying none claims no module of its own beyond holding the family together.

Return type:

NoReturn

Footnotes