Repository navigation
fix(foundation): read every frame from a pipe or non-seekable stream (#1586) - #1595
Conversation
|
Coverage: 89.66% (unit tier, Python 3.14,
Per-file detail: the |
|
Verdict: NEEDS CHANGES at Blocking: a live PCAP-NG pipe now stalls.
Fix: limit the PCAP-NG read-ahead to the block's declared length, capped at the buffer. Confirmed:
|
912118b to
12192f5
Compare
12192f5 to
35117e1
Compare
|
Verdict: NEEDS CHANGES at Confirmed:
Required: a block or record larger than the 131,072-octet buffer still differs from a path read. I measured this on
Round 3 will also cover what round 2 did not reach: malformed blocks other than oversize ones, fail-before on |
…1586) - SeekableReader.seek(0, SEEK_END) answered with the buffer's far edge, which once the buffer has filled is how far the stream has been read; the record after the one that filled it measured nothing left, so a pipe ended at frame 870 of 1,117. It now reads ahead past the position, or to the real end. - SeekableReader.read and a forward seek took one short read for all there was; both now read until the octets asked for arrive or the stream ends, and a forward seek lands on its target rather than wherever the read stopped. - Extractor and the PCAP-NG engine read the magic number and block type again when peek() comes back short. - The engines read ahead exactly the record about to be read -- a PCAP record by its incl_len, a PCAP-NG block by its Total Length -- and the buffer grows to hold one longer than itself, so a live capture is not held back and an oversize record is read whole. - SeekableReader.flush skips a stream its owner has already closed. Tests: a pipe and a short-read raw stream at 1, 7, 4096 and 65536 octets, PCAP and PCAP-NG, default and dpkt, no_eof, truncation, malformed and oversize records, live pipes, the CLI on stdin; all fail on e9f3340.
35117e1 to
4bb6549
Compare
|
Verdict: GOOD TO GO at
Non-blocking. The grown buffer never shrinks, so it is bounded by the largest record seen. A bogus 32-bit length on a never-ending live pipe can buffer up to ~4 GiB before it errors, where a file read stops at the file's end. |
Please follow the guide below
You will be asked some questions, please read them carefully and answer honestly
Put an
xinto all the boxes [ ] relevant to your pull request (like that [x])Use Preview tab to see how your pull request will actually look like
Searched for similar pull requests
Followed the coding style (
make pylint,make mypy,make isort)make testpasses, and a test case covers the changeAdded a changelog entry under
docs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
Tick the commit type your subject line carries.
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingrelease— bumps the version or rolls up a distributionchore— anything elseDescription of your pull request and other information
Root cause (lines on
e9f3340f7), all inSeekableReader, which wraps every non-seekablefin:pcapkit/corekit/io.py:351—seek(0, SEEK_END)was the buffer's far edge. Once the 131,072-octet buffer fills, that edge is exactly how far the stream has been read, soFrame._check_record_floor(frame.py:487) measures 0 left after the first record ending past it: frame 870 ofhttp.pcap. Now_read_aheadreads past the position, or to the real end.io.py:522,io.py:538—read(n)made one stream read, short on a pipe or raw stream (7-octet chunks gave 0 frames).io.py:386-387— a forward seek did the same, and could overshoot. Both now go through_fill.extraction.py:1391,engines/pcapng.py:372— a shortpeek(4)failed the magic number, and skipped the packet-block-before-IDB check. Both fall back to read-and-seek-back.Read-ahead covers exactly the next record, so a live
pcapkit -is not held back: a PCAP record header and itsincl_len; a PCAP-NG block's Total Length (BOM byte order for an SHB). The buffer grows to hold a record longer than itself, so an oversize EPB, SHB or PCAP record is read whole; a bogus length reads in 1 MiB pieces to the real end, as a file read does. A block shorter than its fixed fields, or not word-aligned, keeps the buffer's worth, and a DSB extends to its Secrets Length: both are parsed past their end from a file too. Live pipe, 100 frames 50 ms apart: PCAP 100 before close (median 0.4 ms), PCAP-NG 100 (0.6 ms);main50 and 48.Also:
flush()skips an already-closed stream (the dpktNeedData"Exception ignored" noise); positions before a read-aheadSEEK_ENDare documented as possibly gone;test_io.py'sseek(-1, SEEK_END)pin moves 3→4.Tests: 24, all failing on
e9f3340f7: pipe/raw/buffered at 1/7/4096/65536 octets ×no_eof0/1, PCAP and two-section PCAP-NG, default and dpkt (scapy refuses streams); truncation; malformed and oversize records, including dpkt's seek back over an oversize SHB; live PCAP and PCAP-NG; CLI on stdin, PCAP→PCAP and PCAP-NG→JSON. Frames and warnings match the path read.Selections run (not the whole suite): the two new test files 11 and 13 passed;
tests/corekit/test_io.py34; othertest_extraction*.pyandtest_io*.py98;tests/foundation/engines142 passed, 840 skipped;tests/protocols/misc,tests/cli,test_cli_subprocess.py250. Coverage:io.py100%,engines/pcapng.py99%, as before.Closes #1586