Skip to main content
IPS options are the rule keywords that appear inside the parentheses of a Snort rule — content, pcre, flags, ttl, and so on. Each keyword is backed by an IpsOption plugin. You can create custom keywords that run arbitrary C++ detection logic on every matching packet.

The IpsOption class

// framework/ips_option.h
class SO_PUBLIC IpsOption
{
public:
    virtual ~IpsOption() = default;

    // --- Main thread ---

    // Hash of this option's configuration, used for rule deduplication.
    virtual uint32_t hash() const;

    // Two options are equal if they would produce identical results.
    virtual bool operator==(const IpsOption& ips) const;

    bool operator!=(const IpsOption& ips) const
    { return !(*this == ips); }

    // --- Packet thread ---

    // Return true if the option reads relative to the current cursor position.
    virtual bool is_relative() { return false; }

    // Try an alternative cursor position after a failed match.
    // Return true if a new position is available.
    virtual bool retry(Cursor&) { return false; }

    // Optional side-effect on match (eg tagging).
    virtual void action(Packet*) { }

    // Core detection logic.  Return MATCH or NO_MATCH.
    enum EvalStatus { NO_MATCH, MATCH, NO_ALERT, FAILED_BIT };
    virtual EvalStatus eval(Cursor&, Packet*) { return MATCH; }

    // Describe how this option interacts with the cursor.
    virtual CursorActionType get_cursor_type() const { return CAT_NONE; }

    // For fast-pattern options like content.
    virtual PatternMatchData* get_pattern(SnortProtocolId,
                                          RuleDirection = RULE_WO_DIR)
    { return nullptr; }

    option_type_t get_type() const { return type; }
    const char*   get_name() const { return name; }

protected:
    IpsOption(const char* name,
              option_type_t t = RULE_OPTION_TYPE_OTHER);

private:
    const char*   name;
    option_type_t type;
};

eval() return values

ValueMeaning
MATCHOption matched; continue evaluating the rule.
NO_MATCHOption did not match; stop evaluating this rule.
NO_ALERTOption matched but suppress the alert (used by threshold options).
FAILED_BITFlowbit check failed; skip the rest of the rule group.

Implementing a custom IPS option

1

Define the option class

#include "framework/ips_option.h"
#include "framework/module.h"

#define OPT_NAME "my_option"
#define OPT_HELP "match packets where my condition holds"

class MyOption : public IpsOption
{
public:
    MyOption(const char* pattern)
        : IpsOption(OPT_NAME), pattern(pattern) { }

    uint32_t hash() const override
    {
        uint32_t a = IpsOption::hash();
        mix_str(a, pattern.c_str());
        finalize(a);
        return a;
    }

    bool operator==(const IpsOption& ips) const override
    {
        if ( !IpsOption::operator==(ips) )
            return false;
        const auto& other = static_cast<const MyOption&>(ips);
        return pattern == other.pattern;
    }

    EvalStatus eval(Cursor& c, Packet* p) override
    {
        // Inspect cursor position / packet contents.
        // Return MATCH or NO_MATCH.
        return MATCH;
    }

private:
    std::string pattern;
};
2

Define the Module

static const Parameter my_params[] =
{
    { "~pattern", Parameter::PT_STRING, nullptr, nullptr,
      "pattern to match" },
    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};

class MyModule : public Module
{
public:
    MyModule() : Module(OPT_NAME, OPT_HELP, my_params) { }

    bool set(const char*, Value& v, SnortConfig*) override
    {
        if ( v.is("~pattern") )
            pattern = v.get_string();
        return true;
    }

    std::string pattern;
};
3

Define IpsApi and register

static Module* mod_ctor() { return new MyModule; }
static void    mod_dtor(Module* m) { delete m; }

static IpsOption* opt_ctor(Module* m, IpsInfo&)
{
    MyModule* mm = static_cast<MyModule*>(m);
    return new MyOption(mm->pattern.c_str());
}
static void opt_dtor(IpsOption* p) { delete p; }

static const IpsApi my_api =
{
    {
        PT_IPS_OPTION,
        sizeof(IpsApi),
        IPSAPI_VERSION,
        0,         // plugin version
        0,         // features
        nullptr,   // options
        OPT_NAME,
        OPT_HELP,
        mod_ctor,
        mod_dtor,
    },
    OPT_TYPE_DETECTION, // rule option type
    1,                  // max_per_rule (0 = unlimited)
    PROTO_BIT__TCP,     // applicable protocols
    nullptr,            // pinit
    nullptr,            // pterm
    nullptr,            // tinit
    nullptr,            // tterm
    opt_ctor,
    opt_dtor,
    nullptr,            // verify
};

SO_PUBLIC const BaseApi* snort_plugins[] =
{
    &my_api.base,
    nullptr
};

The IpsApi structure

// framework/ips_option.h
struct IpsApi
{
    BaseApi base;         // common plugin header
    RuleOptType type;     // OPT_TYPE_DETECTION, OPT_TYPE_LOGGING, or OPT_TYPE_META

    int max_per_rule;     // max instances per rule; 0 = unlimited; negative = warn
    unsigned protos;      // bitmask of PROTO_BIT_* from decode_data.h

    IpsApiFunc pinit;     // plugin-level init
    IpsApiFunc pterm;     // plugin-level cleanup
    IpsApiFunc tinit;     // thread-local init
    IpsApiFunc tterm;     // thread-local cleanup
    IpsNewFunc ctor;      // construct IpsOption from Module
    IpsDelFunc dtor;      // destroy IpsOption
    IpsOptFunc verify;    // optional post-parse verification
};

RuleOptType values

ValuePurpose
OPT_TYPE_DETECTIONParticipates in packet matching (most options)
OPT_TYPE_LOGGINGAffects what is logged when a rule fires
OPT_TYPE_METACarries metadata only (e.g. msg, sid, rev)

IPS Actions

IPS Actions are distinct from IPS Options. An action plugin specifies a builtin action in its API to determine the packet verdict. Unlike builtin actions, action plugins can carry additional logic (e.g. sending a TCP reset and logging simultaneously).
// Builtin actions do not have a plugin function.
// Action plugins specify a builtin action in the API
// which is used to determine verdict.
Action plugins use PT_IPS_ACTION as their PlugType and are registered in snort_plugins[] the same way as any other plugin.

LuaJIT rule options

Snort supports rule options written in Lua via LuaJIT. Lua-based options are loaded with --script-path and can be used directly in rules with their script filename (without the .lua extension) as the keyword. Example using the built-in find script:
snort --script-path /path/to/lua/scripts \
      --stdin-rules \
      -A cmg \
      -r traffic.pcap << END
alert tcp any any -> any 80 (
    sid:3;
    msg:"found";
    content:"GET";
    find:"pat='HTTP/1%.%d'";
)
END
The find option’s argument is a Lua table (written inline as a string). The script receives the current packet buffer and cursor position, performs matching using Lua pattern syntax, and returns a match result.
Lua patterns use % as the escape character (not \). For example, HTTP/1%.%d matches HTTP/1.1 and HTTP/1.0.
LuaJIT rule options are ideal for rapid prototyping. Once performance requirements are clear, rewrite them as compiled C++ IpsOption plugins.

Key design rules

  • eval() and action() run on packet threads — they must be thread-safe and avoid shared mutable state.
  • hash() and operator==() run on the main thread during rule compilation. Two options that produce identical results must compare equal and hash to the same value; Snort uses this to deduplicate rule option trees.
  • IpsOption objects are created once per distinct rule option at parse time, then reused across all packets matching that rule.