Repository navigation
fix(toolkit): the default engine ends the extraction on an IPv6 header whose Version is 4 #1666
Description
Describe the bug
With the default engine, PCAP and PCAP-NG alike, an IPv6 layer whose Version is 4 and Next Header is TCP, carrying a TCP header the TCP parser rejects, raises AttributeError: 'IPv6' object has no attribute 'offset' at pcapkit/toolkit/pcap.py:168. It comes through tcp_reassembly (:324), tcp_traceflow (:371) and their PCAP-NG twins (pcapkit/toolkit/pcapng.py:167, :216), and ends the extraction, so later frames are lost. dpkt and scapy read the frame.
Root cause: tcp_segment takes the IPv4 branch of its later-fragment check by the header's Version, if ip_info.version == 4: (pcap.py:167, added by #1556 for #1518), not by the layer's class. IPv6.read copies the Version unchecked (pcapkit/protocols/internet/ipv6.py:206), though Data_IPv6.version is typed Literal[6] (pcapkit/protocols/data/internet/ipv6.py:38) and IPv4.read rejects any Version but 4 (pcapkit/protocols/internet/ipv4.py:296). Versions 0, 5 and 15 take the IPv6 branch, and a TCP header that parses returns before line 167.
Reproduction
main (85e4198c8, 1.5.0b10), CPython 3.14.8 (dpkt 1.9.8, scapy 2.7.0) and 3.11.15 (dpkt 1.9.8, scapy 2.8.0, pypcapfile 0.12.0, pyshark 0.6, tshark 4.6.9), same output on both:
import struct, pcapkit
tcp = struct.pack('!HHIIBBHHH', 1234, 80, 1000, 0, 0xF0, 0x18, 65535, 0, 0) + b'hello' # Data Offset 15
ip = struct.pack('!IHBB', 4 << 28, len(tcp), 6, 64) + bytes.fromhex('fe80' + '00' * 13 + '01' + 'fe80' + '00' * 13 + '02')
frame = bytes(12) + b'\x86\xdd' + ip + tcp # EtherType IPv6, header Version 4
rec = struct.pack('<IIII', 0, 0, len(frame), len(frame)) + frame
open('bad.pcap', 'wb').write(struct.pack('<IHHiIII', 0xA1B2C3D4, 2, 4, 0, 0, 65535, 1) + rec * 2)
for engine in ('default', 'dpkt', 'scapy'):
try:
ext = pcapkit.extract(fin='bad.pcap', engine=engine, nofile=True, trace=True, tcp=True,
trace_format='json', trace_fout='flows')
print(engine, 'traced frames:', [flow.index for flow in ext.trace.tcp])
except AttributeError as exc:
print(engine, 'AttributeError:', exc)default AttributeError: 'IPv6' object has no attribute 'offset'
dpkt traced frames: [(1, 2)]
scapy traced frames: [(1, 2)]
Family survey. H4 is the 40-octet IPv6 header above (Version 4, Next Header 6); bad TCP is the segment above; TCP is a valid one. Cells are TCP reassembly / trace inputs, or IP reassembly inputs where named; - is no such adapter.
| # | Frame | default | dpkt | scapy | pypcapfile | pyshark |
|---|---|---|---|---|---|---|
| 1 | 0x86DD, H4, bad TCP | AttributeError | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 2 | 802.1Q, 0x86DD, H4, bad TCP | AttributeError | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 3 | IPv4 protocol 41, H4, bad TCP | AttributeError | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 4 | 802.1Q, IPv4 protocol 41, H4, bad TCP | AttributeError | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 5 | IPv6 Next Header 41, H4, bad TCP | AttributeError | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 6 | row 1, PCAP-NG | AttributeError | 1 / 1 | 1 / 1 | not run | not run |
| 7 | row 1, Version 0, 5 or 15 | 1 / 1 | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 8 | 0x86DD, H4, TCP (control) | 1 / 1 | 1 / 1 | 1 / 1 | 0 / 0 | - / 0 |
| 9 | 0x86DD, IPv4, TCP | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | - / 0 |
| 10 | IPv4 protocol 41 or IPv6 Next Header 41, IPv4, TCP | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | - / 0 |
| 11 | 0x0800 or IPv4 protocol 4, IPv4 header with Version 6, TCP | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | - / 0 |
| 12 | 0x0800 (also behind 802.1Q), IPv6, TCP | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | - / 1 |
| 13 | IPv4 protocol 4 or IPv6 Next Header 4, IPv6, TCP | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | - / 1 |
| 14 | 0x86DD or IPv6 Next Header 41, two IPv4 fragments: IPv4 reassembly | 0 | 0 | 0 | 0 | - |
| 15 | 0x86DD, two H4 fragments with a Fragment header: IPv6 reassembly | 2 | 2 | 2 | - | - |
| 16 | 0x0800 or IPv4 protocol 4, two IPv6 fragments: IPv6 reassembly | 0 | 0 | 0 | - | - |
Without tcp=True the extraction does not raise; rows 9-16 read without error. pypcapfile has no IPv6 decoder. pyshark differs separately: tshark stops at "Bogus IPv6 version" in rows 1-8, and in rows 12-13 dissects the IPv6 header that _carrier (pcapkit/toolkit/pyshark.py:541) then keeps. #1639 has that shape but not these rows.
Expected behavior
The default engine reads rows 1-6 without raising and hands dpkt's and scapy's inputs, as it already does for rows 7 and 8. Testing isinstance(ip, IPv4) at pcap.py:167, patched at runtime only, gives inputs identical to dpkt's and scapy's on rows 1-5. Whether IPv6.read should reject a Version other than 6, as IPv4.read and tshark do, is a separate decision that changes rows 1-8 and 15 for every engine.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
JarryShaw commentedon Oct 13, 2026
A worker has this. The fix picks
tcp_segment's branch by the layer's class rather than the header's Version, inpcap.pyandpcapng.py, with a test covering the 16 rows above.IPv6.readkeeps accepting any Version for now. Whether it should reject one other than 6, asIPv4.readand tshark do, is your call, because that changes every engine's rows 1-8 and 15.fix(toolkit): the default engine reads TCP under an IPv6 header of Ve…
fix(protocols): reject an IPv6 header whose Version is not 6 (#1680) (#…