rust/ffi: add eve callback handler

Wrap the EVE callback handler with a Rust friendly variant that allows
the user to register a callback as a closure which is provided an
already wrapped JsonBuilder object.

Ticket: #8477
pull/15217/head
Jason Ish 4 months ago committed by Victor Julien
parent 199e844a07
commit eb46d0129e

@ -7,10 +7,13 @@ an EVE record before it is written.
It is important to note that it does not allow for modification of the
EVE record due to the append only nature of Suricata's EVE output.
C API
*****
Registration
************
============
Registering the callback is done with ``SCEveRegisterCallback``.
In C, registering the callback is done with ``SCEveRegisterCallback``.
.. literalinclude:: ../../../../../src/output-eve.h
:language: c
@ -18,7 +21,7 @@ Registering the callback is done with ``SCEveRegisterCallback``.
:end-at: );
Callback
********
========
The callback function is provided with an open ``SCJsonBuilder``
instance just before being closed out with a final ``}``. Additional
@ -30,14 +33,51 @@ fields can be added with the ``SCJsonBuilder`` API.
:end-at: );
Example
*******
=======
For a real-life example, see the ``ndpi`` plugin included in the
For a real-life C example, see the ``ndpi`` plugin included in the
Suricata source.
The example demonstrates:
That example demonstrates:
- Registering an EVE callback during plugin initialization
- Using thread-local storage to maintain state
- Adding protocol-specific information to EVE records
- Properly checking for NULL pointers before accessing data
Rust API
********
In Rust, use ``suricata_ffi::eve::register_callback``. This wraps the C
API and lets the callback be registered as a Rust closure instead of a C
function pointer plus ``user`` pointer.
The closure receives:
- ``tv``: the ``ThreadVars`` for the thread performing the logging
- ``p``: the ``Packet``, if available
- ``f``: the ``Flow``, if available
- ``jb``: a Rust ``JsonBuilder`` wrapper for the current EVE record
Unlike the C API, the Rust callback returns ``Result<(), Error>``. If it
returns ``Err``, any JSON emitted by that callback is discarded.
.. code-block:: rust
use suricata_ffi::eve;
eve::register_callback(|_tv, _p, _f, jb| {
jb.open_object("my_plugin")?;
jb.set_string("key", "value")?;
jb.close()?;
Ok(())
}).expect("failed to register EVE callback");
The Rust callback is invoked at the same point, but it receives a
``JsonBuilder`` wrapper instead of a raw ``SCJsonBuilder`` pointer.
The raw pointers passed into the callback are only valid for the
duration of the callback and must not be stored. The callback must also
not panic.
This API is safe for library and plugins.

@ -16,10 +16,13 @@
*/
use std::ffi::CString;
use std::os::raw::c_void;
pub use suricata_sys::sys::{Flow, Packet, SCEveUserCallbackFn, SCJsonBuilder, ThreadVars};
use suricata_sys::sys::{
SCEveFileType, SCEveFileTypeDeinitFunc, SCEveFileTypeInitFunc, SCEveFileTypeThreadDeinitFunc,
SCEveFileTypeThreadInitFunc, SCEveFileTypeWriteFunc, SCRegisterEveFileType,
SCEveFileTypeThreadInitFunc, SCEveFileTypeWriteFunc, SCEveRegisterCallback,
SCRegisterEveFileType,
};
pub struct EveFileType {
@ -67,3 +70,86 @@ impl EveFileType {
}
}
}
/// Register an EVE callback.
///
/// The callback is invoked just before the top-level EVE JSON object
/// is closed. New fields may be added at that point, but objects and
/// fields already written to the `JsonBuilder` cannot be altered.
///
/// The callback receives:
/// - `tv`: the `ThreadVars` for the thread performing the logging
/// - `p`: the `Packet`, if available
/// - `f`: the `Flow`, if available
/// - `jb`: the JSON builder for the current EVE record
///
/// This API is intended for plugin and library users.
///
/// # Example
///
/// ```no_run
/// use suricata_ffi::eve;
///
/// eve::register_callback(|_tv, _p, _f, jb| {
/// jb.open_object("my_plugin")?;
/// jb.set_string("key", "value")?;
/// jb.close()?;
/// Ok(())
/// }).expect("failed to register EVE callback");
/// ```
///
/// If the callback returns `Err`, any JSON emitted by that callback
/// is discarded by restoring the builder to its initial mark.
///
/// # 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_callback<F>(callback: F) -> Result<(), &'static str>
where
F: Fn(
*mut ThreadVars,
*const Packet,
*mut Flow,
&mut crate::jsonbuilder::JsonBuilder,
) -> Result<(), crate::jsonbuilder::Error>
+ Send
+ Sync
+ 'static,
{
let user = Box::into_raw(Box::new(callback)) as *mut c_void;
if unsafe { SCEveRegisterCallback(Some(callback_wrapper::<F>), user) } {
Ok(())
} else {
unsafe {
drop(Box::from_raw(user as *mut F));
}
Err("Failed to register EVE callback")
}
}
/// Internal wrapper used to adapt the C EVE callback to a Rust
/// closure callback.
unsafe extern "C" fn callback_wrapper<F>(
tv: *mut ThreadVars, p: *const Packet, f: *mut Flow, jb: *mut SCJsonBuilder, user: *mut c_void,
) where
F: Fn(
*mut ThreadVars,
*const Packet,
*mut Flow,
&mut crate::jsonbuilder::JsonBuilder,
) -> Result<(), crate::jsonbuilder::Error>
+ Send
+ Sync
+ 'static,
{
let callback = &*(user as *const F);
let mut jb = crate::jsonbuilder::JsonBuilder::from_raw(jb);
let mark = jb.get_mark();
if callback(tv, p, f, &mut jb).is_err() {
let _ = jb.restore_mark(&mark);
}
}

Loading…
Cancel
Save