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