Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
automake \
cargo-vendor \
cbindgen \
clippy \
diffutils \
numactl-devel \
dpdk-devel \
Expand Down Expand Up @@ -120,6 +121,7 @@ jobs:
python3-devel \
python3-sphinx \
python3-yaml \
rustfmt \
rust-toolset \
sudo \
which \
Expand Down Expand Up @@ -212,9 +214,12 @@ jobs:
working-directory: examples/plugins/c-custom-loggers
run: make clean all

- name: Build Rust example plugin
- name: Check and build Rust example plugin
working-directory: examples/plugins/rust
run: cargo build
run: |
cargo fmt --check
cargo clippy -- -D warnings
cargo build

- name: Install Suricata and library
run: make install install-headers install-library
Expand Down
131 changes: 131 additions & 0 deletions doc/userguide/devguide/extending/flow-lifecycle-callbacks.rst
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.
1 change: 1 addition & 0 deletions doc/userguide/devguide/extending/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Extending Suricata
output/index.rst
output/eve-filetypes.rst
output/eve-hooks.rst
flow-lifecycle-callbacks.rst
41 changes: 37 additions & 4 deletions examples/plugins/rust/src/mod.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
use std::ptr::null_mut;

use suricata_ffi::eve::{self, Flow, Packet, SCJsonBuilder, ThreadVars};
use suricata_ffi::eve::{self, SCJsonBuilder};
use suricata_ffi::flow;
use suricata_ffi::jsonbuilder::JsonBuilder;
use suricata_ffi::{SCLogError, SCLogNotice};
use suricata_sys::sys::{SCEveRegisterCallback, SCPlugin};
use suricata_sys::sys::{Flow, Packet, SCEveRegisterCallback, SCPlugin, ThreadVars};

unsafe extern "C" fn init() {
suricata_ffi::plugin::init();
SCLogNotice!("Initializing rust example plugin");

if let Err(err) = register() {
SCLogError!("Failed to register rust example EVE callback: {}", err);
if let Err(err) = register_eve_callbacks() {
SCLogError!("Failed to register rust example EVE callbacks: {}", err);
}
if let Err(err) = register_flow_callbacks() {
SCLogError!("Failed to register rust example flow callbacks: {}", err);
}
}

pub fn register() -> Result<(), &'static str> {
register_eve_callbacks()?;
register_flow_callbacks()?;
Ok(())
}

pub fn register_eve_callbacks() -> Result<(), &'static str> {
if !unsafe { SCEveRegisterCallback(Some(log_eve_raw), null_mut()) } {
return Err("Failed to register raw EVE callback");
}
eve::register_callback(log_eve_wrapped)
}

pub fn register_flow_callbacks() -> Result<(), &'static str> {
flow::register_init_callback(log_flow_init)?;
flow::register_update_callback(log_flow_update)?;
flow::register_finish_callback(log_flow_finish)?;
Ok(())
}

unsafe extern "C" fn log_eve_raw(
_tv: *mut ThreadVars,
_p: *const Packet,
Expand All @@ -47,6 +64,22 @@ fn log_eve_wrapped(
Ok(())
}

fn log_flow_init(_tv: *mut ThreadVars, _f: *mut Flow, _p: *const Packet) {
SCLogNotice!("rust example flow init callback: flow={:p}", _f);
}

fn log_flow_update(_tv: *mut ThreadVars, _f: *mut Flow, _p: *mut Packet) {
SCLogNotice!(
"rust example flow update callback: flow={:p}, packet={:p}",
_f,
_p
);
}

fn log_flow_finish(_tv: *mut ThreadVars, _f: *mut Flow) {
SCLogNotice!("rust example flow finish callback: flow={:p}", _f);
}

#[no_mangle]
extern "C" fn SCPluginRegister() -> *mut SCPlugin {
suricata_ffi::plugin::Plugin {
Expand Down
1 change: 1 addition & 0 deletions rules/ftp-events.rules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

alert ftp any any -> any any (msg:"SURICATA FTP Request command too long"; flow:to_server; app-layer-event:ftp.request_command_too_long; classtype:protocol-command-decode; sid:2232000; rev:1;)
alert ftp any any -> any any (msg:"SURICATA FTP Response command too long"; flow:to_client; app-layer-event:ftp.response_command_too_long; classtype:protocol-command-decode; sid:2232001; rev:1;)
alert ftp any any -> any any (msg:"SURICATA FTP too many transactions"; app-layer-event:ftp.too_many_transactions; classtype:protocol-command-decode; sid:2232002; rev:1;)
134 changes: 134 additions & 0 deletions rust/ffi/src/flow.rs
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;

use suricata_sys::sys::{Flow, Packet, ThreadVars};
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);
}
1 change: 1 addition & 0 deletions rust/ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod conf;
pub mod debug;
pub mod detect;
pub mod eve;
pub mod flow;
pub mod jsonbuilder;
pub mod plugin;

Expand Down
Loading
Loading