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