What is wrong
IPv6InterfaceField.post_process (pcapkit/corekit/fields/ipaddress.py:269) reads the trailing prefix-length octet as if it were an ASCII decimal string:
def post_process(self, value: 'bytes', packet: 'dict[str, Any]') -> 'IPv6Interface':
ip = ipaddress.IPv6Address(value[:16])
mask = int(value[16:]) # line 269 -- value[16:] is one raw byte
int(b'\x40') is int() on a one-byte bytes, which Python interprets as a decimal literal, not as the number 64.
The field's own pre_process, 14 lines above, writes it as a binary octet:
prefixlen = cast('int', val._prefixlen)
return ip.packed + prefixlen.to_bytes(1, 'big') # line 255
so the field cannot read back what it writes. § 4.2 of draft-ietf-opsawg-pcapng agrees with pre_process: if_IPv6addr is option type 5, fixed length 17, and
The first 16 octets are the IP address and the next octet is the prefix length.
Example: 2001:0db8:85a3:08d3:1319:8a2e:0370:7344/64 is written (in hex) as '20 01 0d b8 85 a3 08 d3 13 19 8a 2e 03 70 73 44 40'.
That trailing 40 is the byte int(b'@') chokes on.
Symptom
The field is used by IF_IPv6AddrOption (pcapkit/protocols/schema/misc/pcapng.py:647), so any Interface Description Block carrying if_IPv6addr is affected. Two outcomes, and the silent one is worse:
-
Raises for every prefix length whose octet is not an ASCII digit — which is all but ten of them, including every common one (/64, /128, /48, /32, /16, /8, /0):
File ".../pcapkit/corekit/fields/ipaddress.py", line 269, in post_process
mask = int(value[16:])
ValueError: invalid literal for int() with base 10: b'@'
-
Silently wrong for the ten prefix lengths 48–57, whose octets happen to be 0x30–0x39, i.e. '0'–'9'. Every one of them decodes to the wrong value:
| encoded |
decoded as |
| /48 |
/0 |
| /49 |
/1 |
| … |
… |
| /56 |
/8 |
| /57 |
/9 |
A capture recording 2001:db8:85a3:8d3:1319:8a2e:370:7344/56 comes back as .../8 with no warning at all.
Reproduction
The field on its own:
import ipaddress
from pcapkit.corekit.fields.ipaddress import IPv6InterfaceField
f = IPv6InterfaceField()
for plen in (64, 56):
raw = f.pre_process(ipaddress.ip_interface(f'2001:db8::1/{plen}'), {}) # what pcapkit writes
try:
print(f'/{plen} ->', f.post_process(raw, {}))
except ValueError as exc:
print(f'/{plen} ->', type(exc).__name__, exc)
# /64 -> ValueError invalid literal for int() with base 10: b'@'
# /56 -> 2001:db8::1/8
End to end, with the spec's own example address:
import struct, ipaddress
def pad4(b): return b + b'\x00' * ((4 - len(b) % 4) % 4)
def block(t, body):
body = pad4(body); n = 12 + len(body)
return struct.pack('<II', t, n) + body + struct.pack('<I', n)
def opt(c, v): return struct.pack('<HH', c, len(v)) + pad4(v)
shb = lambda: block(0x0A0D0D0A, struct.pack('<IHHq', 0x1A2B3C4D, 1, 0, -1))
epb = lambda d: block(0x00000006, struct.pack('<IIIII', 0, 0, 0, len(d), len(d)) + pad4(d))
ETH = bytes.fromhex('ffffffffffff001122334455') + b'\x08\x06' + b'\x00' * 28
import pcapkit
for plen in (64, 56):
val = ipaddress.IPv6Address('2001:db8:85a3:8d3:1319:8a2e:370:7344').packed + bytes([plen])
idb = block(0x00000001, struct.pack('<HHI', 1, 0, 0x40000) + opt(5, val) + opt(0, b''))
open(f'/tmp/if6_{plen}.pcapng', 'wb').write(shb() + idb + epb(ETH))
try:
pcapkit.extract(fin=f'/tmp/if6_{plen}.pcapng', store=True, nofile=True)
print(plen, 'parsed')
except ValueError as exc:
print(plen, '->', type(exc).__name__, exc)
/64 raises; /56 "succeeds" and yields IF_IPv6AddrOption(type=if_IPv6addr [5], length=17, interface=2001:db8:85a3:8d3:1319:8a2e:370:7344/8).
What a fix would need to touch
Line 269: value[16], or int.from_bytes(value[16:], 'big'). Then build the interface from the prefix length rather than from a netmask string — ipaddress.ip_interface(f'{ip}/{mask}') at line 271 happens to work for a prefix length, but IPv4InterfaceField.post_process (line 206) deliberately passes a dotted netmask there, so the two paths are not interchangeable and a test pinning both would be worth having.
How it was found
While building deterministic sample captures for the test suite. The generators' module docstrings record it: examples/samples/pcapng.py lists if_IPv6addr among the constructs deliberately left out of the fixtures, because a /64 prefix raises.
Line numbers are from main at 72f950d.
What is wrong
IPv6InterfaceField.post_process(pcapkit/corekit/fields/ipaddress.py:269) reads the trailing prefix-length octet as if it were an ASCII decimal string:int(b'\x40')isint()on a one-bytebytes, which Python interprets as a decimal literal, not as the number 64.The field's own
pre_process, 14 lines above, writes it as a binary octet:so the field cannot read back what it writes. § 4.2 of draft-ietf-opsawg-pcapng agrees with
pre_process:if_IPv6addris option type 5, fixed length 17, andThat trailing
40is the byteint(b'@')chokes on.Symptom
The field is used by
IF_IPv6AddrOption(pcapkit/protocols/schema/misc/pcapng.py:647), so any Interface Description Block carryingif_IPv6addris affected. Two outcomes, and the silent one is worse:Raises for every prefix length whose octet is not an ASCII digit — which is all but ten of them, including every common one (
/64,/128,/48,/32,/16,/8,/0):Silently wrong for the ten prefix lengths 48–57, whose octets happen to be
0x30–0x39, i.e.'0'–'9'. Every one of them decodes to the wrong value:A capture recording
2001:db8:85a3:8d3:1319:8a2e:370:7344/56comes back as.../8with no warning at all.Reproduction
The field on its own:
End to end, with the spec's own example address:
/64raises;/56"succeeds" and yieldsIF_IPv6AddrOption(type=if_IPv6addr [5], length=17, interface=2001:db8:85a3:8d3:1319:8a2e:370:7344/8).What a fix would need to touch
Line 269:
value[16], orint.from_bytes(value[16:], 'big'). Then build the interface from the prefix length rather than from a netmask string —ipaddress.ip_interface(f'{ip}/{mask}')at line 271 happens to work for a prefix length, butIPv4InterfaceField.post_process(line 206) deliberately passes a dotted netmask there, so the two paths are not interchangeable and a test pinning both would be worth having.How it was found
While building deterministic sample captures for the test suite. The generators' module docstrings record it:
examples/samples/pcapng.pylistsif_IPv6addramong the constructs deliberately left out of the fixtures, because a/64prefix raises.Line numbers are from
mainat 72f950d.