plugins: add --plugin command line option to load plugins

Add --plugin <PATH> to load an additional plugin from the command
line. This is more convenient than "--set plugins.X" especially when
you may already have a plugins loaded and you want to load an
additional one.

Ticket: 8463
This commit is contained in:
Jason Ish 2026-03-24 17:00:29 -06:00 committed by Victor Julien
parent aa9c60993b
commit b3fb3518f5
8 changed files with 103 additions and 68 deletions

View file

@ -187,7 +187,7 @@ jobs:
working-directory: examples/plugins/ci-capture
run: |
make
../../../src/suricata -S /dev/null --set plugins.0=./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml
../../../src/suricata -S /dev/null --plugin ./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml
cat eve.json | jq -c 'select(.dns)'
test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1"
@ -195,7 +195,7 @@ jobs:
working-directory: examples/plugins/altemplate
run: |
cargo build
../../../src/suricata -S altemplate.rules --set plugins.0=./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap
../../../src/suricata -S altemplate.rules --plugin ./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap
cat eve.json | jq -c 'select(.altemplate)'
test $(cat eve.json | jq -c 'select(.altemplate)' | wc -l) = "3"
# we get 2 alerts and 1 altemplate events
@ -419,7 +419,7 @@ jobs:
working-directory: examples/plugins/ci-capture
run: |
make
../../../src/suricata -S /dev/null --set plugins.0=./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml
../../../src/suricata -S /dev/null --plugin ./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml
cat eve.json | jq -c 'select(.dns)'
test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1"
@ -427,7 +427,7 @@ jobs:
working-directory: examples/plugins/altemplate
run: |
cargo build
../../../src/suricata -S altemplate.rules --set plugins.0=./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap
../../../src/suricata -S altemplate.rules --plugin ./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap
cat eve.json | jq -c 'select(.altemplate)'
test $(cat eve.json | jq -c 'select(.altemplate)' | wc -l) = "3"
# we get 2 alerts and 1 altemplate events

View file

@ -272,6 +272,14 @@
.. Advanced options.
.. option:: --plugin <path>
Load a plugin from *path* in addition to the plugins listed in the
configuration file. This option can be specified multiple times.
If *path* is a directory, Suricata will attempt to load each
``.so`` file in that directory.
.. option:: --set <key>=<value>
Set a configuration value. Useful for overriding basic

View file

@ -9,14 +9,15 @@ optionally the `--with-napatech-includes` and
## Running
```
/usr/local/suricata/bin/suricata \
--set plugins.0=/usr/local/lib/suricata/napatech.so \
--plugin /usr/local/lib/suricata/napatech.so \
--capture-plugin=napatech
```
### --set plugins.0=/usr/local/lib/suricata/napatech.so
### --plugin /usr/local/lib/suricata/napatech.so
This command line option tells Suricata about this plugin. This could also
be done in `suricata.yaml` with the following section:
This command line option tells Suricata about this plugin in addition to any
plugins listed in `suricata.yaml`. This could also be done in `suricata.yaml`
with the following section:
```
plugins:
- /usr/local/lib/suricata/napatech.so

View file

@ -9,15 +9,16 @@ optionally the `--with-libpfring-includes` and
## Running
```
/usr/local/suricata/bin/suricata \
--set plugins.0=/usr/local/lib/suricata/pfring.so \
--capture-plugin=pfring-plugin \
--plugin /usr/local/lib/suricata/pfring.so \
--capture-plugin=pfring \
--set pfring.0.interface=eno1
```
### --set plugins.0=/usr/local/lib/suricata/pfring.so
### --plugin /usr/local/lib/suricata/pfring.so
This command line option tells Suricata about this plugin. This could also
be done in `suricata.yaml` with the following section:
This command line option tells Suricata about this plugin in addition to any
plugins listed in `suricata.yaml`. This could also be done in `suricata.yaml`
with the following section:
```
plugins:
- /usr/local/lib/suricata/pfring.so

View file

@ -644,6 +644,7 @@ static void PrintUsage(const char *progname)
printf("\t--runmode <runmode_id> : specific runmode modification the engine should run. The argument\n"
"\t supplied should be the id for the runmode obtained by running\n"
"\t --list-runmodes\n");
printf("\t--plugin <path> : load plugin in addition to config\n");
printf("\n Capture and IPS:\n");
@ -1382,6 +1383,38 @@ static bool IsLogDirectoryWritable(const char* str)
return access(str, W_OK) == 0;
}
/**
* Helper functions to append option values to an array where the
* option is allowed multiple times. For example:
* - --include
* - --plugin
*/
static void AddCommandLineOptionValue(
const char ***values, const char *value, const char *description)
{
if (*values == NULL) {
*values = SCCalloc(2, sizeof(char *));
if (*values == NULL) {
FatalError("Failed to allocate memory for %s: %s", description, strerror(errno));
}
(*values)[0] = value;
} else {
for (int i = 0;; i++) {
if ((*values)[i] == NULL) {
const char **new_values = SCRealloc(*values, (i + 2) * sizeof(char *));
if (new_values == NULL) {
FatalError(
"Failed to allocate memory for %s: %s", description, strerror(errno));
}
*values = new_values;
(*values)[i] = value;
(*values)[i + 1] = NULL;
break;
}
}
}
}
extern int g_skip_prefilter;
TmEcode SCParseCommandLine(int argc, char **argv)
@ -1434,6 +1467,7 @@ TmEcode SCParseCommandLine(int argc, char **argv)
{"no-random", 0, &g_disable_randomness, 1},
{"strict-rule-keywords", optional_argument, 0, 0},
{"plugin", required_argument, 0, 0},
{"capture-plugin", required_argument, 0, 0},
{"capture-plugin-args", required_argument, 0, 0},
@ -1550,6 +1584,8 @@ TmEcode SCParseCommandLine(int argc, char **argv)
"to pass --enable-pfring to configure when building.");
return TM_ECODE_FAILED;
#endif /* HAVE_PFRING */
} else if (strcmp((long_opts[option_index]).name, "plugin") == 0) {
AddCommandLineOptionValue(&suri->additional_plugins, optarg, "additional plugins");
} else if (strcmp((long_opts[option_index]).name, "capture-plugin") == 0) {
suri->run_mode = RUNMODE_PLUGIN;
suri->capture_plugin_name = optarg;
@ -1870,32 +1906,8 @@ TmEcode SCParseCommandLine(int argc, char **argv)
FatalError("failed to duplicate 'strict' string");
}
} else if (strcmp((long_opts[option_index]).name, "include") == 0) {
if (suri->additional_configs == NULL) {
suri->additional_configs = SCCalloc(2, sizeof(char *));
if (suri->additional_configs == NULL) {
FatalError(
"Failed to allocate memory for additional configuration files: %s",
strerror(errno));
}
suri->additional_configs[0] = optarg;
} else {
for (int i = 0;; i++) {
if (suri->additional_configs[i] == NULL) {
const char **additional_configs =
SCRealloc(suri->additional_configs, (i + 2) * sizeof(char *));
if (additional_configs == NULL) {
FatalError("Failed to allocate memory for additional configuration "
"files: %s",
strerror(errno));
} else {
suri->additional_configs = additional_configs;
}
suri->additional_configs[i] = optarg;
suri->additional_configs[i + 1] = NULL;
break;
}
}
}
AddCommandLineOptionValue(
&suri->additional_configs, optarg, "additional configuration files");
} else if (strcmp((long_opts[option_index]).name, "firewall-rules-exclusive") == 0) {
if (suri->firewall_rule_file != NULL) {
SCLogError("can't have multiple --firewall-rules-exclusive options");
@ -2828,7 +2840,7 @@ int PostConfLoadedSetup(SCInstance *suri)
SigTableInit();
#ifdef HAVE_PLUGINS
SCPluginsLoad(suri->capture_plugin_name, suri->capture_plugin_args);
SCPluginsLoad(suri->capture_plugin_name, suri->capture_plugin_args, suri->additional_plugins);
#endif
LiveDeviceFinalize(); // must be after EBPF extension registration

View file

@ -176,6 +176,7 @@ typedef struct SCInstance_ {
const char *progname; /**< pointer to argv[0] */
const char *conf_filename;
const char **additional_configs;
const char **additional_plugins;
char *strict_rule_parsing_string;
const char *capture_plugin_name;

View file

@ -76,7 +76,7 @@ bool RegisterPlugin(SCPlugin *plugin, void *lib)
return true;
}
static void InitPlugin(char *path)
static void InitPlugin(const char *path)
{
void *lib = dlopen(path, RTLD_NOW);
if (lib == NULL) {
@ -99,37 +99,48 @@ static void InitPlugin(char *path)
}
}
void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args)
static void LoadPluginsFromPath(const char *plugin_path)
{
SCConfNode *conf = SCConfGetNode("plugins");
if (conf == NULL) {
struct stat statbuf;
if (stat(plugin_path, &statbuf) == -1) {
SCLogError("Bad plugin path: %s: %s", plugin_path, strerror(errno));
return;
}
SCConfNode *plugin = NULL;
TAILQ_FOREACH(plugin, &conf->head, next) {
struct stat statbuf;
if (stat(plugin->val, &statbuf) == -1) {
SCLogError("Bad plugin path: %s: %s", plugin->val, strerror(errno));
continue;
if (S_ISDIR(statbuf.st_mode)) {
// coverity[toctou : FALSE]
DIR *dir = opendir(plugin_path);
if (dir == NULL) {
SCLogError("Failed to open plugin directory %s: %s", plugin_path, strerror(errno));
return;
}
if (S_ISDIR(statbuf.st_mode)) {
// coverity[toctou : FALSE]
DIR *dir = opendir(plugin->val);
if (dir == NULL) {
SCLogError("Failed to open plugin directory %s: %s", plugin->val, strerror(errno));
continue;
struct dirent *entry = NULL;
char path[PATH_MAX];
while ((entry = readdir(dir)) != NULL) {
if (strstr(entry->d_name, ".so") != NULL) {
snprintf(path, sizeof(path), "%s/%s", plugin_path, entry->d_name);
InitPlugin(path);
}
struct dirent *entry = NULL;
char path[PATH_MAX];
while ((entry = readdir(dir)) != NULL) {
if (strstr(entry->d_name, ".so") != NULL) {
snprintf(path, sizeof(path), "%s/%s", plugin->val, entry->d_name);
InitPlugin(path);
}
}
closedir(dir);
} else {
InitPlugin(plugin->val);
}
closedir(dir);
} else {
InitPlugin(plugin_path);
}
}
void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args,
const char **additional_plugins)
{
SCConfNode *conf = SCConfGetNode("plugins");
if (conf != NULL) {
SCConfNode *plugin = NULL;
TAILQ_FOREACH (plugin, &conf->head, next) {
LoadPluginsFromPath(plugin->val);
}
}
if (additional_plugins != NULL) {
for (int i = 0; additional_plugins[i] != NULL; i++) {
LoadPluginsFromPath(additional_plugins[i]);
}
}

View file

@ -20,7 +20,8 @@
#include "suricata-plugin.h"
void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args);
void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args,
const char **additional_plugins);
SCCapturePlugin *SCPluginFindCaptureByName(const char *name);
bool RegisterPlugin(SCPlugin *, void *);