void snort_inspect(Packet* p)
{
- uint64_t pktcnt = 0;
-
{
PacketLatency::Context pkt_latency_ctx { p };
InspectorManager::clear(p);
}
- if ( PPM_RULES_ENABLED() )
- PPM_RULE_LOG(pktcnt, p);
-
Profile profile(eventqPerfStats);
SnortEventqLog(p);
SnortEventqReset();
hr_duration elapsed_match;
hr_duration elapsed_no_match;
uint64_t checks;
- uint64_t ppm_disables;
+ uint64_t latency_timeouts;
+ uint64_t latency_suspends;
};
static void detection_option_node_update_otn_stats(detection_option_tree_node_t* node,
- node_profile_stats* stats, uint64_t checks, uint64_t disables)
+ node_profile_stats* stats, uint64_t checks, uint64_t timeouts, uint64_t suspends)
{
int i;
node_profile_stats local_stats; /* cumulative stats for this node */
node_stats.elapsed_no_match += node->state[i].elapsed_no_match;
node_stats.checks += node->state[i].checks;
}
- if (stats)
+
+ if ( stats )
{
local_stats.elapsed = stats->elapsed + node_stats.elapsed;
local_stats.elapsed_match = stats->elapsed_match + node_stats.elapsed_match;
local_stats.elapsed_no_match = stats->elapsed_no_match + node_stats.elapsed_no_match;
+
if (node_stats.checks > stats->checks)
local_stats.checks = node_stats.checks;
else
local_stats.checks = stats->checks;
- local_stats.ppm_disables = disables;
+ local_stats.latency_timeouts = timeouts;
+ local_stats.latency_suspends = suspends;
}
+
else
{
local_stats.elapsed = node_stats.elapsed;
local_stats.elapsed_match = node_stats.elapsed_match;
local_stats.elapsed_no_match = node_stats.elapsed_no_match;
local_stats.checks = node_stats.checks;
- local_stats.ppm_disables = disables;
+ local_stats.latency_timeouts = timeouts;
+ local_stats.latency_suspends = suspends;
}
- if (node->option_type == RULE_OPTION_TYPE_LEAF_NODE)
+ if ( node->option_type == RULE_OPTION_TYPE_LEAF_NODE )
{
- /* Update stats for this otn */
- // FIXIT-H J this should either be called from the packet threads at exit
+ // Update stats for this otn
+ // FIXIT-L J this should either be called from the packet threads at exit
// or *all* the states should get totalled by the main thread
// Right now, it looks like we're missing out on some stats although it's possible
// that this is "corrected" in the profiler code
- OptTreeNode* otn = (OptTreeNode*)node->option_data;
- OtnState* state = otn->state + get_instance_id();
- state->elapsed += local_stats.elapsed;
- state->elapsed_match += local_stats.elapsed_match;
- state->elapsed_no_match += local_stats.elapsed_no_match;
- if (local_stats.checks > state->checks)
- state->checks = local_stats.checks;
-
- state->ppm_disable_cnt += local_stats.ppm_disables;
+ auto* otn = (OptTreeNode*)node->option_data;
+ auto& state = otn->state[get_instance_id()];
+
+ state.elapsed += local_stats.elapsed;
+ state.elapsed_match += local_stats.elapsed_match;
+ state.elapsed_no_match += local_stats.elapsed_no_match;
+
+ if (local_stats.checks > state.checks)
+ state.checks = local_stats.checks;
+
+ state.latency_timeouts += local_stats.latency_timeouts;
+ state.latency_suspends += local_stats.latency_suspends;
}
if ( node->num_children )
{
for ( i=0; i < node->num_children; ++i )
detection_option_node_update_otn_stats(node->children[i], &local_stats, checks,
- disables);
+ timeouts, suspends);
}
}
void detection_option_tree_update_otn_stats(SFXHASH* doth)
{
- if (doth == NULL)
+ if ( !doth )
return;
- /* Find the first tree root in the table */
- SFXHASH_NODE* hashnode = sfxhash_findfirst(doth);
-
- while (hashnode)
+ for ( auto hnode = sfxhash_findfirst(doth); hnode; hnode = sfxhash_findnext(doth) )
{
- detection_option_tree_node_t* node =
- (detection_option_tree_node_t*)hashnode->data;
+ auto* node = (detection_option_tree_node_t*)hnode->data;
+ assert(node);
uint64_t checks = 0;
- uint64_t disables = 0;
+ uint64_t timeouts = 0;
+ uint64_t suspends = 0;
for ( unsigned i = 0; i < get_instance_max(); ++i )
{
checks += node->state[i].checks;
- disables += node->state[i].ppm_disable_cnt;
+ timeouts += node->state[i].latency_timeouts;
+ suspends += node->state[i].latency_suspends;
}
if ( checks )
- detection_option_node_update_otn_stats(node, nullptr, checks, disables);
-
- hashnode = sfxhash_findnext(doth);
+ detection_option_node_update_otn_stats(node, nullptr, checks, timeouts, suspends);
}
}
detection_option_tree_root_t* p = (detection_option_tree_root_t*)
SnortAlloc(sizeof(detection_option_tree_root_t));
- p->state = (ppm_dot_root_state_t*)
- SnortAlloc(sizeof(ppm_dot_root_state_t)*get_instance_max());
+ p->latency_state = new RuleLatencyState[get_instance_max()]();
return p;
}
root = (detection_option_tree_root_t*)*existing_tree;
free(root->children);
- free(root->state);
+ delete[] root->latency_state;
free(root);
*existing_tree = NULL;
}
#endif
#include <sys/time.h>
-#include "main/snort_types.h"
#include "detection/rule_option_types.h"
+#include "main/snort_types.h"
+#include "latency/rule_latency_state.h"
#include "time/clock_defs.h"
struct Packet;
uint64_t checks;
uint64_t disables;
- uint64_t ppm_disable_cnt;
- uint64_t ppm_enable_cnt;
+ unsigned latency_timeouts;
+ unsigned latency_suspends;
// FIXIT-L J perf profiler stuff should be factored of the node state struct
void update(hr_duration delta, bool match)
dot_node_state_t* state;
};
-// this is per packet thread
-struct ppm_dot_root_state_t
-{
- uint64_t ppm_suspend_time;
- uint64_t ppm_disable_cnt;
- bool enabled;
-};
-
struct detection_option_tree_root_t
{
int num_children;
detection_option_tree_node_t** children;
- ppm_dot_root_state_t* state;
+ RuleLatencyState* latency_state;
};
struct detection_option_eval_data_t
detection_option_tree_root_t* root = (detection_option_tree_root_t*)*existing_tree;
- for ( unsigned i = 0; i < get_instance_max(); ++i )
- root->state[i].enabled = true;
-
OptFpList* opt_fp = otn->opt_func;
if (!root->children)
#include "treenodes.h"
#include "latency/packet_latency.h"
+#include "latency/rule_latency.h"
#include "main/snort_config.h"
#include "main/snort_debug.h"
#include "framework/cursor.h"
return 1;
}
-static int detection_option_tree_evaluate(
- detection_option_tree_root_t* root,
+static int detection_option_tree_evaluate(detection_option_tree_root_t* root,
detection_option_eval_data_t* eval_data)
{
- if (!root)
+ if ( !root )
return 0;
- /* Start Rule Timer */
- if ( PPM_RULES_ENABLED() )
- {
- PPM_GET_TIME();
- PPM_INIT_RULE_TIMER();
- ppm_dot_root_state_t* root_state = root->state + get_instance_id();
-
- if ( !root_state->enabled )
- {
- PPM_REENABLE_TREE(root, eval_data->p);
+ RuleLatency::Context rule_latency_ctx(root);
- if ( !root_state->enabled )
- {
- PPM_END_RULE_TIMER();
- return 0;
- }
- }
- }
+ if ( !RuleLatency::enabled() )
+ return 0;
Cursor c(eval_data->p);
-
int rval = 0;
- for ( int i = 0; i< root->num_children; i++)
- {
- /* Increment number of events generated from that child */
- rval += detection_option_node_evaluate(root->children[i], eval_data, c);
- }
- if ( PPM_ENABLED() )
+ for ( int i = 0; i < root->num_children; ++i )
{
- PPM_GET_TIME();
-
- /* Rule test */
- if ( PPM_RULES_ENABLED() )
- {
- PPM_RULE_TEST(root, eval_data->p);
- PPM_ACCUM_RULE_TIME();
- PPM_END_RULE_TIMER();
- }
+ // Increment number of events generated from that child
+ rval += detection_option_node_evaluate(root->children[i], eval_data, c);
}
return rval;
return 0;
}
- if ( PPM_ENABLED() )
- PPM_GET_TIME();
-
do
{
// FIXIT-L restrict to non-data packets? (non-data includes
{
// profiling
// FIXIT-L J factor the profiling stuff out
- hr_duration elapsed;
- hr_duration elapsed_match;
- hr_duration elapsed_no_match;
+ hr_duration elapsed = 0_ticks;
+ hr_duration elapsed_match = 0_ticks;
+ hr_duration elapsed_no_match = 0_ticks;
- uint64_t checks;
- uint64_t matches;
- uint8_t noalerts;
- uint64_t alerts;
+ uint64_t checks = 0;
+ uint64_t matches = 0;
+ uint8_t noalerts = 0;
+ uint64_t alerts = 0;
- // ppm
- uint64_t ppm_suspend_time;
- uint64_t ppm_disable_cnt;
+ uint64_t latency_timeouts = 0;
+ uint64_t latency_suspends = 0;
operator bool() const
{ return elapsed > 0_ticks || checks > 0; }
-
- void reset()
- {
- elapsed = elapsed_match = elapsed_no_match = 0_ticks;
- checks = matches = noalerts = alerts = 0;
- ppm_suspend_time = ppm_disable_cnt = 0;
- }
};
// one of these for each rule
set ( LATENCY_INCLUDES
packet_latency.h
+ rule_latency.h
latency_rules.h
)
latency_timer.h
latency_util.h
packet_latency.cc
+ rule_latency.cc
latency_module.cc
)
x_includedir = $(pkgincludedir)/latency
x_include_HEADERS = \
-packet_latency.h \
latency_rules.h
liblatency_a_SOURCES = \
latency_module.cc \
packet_latency_config.h \
packet_latency.h \
-packet_latency.cc
+packet_latency.cc \
+rule_latency_config.h \
+rule_latency_state.h \
+rule_latency.h \
+rule_latency.cc
based on whether the packet was fastpathed and depending on
how the manager was configured.
-* Rule latency: To be ported over from ppm/
+* Rule latency: tracks and manages latency in rule tree evaluation.
+ Rule latency works much like packet latency. Instead of fastpath
+ the API contains an enabled() check that tests whether the
+ current rule tree should be evaluated. Pushing a rule tree
+ onto the stack has the side effect of re-enabling the rule
+ tree if conditions are met. Popping a rule tree off of the stack
+ has the side effect of suspending the rule tree if certain conditions
+ are met. Re-enables, suspends, and rule tree timeouts all
+ propagate log events. Re-enables and suspends propagate
+ alerts.
-Notes:
+ Pushing a rule tree side-effect: A rule tree is re-enabled if
+ 1) it is currently disabled and 2) the suspend timeout is nonzero
+ and has been reached.
- * Latency is intended to replace the legacy ppm/ code
+ Popping a rule tree side-effect: A rule tree is suspended if
+ 1) it is timed out and 2) the timeout threshold is met or
+ exceeded.
#define LATENCY_CONFIG_H
#include "packet_latency_config.h"
+#include "rule_latency_config.h"
struct LatencyConfig
{
PacketLatencyConfig packet_latency;
+ RuleLatencyConfig rule_latency;
};
#endif
static const Parameter s_packet_params[] =
{
- { "enable", Parameter::PT_BOOL, nullptr, "true",
- "enable packet latency" },
-
- { "max_time", Parameter::PT_INT, "0:", "0",
+ { "max_time", Parameter::PT_INT, "0:", "500",
"set timeout for packet latency thresholding (usec)" },
{ "fastpath", Parameter::PT_BOOL, nullptr, "false",
"fastpath expensive packets (max_time exceeded)" },
+ // FIXIT-L J what is the most intuitive default action?
+ { "action", Parameter::PT_ENUM, "none | alert | log | alert_and_log", "alert_and_log",
+ "event action if packet times out and is fastpathed" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const Parameter s_rule_params[] =
+{
+ { "max_time", Parameter::PT_INT, "0:", "500",
+ "set timeout for rule evaluation (usec)" },
+
+ // We could just treat suspend_threshold == 0 as suspend == false
+ // but we leave this here for parity with packet latency
+ { "suspend", Parameter::PT_BOOL, nullptr, "false",
+ "temporarily suspend expensive rules" },
+
+ // FIXIT-L J what is a sensible default for this?
+ { "suspend_threshold", Parameter::PT_INT, "1:", "5",
+ "set threshold for number of timeouts before suspending a rule" },
+
+ { "max_suspend_time", Parameter::PT_INT, "0:", "30000",
+ "set max time for suspending a rule (ms, 0 means permanently disable rule)" },
+
+ // FIXIT-L J what is the most intuitive default action?
{ "action", Parameter::PT_ENUM, "none | alert | log | alert_and_log", "alert_and_log",
- "event action if packet latency times out" },
+ "event action for rule latency enable and suspend events" },
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
{ "packet", Parameter::PT_TABLE, s_packet_params, nullptr,
"packet latency" },
+ { "rule", Parameter::PT_TABLE, s_rule_params, nullptr,
+ "rule latency" },
+
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
static const RuleMap latency_rules[] =
{
+ { LATENCY_EVENT_RULE_TREE_SUSPENDED, "rule tree suspended due to latency" },
+ { LATENCY_EVENT_RULE_TREE_ENABLED, "rule tree re-enabled after suspend timeout" },
{ LATENCY_EVENT_PACKET_FASTPATHED, "packet fastpathed due to latency" },
{ 0, nullptr }
static const PegInfo latency_pegs[] =
{
- { "packets", "total packets monitored" },
- { "timeouts", "packets that timed out" },
+ { "total_packets", "total packets monitored" },
+ { "packet_timeouts", "packets that timed out" },
+ { "total_rule_evals", "total rule evals monitored" },
+ { "rule_eval_timeouts", "rule evals that timed out" },
+ { "rule_tree_enables", "rule tree re-enables" },
{ nullptr, nullptr }
};
using std::chrono::duration_cast;
using std::chrono::microseconds;
- if ( v.is("enable") )
- config.enable = v.get_bool();
-
- else if ( v.is("max_time") )
+ if ( v.is("max_time") )
config.max_time =
duration_cast<decltype(config.max_time)>(microseconds(v.get_long()));
return true;
}
+static inline bool latency_set(Value& v, RuleLatencyConfig& config)
+{
+ using std::chrono::duration_cast;
+ using std::chrono::microseconds;
+ using std::chrono::milliseconds;
+
+ if ( v.is("max_time") )
+ config.max_time =
+ duration_cast<decltype(config.max_time)>(microseconds(v.get_long()));
+
+ else if ( v.is("suspend") )
+ config.suspend = v.get_bool();
+
+ else if ( v.is("suspend_threshold") )
+ config.suspend_threshold = v.get_long();
+
+ else if ( v.is("max_suspend_time") )
+ config.max_suspend_time =
+ duration_cast<decltype(config.max_time)>(milliseconds(v.get_long()));
+
+ else if ( v.is("action") )
+ config.action =
+ static_cast<decltype(config.action)>(v.get_long());
+
+ else
+ return false;
+
+ return true;
+}
+
LatencyModule::LatencyModule() :
Module(s_name, s_help, s_params)
{ }
bool LatencyModule::set(const char* fqn, Value& v, SnortConfig* sc)
{
const char* slp = "latency.packet";
+ const char* slr = "latency.rule";
if ( !strncmp(fqn, slp, strlen(slp)) )
return latency_set(v, sc->latency->packet_latency);
+
+ else if ( !strncmp(fqn, slr, strlen(slr)) )
+ return latency_set(v, sc->latency->rule_latency);
+
else
return false;
#define GID_LATENCY 134
// SIDs
+#define LATENCY_EVENT_RULE_TREE_SUSPENDED 1
+#define LATENCY_EVENT_RULE_TREE_ENABLED 2
#define LATENCY_EVENT_PACKET_FASTPATHED 3
#endif
struct LatencyStats
{
- PegCount packets;
- PegCount timeouts;
+ PegCount total_packets;
+ PegCount packet_timeouts;
+ PegCount total_rule_evals;
+ PegCount rule_eval_timeouts;
+ PegCount rule_tree_enables;
};
extern THREAD_LOCAL LatencyStats latency_stats;
bool timed_out() const
{ return elapsed() > max_time; }
- bool marked = false;
-
private:
duration max_time;
Stopwatch<Clock> sw;
#include "packet_latency.h"
#include <cassert>
-#include <mutex>
#include <sstream>
#include <vector>
typename DefaultClock::duration elapsed;
};
+template<typename Clock>
+class PacketTimer : public LatencyTimer<Clock>
+{
+public:
+ PacketTimer(typename Clock::duration d) :
+ LatencyTimer<Clock>(d) { }
+
+ bool marked_as_fastpathed = false;
+};
+
using ConfigWrapper = ReferenceWrapper<PacketLatencyConfig>;
using EventHandler = EventingWrapper<Event>;
os << "latency: packet timed out";
if ( e.fastpathed )
- os << " (fastpathed): ";
- else
- os << ": ";
+ os << " (fastpathed)";
+
+ os << ": ";
os << duration_cast<microseconds>(e.elapsed).count() << " usec, [";
os << e.packet->ptrs.ip_api.get_src() << " -> " <<
bool fastpath();
private:
- std::vector<LatencyTimer<Clock>> timers;
+ // FIXIT-H J use custom struct instead of std::pair for better semantics
+ // std::vector<std::pair<LatencyTimer<Clock>, bool>> contexts;
+ std::vector<PacketTimer<Clock>> timers;
const ConfigWrapper& config;
EventHandler& event_handler;
EventHandler& log_handler;
inline void Impl<Clock>::push()
{
using std::chrono::duration_cast;
- timers.push_back(duration_cast<typename Clock::duration>(
- config->max_time));
+ auto max_time = duration_cast<typename Clock::duration>(config->max_time);
+ timers.emplace_back(max_time);
}
template<typename Clock>
{
assert(!timers.empty());
const auto& timer = timers.back();
- // timer.mark implies fastpath-related timeout
- bool timed_out = timer.marked;
+
+ auto timed_out = timer.marked_as_fastpathed;
if ( timer.timed_out() )
{
- Event e { p, timed_out, timers.back().elapsed() };
+ timed_out = true;
+
+ // timer.mark implies fastpath-related timeout
+ Event e { p, timer.marked_as_fastpathed, timer.elapsed() };
if ( config->action & PacketLatencyConfig::LOG )
log_handler.handle(e);
- if ( timed_out and (config->action & PacketLatencyConfig::ALERT) )
+ if ( timer.marked_as_fastpathed and (config->action & PacketLatencyConfig::ALERT) )
event_handler.handle(e);
}
assert(!timers.empty());
auto& timer = timers.back();
- if ( timer.timed_out() )
- timer.marked = true;
- return timer.marked;
+ if ( !timer.marked_as_fastpathed )
+ {
+ if ( timer.timed_out() )
+ timer.marked_as_fastpathed = true;
+ }
+
+ return timer.marked_as_fastpathed;
}
// -----------------------------------------------------------------------------
void PacketLatency::push()
{
- if ( packet_latency::config->enable )
+ if ( packet_latency::config->enabled() )
{
packet_latency::get_impl().push();
- ++latency_stats.packets;
+ ++latency_stats.total_packets;
}
}
void PacketLatency::pop(const Packet* p)
{
- if ( packet_latency::config->enable )
+ if ( packet_latency::config->enabled() )
{
if ( packet_latency::get_impl().pop(p) )
- ++latency_stats.timeouts;
+ ++latency_stats.packet_timeouts;
}
}
bool PacketLatency::fastpath()
{
- if ( packet_latency::config->enable )
+ if ( packet_latency::config->enabled() )
return packet_latency::get_impl().fastpath();
return false;
MockClock::inc(config.config.max_time + 1_ticks);
CHECK_FALSE( impl.fastpath() );
- CHECK_FALSE( impl.pop(nullptr) );
+ CHECK( impl.pop(nullptr) );
CHECK( event_handler.count == 0 );
CHECK( log_handler.count == 1 );
struct PacketLatencyConfig
{
- bool enable = false;
- hr_duration max_time = 0_ticks;
- bool fastpath = false;
- enum Action : uint8_t
+ enum Action
{
NONE = 0x00,
ALERT = 0x01,
LOG = 0x02,
ALERT_AND_LOG = ALERT | LOG
- } action = NONE;
+ };
+
+ hr_duration max_time = 0_ticks;
+ bool fastpath = false;
+ Action action = NONE;
+
+ bool enabled() const { return max_time > 0_ticks; }
};
#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// rule_latency.cc author Joel Cornett <jocornet@cisco.com>
+
+#include "rule_latency.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cassert>
+
+#include "detection/detection_options.h"
+#include "events/event_queue.h"
+#include "log/messages.h"
+#include "main/snort_config.h"
+#include "latency_config.h"
+#include "latency_rules.h"
+#include "latency_stats.h"
+#include "latency_timer.h"
+#include "latency_util.h"
+
+#ifdef UNIT_TEST
+#include "catch/catch.hpp"
+#endif
+
+namespace rule_latency
+{
+// -----------------------------------------------------------------------------
+// helpers
+// -----------------------------------------------------------------------------
+
+using DefaultClock = hr_clock;
+
+struct Event
+{
+ enum Type
+ {
+ EVENT_ENABLED,
+ EVENT_TIMED_OUT,
+ EVENT_SUSPENDED
+ };
+
+ Type type;
+ typename DefaultClock::duration elapsed;
+ detection_option_tree_root_t* root;
+};
+
+template<typename Clock>
+class RuleTimer : public LatencyTimer<Clock>
+{
+public:
+ RuleTimer(typename Clock::duration d, detection_option_tree_root_t* root) :
+ LatencyTimer<Clock>(d), root(root) { }
+
+ detection_option_tree_root_t* root;
+};
+
+using ConfigWrapper = ReferenceWrapper<RuleLatencyConfig>;
+using EventHandler = EventingWrapper<Event>;
+
+static inline std::ostream& operator<<(std::ostream& os, const Event& e)
+{
+ using std::chrono::duration_cast;
+ using std::chrono::microseconds;
+
+ os << "latency: ";
+
+ if ( e.type == Event::EVENT_ENABLED )
+ os << "rule tree enabled: ";
+
+ else
+ {
+ os << "rule tree timed out";
+ if ( e.type == Event::EVENT_SUSPENDED )
+ os << " (suspended)";
+
+ os << ": ";
+
+ os << duration_cast<microseconds>(e.elapsed).count() << " usec, ";
+ }
+
+ // FIXIT-L J seeing the address of the dot root is not particularly helpful
+ // except during debugging (ported from legacy ppm)
+ os << "[" << e.root << "]";
+
+ return os;
+}
+
+// -----------------------------------------------------------------------------
+// rule tree interface
+// -----------------------------------------------------------------------------
+
+// goes in a static structure so we can templatize Impl
+struct DefaultRuleInterface
+{
+ static bool is_enabled(const detection_option_tree_root_t& root)
+ { return root.latency_state[get_instance_id()].enabled; }
+
+ // return true if rule was *reenabled*
+ template<typename Duration, typename Time>
+ static bool reenable(detection_option_tree_root_t& root, Duration max_suspend_time,
+ Time cur_time)
+ {
+ auto& state = root.latency_state[get_instance_id()];
+ if ( !state.enabled && (cur_time - state.suspend_time > max_suspend_time) )
+ {
+ state.enable();
+ return true;
+ }
+
+ return false;
+ }
+
+ // FIXIT-L J traversing the children 2 separate times with timeout & suspend is inefficient
+ static inline void timeout(detection_option_tree_root_t& root)
+ {
+ ++root.latency_state[get_instance_id()].timeouts;
+ for ( int i = 0; i < root.num_children; ++i )
+ // FIXIT-L J rename to something like latency_timeout_count
+ ++root.children[i]->state[get_instance_id()].latency_timeouts;
+ }
+
+ template<typename Time>
+ static bool suspend(detection_option_tree_root_t& root, unsigned threshold,
+ Time cur_time)
+ {
+ auto& state = root.latency_state[get_instance_id()];
+ if ( state.timeouts >= threshold )
+ {
+ state.suspend(cur_time);
+ for ( int i = 0; i < root.num_children; ++i )
+ ++root.children[i]->state[get_instance_id()].latency_suspends;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ template<typename Time>
+ static bool timeout_and_suspend(detection_option_tree_root_t& root, unsigned threshold,
+ Time time, bool do_suspend)
+ {
+ auto& state = root.latency_state[get_instance_id()];
+
+ ++state.timeouts;
+
+ // the separate loops in each branch are so we can avoid iterating
+ // over the children twice in the suspend case
+
+ if ( do_suspend )
+ {
+ if ( state.timeouts >= threshold )
+ {
+ state.suspend(time);
+
+ for ( int i = 0; i < root.num_children; ++i )
+ {
+ auto& child_state = root.children[i]->state[get_instance_id()];
+ // FIXIT-L J rename to something like latency_timeout_count
+ ++child_state.latency_timeouts;
+ ++child_state.latency_suspends;
+ }
+
+ return true;
+ }
+ }
+
+ else
+ {
+ for ( int i = 0; i < root.num_children; ++i )
+ {
+ // FIXIT-L J rename to something like latency_timeout_count
+ ++root.children[i]->state[get_instance_id()].latency_timeouts;
+ }
+ }
+
+ return false;
+ }
+};
+
+
+// -----------------------------------------------------------------------------
+// implementation
+// -----------------------------------------------------------------------------
+
+template<typename Clock = DefaultClock, typename RuleTree = DefaultRuleInterface>
+class Impl
+{
+public:
+ Impl(const ConfigWrapper&, EventHandler&, EventHandler&);
+
+ bool push(detection_option_tree_root_t*);
+ bool pop();
+ // FIXIT-L J should this logic be inverted to suspended()?
+ // returns whether current rule tree is enabled
+ bool enabled() const;
+
+private:
+ void handle(const Event&);
+
+ std::vector<RuleTimer<Clock>> timers;
+ const ConfigWrapper& config;
+ EventHandler& event_handler;
+ EventHandler& log_handler;
+};
+
+template<typename Clock, typename RuleTree>
+inline Impl<Clock, RuleTree>::Impl(const ConfigWrapper& cfg, EventHandler& eh, EventHandler& lh) :
+ config(cfg), event_handler(eh), log_handler(lh)
+{ }
+
+template<typename Clock, typename RuleTree>
+inline bool Impl<Clock, RuleTree>::push(detection_option_tree_root_t* root)
+{
+ assert(root);
+
+ // FIXIT-L J rule timer is pushed even if rule is not enabled (no visible side-effects)
+ timers.emplace_back(config->max_time, root);
+
+ if ( config->allow_reenable() )
+ {
+ if ( RuleTree::reenable(*root, config->max_suspend_time, Clock::now()) )
+ {
+ Event e {
+ Event::EVENT_ENABLED,
+ typename Clock::duration(0),
+ root
+ };
+
+ handle(e);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template<typename Clock, typename RuleTree>
+inline bool Impl<Clock, RuleTree>::pop()
+{
+ assert(!timers.empty());
+ const auto& timer = timers.back();
+
+ auto timed_out = timer.timed_out();
+ if ( timed_out )
+ {
+ auto suspended = RuleTree::timeout_and_suspend(*timer.root, config->suspend_threshold,
+ Clock::now(), config->suspend);
+
+ // FIXIT-L J first field doesn't matter
+ Event e {
+ suspended ? Event::EVENT_SUSPENDED : Event::EVENT_TIMED_OUT,
+ timer.elapsed(),
+ timer.root
+ };
+
+ handle(e);
+ }
+
+ timers.pop_back();
+ return timed_out;
+}
+
+template<typename Clock, typename RuleTree>
+inline bool Impl<Clock, RuleTree>::enabled() const
+{
+ if ( !config->suspend )
+ return true;
+
+ assert(!timers.empty());
+ return RuleTree::is_enabled(*timers.back().root);
+}
+
+template<typename Clock, typename RuleTree>
+inline void Impl<Clock, RuleTree>::handle(const Event& e)
+{
+ if ( config->action & RuleLatencyConfig::LOG )
+ log_handler.handle(e);
+
+ if ( config->action & RuleLatencyConfig::ALERT )
+ event_handler.handle(e);
+}
+
+// -----------------------------------------------------------------------------
+// static variables
+// -----------------------------------------------------------------------------
+
+static struct SnortConfigWrapper : ConfigWrapper
+{
+ const RuleLatencyConfig* operator->() const override
+ { return &snort_conf->latency->rule_latency; }
+
+} config;
+
+static struct SnortEventHandler : EventHandler
+{
+ void handle(const Event& e) override
+ {
+ switch ( e.type )
+ {
+ case Event::EVENT_ENABLED:
+ SnortEventqAdd(GID_LATENCY, LATENCY_EVENT_RULE_TREE_ENABLED);
+ break;
+
+ case Event::EVENT_SUSPENDED:
+ SnortEventqAdd(GID_LATENCY, LATENCY_EVENT_RULE_TREE_SUSPENDED);
+ break;
+
+ default:
+ break;
+ }
+ }
+} event_handler;
+
+static struct SnortLogHandler : EventHandler
+{
+ void handle(const Event& e) override
+ {
+ std::ostringstream ss;
+ ss << e;
+ LogMessage("%s\n", ss.str().c_str());
+ }
+} log_handler;
+
+static THREAD_LOCAL Impl<>* impl = nullptr;
+
+static inline Impl<>& get_impl()
+{
+ if ( !impl )
+ impl = new Impl<>(config, event_handler, log_handler);
+
+ return *impl;
+}
+
+} // namespace rule_latency
+
+// -----------------------------------------------------------------------------
+// rule latency interface
+// -----------------------------------------------------------------------------
+
+void RuleLatency::push(detection_option_tree_root_t* root)
+{
+ if ( rule_latency::config->enabled() )
+ {
+ if ( rule_latency::get_impl().push(root) )
+ ++latency_stats.rule_tree_enables;
+
+ ++latency_stats.total_rule_evals;
+ }
+}
+
+void RuleLatency::pop()
+{
+ if ( rule_latency::config->enabled() )
+ {
+ if ( rule_latency::get_impl().pop() )
+ ++latency_stats.rule_eval_timeouts;
+ }
+}
+
+bool RuleLatency::enabled()
+{
+ if ( rule_latency::config->enabled() )
+ return rule_latency::get_impl().enabled();
+
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// unit tests
+// -----------------------------------------------------------------------------
+
+#ifdef UNIT_TEST
+
+namespace t_rule_latency
+{
+
+struct MockConfigWrapper : rule_latency::ConfigWrapper
+{
+ RuleLatencyConfig config;
+
+ const RuleLatencyConfig* operator->() const override
+ { return &config; }
+};
+
+struct EventHandlerSpy : rule_latency::EventHandler
+{
+ unsigned count = 0;
+ void handle(const rule_latency::Event&) override
+ { ++count; }
+};
+
+struct MockClock : ClockTraits<hr_clock>
+{
+ static hr_time t;
+
+ static void reset()
+ { t = hr_time(0_ticks); }
+
+ static void inc(hr_duration d = 1_ticks)
+ { t += d; }
+
+ static hr_time now()
+ { return t; }
+};
+
+hr_time MockClock::t = hr_time(0_ticks);
+
+struct RuleInterfaceSpy
+{
+ static bool is_enabled_result;
+ static bool is_enabled_called;
+ static bool reenable_result;
+ static bool reenable_called;
+ static bool timeout_and_suspend_result;
+ static bool timeout_and_suspend_called;
+
+ static void reset()
+ {
+ is_enabled_result = false;
+ is_enabled_called = false;
+ reenable_result = false;
+ reenable_called = false;
+ timeout_and_suspend_result = false;
+ timeout_and_suspend_called = false;
+ }
+
+ static bool is_enabled(const detection_option_tree_root_t&)
+ { is_enabled_called = true; return is_enabled_result; }
+
+ template<typename Duration, typename Time>
+ static bool reenable(detection_option_tree_root_t&, Duration, Time)
+ { reenable_called = true; return reenable_result; }
+
+ template<typename Time>
+ static bool timeout_and_suspend(detection_option_tree_root_t&, unsigned, Time, bool)
+ { timeout_and_suspend_called = true; return timeout_and_suspend_result; }
+};
+
+bool RuleInterfaceSpy::is_enabled_result = false;
+bool RuleInterfaceSpy::is_enabled_called = false;
+bool RuleInterfaceSpy::reenable_result = false;
+bool RuleInterfaceSpy::reenable_called = false;
+bool RuleInterfaceSpy::timeout_and_suspend_result = false;
+bool RuleInterfaceSpy::timeout_and_suspend_called = false;
+
+} // namespace t_rule_latency
+
+TEST_CASE ( "rule latency impl", "[latency]" )
+{
+ using namespace t_rule_latency;
+
+ MockConfigWrapper config;
+ EventHandlerSpy event_handler;
+ EventHandlerSpy log_handler;
+
+ MockClock::reset();
+ RuleInterfaceSpy::reset();
+
+ config.config.action = RuleLatencyConfig::ALERT_AND_LOG;
+
+ detection_option_tree_root_t root;
+
+ rule_latency::Impl<MockClock, RuleInterfaceSpy> impl(config, event_handler, log_handler);
+
+ SECTION( "push" )
+ {
+ SECTION( "reenable allowed" )
+ {
+ config.config.max_suspend_time = 1_ticks;
+
+ SECTION( "push rule" )
+ {
+ CHECK_FALSE( impl.push(&root) );
+ CHECK( log_handler.count == 0 );
+ CHECK( event_handler.count == 0 );
+ CHECK( RuleInterfaceSpy::reenable_called );
+ }
+
+ SECTION( "push rule -- reenabled" )
+ {
+ RuleInterfaceSpy::reenable_result = true;
+
+ CHECK( impl.push(&root) );
+ CHECK( log_handler.count == 1 );
+ CHECK( event_handler.count == 1 );
+ CHECK( RuleInterfaceSpy::reenable_called );
+ }
+ }
+
+ SECTION( "reenable not allowed" )
+ {
+ config.config.max_suspend_time = 0_ticks;
+
+ SECTION( "push rule" )
+ {
+ CHECK_FALSE( impl.push(&root) );
+ CHECK( log_handler.count == 0 );
+ CHECK( event_handler.count == 0 );
+ CHECK_FALSE( RuleInterfaceSpy::reenable_called );
+ }
+ }
+ }
+
+ SECTION( "enabled" )
+ {
+ RuleInterfaceSpy::is_enabled_result = false;
+
+ impl.push(&root);
+
+ SECTION( "suspending of rules disabled" )
+ {
+ config.config.suspend = false;
+
+ CHECK( impl.enabled() );
+ CHECK_FALSE( RuleInterfaceSpy::is_enabled_called );
+ }
+
+ SECTION( "suspend of rules enabled" )
+ {
+ config.config.suspend = true;
+
+ CHECK_FALSE( impl.enabled() );
+ CHECK( RuleInterfaceSpy::is_enabled_called );
+ }
+ }
+
+ SECTION( "pop" )
+ {
+ config.config.max_time = 1_ticks;
+
+ impl.push(&root);
+
+ SECTION( "rule timeout" )
+ {
+ MockClock::inc(2_ticks);
+
+ SECTION( "rule suspended" )
+ {
+ RuleInterfaceSpy::timeout_and_suspend_result = true;
+
+ CHECK( impl.pop() );
+ CHECK( log_handler.count == 1 );
+ CHECK( event_handler.count == 1 );
+ CHECK( RuleInterfaceSpy::timeout_and_suspend_called );
+ }
+
+ SECTION( "rule not suspended" )
+ {
+ RuleInterfaceSpy::timeout_and_suspend_result = false;
+
+ CHECK( impl.pop() );
+ CHECK( log_handler.count == 1 );
+ CHECK( event_handler.count == 1 );
+ CHECK( RuleInterfaceSpy::timeout_and_suspend_called );
+ }
+ }
+
+ SECTION( "no rule timeout" )
+ {
+ CHECK_FALSE( impl.pop() );
+ CHECK( log_handler.count == 0 );
+ CHECK( event_handler.count == 0 );
+ CHECK_FALSE( RuleInterfaceSpy::timeout_and_suspend_called );
+ }
+ }
+}
+
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// rule_latency.h author Joel Cornett <jocornet@cisco.com>
+
+#ifndef RULE_LATENCY_H
+#define RULE_LATENCY_H
+
+struct detection_option_tree_root_t;
+
+class RuleLatency
+{
+public:
+ static void push(detection_option_tree_root_t*);
+ static void pop();
+ static bool enabled();
+
+ class Context
+ {
+ public:
+ Context(detection_option_tree_root_t* root)
+ { RuleLatency::push(root); }
+
+ ~Context()
+ { RuleLatency::pop(); }
+ };
+};
+
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// rule_latency_config.h author Joel Cornett <jocornet@cisco.com>
+
+#ifndef RULE_LATENCY_CONFIG_H
+#define RULE_LATENCY_CONFIG_H
+
+#include <cstdint>
+#include "time/clock_defs.h"
+
+struct RuleLatencyConfig
+{
+ enum Action
+ {
+ NONE = 0x00,
+ ALERT = 0x01,
+ LOG = 0x02,
+ ALERT_AND_LOG = ALERT | LOG
+ };
+
+ hr_duration max_time = 0_ticks;
+ bool suspend = false;
+ unsigned suspend_threshold = 0;
+ hr_duration max_suspend_time = 0_ticks;
+ Action action = NONE;
+
+ bool enabled() const { return max_time > 0_ticks; }
+ bool allow_reenable() const { return max_suspend_time > 0_ticks; }
+};
+
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// rule_latency_state.h author Joel Cornett <jocornet@cisco.com>
+
+#ifndef RULE_LATENCY_STATE_H
+#define RULE_LATENCY_STATE_H
+
+#include "time/clock_defs.h"
+
+struct RuleLatencyState
+{
+ hr_time suspend_time { };
+ unsigned timeouts = 0;
+ // FIXIT-L J should this be inverted to suspended?
+ bool enabled = true;
+
+ void enable()
+ {
+ timeouts = 0;
+ enabled = true;
+ }
+
+ void suspend(hr_time cur_time)
+ {
+ suspend_time = cur_time;
+ enabled = false;
+ }
+};
+
+#endif
{ "avg/check", 10, '\0', 1, std::ios_base::fmtflags() },
{ "avg/match", 10, '\0', 1, std::ios_base::fmtflags() },
{ "avg/non-match", 14, '\0', 1, std::ios_base::fmtflags() },
- { "disables", 9, '\0', 0, std::ios_base::fmtflags() },
+ { "timeouts", 9, '\0', 0, std::ios_base::fmtflags() },
+ { "suspends", 9, '\0', 0, std::ios_base::fmtflags() },
{ nullptr, 0, '\0', 0, std::ios_base::fmtflags() }
};
uint64_t alerts() const
{ return state.alerts; }
- uint64_t ppm_disable_count() const
- { return state.ppm_disable_cnt; }
+ uint64_t timeouts() const
+ { return state.latency_timeouts; }
+
+ uint64_t suspends() const
+ { return state.latency_suspends; }
hr_duration time_per(hr_duration d, uint64_t v) const
{
table << duration_cast<microseconds>(v.avg_match()).count(); // avg/match
table << duration_cast<microseconds>(v.avg_no_match()).count(); // avg/non-match
- table << v.ppm_disable_count(); // disables
+ table << v.timeouts();
+ table << v.suspends();
}
LogMessage("%s", ss.str().c_str());
for ( unsigned i = 0; i < get_instance_max(); ++i )
{
auto& state = otn->state[i];
- state.reset();
+ state = OtnState();
}
}
}
hr_duration elapsed, hr_duration elapsed_match,
uint64_t checks, uint64_t matches)
{
- return {
- elapsed,
- elapsed_match,
- 0_ticks,
-
- checks,
- matches,
- 0,
- 0,
- 0,
- 0
- };
+ OtnState state;
+
+ state.elapsed = elapsed;
+ state.elapsed_match = elapsed_match;
+ state.checks = checks;
+ state.matches = matches;
+
+ return state;
}
static inline rule_stats::View make_rule_entry(
TEST_CASE( "otn state", "[profiler][rule_profiler]" )
{
- OtnState state_a = { 1_ticks, 2_ticks, 3_ticks, 1, 2, 3, 4, 0, 0};
+ OtnState state_a;
+
+ state_a.elapsed = 1_ticks;
+ state_a.elapsed_match = 2_ticks;
+ state_a.elapsed_no_match = 2_ticks;
+ state_a.checks = 1;
+ state_a.matches = 2;
+ state_a.noalerts = 3;
+ state_a.alerts = 4;
SECTION( "incremental addition" )
{
- OtnState state_b = { 4_ticks, 5_ticks, 6_ticks, 5, 6, 7, 8, 0, 0};
+ OtnState state_b;
+
+ state_b.elapsed = 4_ticks;
+ state_b.elapsed_match = 5_ticks;
+ state_b.elapsed_no_match = 6_ticks;
+ state_b.checks = 5;
+ state_b.matches = 6;
+ state_b.noalerts = 7;
+ state_b.alerts = 8;
state_a += state_b;
SECTION( "reset" )
{
- state_a.reset();
+ state_a = OtnState();
+
CHECK( state_a.elapsed == 0_ticks );
CHECK( state_a.elapsed_match == 0_ticks );
CHECK( state_a.checks == 0 );
SigInfo sig_info;
auto entry = make_rule_entry(3_ticks, 2_ticks, 3, 2);
entry.state.alerts = 77;
- entry.state.ppm_disable_cnt = 5;
+ entry.state.latency_timeouts = 5;
+ entry.state.latency_suspends = 2;
SECTION( "copy assignment" )
{
CHECK( entry.alerts() == 77 );
}
- SECTION( "ppm_disable_count" )
+ SECTION( "timeouts" )
+ {
+ CHECK( entry.timeouts() == 5 );
+ }
+
+ SECTION( "suspends" )
{
- CHECK( entry.ppm_disable_count() == 5 );
+ CHECK( entry.suspends() == 2 );
}
+
SECTION( "avg_match" )
{
auto ticks = entry.avg_match();
LogLabel("Summary Statistics");
show_stats((PegCount*)&gpc, pc_names, array_size(pc_names)-1, "detection");
- PPM_PRINT_SUMMARY(snort_conf->ppm_cfg);
-
proc_stats.attribute_table_hosts = SFAT_NumberOfHosts();
show_stats((PegCount*)&proc_stats, proc_names, array_size(proc_names)-1, "process");