29.4.10. 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.

29.4.10.1. C API

29.4.10.1.1. Flow Init Callback

The init callback is called when Suricata initializes a flow.

/** \brief Function type for flow initialization callbacks.
 *
 * Once registered with SCFlowRegisterInitCallback, this function will
 * be called every time a flow is initialized, or in other words,
 * every time Suricata picks up a flow.
 *
 * \param tv The ThreadVars data structure for the thread creating the
 *     flow.
 * \param f The newly initialized flow.
 * \param p The packet related to creating the new flow.
 * \param user The user data provided during callback registration.
 */
typedef void (*SCFlowInitCallbackFn)(ThreadVars *tv, Flow *f, const Packet *p, void *user);

Register an init callback with SCFlowRegisterInitCallback.

/** \brief Register a flow init callback.
 *
 * Register a user provided function to be called every time a flow is
 * initialized for use.
 *
 * \param fn Pointer to function to be called
 * \param user Additional user data to be passed to callback
 *
 * \returns true if callback was registered, otherwise false if the
 *     callback could not be registered due to memory allocation error.
 */
bool SCFlowRegisterInitCallback(SCFlowInitCallbackFn fn, void *user);

29.4.10.1.2. Flow Update Callback

The update callback is called when Suricata updates a flow with a packet.

/** \brief Function type for flow update callbacks.
 *
 * Once registered with SCFlowRegisterUpdateCallback, this function
 * will be called every time a flow is updated by a packet (basically
 * everytime a packet is seen on a flow).
 *
 * \param tv The ThreadVars data structure for the thread updating the
 *     flow.
 * \param f The flow being updated.
 * \param p The packet responsible for the flow update.
 * \param user The user data provided during callback registration.
 */
typedef void (*SCFlowUpdateCallbackFn)(ThreadVars *tv, Flow *f, Packet *p, void *user);

Register an update callback with SCFlowRegisterUpdateCallback.

/** \brief Register a flow update callback.
 *
 * Register a user provided function to be called everytime a flow is
 * updated.
 *
 * \param fn Pointer to function to be called
 * \param user Additional user data to be passed to callback
 *
 * \returns true if callback was registered, otherwise false if the
 *     callback could not be registered due to memory allocation error.
 */
bool SCFlowRegisterUpdateCallback(SCFlowUpdateCallbackFn fn, void *user);

29.4.10.1.3. Flow Finish Callback

The finish callback is called when Suricata is done with a flow.

/** \brief Function type for flow finish callbacks.
 *
 * Once registered with SCFlowRegisterFinshCallback, this function
 * will be called when Suricata is done with a flow.
 *
 * \param tv The ThreadVars data structure for the thread finishing
 *     the flow.
 * \param f The flow being finshed.
 * \param user The user data provided during callback registration.
 */
typedef void (*SCFlowFinishCallbackFn)(ThreadVars *tv, Flow *f, void *user);

Register a finish callback with SCFlowRegisterFinishCallback.

bool SCFlowRegisterFinishCallback(SCFlowFinishCallbackFn fn, void *user);

29.4.10.1.4. Example

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);
}

29.4.10.2. 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>.

use suricata_ffi::flow::{self, Flow};
use suricata_ffi::thread::ThreadVars;
use suricata_sys::sys::Packet;
use suricata_ffi::SCLogNotice;

fn flow_init(_tv: &mut ThreadVars, f: &mut Flow, _p: *const Packet) {
    SCLogNotice!("flow initialized: {:p}", f.as_ptr());
}

fn flow_update(_tv: &mut ThreadVars, f: &mut Flow, p: *mut Packet) {
    SCLogNotice!("flow updated: {:p} packet: {:p}", f.as_ptr(), p);
}

fn flow_finish(_tv: &mut ThreadVars, f: &mut Flow) {
    SCLogNotice!("flow finished: {:p}", f.as_ptr());
}

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.

29.4.10.2.1. Flow Storage

flow::FlowStorage<T> provides typed, per-flow storage backed by Suricata's flow storage API. Each registered slot holds an independent value of type T for every flow.

Register a slot once during initialization with FlowStorage::<T>::register. Registration must happen before Suricata finalizes its storage registration, which is the case during plugin initialization.

use suricata_ffi::flow::{self, Flow, FlowStorage};
use suricata_ffi::thread::ThreadVars;
use suricata_sys::sys::Packet;

#[derive(Default)]
struct FlowState {
    packets: u64,
}

fn register(storage: FlowStorage<FlowState>) -> Result<(), &'static str> {
    flow::register_update_callback(move |tv, f, p| on_flow_update(storage, tv, f, p))
}

Values are owned by Suricata's flow storage and are dropped automatically when the flow's storage is freed.

fn on_flow_init(storage: FlowStorage<FlowState>, _tv: &mut ThreadVars, f: &mut Flow, _p: *const Packet) {
    let _ = storage.get_or_insert_with(f, FlowState::default);
}

fn on_flow_update(storage: FlowStorage<FlowState>, _tv: &mut ThreadVars, f: &mut Flow, _p: *mut Packet) {
    if let Some(state) = storage.get_mut(f) {
        state.packets += 1;
    }
}