falco_plugin/async_event/mod.rs
1//! # Asynchronous event support
2//!
3//! Plugins with async events capability can enrich an event stream from a given source (not
4//! necessarily implemented by itself) by injecting events asynchronously in the stream. Such
5//! a feature can be used for implementing notification systems or recording state transitions
6//! in the event-driven model of the Falcosecurity libraries, so that they can be available to other
7//! components at runtime or when the event stream is replayed through a capture file.
8//!
9//! For example, the Falcosecurity libraries leverage this feature internally to implement metadata
10//! enrichment systems such as the one related to container runtimes. In that case, the libraries
11//! implement asynchronous jobs responsible for retrieving such information externally outside
12//! the main event processing loop so that it's non-blocking. The worker jobs produce a notification
13//! event every time a new container is detected and inject it asynchronously in the system event
14//! stream to be later processed for state updates and for evaluating Falco rules.
15//!
16//! For your plugin to support asynchronous events, you will need to implement the [`AsyncEventPlugin`]
17//! trait and invoke the [`async_event_plugin`](crate::async_event_plugin) macro, for example:
18//!
19//! ```
20//! use std::ffi::{CStr, CString};
21//! use std::sync::Arc;
22//! use std::thread::JoinHandle;
23//! use falco_plugin::anyhow::Error;
24//! use falco_plugin::event::events::Event;
25//! use falco_plugin::event::events::EventMetadata;
26//! use falco_plugin::base::Plugin;
27//! use falco_plugin::{async_event_plugin, plugin};
28//! use falco_plugin::async_event::{
29//! AsyncEventPlugin,
30//! AsyncHandler,
31//! BackgroundTask};
32//! use falco_plugin::tables::TablesInput;
33//!
34//! struct MyAsyncPlugin {
35//! task: Arc<BackgroundTask>,
36//! thread: Option<JoinHandle<Result<(), Error>>>,
37//! }
38//!
39//! impl Plugin for MyAsyncPlugin {
40//! // ...
41//! # const NAME: &'static CStr = c"sample-plugin-rs";
42//! # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
43//! # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
44//! # const CONTACT: &'static CStr = c"you@example.com";
45//! # type ConfigType = ();
46//! #
47//! # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
48//! # -> Result<Self, Error> {
49//! # Ok(MyAsyncPlugin {
50//! # task: Arc::new(Default::default()),
51//! # thread: None,
52//! # })
53//! # }
54//! }
55//!
56//! impl AsyncEventPlugin for MyAsyncPlugin {
57//! const ASYNC_EVENTS: &'static [&'static str] = &[]; // generate any async events
58//! const EVENT_SOURCES: &'static [&'static str] = &[]; // attach to all event sources
59//!
60//! fn start_async(&mut self, handler: AsyncHandler) -> Result<(), Error> {
61//! // stop the thread if it was already running
62//! if self.thread.is_some() {
63//! self.stop_async()?;
64//! }
65//!
66//! // start a new thread
67//! // waiting up to 100ms between events for the stop request
68//! self.thread = Some(self.task.spawn(std::time::Duration::from_millis(100), move || {
69//! // submit an async event to the main event loop
70//! handler.emit(Self::async_event(c"sample_async", b"hello"))?;
71//! Ok(())
72//! })?);
73//! Ok(())
74//! }
75//!
76//! fn stop_async(&mut self) -> Result<(), Error> {
77//! self.task.request_stop_and_notify()?;
78//! let Some(handle) = self.thread.take() else {
79//! return Ok(());
80//! };
81//!
82//! match handle.join() {
83//! Ok(res) => res,
84//! Err(e) => std::panic::resume_unwind(e),
85//! }
86//! }
87//! }
88//!
89//! plugin!(MyAsyncPlugin);
90//! async_event_plugin!(MyAsyncPlugin);
91//! ```
92
93use crate::async_event::wrappers::AsyncPluginExported;
94use crate::base::Plugin;
95use falco_event::events::Event;
96
97mod async_handler;
98mod background_task;
99#[doc(hidden)]
100pub mod wrappers;
101
102pub use crate::event::AsyncEvent;
103pub use async_handler::AsyncHandler;
104pub use background_task::BackgroundTask;
105
106/// Support for asynchronous event plugins
107pub trait AsyncEventPlugin: Plugin + AsyncPluginExported {
108 /// # Event names coming from this plugin
109 ///
110 /// This constant contains a list describing the name list of all asynchronous events
111 /// that this plugin is capable of pushing into a live event stream. The framework rejects
112 /// async events produced by a plugin if their name is not on the name list returned by this
113 /// function.
114 const ASYNC_EVENTS: &'static [&'static str];
115 /// # Event sources to attach asynchronous events to
116 ///
117 /// This constant contains a list describing the event sources for which this plugin
118 /// is capable of injecting async events in the event stream of a capture.
119 ///
120 /// This is optional--if NULL or an empty array, then async events produced by this plugin will
121 /// be injected in the event stream of any data source.
122 ///
123 /// **Note**: one notable event source is called `syscall`
124 const EVENT_SOURCES: &'static [&'static str];
125
126 /// # Start asynchronous event generation
127 ///
128 /// When this method is called, your plugin should start whatever background mechanism
129 /// is necessary (e.g. spawn a separate thread) and use the [`AsyncHandler::emit`] method
130 /// to inject events to the main event loop.
131 ///
132 /// **Note**: you must provide a mechanism to shut down the thread upon a call to [`AsyncEventPlugin::stop_async`].
133 /// This may involve e.g. a [`std::sync::Condvar`] that's checked via [`std::sync::Condvar::wait_timeout`]
134 /// by the thread.
135 ///
136 /// **Note**: one notable event source is called `syscall`
137 fn start_async(&mut self, handler: AsyncHandler) -> Result<(), anyhow::Error>;
138
139 /// # Stop asynchronous event generation
140 ///
141 /// When this method is called, your plugin must stop the background mechanism started by
142 /// [`AsyncEventPlugin::start_async`] and wait for it to finish (no calls to [`AsyncHandler::emit`]
143 /// are permitted after this method returns).
144 ///
145 /// **Note**: [`AsyncEventPlugin::start_async`] can be called again, with a different [`AsyncHandler`].
146 fn stop_async(&mut self) -> Result<(), anyhow::Error>;
147
148 /// # Dump the plugin state as a series of async events
149 ///
150 /// When this method is called, your plugin may save its state via a series of async events
151 /// that will be replayed when a capture file is loaded.
152 ///
153 /// The default implementation does nothing.
154 fn dump_state(&mut self, _handler: AsyncHandler) -> Result<(), anyhow::Error> {
155 Ok(())
156 }
157
158 /// # A helper method to create an asynchronous event
159 fn async_event<'a>(
160 name: &'a std::ffi::CStr,
161 data: &'a [u8],
162 ) -> Event<AsyncEvent<'a, &'a [u8]>> {
163 let event = AsyncEvent {
164 plugin_id: 0, // gets populated by the framework, shall be None
165 name,
166 data,
167 };
168
169 let metadata = falco_event::events::EventMetadata::default();
170
171 Event {
172 metadata,
173 params: event,
174 }
175 }
176}