falco_plugin/lib.rs
1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(missing_debug_implementations)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6// reexport dependencies
7pub use anyhow;
8pub use falco_event as event;
9pub use falco_plugin_api as api;
10pub use phf;
11pub use schemars;
12pub use serde;
13
14pub use crate::plugin::error::FailureReason;
15
16/// # The common foundation for all Falco plugins
17///
18/// All plugins must implement the [`base::Plugin`] trait which specifies some basic metadata
19/// about the plugin.
20///
21/// See the [`base::Plugin`] trait documentation for details.
22pub mod base {
23 pub use crate::plugin::base::metrics::{Metric, MetricLabel, MetricType, MetricValue};
24 pub use crate::plugin::base::Plugin;
25 pub use crate::plugin::schema::Json;
26}
27
28/// # Field extraction plugin support
29///
30/// Plugins with field extraction capability have the ability to extract information from events
31/// based on fields. For example, a field (e.g. `proc.name`) extracts a value (e.g. process name
32/// like `nginx`) from a syscall event. The plugin returns a set of supported fields, and there are
33/// functions to extract a value given an event and field. The plugin framework can then build
34/// filtering expressions (e.g. rule conditions) based on these fields combined with relational
35/// and/or logical operators.
36///
37/// For example, given the expression `ct.name=root and ct.region=us-east-1`,
38/// the plugin framework handles parsing the expression, calling the plugin to extract values for
39/// fields `ct.name`/`ct.region` for a given event, and determining the result of the expression.
40/// In a Falco output string like `An EC2 Node was created (name=%ct.name region=%ct.region)`,
41/// the plugin framework handles parsing the output string, calling the plugin to extract values
42/// for fields, and building the resolved string, replacing the template field names
43/// (e.g. `%ct.region`) with values (e.g. `us-east-1`).
44///
45/// Plugins with this capability only focus on field extraction from events generated by other
46/// plugins or by the core libraries. They do not provide an event source but can extract fields
47/// from other event sources. The supported field extraction can be generic or be tied to a specific
48/// event source. An example is JSON field extraction, where a plugin might be able to extract
49/// fields from generic JSON payloads.
50///
51/// For your plugin to support field extraction, you will need to implement the [`extract::ExtractPlugin`]
52/// trait and invoke the [`extract_plugin`] macro, for example:
53///
54/// ```
55/// use std::ffi::{CStr, CString};
56/// use anyhow::Error;
57/// use falco_event::events::types::EventType;
58/// use falco_plugin::base::{Metric, Plugin};
59/// use falco_plugin::{extract_plugin, plugin};
60/// use falco_plugin::extract::{
61/// EventInput,
62/// ExtractFieldInfo,
63/// ExtractPlugin,
64/// ExtractRequest,
65/// field};
66/// use falco_plugin::tables::TablesInput;
67///
68/// struct MyExtractPlugin;
69/// impl Plugin for MyExtractPlugin {
70/// // ...
71/// # const NAME: &'static CStr = c"sample-plugin-rs";
72/// # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
73/// # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
74/// # const CONTACT: &'static CStr = c"you@example.com";
75/// # type ConfigType = ();
76/// #
77/// # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
78/// # -> Result<Self, anyhow::Error> {
79/// # Ok(MyExtractPlugin)
80/// # }
81/// #
82/// # fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
83/// # Ok(())
84/// # }
85/// #
86/// # fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
87/// # []
88/// # }
89/// }
90///
91/// impl MyExtractPlugin { // note this is not the trait implementation
92/// fn extract_sample(
93/// &mut self,
94/// _req: ExtractRequest<Self>,
95/// ) -> Result<CString, Error> {
96/// Ok(c"hello".to_owned())
97/// }
98/// }
99///
100/// impl ExtractPlugin for MyExtractPlugin {
101/// const EVENT_TYPES: &'static [EventType] = &[]; // all event types
102/// const EVENT_SOURCES: &'static [&'static str] = &[]; // all event sources
103/// type ExtractContext = ();
104///
105/// const EXTRACT_FIELDS: &'static [ExtractFieldInfo<Self>] = &[
106/// field("my_extract.sample", &Self::extract_sample),
107/// ];
108/// }
109///
110/// plugin!(MyExtractPlugin);
111/// extract_plugin!(MyExtractPlugin);
112/// ```
113///
114/// See the [`extract::ExtractPlugin`] trait documentation for details.
115pub mod extract {
116 pub use crate::plugin::event::EventInput;
117 pub use crate::plugin::extract::schema::field;
118 pub use crate::plugin::extract::schema::ExtractFieldInfo;
119 pub use crate::plugin::extract::ExtractPlugin;
120 pub use crate::plugin::extract::ExtractRequest;
121}
122
123/// # Event parsing support
124///
125/// Plugins with event parsing capability can hook into an event stream and receive all of its events
126/// sequentially. The parsing phase is the stage in the event processing loop in which
127/// the Falcosecurity libraries inspect the content of the events' payload and use it to apply
128/// internal state updates or implement additional logic. This phase happens before any field
129/// extraction for a given event. Each event in a given stream is guaranteed to be received at most once.
130///
131/// For your plugin to support event parsing, you will need to implement the [`parse::ParsePlugin`]
132/// trait and invoke the [`parse_plugin`] macro, for example:
133///
134/// ```
135///# use std::ffi::CStr;
136/// use falco_plugin::anyhow::Error;
137/// use falco_plugin::event::events::types::EventType;
138/// use falco_plugin::base::Plugin;
139/// use falco_plugin::{parse_plugin, plugin};
140/// use falco_plugin::parse::{EventInput, ParseInput, ParsePlugin};
141///# use falco_plugin::tables::TablesInput;
142///
143/// struct MyParsePlugin;
144///
145/// impl Plugin for MyParsePlugin {
146/// // ...
147/// # const NAME: &'static CStr = c"sample-plugin-rs";
148/// # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
149/// # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
150/// # const CONTACT: &'static CStr = c"you@example.com";
151/// # type ConfigType = ();
152/// #
153/// # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
154/// # -> Result<Self, Error> {
155/// # Ok(MyParsePlugin)
156/// # }
157/// }
158///
159/// impl ParsePlugin for MyParsePlugin {
160/// const EVENT_TYPES: &'static [EventType] = &[]; // inspect all events...
161/// const EVENT_SOURCES: &'static [&'static str] = &[]; // ... from all event sources
162///
163/// fn parse_event(&mut self, event: &EventInput, parse_input: &ParseInput)
164/// -> Result<(), Error> {
165/// let event = event.event()?;
166/// let event = event.load_any()?;
167///
168/// // any processing you want here, e.g. involving tables
169///
170/// Ok(())
171/// }
172/// }
173///
174/// plugin!(MyParsePlugin);
175/// parse_plugin!(MyParsePlugin);
176/// ```
177pub mod parse {
178 pub use crate::plugin::event::EventInput;
179 pub use crate::plugin::parse::ParseInput;
180 pub use crate::plugin::parse::ParsePlugin;
181}
182
183/// # Asynchronous event support
184///
185/// Plugins with async events capability can enrich an event stream from a given source (not
186/// necessarily implemented by itself) by injecting events asynchronously in the stream. Such
187/// a feature can be used for implementing notification systems or recording state transitions
188/// in the event-driven model of the Falcosecurity libraries, so that they can be available to other
189/// components at runtime or when the event stream is replayed through a capture file.
190///
191/// For example, the Falcosecurity libraries leverage this feature internally to implement metadata
192/// enrichment systems such as the one related to container runtimes. In that case, the libraries
193/// implement asynchronous jobs responsible for retrieving such information externally outside
194/// the main event processing loop so that it's non-blocking. The worker jobs produce a notification
195/// event every time a new container is detected and inject it asynchronously in the system event
196/// stream to be later processed for state updates and for evaluating Falco rules.
197///
198/// For your plugin to support asynchronous events, you will need to implement the [`async_event::AsyncEventPlugin`]
199/// trait and invoke the [`async_event`] macro, for example:
200///
201/// ```
202/// use std::ffi::{CStr, CString};
203/// use std::sync::Arc;
204/// use std::thread::JoinHandle;
205/// use falco_plugin::anyhow::Error;
206/// use falco_plugin::event::events::Event;
207/// use falco_plugin::event::events::EventMetadata;
208/// use falco_plugin::base::Plugin;
209/// use falco_plugin::{async_event_plugin, plugin};
210/// use falco_plugin::async_event::{
211/// AsyncEventPlugin,
212/// AsyncHandler,
213/// BackgroundTask};
214/// use falco_plugin::tables::TablesInput;
215///
216/// struct MyAsyncPlugin {
217/// task: Arc<BackgroundTask>,
218/// thread: Option<JoinHandle<Result<(), Error>>>,
219/// }
220///
221/// impl Plugin for MyAsyncPlugin {
222/// // ...
223/// # const NAME: &'static CStr = c"sample-plugin-rs";
224/// # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
225/// # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
226/// # const CONTACT: &'static CStr = c"you@example.com";
227/// # type ConfigType = ();
228/// #
229/// # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
230/// # -> Result<Self, Error> {
231/// # Ok(MyAsyncPlugin {
232/// # task: Arc::new(Default::default()),
233/// # thread: None,
234/// # })
235/// # }
236/// }
237///
238/// impl AsyncEventPlugin for MyAsyncPlugin {
239/// const ASYNC_EVENTS: &'static [&'static str] = &[]; // generate any async events
240/// const EVENT_SOURCES: &'static [&'static str] = &[]; // attach to all event sources
241///
242/// fn start_async(&mut self, handler: AsyncHandler) -> Result<(), Error> {
243/// // stop the thread if it was already running
244/// if self.thread.is_some() {
245/// self.stop_async()?;
246/// }
247///
248/// // start a new thread
249/// // waiting up to 100ms between events for the stop request
250/// self.thread = Some(self.task.spawn(std::time::Duration::from_millis(100), move || {
251/// // submit an async event to the main event loop
252/// handler.emit(Self::async_event(c"sample_async", b"hello"))?;
253/// Ok(())
254/// })?);
255/// Ok(())
256/// }
257///
258/// fn stop_async(&mut self) -> Result<(), Error> {
259/// self.task.request_stop_and_notify()?;
260/// let Some(handle) = self.thread.take() else {
261/// return Ok(());
262/// };
263///
264/// match handle.join() {
265/// Ok(res) => res,
266/// Err(e) => std::panic::resume_unwind(e),
267/// }
268/// }
269/// }
270///
271/// plugin!(MyAsyncPlugin);
272/// async_event_plugin!(MyAsyncPlugin);
273/// ```
274pub mod async_event {
275 /// The event type that can be emitted from async event plugins
276 pub use falco_event::events::types::PPME_ASYNCEVENT_E as AsyncEvent;
277
278 pub use crate::plugin::async_event::async_handler::AsyncHandler;
279 pub use crate::plugin::async_event::AsyncEventPlugin;
280
281 pub use crate::plugin::async_event::background_task::BackgroundTask;
282}
283
284/// # Event sourcing support
285///
286/// Plugins with event sourcing capability provide a new event source and make it available to
287/// libscap and libsinsp. They have the ability to "open" and "close" a stream of events and return
288/// those events to the plugin framework. They also provide a plugin ID, which is globally unique
289/// and is used in capture files. Event sources provided by plugins with this capability are tied
290/// to the events they generate and can be used by [plugins with field extraction](crate::source)
291/// capabilities and within Falco rules.
292/// For your plugin to support event sourcing, you will need to implement the [`source::SourcePlugin`]
293/// trait and invoke the [`source_plugin`] macro, for example:
294///
295/// ```
296/// use std::ffi::{CStr, CString};
297/// use anyhow::Error;
298/// use falco_event::events::Event;
299/// use falco_plugin::base::{Metric, Plugin};
300/// use falco_plugin::{plugin, source_plugin};
301/// use falco_plugin::source::{
302/// EventBatch,
303/// EventInput,
304/// PluginEvent,
305/// SourcePlugin,
306/// SourcePluginInstance};
307/// use falco_plugin::tables::TablesInput;
308/// use falco_plugin_api::ss_plugin_event_input;
309///
310/// struct MySourcePlugin;
311///
312/// impl Plugin for MySourcePlugin {
313/// // ...
314/// # const NAME: &'static CStr = c"sample-plugin-rs";
315/// # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
316/// # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
317/// # const CONTACT: &'static CStr = c"you@example.com";
318/// # type ConfigType = ();
319/// #
320/// # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
321/// # -> Result<Self, anyhow::Error> {
322/// # Ok(MySourcePlugin)
323/// # }
324/// #
325/// # fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
326/// # Ok(())
327/// # }
328/// #
329/// # fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
330/// # []
331/// # }
332/// }
333///
334/// struct MySourcePluginInstance;
335///
336/// impl SourcePlugin for MySourcePlugin {
337/// type Instance = MySourcePluginInstance;
338/// const EVENT_SOURCE: &'static CStr = c"my-source-plugin";
339/// const PLUGIN_ID: u32 = 0; // we do not have one assigned for this example :)
340///
341/// fn open(&mut self, params: Option<&str>) -> Result<Self::Instance, Error> {
342/// // we do not use the open parameters in this example
343/// Ok((MySourcePluginInstance))
344/// }
345///
346/// fn event_to_string(&mut self, event: &EventInput) -> Result<CString, Error> {
347/// // a string representation for our event; just copy out the whole event data
348/// // (it's an ASCII string); please note we need the copy because we need to add
349/// // a NUL terminator to convert the byte buffer to a C string
350///
351/// // get the raw event
352/// let event = event.event()?;
353/// // parse the fields into a PluginEvent
354/// let plugin_event = event.load::<PluginEvent>()?;
355///
356/// // take a copy of the event data (it's in an Option because we never know if events
357/// // have all the fields, and it's important to handle short events for backwards
358/// // compatibility).
359/// let data = plugin_event.params.event_data.map(|e| e.to_vec()).unwrap_or_default();
360///
361/// // convert the data to a CString and return it
362/// Ok(CString::new(data)?)
363/// }
364/// }
365///
366/// impl SourcePluginInstance for MySourcePluginInstance {
367/// type Plugin = MySourcePlugin;
368///
369/// fn next_batch(&mut self, plugin: &mut Self::Plugin, batch: &mut EventBatch)
370/// -> Result<(), Error> {
371/// let event = Self::plugin_event(b"hello, world");
372/// batch.add(event)?;
373///
374/// Ok(())
375/// }}
376///
377/// plugin!(MySourcePlugin);
378/// source_plugin!(MySourcePlugin);
379/// ```
380pub mod source {
381 pub use crate::plugin::event::EventInput;
382 pub use crate::plugin::source::event_batch::EventBatch;
383 pub use crate::plugin::source::open_params::{serialize_open_params, OpenParam};
384 pub use crate::plugin::source::{ProgressInfo, SourcePlugin, SourcePluginInstance};
385 pub use falco_event::events::types::PPME_PLUGINEVENT_E as PluginEvent;
386}
387
388/// # Capture listening plugins
389///
390/// Plugins with capture listening capability can receive notifications whenever a capture is
391/// started or stopped. Note that a capture may be stopped and restarted multiple times
392/// over the lifetime of a plugin.
393///
394/// ## Background tasks
395///
396/// Capture listening plugins receive a reference to a thread pool, which can be used to submit
397/// "routines" (tasks running in a separate thread, effectively).
398///
399/// *Note* there is no built-in mechanism to stop a running routine, so you should avoid doing this
400/// in the routine:
401/// ```ignore
402/// loop {
403/// do_something();
404/// std::thread::sleep(some_time);
405/// }
406/// ```
407///
408/// Instead, have your routine just do a single iteration and request a rerun from the scheduler:
409/// ```ignore
410/// do_something();
411/// std::thread::sleep(some_time)
412/// std::ops::ControlFlow::Continue(())
413/// ```
414///
415/// If you insist on using an infinite loop inside a routine, consider using e.g. [`async_event::BackgroundTask`]
416/// to manage the lifetime of the routine.
417///
418/// For your plugin to support event parsing, you will need to implement the [`listen::CaptureListenPlugin`]
419/// trait and invoke the [`capture_listen_plugin`] macro, for example:
420///
421/// ```
422///# use std::ffi::CStr;
423///# use std::time::Duration;
424/// use falco_plugin::anyhow::Error;
425/// use falco_plugin::base::Plugin;
426/// use falco_plugin::{capture_listen_plugin, plugin};
427/// use falco_plugin::listen::{CaptureListenInput, CaptureListenPlugin, Routine};
428///# use falco_plugin::tables::TablesInput;
429///# use log;
430///
431/// struct MyListenPlugin {
432/// tasks: Vec<Routine>,
433/// }
434///
435/// impl Plugin for MyListenPlugin {
436/// // ...
437/// # const NAME: &'static CStr = c"sample-plugin-rs";
438/// # const PLUGIN_VERSION: &'static CStr = c"0.0.1";
439/// # const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
440/// # const CONTACT: &'static CStr = c"you@example.com";
441/// # type ConfigType = ();
442/// #
443/// # fn new(input: Option<&TablesInput>, config: Self::ConfigType)
444/// # -> Result<Self, Error> {
445/// # Ok(MyListenPlugin {
446/// # tasks: Vec::new(),
447/// # })
448/// # }
449/// }
450///
451/// impl CaptureListenPlugin for MyListenPlugin {
452/// fn capture_open(&mut self, listen_input: &CaptureListenInput) -> Result<(), Error> {
453/// log::info!("Capture started");
454/// self.tasks.push(listen_input.thread_pool.subscribe(|| {
455/// log::info!("Doing stuff in the background");
456/// std::thread::sleep(Duration::from_millis(500));
457/// std::ops::ControlFlow::Continue(())
458/// })?);
459///
460/// Ok(())
461/// }
462///
463/// fn capture_close(&mut self, listen_input: &CaptureListenInput) -> Result<(), Error> {
464/// log::info!("Capture stopped");
465/// for routine in self.tasks.drain(..) {
466/// listen_input.thread_pool.unsubscribe(&routine)?;
467/// }
468///
469/// Ok(())
470/// }
471/// }
472///
473/// plugin!(MyListenPlugin);
474/// capture_listen_plugin!(MyListenPlugin);
475/// ```
476pub mod listen {
477 pub use crate::plugin::listen::CaptureListenInput;
478 pub use crate::plugin::listen::CaptureListenPlugin;
479
480 pub use crate::plugin::listen::routine::Routine;
481 pub use crate::plugin::listen::routine::ThreadPool;
482}
483
484/// # Creating and accessing tables
485///
486/// Tables are a mechanism to share data between plugins (and Falco core). There are three major
487/// concepts that relate to working with Falco plugin tables:
488/// - a table is a collection of entries, each under a different key, like a hash map or a SQL
489/// table with a single primary key
490/// - an entry is a struct containing the actual values (corresponding to an entry in the hash map
491/// or a row in the SQL table)
492/// - a field is a descriptor for a particular item in an entry. It does not have an equivalent
493/// in the hash map analogy, but corresponds to a column in the SQL table. In particular, a field
494/// is not attached to any particular entry.
495///
496/// ## Example (in pseudocode)
497///
498/// Consider a table called `threads` that has two fields:
499/// ```ignore
500/// struct Thread {
501/// uid: u64,
502/// comm: CString,
503/// }
504/// ```
505///
506/// and uses the thread id (`tid: u64`) as the key. To read the `comm` of the thread with tid 1,
507/// you would need the following operations:
508///
509/// ```ignore
510/// // get the table (at initialization time)
511/// let threads_table = get_table("threads");
512///
513/// // get the field (at initialization time)
514/// let comm_field = threads_table.get_field("comm");
515///
516/// // get an entry in the table (during parsing or extraction)
517/// let tid1_entry = threads_table.get_entry(1);
518///
519/// // get the field value from an entry
520/// let comm = tid1_entry.get_field_value(comm_field);
521/// ```
522///
523/// The Rust SDK tries to hide this and expose a more struct-oriented approach, though you can
524/// access fields in entries manually if you want (e.g. if you only know the field name at runtime).
525///
526/// # Supported field types
527///
528/// The following types can be used in fields visible over the plugin API:
529/// - integer types (u8/i8, u16/i16, u32/i32, u64/i64)
530/// - the bool type
531/// - CString
532///
533/// Any other types are not supported, including in particular e.g. collections (`Vec<T>`),
534/// enums or any structs.
535///
536/// # Nested tables
537///
538/// Fields can also have a table type. This amounts to nested tables, like:
539/// ```ignore
540/// let fd_type = threads[tid].file_descriptors[fd].fd_type;
541/// ```
542///
543/// One important limitation is that you cannot add a nested table at runtime, so the only
544/// nested tables that exist are defined by the plugin (or Falco core) which owns the parent table.
545///
546/// # Exporting and importing tables
547///
548/// Tables can be exported (exposed to other plugins) using the [`tables::export`] module.
549///
550/// Existing tables (from other plugins) can be imported using the [`tables::import`] module.
551///
552/// See the corresponding modules' documentation for details.
553///
554/// # Access control
555///
556/// Not all plugins are created equal when it comes to accessing tables. Only
557/// [parse plugins](`crate::parse::ParsePlugin`), [listen plugins](`crate::listen::CaptureListenPlugin`)
558/// and [extract plugins](`crate::extract::ExtractPlugin`) can access tables. Moreover, during field
559/// extraction you can only read tables, not write them.
560///
561/// To summarize:
562///
563/// | Plugin type | Initialization phase | Action phase ^1 |
564/// |-------------|----------------------|-----------------|
565/// | source | no access | no access |
566/// | parse | full access | read/write |
567/// | extract | full access ^2 | read only |
568/// | listen | full access | n/a ^3 |
569/// | async | no access | no access |
570///
571/// **Notes**:
572/// 1. "Action phase" is anything that happens after [`crate::base::Plugin::new`] returns, i.e.
573/// event generation, parsing/extraction or any background activity (in async plugins).
574///
575/// 2. Even though you can create tables and fields during initialization of an extract plugin,
576/// there's no way to modify them later (create table entries or write to fields), so it's
577/// more useful to constrain yourself to looking up existing tables/fields.
578///
579/// 3. Listen plugins don't really have an action phase as they only expose methods to run
580/// on capture start/stop. The routines they spawn cannot access tables, since the table
581/// API is explicitly not thread safe (but with the `thread-safe-tables` feature you can
582/// safely access tables from Rust plugins across many threads).
583///
584/// ## Access control implementation
585///
586/// Access control is implemented by requiring a particular object to actually perform table
587/// operations:
588/// - [`tables::TablesInput`] to manage (look up/create) tables and fields
589/// - [`tables::TableReader`] to look up table entries and get field values
590/// - [`tables::TableWriter`] to create entries and write field values
591///
592/// These get passed to your plugin whenever a particular class of operations is allowed.
593/// Note that [`crate::base::Plugin::new`] receives an `Option<&TablesInput>` and the option
594/// is populated only for parsing and extraction plugins (source and async plugins receive `None`).
595///
596/// # The flow of using tables
597///
598/// The access controls described above push you into structuring your plugins in a specific way.
599/// You cannot e.g. define tables in a source plugin, which is good, since that would break
600/// when reading capture files (the source plugin is not involved in that case). To provide
601/// a full-featured plugin that generates events, maintains some state and exposes it via
602/// extracted fields, you need separate capabilities (that may live in a single plugin or be
603/// spread across different ones):
604/// - a source plugin *only* generates events
605/// - a parse plugin creates the state tables and updates them during event parsing
606/// - an extract plugin reads the tables and returns field values
607///
608/// # Dynamic fields
609///
610/// Tables can have fields added to them at runtime, from other plugins than the one that
611/// created them (you can add dynamic fields to tables you created too, but that makes little sense).
612///
613/// These fields behave just like fields defined statically in the table and can be used by plugins
614/// loaded after the current one. This can be used to e.g. add some data to an existing table
615/// in a parse plugin and expose it in an extract plugin.
616///
617/// # Thread safety
618///
619/// Tables in the Falco plugin API are explicitly *not* thread safe. However, when you enable
620/// the `thread-safe-tables` feature, tables exported from your plugin become thread-safe, so you
621/// can use them from your plugin (e.g. in a separate thread) concurrently to other plugins
622/// (in the main thread).
623pub mod tables {
624 pub use crate::plugin::tables::vtable::reader::LazyTableReader;
625 pub use crate::plugin::tables::vtable::reader::TableReader;
626 pub use crate::plugin::tables::vtable::reader::ValidatedTableReader;
627 pub use crate::plugin::tables::vtable::writer::LazyTableWriter;
628 pub use crate::plugin::tables::vtable::writer::TableWriter;
629 pub use crate::plugin::tables::vtable::writer::ValidatedTableWriter;
630 pub use crate::plugin::tables::vtable::TablesInput;
631
632 /// Exporting tables to other plugins
633 ///
634 /// Exporting a table to other plugins is done using the [`crate::tables::export::Entry`] derive macro.
635 /// It lets you use a struct type as a parameter to [`export::Table`]. You can then create
636 /// a new table using [`TablesInput::add_table`].
637 ///
638 /// Every field in the entry struct must be wrapped in [`Public`](`crate::tables::export::Public`),
639 /// [`Private`](`crate::tables::export::Private`) or [`Readonly`](`crate::tables::export::Readonly`),
640 /// except for nested tables. These just need to be a `Box<Table<K, E>>`, as it makes no sense
641 /// to have a private nested table and the distinction between writable and readonly is meaningless
642 /// for tables (they have no setter to replace the whole table and you can always add/remove
643 /// entries from the nested table).
644 ///
645 /// # Example
646 ///
647 /// ```
648 /// use std::ffi::{CStr, CString};
649 /// use falco_plugin::base::Plugin;
650 ///# use falco_plugin::plugin;
651 /// use falco_plugin::tables::TablesInput;
652 /// use falco_plugin::tables::export;
653 ///
654 /// // define the struct representing each table entry
655 /// #[derive(export::Entry)]
656 /// struct ExportedTable {
657 /// int_field: export::Readonly<u64>, // do not allow writes via the plugin API
658 /// string_field: export::Public<CString>, // allow writes via the plugin API
659 /// secret: export::Private<Vec<u8>>, // do not expose over the plugin API at all
660 /// }
661 ///
662 /// // define the type holding the plugin state
663 /// struct MyPlugin {
664 /// // you can use methods on this instance to access fields bypassing the Falco table API
665 /// // (for performance within your own plugin)
666 /// exported_table: Box<export::Table<u64, ExportedTable>>,
667 /// }
668 ///
669 /// // implement the base::Plugin trait
670 /// impl Plugin for MyPlugin {
671 /// // ...
672 ///# const NAME: &'static CStr = c"sample-plugin-rs";
673 ///# const PLUGIN_VERSION: &'static CStr = c"0.0.1";
674 ///# const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
675 ///# const CONTACT: &'static CStr = c"you@example.com";
676 ///# type ConfigType = ();
677 ///
678 /// fn new(input: Option<&TablesInput>, config: Self::ConfigType)
679 /// -> Result<Self, anyhow::Error> {
680 ///
681 /// let Some(input) = input else {
682 /// anyhow::bail!("Did not get tables input");
683 /// };
684 ///
685 /// // create a new table called "exported"
686 /// //
687 /// // The concrete type is inferred from the field type the result is stored in.
688 /// let exported_table = input.add_table(export::Table::new(c"exported")?)?;
689 ///
690 /// Ok(MyPlugin { exported_table })
691 /// }
692 /// }
693 ///# plugin!(#[no_capabilities] MyPlugin);
694 /// ```
695 pub mod export {
696 pub use crate::plugin::exported_tables::field::private::Private;
697 pub use crate::plugin::exported_tables::field::public::Public;
698 pub use crate::plugin::exported_tables::field::readonly::Readonly;
699 pub use crate::plugin::exported_tables::table::Table;
700
701 /// Mark a struct type as a table value
702 ///
703 /// See the [module documentation](`crate::tables::export`) for details.
704 pub use falco_plugin_derive::Entry;
705 }
706
707 /// # Importing tables from other plugins (or Falco core)
708 ///
709 /// Your plugin can access tables exported by other plugins (or Falco core) by importing them.
710 /// The recommended approach is to use the `#[derive(TableMetadata)]` macro for that purpose.
711 ///
712 /// You will probably want to define two additional type aliases, so that the full definition
713 /// involves:
714 /// - a type alias for the whole table
715 /// - a type alias for a single table entry
716 /// - a metadata struct, describing an entry (somewhat indirectly)
717 ///
718 /// For example:
719 ///
720 /// ```
721 /// # use std::ffi::CStr;
722 /// # use std::sync::Arc;
723 /// # use falco_plugin::tables::import::{Entry, Field, Table, TableMetadata};
724 /// #
725 /// type NestedThing = Entry<Arc<NestedThingMetadata>>;
726 /// type NestedThingTable = Table<u64, NestedThing>;
727 ///
728 /// #[derive(TableMetadata)]
729 /// #[entry_type(NestedThing)]
730 /// struct NestedThingMetadata {
731 /// number: Field<u64, NestedThing>,
732 /// }
733 ///
734 /// type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
735 /// type ImportedThingTable = Table<u64, ImportedThing>;
736 ///
737 /// #[derive(TableMetadata)]
738 /// #[entry_type(ImportedThing)]
739 /// struct ImportedThingMetadata {
740 /// imported: Field<u64, ImportedThing>,
741 /// nested: Field<NestedThingTable, ImportedThing>,
742 ///
743 /// #[name(c"type")]
744 /// thing_type: Field<u64, ImportedThing>,
745 ///
746 /// #[custom]
747 /// added: Field<CStr, ImportedThing>,
748 /// }
749 ///
750 /// # // make this doctest a module, not a function: https://github.com/rust-lang/rust/issues/83583#issuecomment-1083300448
751 /// # fn main() {}
752 /// ```
753 ///
754 /// In contrast to [exported tables](`crate::tables::export`), the entry struct does not
755 /// contain any accessible fields. It only provides generated methods to access each field
756 /// using the plugin API. This means that each read/write is fairly expensive (involves
757 /// method calls), so you should probably cache the values in local variables.
758 ///
759 /// ## Declaring fields
760 ///
761 /// You need to declare each field you're going to use in a particular table, by providing
762 /// a corresponding [`import::Field`] field in the metadata struct. You do **not** need
763 /// to declare all fields in the table, or put the fields in any particular order, but you
764 /// **do** need to get the type right (otherwise you'll get an error at initialization time).
765 ///
766 /// The Falco table field name is the same as the field name in your metadata struct,
767 /// unless overridden by `#[name(c"foo")]`. This is useful if a field's name is a Rust reserved
768 /// word (e.g. `type`).
769 ///
770 /// You can also add fields to imported tables. To do that, tag the field with a `#[custom]`
771 /// attribute. It will be then added to the table instead of looking it up in existing fields.
772 /// Note that multiple plugins can add a field with the same name and type, which will make them
773 /// all use the same field (they will share the data). Adding a field multiple times
774 /// with different types is not allowed and will cause an error at initialization time.
775 ///
776 /// ## Generated methods
777 ///
778 /// Each scalar field gets a getter and setter method, e.g. declaring a metadata struct like
779 /// the above example will generate the following methods **on the `ImportedThing` type**
780 /// (for the scalar fields):
781 ///
782 /// ```ignore
783 /// fn get_imported(&self, reader: &TableReader) -> Result<u64, anyhow::Error>;
784 /// fn set_imported(&self, writer: &TableWriter, value: &u64) -> Result<(), anyhow::Error>;
785 ///
786 /// fn get_thing_type(&self, reader: &TableReader) -> Result<u64, anyhow::Error>;
787 /// fn set_thing_type(&self, writer: &TableWriter, value: &u64) -> Result<(), anyhow::Error>;
788 ///
789 /// fn get_added<'a>(&'a self, reader: &TableReader) -> Result<&'a CStr, anyhow::Error>;
790 /// fn set_added(&self, writer: &TableWriter, value: &CStr) -> Result<(), anyhow::Error>;
791 /// ```
792 ///
793 /// Each table-typed field (nested table) gets a getter and a nested getter, so the above example
794 /// will generate the following methods for the `nested` field:
795 ///
796 /// ```ignore
797 /// fn get_nested(&self, reader: &TableReader) -> Result<NestedThingTable, anyhow::Error>;
798 /// fn get_nested_by_key(&self, reader: &TableReader, key: &u64)
799 /// -> Result<NestedThing, anyhow::Error>;
800 /// ```
801 ///
802 /// **Note**: setters do not take `&mut self` as all the mutation happens on the other side
803 /// of the API (presumably in another plugin).
804 ///
805 /// ### Visibility of generated methods
806 ///
807 /// The generated methods are actually trait implementations, not inherent impls (due to proc
808 /// macro limitations). The traits in question are automatically generated and `use`d
809 /// in the scope that defines the table struct. However, when you try to use them from
810 /// a different module, you will get an error since they're not in scope (as traits need to
811 /// be `use`d explicitly before their methods can be called).
812 ///
813 /// The module they're defined in has an autogenerated name (`__falco_plugin_private_<TABLE_TYPE_NAME>`)
814 /// that doesn't look too nice when explicitly used, so for larger plugins (where the table
815 /// access is split across modules) you can use the `#[accessors_mod]` attribute to choose
816 /// a friendlier name for this module:
817 ///
818 /// ```
819 /// pub mod imported_table {
820 /// # use std::sync::Arc;
821 /// # use falco_plugin::tables::import::{Entry, Field, Table, TableMetadata};
822 /// #
823 /// pub type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
824 /// type ImportedThingTable = Table<u64, ImportedThing>;
825 ///
826 /// #[derive(TableMetadata)]
827 /// #[entry_type(ImportedThing)]
828 /// #[accessors_mod(thing_accessors)]
829 /// pub struct ImportedThingMetadata {
830 /// imported: Field<u64, ImportedThing>,
831 /// }
832 /// }
833 ///
834 /// mod use_the_table {
835 /// # use falco_plugin::tables::TableReader;
836 /// # use falco_plugin::anyhow;
837 /// use super::imported_table::ImportedThing;
838 /// use super::imported_table::thing_accessors::*;
839 ///
840 /// fn use_the_table(thing: &ImportedThing, reader: &impl TableReader)
841 /// -> Result<u64, anyhow::Error> {
842 /// thing.get_imported(reader)
843 /// }
844 /// }
845 ///
846 /// # // make this doctest a module, not a function: https://github.com/rust-lang/rust/issues/83583#issuecomment-1083300448
847 /// # fn main() {}
848 ///
849 /// ```
850 ///
851 /// # Example
852 ///
853 /// ```
854 /// use std::ffi::CStr;
855 /// use std::sync::Arc;
856 /// use falco_plugin::anyhow::Error;
857 /// use falco_plugin::base::Plugin;
858 /// use falco_plugin::event::events::types::EventType;
859 /// use falco_plugin::parse::{EventInput, ParseInput, ParsePlugin};
860 /// use falco_plugin::tables::TablesInput;
861 /// use falco_plugin::tables::import::{Entry, Field, Table, TableMetadata};
862 ///# use falco_plugin::plugin;
863 ///# use falco_plugin::parse_plugin;
864 ///
865 /// #[derive(TableMetadata)]
866 /// #[entry_type(ImportedThing)]
867 /// struct ImportedThingMetadata {
868 /// imported: Field<u64, ImportedThing>,
869 ///
870 /// #[name(c"type")]
871 /// thing_type: Field<u64, ImportedThing>,
872 ///
873 /// #[custom]
874 /// added: Field<CStr, ImportedThing>,
875 /// }
876 ///
877 /// type ImportedThing = Entry<Arc<ImportedThingMetadata>>;
878 /// type ImportedThingTable = Table<u64, ImportedThing>;
879 ///
880 /// struct MyPlugin {
881 /// things: ImportedThingTable,
882 /// }
883 ///
884 /// impl Plugin for MyPlugin {
885 /// // ...
886 ///# const NAME: &'static CStr = c"dummy_extract";
887 ///# const PLUGIN_VERSION: &'static CStr = c"0.0.0";
888 ///# const DESCRIPTION: &'static CStr = c"test plugin";
889 ///# const CONTACT: &'static CStr = c"rust@localdomain.pl";
890 ///# type ConfigType = ();
891 ///
892 /// fn new(input: Option<&TablesInput>, _config: Self::ConfigType) -> Result<Self, Error> {
893 /// let input = input.ok_or_else(|| anyhow::anyhow!("did not get table input"))?;
894 /// let things: ImportedThingTable = input.get_table(c"things")?;
895 ///
896 /// Ok(Self { things })
897 /// }
898 /// }
899 ///
900 /// impl ParsePlugin for MyPlugin {
901 /// const EVENT_TYPES: &'static [EventType] = &[];
902 /// const EVENT_SOURCES: &'static [&'static str] = &[];
903 ///
904 /// fn parse_event(&mut self, event: &EventInput, parse_input: &ParseInput)
905 /// -> anyhow::Result<()> {
906 /// // creating and accessing entries
907 /// let reader = &parse_input.reader;
908 /// let writer = &parse_input.writer;
909 ///
910 /// // create a new entry (not yet attached to a table key)
911 /// let entry = self.things.create_entry(writer)?;
912 /// entry.set_imported(writer, &5u64)?;
913 ///
914 /// // attach the entry to a table key
915 /// self.things.insert(reader, writer, &1u64, entry)?;
916 ///
917 /// // look up the entry we have just added
918 /// let entry = self.things.get_entry(reader, &1u64)?;
919 /// assert_eq!(entry.get_imported(reader).ok(), Some(5u64));
920 ///
921 /// Ok(())
922 /// }
923 /// }
924 ///
925 /// # plugin!(MyPlugin);
926 /// # parse_plugin!(MyPlugin);
927 /// # // make this doctest a module, not a function: https://github.com/rust-lang/rust/issues/83583#issuecomment-1083300448
928 /// # fn main() {}
929 /// ```
930 ///
931 /// **Note**: The derive macro involves creating a private module (to avoid polluting
932 /// the top-level namespace with a bunch of one-off traits), so you cannot use it inside
933 /// a function due to scoping issues. See <https://github.com/rust-lang/rust/issues/83583>
934 /// for details.
935 ///
936 /// # Bypassing the derive macro
937 ///
938 /// The derive macro boils down to automatically calling get_field/add_field for each
939 /// field defined in the metadata struct (and generating getters/setters). If you don't know
940 /// the field names in advance (e.g. when supporting different versions of "parent" plugins),
941 /// there is the [`import::RuntimeEntry`] type alias, which makes you responsible for holding
942 /// the field structs (probably in your plugin type) and requires you to use the generic
943 /// read_field/write_field methods, in exchange for the flexibility.
944 ///
945 /// The above example can be rewritten without the derive macro as follows:
946 ///
947 /// ```
948 /// use std::ffi::CStr;
949 /// use falco_plugin::anyhow::Error;
950 /// use falco_plugin::base::Plugin;
951 /// use falco_plugin::event::events::types::EventType;
952 /// use falco_plugin::parse::{EventInput, ParseInput, ParsePlugin};
953 ///# use falco_plugin::{parse_plugin, plugin};
954 /// use falco_plugin::tables::TablesInput;
955 /// use falco_plugin::tables::import::{Field, RuntimeEntry, Table};
956 ///
957 /// struct ImportedThingTag;
958 /// type ImportedThing = RuntimeEntry<ImportedThingTag>;
959 /// type ImportedThingTable = Table<u64, ImportedThing>;
960 ///
961 /// struct MyPlugin {
962 /// things: ImportedThingTable,
963 /// thing_imported_field: Field<u64, ImportedThing>,
964 /// thing_type_field: Field<u64, ImportedThing>,
965 /// thing_added_field: Field<CStr, ImportedThing>,
966 /// }
967 ///
968 /// impl Plugin for MyPlugin {
969 /// // ...
970 ///# const NAME: &'static CStr = c"dummy_extract";
971 ///# const PLUGIN_VERSION: &'static CStr = c"0.0.0";
972 ///# const DESCRIPTION: &'static CStr = c"test plugin";
973 ///# const CONTACT: &'static CStr = c"rust@localdomain.pl";
974 ///# type ConfigType = ();
975 ///
976 /// fn new(input: Option<&TablesInput>, _config: Self::ConfigType) -> Result<Self, Error> {
977 /// let input = input.ok_or_else(|| anyhow::anyhow!("did not get table input"))?;
978 /// let things: ImportedThingTable = input.get_table(c"things")?;
979 /// let thing_imported_field = things.get_field(input, c"imported")?;
980 /// let thing_type_field = things.get_field(input, c"type")?;
981 /// let thing_added_field = things.add_field(input, c"added")?;
982 ///
983 /// Ok(Self {
984 /// things,
985 /// thing_imported_field,
986 /// thing_type_field,
987 /// thing_added_field,
988 /// })
989 /// }
990 /// }
991 ///
992 /// impl ParsePlugin for MyPlugin {
993 /// const EVENT_TYPES: &'static [EventType] = &[];
994 /// const EVENT_SOURCES: &'static [&'static str] = &[];
995 ///
996 /// fn parse_event(&mut self, event: &EventInput, parse_input: &ParseInput)
997 /// -> anyhow::Result<()> {
998 /// // creating and accessing entries
999 /// let reader = &parse_input.reader;
1000 /// let writer = &parse_input.writer;
1001 ///
1002 /// // create a new entry (not yet attached to a table key)
1003 /// let entry = self.things.create_entry(writer)?;
1004 /// entry.write_field(writer, &self.thing_imported_field, &5u64)?;
1005 ///
1006 /// // attach the entry to a table key
1007 /// self.things.insert(reader, writer, &1u64, entry)?;
1008 ///
1009 /// // look up the entry we have just added
1010 /// let entry = self.things.get_entry(reader, &1u64)?;
1011 /// assert_eq!(
1012 /// entry.read_field(reader, &self.thing_imported_field).ok(),
1013 /// Some(5u64),
1014 /// );
1015 ///
1016 /// Ok(())
1017 /// }
1018 /// }
1019 ///# plugin!(MyPlugin);
1020 ///# parse_plugin!(MyPlugin);
1021 /// ```
1022 ///
1023 /// **Note**: in the above example, `ImportedThingTag` is just an empty struct, used to
1024 /// distinguish entries (and fields) from different types between one another. You can
1025 /// skip this and do not pass the second generic argument to `Field` and `Table`
1026 /// (it will default to `RuntimeEntry<()>`), but you lose compile time validation for
1027 /// accessing fields from the wrong table. It will still be caught at runtime.
1028 ///
1029 /// See the [`import::Table`] type for additional methods on tables, to e.g. iterate
1030 /// over entries or clear the whole table.
1031 pub mod import {
1032 pub use crate::plugin::tables::data::Bool;
1033 pub use crate::plugin::tables::data::TableData;
1034 pub use crate::plugin::tables::field::Field;
1035 pub use crate::plugin::tables::runtime::RuntimeEntry;
1036 pub use crate::plugin::tables::table::Table;
1037 pub use crate::plugin::tables::Entry;
1038
1039 /// Mark a struct type as an imported table entry metadata
1040 ///
1041 /// See the [module documentation](`crate::tables::import`) for details.
1042 pub use falco_plugin_derive::TableMetadata;
1043 }
1044}
1045
1046mod plugin;
1047pub mod strings;
1048
1049#[doc(hidden)]
1050pub mod internals {
1051 pub mod base {
1052 pub use crate::plugin::base::wrappers;
1053 }
1054
1055 pub mod source {
1056 pub use crate::plugin::source::wrappers;
1057 }
1058
1059 pub mod extract {
1060 pub use crate::plugin::extract::wrappers;
1061 }
1062
1063 pub mod listen {
1064 pub use crate::plugin::listen::wrappers;
1065 }
1066
1067 pub mod parse {
1068 pub use crate::plugin::parse::wrappers;
1069 }
1070
1071 pub mod async_event {
1072 pub use crate::plugin::async_event::wrappers;
1073 }
1074
1075 pub mod tables {
1076 crate::table_import_expose_internals!();
1077 crate::table_export_expose_internals!();
1078 }
1079}