-
Notifications
You must be signed in to change notification settings - Fork 1.7k
rust/ffi: add flow lifecycle callback wrappers - v12 #15318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
22dba7e
rust: bindgen flow lifecycle callbacks
jasonish b4460d3
rust/ffi: add flow lifecycle callback wrappers
jasonish 5624a20
doc: document flow life cycle callback API
jasonish afde209
examples: add flow callbacks to rust plugin example
jasonish 70bf5c9
github-ci: check formatting and clippy on example rust plugin
jasonish File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
doc/userguide/devguide/extending/flow-lifecycle-callbacks.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| Flow Life Cycle Callbacks | ||
| ######################### | ||
|
|
||
| Flow lifecycle callbacks let plugins and library users observe when | ||
| Suricata initializes a flow, updates a flow with a packet, and finishes | ||
| with a flow. | ||
|
|
||
| These callbacks are useful for maintaining plugin state that follows the | ||
| lifetime of a Suricata flow. For example, a plugin can allocate per-flow | ||
| state from the init callback, update it as packets are seen, and perform | ||
| final accounting from the finish callback. | ||
|
|
||
| C API | ||
| ***** | ||
|
|
||
| Flow Init Callback | ||
| ================== | ||
|
|
||
| The init callback is called when Suricata initializes a flow. | ||
|
|
||
| .. literalinclude:: ../../../../src/flow-callbacks.h | ||
| :language: c | ||
| :start-at: /** \brief Function type for flow initialization callbacks. | ||
| :end-at: typedef void (*SCFlowInitCallbackFn)(ThreadVars *tv, Flow *f, const Packet *p, void *user); | ||
|
|
||
| Register an init callback with ``SCFlowRegisterInitCallback``. | ||
|
|
||
| .. literalinclude:: ../../../../src/flow-callbacks.h | ||
| :language: c | ||
| :start-at: /** \brief Register a flow init callback. | ||
| :end-at: bool SCFlowRegisterInitCallback(SCFlowInitCallbackFn fn, void *user); | ||
|
|
||
| Flow Update Callback | ||
| ==================== | ||
|
|
||
| The update callback is called when Suricata updates a flow with a packet. | ||
|
|
||
| .. literalinclude:: ../../../../src/flow-callbacks.h | ||
| :language: c | ||
| :start-at: /** \brief Function type for flow update callbacks. | ||
| :end-at: typedef void (*SCFlowUpdateCallbackFn)(ThreadVars *tv, Flow *f, Packet *p, void *user); | ||
|
|
||
| Register an update callback with ``SCFlowRegisterUpdateCallback``. | ||
|
|
||
| .. literalinclude:: ../../../../src/flow-callbacks.h | ||
| :language: c | ||
| :start-at: /** \brief Register a flow update callback. | ||
| :end-at: bool SCFlowRegisterUpdateCallback(SCFlowUpdateCallbackFn fn, void *user); | ||
|
|
||
| Flow Finish Callback | ||
| ==================== | ||
|
|
||
| The finish callback is called when Suricata is done with a flow. | ||
|
|
||
| .. literalinclude:: ../../../../src/flow-callbacks.h | ||
| :language: c | ||
| :start-at: /** \brief Function type for flow finish callbacks. | ||
| :end-at: typedef void (*SCFlowFinishCallbackFn)(ThreadVars *tv, Flow *f, void *user); | ||
|
|
||
| Register a finish callback with ``SCFlowRegisterFinishCallback``. | ||
|
|
||
| .. code-block:: c | ||
|
|
||
| bool SCFlowRegisterFinishCallback(SCFlowFinishCallbackFn fn, void *user); | ||
|
|
||
| Example | ||
| ======= | ||
|
|
||
| .. code-block:: c | ||
|
|
||
| static void ExampleFlowInit(ThreadVars *tv, Flow *f, const Packet *p, void *user) | ||
| { | ||
| SCLogNotice("flow initialized: %p", f); | ||
| } | ||
|
|
||
| static void ExampleFlowUpdate(ThreadVars *tv, Flow *f, Packet *p, void *user) | ||
| { | ||
| SCLogNotice("flow updated: %p packet: %p", f, p); | ||
| } | ||
|
|
||
| static void ExampleFlowFinish(ThreadVars *tv, Flow *f, void *user) | ||
| { | ||
| SCLogNotice("flow finished: %p", f); | ||
| } | ||
|
|
||
| static void ExampleInit(void) | ||
| { | ||
| SCFlowRegisterInitCallback(ExampleFlowInit, NULL); | ||
| SCFlowRegisterUpdateCallback(ExampleFlowUpdate, NULL); | ||
| SCFlowRegisterFinishCallback(ExampleFlowFinish, NULL); | ||
| } | ||
|
|
||
| Rust API | ||
| ******** | ||
|
|
||
| In Rust, use the ``suricata_ffi::flow`` module: | ||
|
|
||
| - ``flow::register_init_callback`` | ||
| - ``flow::register_update_callback`` | ||
| - ``flow::register_finish_callback`` | ||
|
|
||
| The Rust wrappers register closures or function items and return | ||
| ``Result<(), &'static str>``. | ||
|
|
||
| .. code-block:: rust | ||
|
|
||
| use suricata_ffi::flow::{self, Flow, Packet, ThreadVars}; | ||
| use suricata_ffi::SCLogNotice; | ||
|
|
||
| fn flow_init(_tv: *mut ThreadVars, f: *mut Flow, _p: *const Packet) { | ||
| SCLogNotice!("flow initialized: {:p}", f); | ||
| } | ||
|
|
||
| fn flow_update(_tv: *mut ThreadVars, f: *mut Flow, p: *mut Packet) { | ||
| SCLogNotice!("flow updated: {:p} packet: {:p}", f, p); | ||
| } | ||
|
|
||
| fn flow_finish(_tv: *mut ThreadVars, f: *mut Flow) { | ||
| SCLogNotice!("flow finished: {:p}", f); | ||
| } | ||
|
|
||
| fn register_flow_callbacks() -> Result<(), &'static str> { | ||
| flow::register_init_callback(flow_init)?; | ||
| flow::register_update_callback(flow_update)?; | ||
| flow::register_finish_callback(flow_finish)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| The raw pointers passed into callbacks are only valid for the duration | ||
| of the callback invocation and must not be stored. Rust callbacks must | ||
| not panic. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,3 +12,4 @@ Extending Suricata | |
| output/index.rst | ||
| output/eve-filetypes.rst | ||
| output/eve-hooks.rst | ||
| flow-lifecycle-callbacks.rst | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| /* Copyright (C) 2026 Open Information Security Foundation | ||
| * | ||
| * You can copy, redistribute or modify this Program under the terms of | ||
| * the GNU General Public License version 2 as published by the Free | ||
| * Software Foundation. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * version 2 along with this program; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
| * 02110-1301, USA. | ||
| */ | ||
|
|
||
| use std::os::raw::c_void; | ||
|
|
||
| pub use suricata_sys::sys::{Flow, Packet, ThreadVars}; | ||
|
jasonish marked this conversation as resolved.
|
||
| use suricata_sys::sys::{ | ||
| SCFlowRegisterFinishCallback, SCFlowRegisterInitCallback, SCFlowRegisterUpdateCallback, | ||
| }; | ||
|
|
||
| /// Register a flow initialization callback. | ||
| /// | ||
| /// The callback is invoked whenever Suricata initializes a flow. It receives: | ||
| /// - `tv`: the `ThreadVars` for the thread creating the flow | ||
| /// - `f`: the newly initialized `Flow` | ||
| /// - `p`: the packet related to creating the flow | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The callback receives raw pointers from Suricata. These pointers are only | ||
| /// valid for the duration of the callback invocation and must not be stored. | ||
| /// | ||
| /// The callback must not panic. | ||
| pub fn register_init_callback<F>(callback: F) -> Result<(), &'static str> | ||
| where | ||
| F: Fn(*mut ThreadVars, *mut Flow, *const Packet) + Send + Sync + 'static, | ||
| { | ||
| let user = Box::into_raw(Box::new(callback)) as *mut c_void; | ||
| if unsafe { SCFlowRegisterInitCallback(Some(init_callback_wrapper::<F>), user) } { | ||
| Ok(()) | ||
| } else { | ||
| unsafe { | ||
| drop(Box::from_raw(user as *mut F)); | ||
| } | ||
| Err("Failed to register flow init callback") | ||
| } | ||
| } | ||
|
|
||
| /// Register a flow update callback. | ||
| /// | ||
| /// The callback is invoked whenever Suricata updates a flow with a packet. It | ||
| /// receives: | ||
| /// - `tv`: the `ThreadVars` for the thread updating the flow | ||
| /// - `f`: the flow being updated | ||
| /// - `p`: the packet responsible for the flow update | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The callback receives raw pointers from Suricata. These pointers are only | ||
| /// valid for the duration of the callback invocation and must not be stored. | ||
| /// | ||
| /// The callback must not panic. | ||
| pub fn register_update_callback<F>(callback: F) -> Result<(), &'static str> | ||
| where | ||
| F: Fn(*mut ThreadVars, *mut Flow, *mut Packet) + Send + Sync + 'static, | ||
| { | ||
| let user = Box::into_raw(Box::new(callback)) as *mut c_void; | ||
| if unsafe { SCFlowRegisterUpdateCallback(Some(update_callback_wrapper::<F>), user) } { | ||
| Ok(()) | ||
| } else { | ||
| unsafe { | ||
| drop(Box::from_raw(user as *mut F)); | ||
| } | ||
| Err("Failed to register flow update callback") | ||
| } | ||
| } | ||
|
|
||
| /// Register a flow finish callback. | ||
| /// | ||
| /// The callback is invoked when Suricata is finished with a flow. It receives: | ||
| /// - `tv`: the `ThreadVars` for the thread finishing the flow | ||
| /// - `f`: the flow being finished | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The callback receives raw pointers from Suricata. These pointers are only | ||
| /// valid for the duration of the callback invocation and must not be stored. | ||
| /// | ||
| /// The callback must not panic. | ||
| pub fn register_finish_callback<F>(callback: F) -> Result<(), &'static str> | ||
| where | ||
| F: Fn(*mut ThreadVars, *mut Flow) + Send + Sync + 'static, | ||
| { | ||
| let user = Box::into_raw(Box::new(callback)) as *mut c_void; | ||
| if unsafe { SCFlowRegisterFinishCallback(Some(finish_callback_wrapper::<F>), user) } { | ||
| Ok(()) | ||
| } else { | ||
| unsafe { | ||
| drop(Box::from_raw(user as *mut F)); | ||
| } | ||
| Err("Failed to register flow finish callback") | ||
| } | ||
| } | ||
|
|
||
| unsafe extern "C" fn init_callback_wrapper<F>( | ||
| tv: *mut ThreadVars, f: *mut Flow, p: *const Packet, user: *mut c_void, | ||
| ) where | ||
| F: Fn(*mut ThreadVars, *mut Flow, *const Packet) + Send + Sync + 'static, | ||
| { | ||
| let callback = &*(user as *const F); | ||
| callback(tv, f, p); | ||
| } | ||
|
|
||
| unsafe extern "C" fn update_callback_wrapper<F>( | ||
| tv: *mut ThreadVars, f: *mut Flow, p: *mut Packet, user: *mut c_void, | ||
| ) where | ||
| F: Fn(*mut ThreadVars, *mut Flow, *mut Packet) + Send + Sync + 'static, | ||
| { | ||
| let callback = &*(user as *const F); | ||
| callback(tv, f, p); | ||
| } | ||
|
|
||
| unsafe extern "C" fn finish_callback_wrapper<F>( | ||
| tv: *mut ThreadVars, f: *mut Flow, user: *mut c_void, | ||
| ) where | ||
| F: Fn(*mut ThreadVars, *mut Flow) + Send + Sync + 'static, | ||
| { | ||
| let callback = &*(user as *const F); | ||
| callback(tv, f); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.