You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(toolkit): the dpkt, scapy and pyshark engines do not stop an extension-header chain at 32 #1609
#1604's ruling is a fixed bound of 32 IPv6 extension headers, shared by every engine. #1608 applies it to the default engine and pypcapfile. dpkt, scapy and pyshark have their own parsers walk the chain, so they still find TCP past the bound.
Repro on #1608's head (e6d87c64c). Each capture is Ethernet, then IPv6, then n Hop-by-Hop headers (PadN 4), then TCP carrying data. TCP datagrams reassembled:
n
default
dpkt
scapy
32
1
1
1
33
0
1
1
1,000
0
1
0
The #1608 worker also measured where each parser gives up: scapy at about 245 layers, and tshark (the pyshark engine) at about 496. I have not re-measured those two figures.
Expected: the dpkt, scapy and pyshark adapters count the headers they step over and stop at EXTENSION_HEADER_LIMIT (pcapkit/protocols/internet/internet.py, added by #1608), as the default engine does. #1608 records the gaps as Gap rows in tests/foundation/engines/test_ext_chain_limit_agreement_1604_unit.py.
Blocked until #1605 and #1608 merge. The fix goes in the tcp_reassembly/tcp_traceflow helpers of pcapkit/toolkit/{dpkt,scapy}.py, which #1605 rewrites, and it needs #1608's constant.
Widened per the #1610 ruling. The adapters must also stop at the new per-frame bound of 64 layers, so that they agree with the default engine on deep nesting of any protocol, not only on extension-header chains. pypcapfile is in scope too, because its own walk does not go through ProtocolBase._parse_next_layer.
Blocked until #1605, #1608 and the #1610 PR merge: #1605 rewrites the adapters' TCP helpers, and the other two add the constants.
Widening: the same gap holds for IPv4 reassembly input, not only TCP. Behind 33 Destination Options headers in IPv6, the default engine hands IPv4 reassembly nothing, while scapy still hands the IPv4 fragment over (measured on ee324e1dc). dpkt stops there once #1622 lands. So the scapy and pyshark adapters' IPv4 (and IPv6) reassembly inputs must also stop at EXTENSION_HEADER_LIMIT. Still blocked until #1620 merges.
Widening: dpkt's IPv6 reassembly input also runs past the bound, not only scapy's and pyshark's. Measured on main (b375656e1), 3.14 and 3.11. Each capture is Ethernet, then IPv6, then Destination Options headers (PadN 4), then a first-fragment Fragment header. IPv6 reassembly inputs (header length):
Fragment header is the
default
dpkt
scapy
32nd extension header
1 (288)
1 (288)
1 (288)
33rd extension header
0
1 (296)
1 (296)
Neither adapter counts the headers before the Fragment header. dpkt reads ipv6.extension_hdrs.get(44) (pcapkit/toolkit/dpkt.py:902) and scapy tests IPv6ExtHdrFragment in ipv6 (pcapkit/toolkit/scapy.py:536). pyshark and pypcapfile decline IPv6 reassembly, so there is nothing for them to bound.
Widening: the dpkt and scapy walkers do not apply the 64-layer bound (FRAME_LAYER_LIMIT, pcapkit/protocols/protocol.py:412, #1610). The walkers are:
dpkt: _tcp_carrier (pcapkit/toolkit/dpkt.py:572) and _first_ipv4 (:717)
scapy: _tcp_carrier (pcapkit/toolkit/scapy.py:429), _first_ipv4 (:365) and ipv6_reassembly (:608)
Measured on main (b375656e1) with Python 3.14.8 and 3.11.15 (dpkt 1.9.8; scapy 2.7.0 and 2.8.0). Each frame is Ethernet, then the nesting named. The nested headers are IP-in-IP carrying protocol 4, or 41 for IPv6 in IPv4. Ethernet counts as layer 1. Cells count the inputs handed to TCP, IPv4 or IPv6 reassembly.
The default engine's rule: each VLAN tag, IP header, AH and IPv6 extension header adds one layer. A Fragment header sits at the IPv6 header's depth, plus the headers before it, plus 1. scapy also reads TCP behind 62 VLAN tags, which puts it at layer 65; the default engine reads 0 there.
On 3.11 with tshark 4.6.9, pypcapfile reads TCP in 63 and in 100 IPv4 (1/1, against the default engine's 0/0). pyshark traces it at both depths (0/1, since it declines reassembly).
Unblocked: #1652 and #1655 have merged. Workers now have the remaining dpkt and scapy parts, one PR per engine file: dpkt together with #1651 and #1654, and scapy together with #1650. Each PR cites this issue as Part of, so this closes only after both merge.
pyshark: still open. The ipv6-33/pyshark/trace Gap in test_ext_chain_limit_agreement_1604_unit.py:165-167 still holds on 3.11 with tshark 4.6.9. It also traces past the bound at 63 IPv4, 32 AH pairs, 62 802.1ad tags and 62 IPv6. That part waits for the fix(toolkit): pyshark traces TCP inside GRE, VXLAN, MPLS, PPPoE, L2TP, LLC/SNAP and ISL #1639 PR, which is editing pyshark.py.
Activity
JarryShaw commentedon Oct 10, 2026
Blocked until #1605 and #1608 merge: #1605 rewrites the dpkt and scapy TCP helpers, and #1608 adds the bound constant.
fix(protocols): bound the IPv6 extension-header chain at 32 headers (#…
JarryShaw commentedon Oct 10, 2026
Widened per the #1610 ruling. The adapters must also stop at the new per-frame bound of 64 layers, so that they agree with the default engine on deep nesting of any protocol, not only on extension-header chains. pypcapfile is in scope too, because its own walk does not go through
ProtocolBase._parse_next_layer.Blocked until #1605, #1608 and the #1610 PR merge: #1605 rewrites the adapters' TCP helpers, and the other two add the constants.
fix(protocols): bound the IPv6 extension-header chain at 32 headers (#…
JarryShaw commentedon Oct 10, 2026
#1605 and #1608 have merged. Still blocked until #1620 (the #1610 PR) merges, since the adapters must stop at its per-frame bound.
JarryShaw commentedon Oct 10, 2026
Widening: the same gap holds for IPv4 reassembly input, not only TCP. Behind 33 Destination Options headers in IPv6, the default engine hands IPv4 reassembly nothing, while scapy still hands the IPv4 fragment over (measured on
ee324e1dc). dpkt stops there once #1622 lands. So the scapy and pyshark adapters' IPv4 (and IPv6) reassembly inputs must also stop atEXTENSION_HEADER_LIMIT. Still blocked until #1620 merges.JarryShaw commentedon Oct 10, 2026
Also blocked until the #1625 PR merges, since both edit the extension-header walk in
pcapkit/toolkit/pyshark.py.fix(toolkit): pyshark traces TCP behind an IPv4 extension header as t…
JarryShaw commentedon Oct 12, 2026
#1620 has merged. Still blocked until the #1625 PR merges (it replaces #1628, closed for conflicts).
JarryShaw commentedon Oct 13, 2026
Widening: dpkt's IPv6 reassembly input also runs past the bound, not only scapy's and pyshark's. Measured on
main(b375656e1), 3.14 and 3.11. Each capture is Ethernet, then IPv6, then Destination Options headers (PadN 4), then a first-fragment Fragment header. IPv6 reassembly inputs (header length):Neither adapter counts the headers before the Fragment header. dpkt reads
ipv6.extension_hdrs.get(44)(pcapkit/toolkit/dpkt.py:902) and scapy testsIPv6ExtHdrFragment in ipv6(pcapkit/toolkit/scapy.py:536). pyshark and pypcapfile decline IPv6 reassembly, so there is nothing for them to bound.JarryShaw commentedon Oct 13, 2026
Widening: the dpkt and scapy walkers do not apply the 64-layer bound (
FRAME_LAYER_LIMIT,pcapkit/protocols/protocol.py:412, #1610). The walkers are:_tcp_carrier(pcapkit/toolkit/dpkt.py:572) and_first_ipv4(:717)_tcp_carrier(pcapkit/toolkit/scapy.py:429),_first_ipv4(:365) andipv6_reassembly(:608)Measured on
main(b375656e1) with Python 3.14.8 and 3.11.15 (dpkt 1.9.8; scapy 2.7.0 and 2.8.0). Each frame is Ethernet, then the nesting named. The nested headers are IP-in-IP carrying protocol 4, or 41 for IPv6 in IPv4. Ethernet counts as layer 1. Cells count the inputs handed to TCP, IPv4 or IPv6 reassembly.main; #1637)The default engine's rule: each VLAN tag, IP header, AH and IPv6 extension header adds one layer. A Fragment header sits at the IPv6 header's depth, plus the headers before it, plus 1. scapy also reads TCP behind 62 VLAN tags, which puts it at layer 65; the default engine reads 0 there.
On 3.11 with tshark 4.6.9, pypcapfile reads TCP in 63 and in 100 IPv4 (1/1, against the default engine's 0/0). pyshark traces it at both depths (0/1, since it declines reassembly).
fix(toolkit): pyshark traces TCP behind an IPv4 extension header as t…
1 remaining item
JarryShaw commentedon Oct 13, 2026
Unblocked: #1652 and #1655 have merged. Workers now have the remaining dpkt and scapy parts, one PR per engine file: dpkt together with #1651 and #1654, and scapy together with #1650. Each PR cites this issue as
Part of, so this closes only after both merge.JarryShaw commentedon Oct 13, 2026
Where each part stands:
ipv6-33/pyshark/traceGap intest_ext_chain_limit_agreement_1604_unit.py:165-167still holds on 3.11 with tshark 4.6.9. It also traces past the bound at 63 IPv4, 32 AH pairs, 62 802.1ad tags and 62 IPv6. That part waits for the fix(toolkit): pyshark traces TCP inside GRE, VXLAN, MPLS, PPPoE, L2TP, LLC/SNAP and ISL #1639 PR, which is editingpyshark.py.fix(toolkit): dpkt walks IP layers off the octets as the default engi…
fix(toolkit): scapy walks link types, layer bounds and IPv6 extension…
test(engines): drop the scapy gap the extension header bound now clos…
docs(protocols): the scapy toolkit stops at the extension header bound (
docs(protocols): the scapy toolkit stops at the frame layer bound (#1609
docs(protocols): the dpkt toolkit stops at both bounds too (#1609)
fix(toolkit): scapy walks link types, layer bounds and IPv6 extension…