Skip to content

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

@JarryShaw

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.

Activity

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

    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions