Repository navigation
fix(reassembly): TCP completion from per-buffer gaps, mod-2**32 sequence numbers, FIN waits for reordered data - #1375
Conversation
|
Cross-review verdict on
Held up: wraps at 2³²−1, within and across buckets, and straddling retransmissions; conflicts reported mod 2³²; RST submits at once per RFC 9293 §3.10.7.4; the revert proof gives 11 failed / 3 passed. Nit: a segment ~2³¹ away still allocates ~2 GiB, which is no worse than |
f224351 to
b6bb34e
Compare
|
Round-2 verdict on The 16 MiB bound also splits a stream whose SYN was captured, and reports both halves COMPLETE. After a SYN, Restrict the split to buffers with no SYN seen. Where a split is unavoidable, report both parts PARTIAL, never COMPLETE. Add a test for the SYN-captured 20 MiB hole. Held up:
Nit: the backward check compares the segment's end, so it is 5 bytes asymmetric with the forward one. |
…nce numbers, FIN waits for reordered data - Judge each payload buffer's completion from its own gap list, not the buffer-wide hole list, which missed gaps below the first segment and was cleared by other ACK buckets (#1354). - Read every sequence number modulo 2**32 relative to the buffered data (RFC 9293 section 3.4); conflicts are reported reduced back mod 2**32 (#1355). - A FIN only fixes the end of the stream: the hole list is cut back to it and the buffer is submitted once every octet below the FIN has arrived, or at end of capture. RST still submits at once (RFC 9293 section 3.10.7.4) (#1356). - A segment that would zero-fill a gap wider than TCP.__window__ (16 MiB) splits the buffer instead. Without a SYN it starts a new stream; with one, both halves record the lost range as a gap and are reported PARTIAL. - The RFC 815 update now fills every hole a segment spans, not only the first. Closes #1354 Closes #1355 Closes #1356
b6bb34e to
c5bbadf
Compare
|
Round-3 verdict on
Nits, not blocking:
|
|
Coverage: 88.99% (unit tier, Python 3.14,
Per-file detail: the |
Searched for similar pull requests
Followed the coding style. I ran
pylintandmypyonreassembly/tcp.py: mypy shows no new errors, and pylint adds onlytoo-many-branches(17/12) onTCP.reassembly. I did not runisortproject-wide.make testpasses, and a test case covers the change. The modules are listed below.Changelog entry. N/A, added centrally after the wave.
fix— corrects a defectCloses #1354
Closes #1355
Closes #1356
fix(reassembly): TCP reports a zero-filled gap as COMPLETE when a segment arrives below the first one seen #1354:
submitjudges each ACK bucket from its ownFragment.gapinstead of the sharedhdl.hdlgets a-sys.maxsizesentinel hole below the first segment, so a later segment below it leaves a real hole.fix(reassembly): TCP has no modulo-2**32 sequence arithmetic, so a wrapped sequence number allocates about 4 GiB #1355: sequence numbers are unwrapped mod 2³² relative to the end of the bucket they continue, following RFC 9293 §3.4.
Datagram.conflictis reported mod 2³².fix(reassembly): TCP submits the buffer on FIN/RST, so data reordered behind the FIN is split off #1356: a FIN cuts
hdlback to the FIN's sequence number. The buffer is submitted once no hole is left, or else at end of capture.Bound:
TCP.__window__, 16 MiB, overridable by subclassing.Packetcarries no advertised window, so that cannot be used. RFC 7323's 2³⁰ maximum would still allow a 1 GiB pad. Linux's defaulttcp_rmemceiling is 6 MiB, so 16 MiB leaves plenty of room above real windows.N*5at0x30000000previously allocated 768 MiB.Fragment.gap, because that is already where completion is decided, so no data-class field is needed.raw, so a merge also removes the newly received range from that entry.bytearrayhooked:The three SYN tests fail on the previous head with a false COMPLETE.
Limitation, documented in the
TCPdocstring: this applies only to a buffer begun without a SYN. A FIN counts the stream as complete from the lowest octet seen. Data that arrives later, below everything seen before that FIN, therefore opens a new buffer, and the first buffer has already been reported COMPLETE although it is not. Example:[1000 A*5, FIN 1005, 990 B*10]givesB*10andA*5.Repro:
/tmp/rtissues/foundation-repro.pycheck_1tocheck_3printREPRO×3 onmainandNOREPRO×3 here.Capture regression: I dumped every datagram (completion, ids, index, payload sha1, conflicts) for 23 captures × {default, dpkt} × strict on/off. All 92 keys are identical to
main, and 28 of them are non-empty (7 captures). My earlier figure of 184 also countedscapyandpyshark, but their output matcheddefaultkey for key, so they are not independent runs.Tests:
test_tcp.py: 26 passed. Its submit test seeded holes intohdl, which pinned the old mechanism, so they now go intoFragment.gap.tests/foundation/reassembly/, plustests/integration/test_reassembly_*,traceflow/test_tcp.pyandinterface/test_core.py: all pass.tests/project: 389 passed.run_unittest_leg.py foundationunder a file-size cap: 499 tests. The only failures are the 3 already onmain(fix(dumpkit): JSON output of DPKT/Scapy frames grows without bound and fills the disk #1372, Errno 27).Revert proof against
main: withmain'stcp.pywritten over the source, the original 9 tests gave11 failed, 3 passed. Onlytest_rst_aborts_at_oncepasses on both.No breaking change: the data-class signatures are unchanged. Only the private
_update_hole_descriptorschanged signature.