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)).info
n=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()) # octets
while :277, for the Schema branch, sets:
length = math.ceil((len(data.pack()) + 4) / 8) # 8-octet-ish units
Measured 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
make emit Hdr Ext Len in 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) % 16 and header.length != 24 guards for the same unit confusion, and whether the other routing types share it.
Summary
IPv6_RouteSource-Route headers cannot be round-tripped:IPv6_Route.make()emits bytes thatIPv6_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:
Two distinct problems
1. The two branches of
makedisagree about the unit oflengthpcapkit/protocols/internet/ipv6_route.py:275sets, for thedictbranch:while
:277, for theSchemabranch, sets:Measured on the same one-address header, total 24 octets:
lengthfield emittedHdr Ext LendictSchemaNeither is right, and they are not even in the same unit. Per :rfc:
8200#section-4.4the Routing header'sHdr Ext Lenis "the length of the Routing header in 8-octet units, not including the first 8 octets", so a 24-octet header must carry 2. Thedictbranch's20is 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: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.ipatpcapkit/protocols/schema/internet/ipv6_route.py:105is aListFieldsizedlength=lambda pkt: pkt['__length__'], sitting after aPaddingField(length=4)for the reserved octets. The obvious hypothesis is that the list length fails to account for those 4 reserved octets — but I testedpkt['__length__'] - 4in 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
FieldValueErrorraised during schema unpacking, before_read_data_type_src's own(header.length - 8) % 16 != 0guard atipv6_route.py:461ever runs. That guard is separately suspect on units — it mixes an 8-octet-unit field with octet constants, and for a correctHdr Ext Len=2it computes(2 - 8) % 16 == 10and would raise — but it is not what fires today.Probably the same class of bug nearby
_read_data_type_2atipv6_route.py:505hardcodesif header.length != 24. Ifheader.lengthis the raw 8-octet-unit field, 24 is impossible for a Type 2 header, whose total is fixed at 24 octets and whoseHdr Ext Lenis 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_srchad no test at all until #485, and #480's fix for #476 exercised the sharedSchema.packListFieldbranch through a synthetic schema rather than throughipv6_route. #485 works around this by round-tripping at the schema level (SourceRoute.unpack) precisely because the fullIPv6_Routeround trip cannot be made to pass — so #485 should not be read as covering this.Suggested scope for a fix
makeemitHdr Ext Lenin 8-octet units, from one shared helper rather than two expressions.(header.length - 8) % 16andheader.length != 24guards for the same unit confusion, and whether the other routing types share it.