Skip to content

fix(l2tp): the Type enum is inverted against RFC 2661, and make() omits L on control messages #1175

Closed

Description

@JarryShaw

Describe the bug
The L2TP Type enum is inverted against RFC 2661 §3.1, which says the T bit "is set to 0 for a data message and 1 for a control message". On main:

  • pcapkit/vendor/l2tp/type.py has DATA = {0: 'Control', 1: 'Data'}, and the generated pcapkit/const/l2tp/type.py:23-25 follows it (Control = 0, Data = 1). Regenerating would not fix it.
  • So the control ZLB ACK c802… (T=1) parses as type=Data, and L2TPv2.make()'s default Enum_Type.Data writes T=1, which is a control header on the wire.

Smaller related gaps in L2TPv2.make():

  • It emits L=0 for a control message when no total_length is given. §3.1 says L MUST be 1 for control messages.
  • It writes total_length as given, without checking it against the real size.
  • A non-zero offset pad is zeroed on rebuild (…0003010203… → …0003000000…). §3.1 calls the pad contents undefined, so this is low severity.

Expected behavior
Type.Data == 0 and Type.Control == 1. make() sets L for control messages, and a parse round-trips byte-exactly.

Additional context
Found by the #1174 worker and confirmed by its cross-review. I re-read the vendor mapping myself. Correcting the enum changes public Enum_Type values, so it is breaking. It edits l2tpv2.py, which #1174 also edits, so this waits for #1174.

Activity

  1. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    Blocked until #1174 merges, because both edit pcapkit/protocols/link/l2tpv2.py.

  2. added
    fixPull requests that fix a defect (fix: subject prefix)
    breakingBreaks public-facing behaviour or API (apply alongside the type label)
    constRegenerated IANA or vendor constant tables; members keep their numeric values
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    on Oct 7, 2026
  3. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    Unblocked: #1174 merged at 04:00Z. Accepted and unstarted, so this is now pending.

  4. added
    pendingAccepted and unstarted - nobody is on it and nothing blocks it
    and removed
    blockedDeferred pending another issue or decision; see the last comment for what unblocks it
    on Oct 7, 2026
  5. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    A worker is on it now.

  6. added
    wipWork in flight - a covering PR is open or an agent is actively on it
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    and removed
    pendingAccepted and unstarted - nobody is on it and nothing blocks it
    on Oct 7, 2026
  7. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    Two design points on #1183 need your call. Neither blocks the merge.

    1. The new make(length_flag=...) keyword. Without it, a captured control message without L gains a Length field on from_data and no longer round-trips; 6 round-trip cases fail. It defaults to None, so existing callers are unchanged, and the review found no way to carry this that adds no keyword. The alternative is to normalise captured L=0 control messages to L=1, which is RFC-correct but drops byte-exactness. Keep the keyword, or normalise?
    2. info.padding shows in to_dict(). It is None without O and b'' at offset 0, so every parsed L2TPv2 packet's json/tree/plist output gains the key. That matches how offset, ns and nr already show None. Keep it visible, or hide it?

    I recommend keeping both: byte-exact rebuilds are the project's rule, and padding follows the existing optional-field pattern.

  8. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    Good take your recommendations

  9. JarryShaw commented on Oct 7, 2026

    @JarryShaw
    OwnerAuthor

    Ruling recorded: both recommendations accepted. #1183 keeps the make(length_flag=...) keyword so captured L=0 control messages stay byte-exact, and keeps info.padding visible in to_dict(), matching offset/ns/nr.

    #1183 at 53f2d0fc1 already implements both, so no further change is needed. needs: decision removed; this stays wip until #1183 merges.

  10. removed
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    on Oct 7, 2026
  11. removed
    wipWork in flight - a covering PR is open or an agent is actively on it
    on Oct 7, 2026
  12. added this to the PyPCAPKit v1.5 milestone on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingBreaks public-facing behaviour or API (apply alongside the type label)constRegenerated IANA or vendor constant tables; members keep their numeric valuesfixPull requests that fix a defect (fix: subject prefix)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions