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
| Value | Meaning |
|---|
MATCH | Option matched; continue evaluating the rule. |
NO_MATCH | Option did not match; stop evaluating this rule. |
NO_ALERT | Option matched but suppress the alert (used by threshold options). |
FAILED_BIT | Flowbit check failed; skip the rest of the rule group. |
Implementing a custom IPS option
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;
};
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;
};
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
| Value | Purpose |
|---|
OPT_TYPE_DETECTION | Participates in packet matching (most options) |
OPT_TYPE_LOGGING | Affects what is logged when a rule fires |
OPT_TYPE_META | Carries 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.