Skip to main content
Snort 3’s plugin framework lets you extend nearly every aspect of packet processing without modifying the Snort core. Codecs decode raw packets, Inspectors analyze traffic, IPS options add detection keywords, Loggers emit alerts, and more — all through a single, unified API contract.

Plugin types

Every plugin has a PlugType that determines its role in the pipeline:

PT_CODEC

Decodes raw packets layer by layer (Ethernet, IP, TCP, custom protocols).

PT_INSPECTOR

Analyzes traffic between decode and detection — the workhorse of Snort preprocessing.

PT_IPS_OPTION

Implements rule keywords used in detection (e.g. content, pcre, custom matchers).

PT_IPS_ACTION

Specifies the verdict action taken when a rule fires (e.g. react, reject, rewrite).

PT_LOGGER

Logs packets and IPS events after thresholding.

PT_SEARCH_ENGINE

Pluggable multi-pattern search engine (MPSE) for fast pattern matching.

PT_SO_RULE

Shared-object rules — compiled detection logic loaded at runtime.

PT_CONNECTOR

Side-channel connectors for inter-process or inter-host communication.
All plugin types are defined as the PlugType enum in framework/base_api.h.

The BaseApi structure

Every plugin API starts with a BaseApi as its first member. This common header gives Snort the information it needs to manage any plugin regardless of type:
// framework/base_api.h
struct BaseApi
{
    PlugType type;        // PT_CODEC, PT_INSPECTOR, etc.
    uint32_t size;        // sizeof(plugin-api struct)
    uint32_t api_version; // (BASE_API_VERSION << 16) | plugin-api-version
    uint32_t version;     // version of this plugin
    uint64_t features;    // PLUGIN_DEFAULT, PLUGIN_SO_RELOAD, etc.
    const char* options;  // API_OPTIONS build string
    const char* name;     // plugin name (matches Lua table name)
    const char* help;     // short description
    ModNewFunc mod_ctor;  // constructs the associated Module
    ModDelFunc mod_dtor;  // destroys the associated Module
};
The current base version is BASE_API_VERSION 25. Each plugin type also has its own version constant that is ORed into api_version:
Plugin typeVersion constant
PT_INSPECTORINSAPI_VERSION
PT_CODECCDAPI_VERSION
PT_IPS_OPTIONIPSAPI_VERSION
PT_LOGGERLOGAPI_VERSION
Because BaseApi is the first member of every plugin API struct, a pointer to any plugin API can be safely cast to BaseApi*, enabling uniform plugin management.

The snort_plugins[] symbol

A dynamic library exposes its plugins through a single exported symbol — a null-terminated array of BaseApi pointers:
#include "framework/codec.h"

// ... codec implementation ...

static const CodecApi my_codec_api =
{
    {
        PT_CODEC,
        sizeof(CodecApi),
        CDAPI_VERSION,
        0,              // plugin version
        0,              // features
        nullptr,        // options
        "my_codec",
        "My custom codec",
        nullptr,        // mod_ctor
        nullptr,        // mod_dtor
    },
    nullptr,  // pinit
    nullptr,  // pterm
    nullptr,  // tinit
    nullptr,  // tterm
    ctor,
    dtor,
};

// This is what Snort looks for when loading a dynamic library.
SO_PUBLIC const BaseApi* snort_plugins[] =
{
    &my_codec_api.base,
    nullptr  // must be null-terminated
};
A single .so library can contain multiple plugins. Add all their BaseApi pointers to the snort_plugins[] array before the terminating nullptr.

Static vs dynamic plugins

There is no functional difference between a static plugin (compiled into Snort) and a dynamic plugin (loaded from a shared library at runtime). Both use the same API and the same snort_plugins[] registration pattern. Dynamic plugins are loaded using --plugin-path:
# Load all plugins in a directory
snort --plugin-path /usr/local/lib/snort_extra

# Load a specific shared library
snort --plugin-path /path/to/my_plugin.so
Dynamic plugins let you ship new protocol support or detection logic independently of the Snort release cycle.

The Module system

Each plugin can have an associated Module that handles Lua configuration. When Snort processes a Lua table whose name matches a module, it instantiates that module, calls its configuration methods, then passes the module to the plugin constructor. For example, a plugin called gadget would be configured in snort.lua as:
gadget =
{
    brain = true,
    claw = 3
}
Snort finds the gadget module, instantiates GadgetModule, sets brain and claw, then passes the configured module to the GadgetInspector constructor.

Module virtual methods

Three methods control the configuration lifecycle:
1

begin()

Called when Snort starts processing the associated Lua table. Allocate any required data and set defaults here.
virtual bool begin(const char* name, int idx, SnortConfig*) { return true; }
Snort sets all parameter defaults immediately after begin() returns, so you should not set defaults inside begin() itself.
2

set()

Called once per parameter after the value has been validated.
virtual bool set(const char* name, Value& val, SnortConfig*) { return true; }
3

end()

Called when Snort finishes processing the Lua table. Perform cross-parameter integrity checks here.
virtual bool end(const char* name, int idx, SnortConfig*) { return true; }
After configuration, the plugin constructor receives the module and pulls the data it needs. For non-trivial configurations the standard pattern is for the Module to hand a pointer to a heap-allocated configuration struct to the plugin, which takes ownership.
There is at most one instance of a given Module at any time, even when multiple plugin instances (via binding) use it.

Module capabilities

Beyond configuration, a module can expose:
MethodPurpose
get_rules() / get_gid()Declare builtin GID/SID rules emitted by the plugin
get_pegs() / get_counts()Expose peg counters for --stats output
get_profile()Expose profiling stats for --profile output
get_commands()Register CLI control commands
get_parameters()Declare accepted Lua parameters with types and defaults

Module usage types

A module’s Usage controls where and how many times it can appear in configuration:
enum Usage { GLOBAL, CONTEXT, INSPECT, DETECT };
Configured at most once, outside any policy. Applies process-wide.
Configured at most once per network policy. Example: event_queue.
Configured in an inspection policy (NAP). Stream and service inspectors (stream_tcp, smtp) are multitons — they may appear more than once per policy. Others like binder are singletons.
Configured at most once per IPS policy. Example: ips.

Building plugins

Plugins are built as ordinary shared libraries that link against the Snort headers. A minimal CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(my_plugin)

find_package(Snort REQUIRED)

add_library(my_plugin MODULE my_plugin.cc)
target_include_directories(my_plugin PRIVATE ${SNORT_INCLUDE_DIRS})
set_target_properties(my_plugin PROPERTIES PREFIX "")
Load at runtime:
snort --plugin-path ./my_plugin.so -c snort.lua