Skip to main content

falco_plugin/listen/
mod.rs

1//! # Capture listening plugins
2//!
3//! Plugins with capture listening capability can receive notifications whenever a capture is
4//! started or stopped. Note that a capture may be stopped and restarted multiple times
5//! over the lifetime of a plugin.
6//!
7//! ## Background tasks
8//!
9//! Capture listening plugins receive a reference to a thread pool, which can be used to submit
10//! "routines" (tasks running in a separate thread, effectively).
11//!
12//! *Note* due to API limitations, this functionality cannot be made fully leak-free.
13//! When a routine gets dropped at precisely the wrong point in time (as it starts executing),
14//! it will leak a small amount of memory. Consider never dropping routines at all,
15//! and instead use some mechanism to signal to the routine that it should stop rescheduling itself
16//! (e.g., an atomic boolean in a shared struct).
17//!
18//! *Note* there is no built-in mechanism to stop a running routine, so you should avoid doing this
19//! in the routine:
20//! ```ignore
21//! loop {
22//!     do_something();
23//!     std::thread::sleep(some_time);
24//! }
25//! ```
26//!
27//! Instead, have your routine just do a single iteration and request a rerun from the scheduler:
28//! ```ignore
29//! do_something();
30//! std::thread::sleep(some_time);
31//! std::ops::ControlFlow::Continue(())
32//! ```
33//!
34//! If you insist on using an infinite loop inside a routine, consider using e.g.
35//! [`BackgroundTask`](crate::async_event::BackgroundTask) to manage the lifetime of the routine.
36//!
37//! For your plugin to support event parsing, you will need to implement the [`CaptureListenPlugin`]
38//! trait and invoke the [`capture_listen_plugin`](crate::capture_listen_plugin) macro, for example:
39//!
40//! ```
41//!# use std::ffi::CStr;
42//!# use std::time::Duration;
43//! use falco_plugin::anyhow::Error;
44//! use falco_plugin::base::Plugin;
45//! use falco_plugin::{capture_listen_plugin, plugin};
46//! use falco_plugin::listen::{CaptureListenInput, CaptureListenPlugin, Routine};
47//!# use falco_plugin::tables::TablesInput;
48//!# use log;
49//!
50//! struct MyListenPlugin {
51//!     tasks: Vec<Routine>,
52//! }
53//!
54//! impl Plugin for MyListenPlugin {
55//!     // ...
56//! #    const NAME: &'static CStr = c"sample-plugin-rs";
57//! #    const PLUGIN_VERSION: &'static CStr = c"0.0.1";
58//! #    const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
59//! #    const CONTACT: &'static CStr = c"you@example.com";
60//! #    type ConfigType = ();
61//! #
62//! #    fn new(input: Option<&TablesInput>, config: Self::ConfigType)
63//! #        -> Result<Self, Error> {
64//! #        Ok(MyListenPlugin {
65//! #             tasks: Vec::new(),
66//! #        })
67//! #    }
68//! }
69//!
70//! impl CaptureListenPlugin for MyListenPlugin {
71//!     fn capture_open(&mut self, listen_input: &CaptureListenInput) -> Result<(), Error> {
72//!         log::info!("Capture started");
73//!         self.tasks.push(listen_input.thread_pool.subscribe(|| {
74//!             log::info!("Doing stuff in the background");
75//!             std::thread::sleep(Duration::from_millis(500));
76//!             std::ops::ControlFlow::Continue(())
77//!         })?);
78//!
79//!         Ok(())
80//!     }
81//!
82//! fn capture_close(&mut self, _listen_input: &CaptureListenInput) -> Result<(), Error> {
83//!         log::info!("Capture stopped");
84//!         self.tasks.clear(); // dropping the handles auto-unsubscribes
85//!         Ok(())
86//!     }
87//! }
88//!
89//! plugin!(MyListenPlugin);
90//! capture_listen_plugin!(MyListenPlugin);
91//! ```
92
93use crate::base::Plugin;
94use crate::error::last_error::LastError;
95use crate::listen::wrappers::CaptureListenPluginExported;
96use crate::tables::LazyTableReader;
97use crate::tables::LazyTableWriter;
98use falco_plugin_api::ss_plugin_capture_listen_input;
99
100mod routine;
101#[doc(hidden)]
102pub mod wrappers;
103
104pub use routine::{Routine, ThreadPool};
105
106/// Support for capture listening plugins
107pub trait CaptureListenPlugin: Plugin + CaptureListenPluginExported {
108    /// # Capture open notification
109    ///
110    /// This method gets called whenever the capture is started
111    fn capture_open(&mut self, listen_input: &CaptureListenInput) -> Result<(), anyhow::Error>;
112
113    /// # Capture close notification
114    ///
115    /// This method gets called whenever the capture is stopped
116    fn capture_close(&mut self, listen_input: &CaptureListenInput) -> Result<(), anyhow::Error>;
117}
118
119/// # The input to a capture listening plugin
120///
121/// It has two fields containing the vtables needed to access tables imported through
122/// the [tables API](`crate::tables`), as well as a [`ThreadPool`] to run tasks
123/// in the background.
124#[derive(Debug)]
125pub struct CaptureListenInput<'t> {
126    /// Accessors to the thread pool for submitting routines to
127    pub thread_pool: ThreadPool,
128    /// Accessors to read table entries
129    pub reader: LazyTableReader<'t>,
130    /// Accessors to modify table entries
131    pub writer: LazyTableWriter<'t>,
132}
133
134impl CaptureListenInput<'_> {
135    unsafe fn try_from(
136        value: *const ss_plugin_capture_listen_input,
137        last_error: LastError,
138    ) -> Result<Self, anyhow::Error> {
139        let input = unsafe {
140            value
141                .as_ref()
142                .ok_or_else(|| anyhow::anyhow!("Got null event parse input"))?
143        };
144
145        let thread_pool = ThreadPool::try_from(input.owner, input.routine, last_error.clone())?;
146
147        let reader = unsafe {
148            input
149                .table_reader_ext
150                .as_ref()
151                .ok_or_else(|| anyhow::anyhow!("Got null reader vtable"))?
152        };
153        let writer = unsafe {
154            input
155                .table_writer_ext
156                .as_ref()
157                .ok_or_else(|| anyhow::anyhow!("Got null writer vtable"))?
158        };
159
160        let reader = LazyTableReader::new(reader, last_error.clone());
161        let writer = LazyTableWriter::try_from(writer, last_error)?;
162
163        Ok(Self {
164            thread_pool,
165            reader,
166            writer,
167        })
168    }
169}