Skip to content

fix(l2tp): L2TPv2.from_data drops the L flag and the Length field - #1174

Merged
JarryShaw merged 1 commit into
mainfrom
fix/1172-l2tpv2-length
Oct 7, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/1172-l2tpv2-length

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner
  • Searched for similar pull requests
  • Followed the coding style (make pylint, make mypy, make isort) — run with the Makefile flags on l2tpv2.py (mypy clean, isort clean, pylint only the pre-existing W0622 on type)
  • make test passes, and a test case covers the change — per-module runs listed below, not the full make test
  • Added a changelog entry — N/A, added centrally after the wave

What is the purpose of your pull request?

  • fix — corrects a defect

Closes #1172

Cause. _make_data returned the parsed Length as length, which ProtocolBase.__init__ takes as the parse length, so make() never saw it and cleared L. make() now takes total_length (as #1166 did for IPv4/UDP). tests/protocols/link/test_link_unit.py called make(length=20) directly and is updated to the new keyword.

Probe. L2TPv2.from_data(L2TPv2(bytes.fromhex('c802000c0001000000010002')).info).data.hex(): before 88020001000000010002, after c802000c0001000000010002. Over every T/L/S/O combination, with and without a 3-octet zero pad: 12/24 round-trip before, 24/24 after. No capture in examples/captures/ carries L2TP (0 frames out of 103 UDP).

Tests. New tests/protocols/link/test_l2tpv2_length_roundtrip_unit.py: 4 passed, 24 subtests passed; with the fix reverted, 15 failed, 1 passed, 12 subtests passed. test_link_unit 24 passed, test_l2tp_version_unit 7 passed, test_make_data_keys_unit 2 passed, test_construction_keyword_check_unit 21 passed, test_option_roundtrip_unit 6 passed, tests/project 389 passed, 1 skipped. util/run_unittest_leg.py protocols --exclude protocols/internet: 831 tests, 0 failures, 0 errors before; 835 tests, 0 failures, 0 errors after.

`_make_data` returned the parsed Length under `length`, which
`ProtocolBase.__init__` consumes as the parse length, so it never reached
`make()` and the rebuild cleared `L` and omitted the Length octets.
`make()` now takes it as `total_length`, as #1166 did for IPv4 and UDP,
and `_make_data` passes it under that key.

`test_link_unit` called `make(length=20)` directly and is updated to the
new keyword. All 24 T/L/S/O(+pad) combinations now round-trip.

Closes #1172
@JarryShaw JarryShaw added breaking Breaks public-facing behaviour or API (apply alongside the type label) fix Pull requests that fix a defect (fix: subject prefix) review: running A cross-review is in flight against the current head - no verdict yet labels Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage: 88.89% (unit tier, Python 3.14, 77800615b, Unit Tests run success)

Package Statements Missed Branches Partial Cover
pcapkit (top level) 104 4 20 4 93.55%
pcapkit/const 18757 1034 2342 845 90.24%
pcapkit/corekit 1878 91 580 22 94.34%
pcapkit/dumpkit 142 0 42 0 100.00%
pcapkit/foundation 2442 102 852 39 94.38%
pcapkit/interface 112 7 40 5 92.11%
pcapkit/protocols 15707 188 3974 163 98.19%
pcapkit/toolkit 487 65 144 1 85.42%
pcapkit/utilities 429 4 122 4 98.55%
pcapkit/vendor 4409 2359 1006 158 42.84%

Per-file detail: the coverage-html artifact of this run.

@JarryShaw

Copy link
Copy Markdown
Owner Author

Cross-review verdict on 77800615b: GOOD TO GO (ran on Sonnet; author Opus). Labelled breaking.

  • Root cause confirmed: _make_data returned the Length under length, which ProtocolBase.__init__ takes as the parse length. The repro now rebuilds c802000c0001000000010002; main gives 88020001000000010002.
  • Flag matrix is clean: 48 datagrams covering T/L/S/O, zero offset pad, with and without payload, from bytes and from BytesIO, gave 0 mismatches. On main, every L=1 case fails.
  • Interaction with fix(protocols): a payload layer parsed from a short stream rebuilds default-filled octets #1171's guard, checked on a merge with main: a 12-octet ZLB with L=1 and a 10-octet one with L=0 both still parse as L2TPv2, including through UDP 1701. The protocols (rest) ordering leg runs 842 tests with 0 failures and 0 errors.
  • Fails without the fix: 15 tests fail.
  • Why breaking: make(length=) was renamed to make(total_length=). Nothing in the repo uses the old name, and the constructor never let it through. A direct make(length=...) call now silently drops the L flag instead of raising.
  • Follow-ups, not blocking, filed separately:
    • The L2TP Type enum is inverted against RFC 2661 §3.1.
    • make() emits L=0 for control messages; §3.1 says L MUST be 1.
    • total_length is written without checking it.
    • A non-zero offset pad is zeroed on rebuild.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: running A cross-review is in flight against the current head - no verdict yet labels Oct 7, 2026
@JarryShaw
JarryShaw merged commit fa62b89 into main Oct 7, 2026
42 checks passed
@JarryShaw
JarryShaw deleted the fix/1172-l2tpv2-length branch October 7, 2026 04:00
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Oct 7, 2026
@JarryShaw JarryShaw added this to the PyPCAPKit v1.5 milestone Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaks public-facing behaviour or API (apply alongside the type label) fix Pull requests that fix a defect (fix: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

fix(l2tp): L2TPv2.from_data drops the L flag and the Length field

1 participant