feat(userspace/libscap): Add a raw block engine - #3038
Conversation
|
The corresponding Stratoshark merge request can be found at https://gitlab.com/wireshark/wireshark/-/merge_requests/25550. |
Perf diff from master - unit testsHeap diff from master - unit testsHeap diff from master - scap fileBenchmarks diff from master |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3038 +/- ##
==========================================
- Coverage 75.74% 75.56% -0.19%
==========================================
Files 299 299
Lines 33251 33331 +80
Branches 5139 5181 +42
==========================================
Hits 25187 25187
- Misses 8064 8144 +80
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
8f2978c to
0466770
Compare
Add an engine that lets you process individual scap / pcapng blocks in memory. This lets another application that might have a scap file open make use of the analysis features in libsinsp. Assisted-by: Claude:claude-opus-4-6 claude-opus-4-8 Signed-off-by: Gerald Combs <gerald@wireshark.org>
0466770 to
8144283
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new libscap “raw_block” engine and corresponding libsinsp API to process scap/pcapng blocks from an in-memory buffer, enabling external applications to reuse libsinsp analysis without concurrently opening capture files.
Changes:
- Introduces the
raw_blocklibscap engine (plus a membuf reader) and wires it into engine selection/build config. - Exposes
sinsp::open_raw_block()and updates the sinsp example + CI to exercise the new engine. - Refactors parts of the savefile engine to share block parsing/event reading code with the new engine.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| userspace/libsinsp/sinsp.h | Adds open_raw_block() API and documentation. |
| userspace/libsinsp/sinsp.cpp | Implements sinsp::open_raw_block() using the new engine/platform. |
| userspace/libsinsp/examples/test.cpp | Adds --raw_block option and in-memory (optionally gzip-inflated) replay logic. |
| userspace/libsinsp/examples/CMakeLists.txt | Links the example against zlib for gzip inflation support. |
| userspace/libscap/scap.h | Includes the raw_block engine public header. |
| userspace/libscap/scap_engines.h | Declares scap_raw_block_engine under HAS_ENGINE_RAW_BLOCK. |
| userspace/libscap/scap_config.h.in | Adds HAS_ENGINE_RAW_BLOCK configure define. |
| userspace/libscap/engine/savefile/scap_savefile.c | Exposes savefile init/next helpers for reuse by raw_block. |
| userspace/libscap/engine/savefile/savefile.h | Declares the newly exposed savefile helper APIs. |
| userspace/libscap/engine/raw_block/scap_reader_membuf.c | New reader implementation over a memory buffer. |
| userspace/libscap/engine/raw_block/scap_raw_block.c | New engine implementation reusing savefile parsing + converter. |
| userspace/libscap/engine/raw_block/raw_block.h | Defines raw_block engine handle type (alias of savefile_engine). |
| userspace/libscap/engine/raw_block/raw_block_public.h | Public params/docs for configuring the raw_block engine. |
| userspace/libscap/engine/raw_block/raw_block_platform.h | Platform struct definition for raw_block. |
| userspace/libscap/engine/raw_block/CMakeLists.txt | Adds the raw_block engine static library target and deps. |
| userspace/libscap/CMakeLists.txt | Builds/links raw_block engine when enabled. |
| cmake/modules/engine_config.cmake | Enables HAS_ENGINE_RAW_BLOCK by default. |
| .github/workflows/ci.yml | Runs sinsp-example with --raw_block and diffs output vs savefile engine. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| scap_platform* platform = scap_raw_block_alloc_platform({::on_proc_table_refresh_start, | ||
| ::on_proc_table_refresh_end, | ||
| ::on_new_entry_from_proc, | ||
| this}); | ||
| params.platform = platform; | ||
| oargs.engine_params = ¶ms; |
There was a problem hiding this comment.
Is this the right place for the check? We don't check for null in the other open_XXX routines, and if we do care we should probably check in sinsp::open_common().
Fix a few issues found by Copilot. Assisted-by: GitHub Copilot Signed-off-by: Gerald Combs <gerald@wireshark.org>
Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Gerald Combs <gerald@wireshark.org>
| this}); | ||
| params.platform = platform; | ||
| oargs.engine_params = ¶ms; | ||
| try_open_common(&oargs, &scap_raw_block_engine, platform, SINSP_MODE_CAPTURE); |
There was a problem hiding this comment.
Should we have a separate SINSP_MODE_RAW_BLOCK and corresponding sinsp::is_raw_block()?
Don't set our mode until our platform has been successfully initialized. Signed-off-by: Gerald Combs <gerald@wireshark.org>
|
LGTM label has been added. DetailsGit tree hash: 571c909d00135556b3a7d4768f217ef7babe924f |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ekoops, geraldcombs The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
leogr
left a comment
There was a problem hiding this comment.
Hi,
This is a really nice work 👍
I took a look by myself and then with the help of https://github.com/leogr/falco-expert
It SGTM already, I just have one doubt before the final approve, plus a few minor things (that you're free to ignore 😅 )
See inline comments 👇
|
|
||
| // Our platform is initialized. Save the mode used by the inspector. | ||
| // (should this be a member of scap_platform instead?) | ||
| m_mode = mode; |
There was a problem hiding this comment.
I have a doubt here 🤔
On master, m_mode is set at the very top of open_common(), before scap_init(). This moves it after scap_platform_init(), so during the proc-table scan (on_new_entry_from_proc) m_mode is still SINSP_MODE_NONE (it hasn't been set to mode yet).
That scan runs inside scap_init() for the capture engines (savefile/raw_block populate the proclist from the metadata blocks via scap_savefile_read_init()), and inside scap_platform_init() for nodriver. In both cases on_new_entry_from_proc reads the mode:
is_capture()(sinsp.cpp:1006 and :1095) gates the internal-filter simulation that setsm_filtered_outon the threads/fds loaded from the capture. With the mode not set yet, that simulation is skipped, so a capture opened with an internal filter wouldn't mark those entries as filtered out (i.e., they'd end up in a re-dumped file).is_nodriver()(sinsp.cpp:979) selects the dedup-by-clone_tspath during the procfs scan.
So this looks like a behavioral change for offline captures (and nodriver), not just raw_block. Was that intended?
If the goal is just to avoid leaving a stale m_mode when the open fails, may we reset it in the try_open_common() catch instead, and keep the assignment before scap_init()? That way the proc-scan callbacks still see the right mode.
wdyt? 🤔
| this}); | ||
| params.platform = platform; | ||
| oargs.engine_params = ¶ms; | ||
| try_open_common(&oargs, &scap_raw_block_engine, platform, SINSP_MODE_CAPTURE); |
There was a problem hiding this comment.
Should we have a separate SINSP_MODE_RAW_BLOCK and corresponding sinsp::is_raw_block()?
I'd avoid it, unless we have a concrete need to tell raw_block apart from a regular capture. Since the engine behaves exactly like an offline capture, SINSP_MODE_CAPTURE looks correct to me, and a new mode would mean auditing all the is_capture()/is_offline() call sites to preserve the offline behavior we get for free here, for no real gain atm.
If a use case for distinguishing them shows up later, we can add it then 👍
| ::on_new_entry_from_proc, | ||
| this}); | ||
| params.platform = platform; | ||
| oargs.engine_params = ¶ms; |
There was a problem hiding this comment.
Is this the right place for the check? [...] if we do care we should probably check in sinsp::open_common()
Agreed, I wouldn't special-case it here. The engine init() already bails out on a NULL platform (scap_raw_block.c:105), and both scap_platform_close() and scap_platform_free() handle NULL (scap_platform.c:109 / :128), so try_open_common() won't blow up even on the OOM path.
So this is consistent with the other open_* routines and fine as-is IMO 👍
| return SCAP_SUCCESS; | ||
| } | ||
|
|
||
| static int32_t next(struct scap_engine_handle engine, |
There was a problem hiding this comment.
Nit / non-blocking.
The conversion loop below is basically a copy of the one in the savefile engine's next() (scap_savefile.c). Since you already extracted scap_savefile_read_init() and scap_savefile_next_event_from_file() into shared helpers, may we do the same for this loop (e.g. a scap_savefile_convert_event(handle, pevent) that both engines call)?
The len > MAX_EVENT_SIZE guard was recently added to both copies, which is exactly the kind of drift that's easy to miss keeping in sync. Not a blocker for this PR, we can do it as a follow-up 🙏
| << std::endl; | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| // Demonstrate incremental feeding: open the engine with only the section header + |
There was a problem hiding this comment.
Nice that the metadata-only-then-grow path is covered here 👍
One thing: raw_block_public.h also documents feeding more blocks after next() returns SCAP_EOF (the streaming case). Here the buffer is grown to full size before the first next(), so SCAP_EOF is only hit at the true end and that resume-after-EOF path isn't exercised. AFAIK sinsp::next() doesn't set any terminal flag on SCAP_EOF, so it should work, but since that's the incremental contract Stratoshark would rely on, may we add a small case that consumes to SCAP_EOF, grows the buffer, and resumes?
Non-blocking, just to lock the contract down 🙏
| block, any metadata blocks, and zero or more event blocks. The buffer | ||
| contents are processed by calling \ref sinsp::next until it returns | ||
| \ref SCAP_EOF, which signals that the current buffer has been | ||
| consumed. Additional blocks can besupplied in one of two ways: |
There was a problem hiding this comment.
| consumed. Additional blocks can besupplied in one of two ways: | |
| consumed. Additional blocks can be supplied in one of two ways: |
Nit. besupplied 🙏
| /* | ||
|
|
||
| This engine lets you process a scap file using a memory buffer. Buffers | ||
| must be a sequence of whole pcapng blocks as defined at |
There was a problem hiding this comment.
N.B. not a blocker, and it's inherited from the savefile parser.
The doc points at the pcapng spec for the block layout, but the parser we reuse (scap_read_section_header) only accepts a native-endian SHB: a byte-swapped SHB_MAGIC (0x4D3C2B1A) is rejected with invalid magic number rather than byte-swapped. So raw_block is effectively native-endian only.
pcapng itself allows both byte orders. Since the whole point here is feeding Stratoshark-produced blocks, may we confirm those are always host-endian? Otherwise we'd want to document the constraint here, or handle the swap. wdyt? 🤔
Add an engine that lets you process individual scap / pcapng blocks in memory. This lets another application that might have a scap file open make use of the analysis features in libsinsp.
Assisted-by: Claude:claude-opus-4-6 claude-opus-4-8
What type of PR is this?
/kind feature
Any specific area of the project related to this PR?
/area libscap
/area libsinsp
Does this PR require a change in the driver versions?
What this PR does / why we need it:
This adds a "raw_block" engine, which lets you process events from scap/pcapng blocks in memory. This would let Stratoshark process scap events without having to open capture files concurrently within Stratoshark and libs: https://gitlab.com/wireshark/wireshark/-/work_items/21014.
Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
Does this PR introduce a user-facing change?: