IPv6_Route Source-Route headers cannot round-trip: make() emits a wrong Hdr Ext Len and the parse path rejects even a correct wire form #487
Description
Summary
IPv6_Route Source-Route headers cannot be round-tripped: IPv6_Route.make() emits bytes that IPv6_Route's own parser rejects, for every address count. A hand-built, spec-correct wire form is rejected too, so this is not only a constructor bug — the parse path appears broken independently.
Found while adding a regression test for #476/#480 (see #485). It is unrelated to those, so it is filed separately rather than folded in.
Reproduction, measured
Constructing through the public API and immediately parsing the result back:
proto = object.__new__(IPv6_Route)
for n in (0, 1, 2, 3):
addrs = tuple(ipaddress.IPv6Address(f'2001:db8::{i+1}') for i in range(n))
raw = proto.make(type=Routing.Source_Route, data={'ip': addrs}, next=0, seg_left=n).pack()
IPv6_Route(io.BytesIO(raw), len(raw)).infon=0 made 8 octets -> PARSE FAILED FieldValueError: Field ip has invalid length.
n=1 made 24 octets -> PARSE FAILED FieldValueError: Field ip has invalid length.
n=2 made 40 octets -> PARSE FAILED FieldValueError: Field ip has invalid length.
n=3 made 56 octets -> PARSE FAILED FieldValueError: Field ip has invalid length.
Two distinct problems
1. The two branches of make disagree about the unit of length
pcapkit/protocols/internet/ipv6_route.py:275 sets, for the dict branch:
length = len(data_val.pack()) # octetswhile :277, for the Schema branch, sets:
length = math.ceil((len(data.pack()) + 4) / 8) # 8-octet-ish unitsMeasured on the same one-address header, total 24 octets:
| branch | length field emitted |
correct Hdr Ext Len |
|---|---|---|
dict |
20 | 2 |
Schema |
3 | 2 |
Neither is right, and they are not even in the same unit. Per :rfc:8200#section-4.4 the Routing header's Hdr Ext Len is "the length of the Routing header in 8-octet units, not including the first 8 octets", so a 24-octet header must carry 2. The dict branch's 20 is read back as a 168-octet header (20 * 8 + 8), which is why the payload runs off the end.
2. The parse path rejects even a correct wire form
This is the part that makes it more than a constructor bug. Hand-building the spec-correct bytes — next=0, Hdr Ext Len=2, type=0, seg_left=1, 4 reserved octets, one 16-octet address, 24 octets total — still fails:
hand-built total=24 hdr-ext-len=2 -> PARSE FAILED FieldValueError: Field ip has invalid length.
So no byte string appears to round-trip in either direction, and Source-Route parsing looks broken for real captures too, not only for output this library produced.
Root cause NOT isolated
I want to be explicit that I have not found it, so nobody assumes it is known.
SourceRoute.ip at pcapkit/protocols/schema/internet/ipv6_route.py:105 is a ListField sized length=lambda pkt: pkt['__length__'], sitting after a PaddingField(length=4) for the reserved octets. The obvious hypothesis is that the list length fails to account for those 4 reserved octets — but I tested pkt['__length__'] - 4 in a throwaway worktree and it does NOT fix it, failing identically for one and two addresses. Recording that so the next person does not repeat it.
Also note the failure is FieldValueError raised during schema unpacking, before _read_data_type_src's own (header.length - 8) % 16 != 0 guard at ipv6_route.py:461 ever runs. That guard is separately suspect on units — it mixes an 8-octet-unit field with octet constants, and for a correct Hdr Ext Len=2 it computes (2 - 8) % 16 == 10 and would raise — but it is not what fires today.
Probably the same class of bug nearby
_read_data_type_2 at ipv6_route.py:505 hardcodes if header.length != 24. If header.length is the raw 8-octet-unit field, 24 is impossible for a Type 2 header, whose total is fixed at 24 octets and whose Hdr Ext Len is therefore 2. Unverified — I did not test Type 2 — but it should be checked by whoever fixes this.
Why this went unnoticed
_make_data_type_src had no test at all until #485, and #480's fix for #476 exercised the shared Schema.pack ListField branch through a synthetic schema rather than through ipv6_route. #485 works around this by round-tripping at the schema level (SourceRoute.unpack) precisely because the full IPv6_Route round trip cannot be made to pass — so #485 should not be read as covering this.
Suggested scope for a fix
- Make both branches of
makeemitHdr Ext Lenin 8-octet units, from one shared helper rather than two expressions. - Fix the parse path so a spec-correct Source-Route header parses, and add a construct-then-parse round-trip test, which is the check that would have caught both halves.
- Re-examine the
(header.length - 8) % 16andheader.length != 24guards for the same unit confusion, and whether the other routing types share it.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
docs: add a fifth sweep strand for duplicated logic wanting a shared …
release: bump to 1.5.0b1, the wave-1 beta (#488)
fix(hip,ipv6-route): stop dropping the ENCRYPTED IV and RPL's pack-ti…
fix(hip,ipv6-route): stop dropping the ENCRYPTED IV and RPL's pack-ti…
fix(hip,ipv6-route): stop dropping the ENCRYPTED IV and RPL's pack-ti…