]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4827: Refactoring of detection engine core functionality
authorYehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco) <yvelykoz@cisco.com>
Wed, 30 Jul 2025 17:09:15 +0000 (17:09 +0000)
committerOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Wed, 30 Jul 2025 17:09:15 +0000 (17:09 +0000)
Merge in SNORT/snort3 from ~YVELYKOZ/snort3:de_core_refactoring to master

Squashed commit of the following:

commit a48bafe1be6519781d05d7bee502fdcb1549b8b8
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Tue Jul 22 12:51:27 2025 +0300

    detection: update the authors

commit e76f8104df4aae15979cdc502ae32e9b12773c67
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Fri Apr 4 14:54:05 2025 +0300

    detection: extract children-related evaluation logic into separated functions

commit 2375d55ec15a2ff0a7457b945bdbd437e29a9184
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Thu Aug 29 11:27:49 2024 +0300

    detection: extract current node evaluation logic into separated function

src/detection/detect_trace.cc
src/detection/detect_trace.h
src/detection/detection_options.cc
src/detection/detection_options.h
src/filters/detection_filter.cc
src/filters/detection_filter.h

index 2db91bab132788543691f36043b9910942cec27c..ccf23ac2d7a2207b58f645a83a7c58eb43d1b9a3 100644 (file)
 #include "protocols/packet.h"
 #include "utils/stats.h"
 #include "utils/util.h"
+#include "utils/util_cstring.h"
 
 #include "detection_options.h"
+#include "extract.h"
 #include "fp_create.h"
 #include "fp_utils.h"
 #include "ips_context.h"
 #include "pattern_match_data.h"
+#include "treenodes.h"
 
 using namespace snort;
 using namespace std;
@@ -139,26 +142,62 @@ void node_eval_trace(const detection_option_tree_node_t* node, const Cursor& cur
     }
 }
 
-#else
-
-void clear_trace_cursor_info()
+void ips_variables_trace(const Packet* const p)
 {
-}
+    if ( !trace_enabled(detection_trace, TRACE_RULE_VARS) )
+        return;
 
-void print_pkt_info(Packet*, const char*)
-{
-}
+    char var_buf[100];
+    std::string rule_vars;
+    rule_vars.reserve(sizeof(var_buf));
+    uint32_t dbg_extract_vars[]{0,0};
 
-void print_pattern(const PatternMatchData*, Packet*)
-{
-}
+    for ( unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i )
+    {
+        GetVarValueByIndex(&(dbg_extract_vars[i]), (int8_t)i);
+        safe_snprintf(var_buf, sizeof(var_buf), "var[%u]=0x%X ", i, dbg_extract_vars[i]);
+        rule_vars.append(var_buf);
+    }
 
-void dump_buffer(const uint8_t*, unsigned, Packet*)
-{
+    debug_logf(detection_trace, TRACE_RULE_VARS, p, "Rule options variables: %s\n",
+        rule_vars.c_str());
 }
 
-void node_eval_trace(const detection_option_tree_node_t*, const Cursor&, Packet*)
+void print_option_tree(detection_option_tree_node_t* node, int level)
 {
+    if ( !trace_enabled(detection_trace, TRACE_OPTION_TREE) )
+        return;
+
+    char buf[32];
+    const char* opt;
+
+    if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE )
+        opt = ((IpsOption*)node->option_data)->get_name();
+    else
+    {
+        const OptTreeNode* otn = (OptTreeNode*)node->option_data;
+        const SigInfo& si = otn->sigInfo;
+        snprintf(buf, sizeof(buf), "%u:%u:%u", si.gid, si.sid, si.rev);
+        opt = buf;
+    }
+
+    const char* srtn = node->otn ? " (rtn)" : "";
+
+    debug_logf(detection_trace, TRACE_OPTION_TREE, nullptr, "%3d %3d  %p %*s%s\n",
+        level+1, node->num_children, node->option_data, (int)(level + strlen(opt)), opt, srtn);
+
+    for ( int i=0; i<node->num_children; i++ )
+        print_option_tree(node->children[i], level+1);
 }
 
+#else
+
+void clear_trace_cursor_info() { }
+void print_pkt_info(Packet*, const char*) { }
+void print_pattern(const PatternMatchData*, Packet*) { }
+void dump_buffer(const uint8_t*, unsigned, Packet*) { }
+void node_eval_trace(const detection_option_tree_node_t*, const Cursor&, Packet*) { }
+void ips_variables_trace(const Packet* const) { }
+void print_option_tree(detection_option_tree_node_t*, int) { }
+
 #endif
index bc6cd75acbd8fcc5bbc8bcb9a8a6a7f522dc89b6..69c910b940ebf22bbcf426b5efa82f7189551175 100644 (file)
@@ -56,6 +56,8 @@ void print_pkt_info(snort::Packet* p, const char*);
 void print_pattern(const PatternMatchData* pmd, snort::Packet*);
 void dump_buffer(const uint8_t* buff, unsigned len, snort::Packet*);
 void node_eval_trace(const detection_option_tree_node_t* node, const Cursor& cursor, snort::Packet*);
+void ips_variables_trace(const snort::Packet* const p);
+void print_option_tree(detection_option_tree_node_t* node, int level);
 
 #endif
 
index 317b9df8c3388209dcb06549ccc85d926a6bb699..eafea746119c6e741324352d956d015a17ee552b 100644 (file)
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-/*
-**  @file        detection_options.c
-**  @author      Steven Sturges
-**  @brief       Support functions for rule option tree
-**
-**  This implements tree processing for rule options, evaluating common
-**  detection options only once per pattern match.
-*/
+// detection_options.cc author Steve Sturges <ssturges@cisco.com>
+// detection_options.cc author Yehor Velykozhon <yvelykoz@cisco.com>
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -49,6 +43,7 @@
 #include "parser/parser.h"
 #include "profiler/rule_profiler_defs.h"
 #include "protocols/packet_manager.h"
+#include "time/packet_time.h"
 #include "utils/util_cstring.h"
 
 #include "detection_continuation.h"
@@ -74,9 +69,6 @@ struct detection_option_key_t
     void* option_data;
 };
 
-// FIXIT-L find a better place for this
-static inline bool operator==(const struct timeval& a, const struct timeval& b)
-{ return a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec; }
 
 class DetectionOptionHashKeyOps : public HashKeyOperations
 {
@@ -102,7 +94,7 @@ public:
         const detection_option_key_t* key1 = (const detection_option_key_t*)k1;
         const detection_option_key_t* key2 = (const detection_option_key_t*)k2;
 
-        assert(key1 && key2);
+        assert(key1 and key2);
 
         if ( key1->option_type != key2->option_type )
             return false;
@@ -155,7 +147,7 @@ static uint32_t detection_option_tree_hash(detection_option_tree_node_t* node)
 
     for ( int i = 0; i < node->num_children; i++)
     {
-#if (defined(__ia64) || defined(__amd64) || defined(_LP64))
+#if (defined(__ia64) or defined(__amd64) or defined(_LP64))
         {
             /* Cleanup warning because of cast from 64bit ptr to 32bit int
              * warning on 64bit OSs */
@@ -172,13 +164,6 @@ static uint32_t detection_option_tree_hash(detection_option_tree_node_t* node)
         mix(a,b,c);
         a += node->children[i]->num_children;
         mix(a,b,c);
-#if 0
-        a += (uint32_t)node->children[i]->option_data;
-        /* Recurse & hash up this guy's children */
-        b += detection_option_tree_hash(node->children[i]);
-        c += node->children[i]->num_children;
-        mix(a,b,c);
-#endif
     }
 
     finalize(a,b,c);
@@ -228,7 +213,7 @@ public:
 
     bool key_compare(const void* k1, const void* k2, size_t) override
     {
-        assert(k1 && k2);
+        assert(k1 and k2);
 
         const detection_option_key_t* key_r = (const detection_option_key_t*)k1;
         const detection_option_key_t* key_l = (const detection_option_key_t*)k2;
@@ -261,12 +246,12 @@ public:
 
 };
 
-static DetectionOptionHash* DetectionHashTableNew()
+static DetectionOptionHash* allocate_option_hash_table()
 {
    return new DetectionOptionHash(HASH_RULE_OPTIONS, sizeof(detection_option_key_t));
 }
 
-static DetectionOptionTreeHash* DetectionTreeHashTableNew()
+static DetectionOptionTreeHash* allocate_tree_hash_table()
 {
     return new DetectionOptionTreeHash(HASH_RULE_TREE, sizeof(detection_option_key_t));
 }
@@ -274,7 +259,7 @@ static DetectionOptionTreeHash* DetectionTreeHashTableNew()
 void* add_detection_option(SnortConfig* sc, option_type_t type, void* option_data)
 {
     if ( !sc->detection_option_hash_table )
-        sc->detection_option_hash_table = DetectionHashTableNew();
+        sc->detection_option_hash_table = allocate_option_hash_table();
 
     detection_option_key_t key;
     key.option_type = type;
@@ -287,45 +272,13 @@ void* add_detection_option(SnortConfig* sc, option_type_t type, void* option_dat
     return nullptr;
 }
 
-void print_option_tree(detection_option_tree_node_t* node, int level)
-{
-#ifdef DEBUG_MSGS
-    if ( !trace_enabled(detection_trace, TRACE_OPTION_TREE) )
-        return;
-
-    char buf[32];
-    const char* opt;
-
-    if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE )
-        opt = ((IpsOption*)node->option_data)->get_name();
-    else
-    {
-        const OptTreeNode* otn = (OptTreeNode*)node->option_data;
-        const SigInfo& si = otn->sigInfo;
-        snprintf(buf, sizeof(buf), "%u:%u:%u", si.gid, si.sid, si.rev);
-        opt = buf;
-    }
-
-    const char* srtn = node->otn ? " (rtn)" : "";
-
-    debug_logf(detection_trace, TRACE_OPTION_TREE, nullptr, "%3d %3d  %p %*s%s\n",
-        level+1, node->num_children, node->option_data, (int)(level + strlen(opt)), opt, srtn);
-
-    for ( int i=0; i<node->num_children; i++ )
-        print_option_tree(node->children[i], level+1);
-#else
-    UNUSED(node);
-    UNUSED(level);
-#endif
-}
-
 void* add_detection_option_tree(SnortConfig* sc, detection_option_tree_node_t* option_tree)
 {
     static std::mutex build_mutex;
     std::lock_guard<std::mutex> lock(build_mutex);
 
     if ( !sc->detection_option_tree_hash_table )
-        sc->detection_option_tree_hash_table = DetectionTreeHashTableNew();
+        sc->detection_option_tree_hash_table = allocate_tree_hash_table();
 
     detection_option_key_t key;
     key.option_data = (void*)option_tree;
@@ -338,60 +291,212 @@ void* add_detection_option_tree(SnortConfig* sc, detection_option_tree_node_t* o
     return nullptr;
 }
 
-int detection_option_node_evaluate(
-    const detection_option_tree_node_t* node, detection_option_eval_data_t& eval_data,
-    const Cursor& orig_cursor)
+static inline bool was_evaluated(const detection_option_tree_node_t* node,
+    const dot_node_state_t& state, uint64_t cur_eval_context_num, const Packet* p)
 {
-    assert(node and eval_data.p);
+    // FIXIT-P: re-order for non-pcap traffic + add attributes
+    if ( node->is_relative )
+        return false;
 
-    node_eval_trace(node, orig_cursor, eval_data.p);
+    // FIXIT-L: once good order is determined, move to dot_node_state_t::operator==
+    auto last_check = state.last_check;
 
-    auto& state = node->state[get_instance_id()];
-    RuleContext profile(state);
+    if ( last_check.ts.tv_sec != p->pkth->ts.tv_sec or
+        last_check.ts.tv_usec != p->pkth->ts.tv_usec )
+        return false;
 
-    uint64_t cur_eval_context_num = eval_data.p->context->context_num;
-    auto p = eval_data.p;
+    if ( last_check.run_num != get_run_num() )
+        return false;
+
+    if ( last_check.context_num != cur_eval_context_num )
+        return false;
 
-    // see if evaluated it before ...
-    if ( !node->is_relative )
+    if ( last_check.rebuild_flag != (p->packet_flags & PKT_REBUILT_STREAM) )
+        return false;
+
+    if (p->packet_flags & PKT_ALLOW_MULTIPLE_DETECT)
+        return false;
+
+    if ( last_check.flowbit_failed )
+        return false;
+
+    if ( !(p->packet_flags & PKT_IP_RULE_2ND) and !p->is_udp_tunneled() )
     {
-        auto last_check = state.last_check;
+        debug_log(detection_trace, TRACE_RULE_EVAL, p,
+            "Was evaluated before, returning last check result\n");
+        return true;
+    }
 
-        if ( last_check.ts == p->pkth->ts &&
-            last_check.run_num == get_run_num() &&
-            last_check.context_num == cur_eval_context_num &&
-            last_check.rebuild_flag == (p->packet_flags & PKT_REBUILT_STREAM) &&
-            !(p->packet_flags & PKT_ALLOW_MULTIPLE_DETECT) )
+    return false;
+}
+
+static inline bool match_rtn(const detection_option_tree_node_t* node, Packet* p)
+{
+    assert(p and node);
+    // FIXIT-L: all those checks could be moved to OptTreeNode
+    if ( !node->otn or node->otn->sigInfo.file_id )
+        return true;
+
+    const SnortProtocolId snort_protocol_id = p->get_snort_protocol_id();
+    // FIXIT-L: rework to use bool
+    int check_ports = 1;
+
+    if ( snort_protocol_id != UNKNOWN_PROTOCOL_ID )
+    {
+        const auto& sig_info = node->otn->sigInfo;
+
+        if ( std::any_of(sig_info.services.cbegin(), sig_info.services.cend(),
+            [snort_protocol_id] (const SignatureServiceInfo& svc)
+            { return snort_protocol_id == svc.snort_protocol_id; }) )
+            check_ports = 0;
+
+        if ( !sig_info.services.empty() and check_ports )
         {
-            if ( !last_check.flowbit_failed &&
-                !(p->packet_flags & PKT_IP_RULE_2ND) &&
-                !p->is_udp_tunneled() )
-            {
-                debug_log(detection_trace, TRACE_RULE_EVAL, p,
-                    "Was evaluated before, returning last check result\n");
-                return last_check.result;
-            }
+            debug_logf(detection_trace, TRACE_RULE_EVAL, p,
+                "SID %u not matched because of service mismatch %d\n",
+                sig_info.sid, snort_protocol_id);
+            return false;
         }
     }
 
-    state.last_check.ts = eval_data.p->pkth->ts;
-    state.last_check.run_num = get_run_num();
-    state.last_check.context_num = cur_eval_context_num;
-    state.last_check.flowbit_failed = 0;
-    state.last_check.rebuild_flag = p->packet_flags & PKT_REBUILT_STREAM;
+    return fp_eval_rtn(getRtnFromOtn(node->otn), p, check_ports);
+}
 
-    // Save some stuff off for repeated pattern tests
-    PmdLastCheck* content_last = nullptr;
+static inline int match_leaf(const detection_option_tree_node_t* node,
+    detection_option_eval_data_t& eval_data)
+{
+    const Packet* p = eval_data.p;
+    OptTreeNode* otn = (OptTreeNode*)node->option_data;
 
-    if ( node->option_type != RULE_OPTION_TYPE_LEAF_NODE )
+    if ( otn->detection_filter )
     {
-        IpsOption* opt = (IpsOption*)node->option_data;
-        PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR);
+        debug_log(detection_trace, TRACE_RULE_EVAL, p,
+            "Evaluating detection filter\n");
 
-        if ( pmd and pmd->is_literal() and pmd->last_check )
-            content_last = pmd->last_check + get_instance_id();
+        if ( detection_filter_test(otn->detection_filter, p) )
+        {
+            debug_log(detection_trace, TRACE_RULE_EVAL, p,
+                "Header check failed\n");
+
+            return (int)IpsOption::NO_MATCH;
+        }
     }
 
+    otn->state[get_instance_id()].matches++;
+
+    eval_data.leaf_reached = 1;
+
+    if ( eval_data.flowbit_noalert )
+        return (int)IpsOption::MATCH;
+
+#ifdef DEBUG_MSGS
+    const SigInfo& si = otn->sigInfo;
+    debug_logf(detection_trace, TRACE_RULE_EVAL, p,
+        "Matched rule gid:sid:rev %u:%u:%u\n", si.gid, si.sid, si.rev);
+#endif
+
+    fpAddMatch(p->context->otnx, otn);
+
+    return (int)IpsOption::MATCH;
+}
+
+static inline int match_flowbit(const detection_option_tree_node_t* node,
+    detection_option_eval_data_t& eval_data, Cursor& cursor)
+{
+    int rval = node->evaluate(node->option_data, cursor, eval_data.p);
+
+    assert((flowbits_setter(node->option_data) and rval == (int)IpsOption::MATCH)
+        or !flowbits_setter(node->option_data) or !eval_data.p->flow);
+
+    return rval;
+}
+
+static inline int match_node(const detection_option_tree_node_t* node,
+    detection_option_eval_data_t& eval_data, Cursor& cursor,
+    IpsOption*& buf_selector)
+{
+    IpsOption* opt = (IpsOption*)node->option_data;
+    if ( opt->is_buffer_setter() )
+        buf_selector = opt;
+
+    int rval = node->evaluate(node->option_data, cursor, eval_data.p);
+
+    return rval;
+}
+
+static inline bool skip_on_retry(const detection_option_tree_node_t* node,
+    const detection_option_tree_node_t* child_node, dot_node_state_t* child_state,
+    int loop_count, int& result)
+{
+    assert(loop_count > 0);
+    assert(child_state);
+
+    if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE )
+        return true;
+
+    bool matched_all_suboption = child_state->result == child_node->num_children;
+    if ( matched_all_suboption )
+        return true;
+
+    if ( child_node->option_type != RULE_OPTION_TYPE_CONTENT )
+        return false;
+
+    if ( child_state->result != (int)IpsOption::NO_MATCH )
+        return false;
+
+    if ( !child_node->is_relative )
+    {
+        // If it's a non-relative content or pcre, no reason
+        // to check again.  Only increment result once.
+        // Should hit this condition on first loop iteration.
+        if ( loop_count == 1 )
+            ++result;
+        return true;
+    }
+
+    IpsOption* opt = (IpsOption*)node->option_data;
+    if ( opt->is_buffer_setter() )
+        return false;
+
+    // FIXIT-L: should be moved to IpsOption option
+    // Check for an unbounded relative search.  If this
+    // failed before, it's going to fail again so don't
+    // go down this path again
+    opt = (IpsOption*)child_node->option_data;
+    PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR);
+
+    if ( pmd and pmd->is_literal() and pmd->is_unbounded() and !pmd->is_negated() )
+    {
+        // Only increment result once. Should hit this
+        // condition on first loop iteration
+        if ( loop_count == 1 )
+            ++result;
+
+        return true;
+    }
+
+    return false;
+}
+
+int detection_option_node_evaluate(
+    const detection_option_tree_node_t* node, detection_option_eval_data_t& eval_data,
+    const Cursor& orig_cursor)
+{
+    assert(node and eval_data.p);
+
+    node_eval_trace(node, orig_cursor, eval_data.p);
+
+    auto& state = node->state[get_instance_id()];
+    RuleContext profile(state);
+    uint64_t cur_eval_context_num = eval_data.p->context->context_num;
+    auto p = eval_data.p;
+
+    if ( was_evaluated(node, state, cur_eval_context_num, eval_data.p) )
+        return state.last_check.result;
+
+    state.last_check.set(*eval_data.p, state.last_check.result);
+    assert(state.last_check.context_num == eval_data.p->context->context_num);
+
     bool continue_loop = true;
     int loop_count = 0;
 
@@ -399,119 +504,26 @@ int detection_option_node_evaluate(
     uint32_t tmp_byte_extract_vars[NUM_IPS_OPTIONS_VARS];
     IpsOption* buf_selector = eval_data.buf_selector;
     Cursor cursor = orig_cursor;
-    int rval;
 
-    // No, haven't evaluated this one before... Check it.
     do
     {
-        rval = (int)IpsOption::NO_MATCH;  // FIXIT-L refactor to eliminate casts to int.
-        if ( node->otn and !node->otn->sigInfo.file_id )
-        {
-            SnortProtocolId snort_protocol_id = p->get_snort_protocol_id();
-            int check_ports = 1;
-
-            if ( snort_protocol_id != UNKNOWN_PROTOCOL_ID )
-            {
-                const auto& sig_info = node->otn->sigInfo;
+        int rval = (int)IpsOption::NO_MATCH;  // FIXIT-L refactor to eliminate casts to int.
 
-                if ( std::any_of(sig_info.services.cbegin(), sig_info.services.cend(),
-                    [snort_protocol_id] (const SignatureServiceInfo& svc)
-                    { return snort_protocol_id == svc.snort_protocol_id; }) )
-                    check_ports = 0;
-
-                if ( !sig_info.services.empty() and check_ports )
-                {
-                    debug_logf(detection_trace, TRACE_RULE_EVAL, p,
-                        "SID %u not matched because of service mismatch %d\n",
-                        sig_info.sid, snort_protocol_id);
-                    break;  // out of case
-                }
-            }
-
-            if ( !fp_eval_rtn(getRtnFromOtn(node->otn), p, check_ports) )
-                break;
-        }
+        if ( !match_rtn(node, p) )
+            break;
 
         switch ( node->option_type )
         {
         case RULE_OPTION_TYPE_LEAF_NODE:
-            {
-                OptTreeNode* otn = (OptTreeNode*)node->option_data;
-                bool f_result = true;
-
-                if ( otn->detection_filter )
-                {
-                    debug_log(detection_trace, TRACE_RULE_EVAL, p,
-                        "Evaluating detection filter\n");
-                    f_result = !detection_filter_test(otn->detection_filter,
-                        p->ptrs.ip_api.get_src(), p->ptrs.ip_api.get_dst(),
-                        p->pkth->ts.tv_sec);
-                }
-
-                if ( !f_result )
-                {
-                    debug_log(detection_trace, TRACE_RULE_EVAL, p, "Header check failed\n");
-                }
-                else
-                {
-                    otn->state[get_instance_id()].matches++;
-
-                    if ( !eval_data.flowbit_noalert )
-                    {
-#ifdef DEBUG_MSGS
-                        const SigInfo& si = otn->sigInfo;
-                        debug_logf(detection_trace, TRACE_RULE_EVAL, p,
-                            "Matched rule gid:sid:rev %u:%u:%u\n", si.gid, si.sid, si.rev);
-#endif
-                        fpAddMatch(p->context->otnx, otn);
-                    }
-                    result = rval = (int)IpsOption::MATCH;
-                    eval_data.leaf_reached = 1;
-                }
-            }
-            break;
-
-        case RULE_OPTION_TYPE_CONTENT:
-            if ( node->evaluate )
-            {
-                // This will be set in the fast pattern matcher if we found
-                // a content and the rule option specifies not that
-                // content. Essentially we've already evaluated this rule
-                // option via the content option processing since only not
-                // contents that are not relative in any way will have this
-                // flag set
-                if ( content_last )
-                {
-                    if ( content_last->ts == p->pkth->ts &&
-                        content_last->run_num == get_run_num() &&
-                        content_last->context_num == cur_eval_context_num &&
-                        content_last->rebuild_flag == (p->packet_flags & PKT_REBUILT_STREAM) )
-                    {
-                        rval = (int)IpsOption::NO_MATCH;
-                        break;
-                    }
-                }
-                rval = node->evaluate(node->option_data, cursor, p);
-            }
+            result = rval = match_leaf(node, eval_data);
             break;
 
         case RULE_OPTION_TYPE_FLOWBIT:
-            if ( node->evaluate )
-            {
-                rval = node->evaluate(node->option_data, cursor, eval_data.p);
-                assert((flowbits_setter(node->option_data) and rval == (int)IpsOption::MATCH)
-                    or !flowbits_setter(node->option_data) or !eval_data.p->flow);
-            }
+            rval = match_flowbit(node, eval_data, cursor);
             break;
 
         default:
-            if ( node->evaluate )
-            {
-                IpsOption* opt = (IpsOption*)node->option_data;
-                if ( opt->is_buffer_setter() )
-                    buf_selector = opt;
-                rval = node->evaluate(node->option_data, cursor, p);
-            }
+            rval = match_node(node, eval_data, cursor, buf_selector);
             break;
         }
 
@@ -520,10 +532,11 @@ int detection_option_node_evaluate(
             debug_log(detection_trace, TRACE_RULE_EVAL, p, "no match\n");
             state.last_check.result = result;
             // See if the option has failed due to incomplete input or its failed evaluation
-            if (orig_cursor.awaiting_data())
+            if ( orig_cursor.awaiting_data() )
                 Continuation::postpone<false>(orig_cursor, *node, eval_data);
             else
                 Continuation::postpone<true>(cursor, *node, eval_data);
+
             return result;
         }
         if ( rval == (int)IpsOption::FAILED_BIT )
@@ -533,7 +546,8 @@ int detection_option_node_evaluate(
             // clear the timestamp so failed flowbit gets eval'd again
             state.last_check.flowbit_failed = 1;
             state.last_check.result = result;
-            return 0;
+
+            return (int)IpsOption::NO_MATCH;
         }
 
         // Cache the current flowbit_noalert flag, and set it
@@ -545,23 +559,7 @@ int detection_option_node_evaluate(
             debug_log(detection_trace, TRACE_RULE_EVAL, p, "flowbit no alert\n");
         }
 
-#ifdef DEBUG_MSGS
-        if ( trace_enabled(detection_trace, TRACE_RULE_VARS) )
-        {
-            char var_buf[100];
-            std::string rule_vars;
-            rule_vars.reserve(sizeof(var_buf));
-            uint32_t dbg_extract_vars[]{0,0};
-            for ( unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i )
-            {
-                GetVarValueByIndex(&(dbg_extract_vars[i]), (int8_t)i);
-                safe_snprintf(var_buf, sizeof(var_buf), "var[%u]=0x%X ", i, dbg_extract_vars[i]);
-                rule_vars.append(var_buf);
-            }
-            debug_logf(detection_trace, TRACE_RULE_VARS, p, "Rule options variables: %s\n",
-                rule_vars.c_str());
-        }
-#endif
+        ips_variables_trace(p);
 
         if ( PacketLatency::fastpath() )
         {
@@ -572,131 +570,73 @@ int detection_option_node_evaluate(
             return result;
         }
 
+        // Passed, check the children.
+        if ( node->num_children )
         {
-            // Passed, check the children.
-            if ( node->num_children )
-            {
-                // Back up byte_extract vars so they don't get overwritten between rules
-                // If node has only 1 child - no need to back up on current step
-                for ( unsigned i = 0; node->num_children > 1 && i < NUM_IPS_OPTIONS_VARS; ++i )
+            // Back up byte_extract vars so they don't get overwritten between rules
+            // If node has only 1 child - no need to back up on current step
+            for ( unsigned i = 0; node->num_children > 1 and i < NUM_IPS_OPTIONS_VARS; ++i )
                     GetVarValueByIndex(&(tmp_byte_extract_vars[i]), (int8_t)i);
 
-                for ( int i = 0; i < node->num_children; ++i )
-                {
-                    detection_option_tree_node_t* child_node = node->children[i];
-                    dot_node_state_t* child_state = child_node->state + get_instance_id();
-
-                    for ( unsigned j = 0; node->num_children > 1 && j < NUM_IPS_OPTIONS_VARS; ++j )
-                        SetVarValueByIndex(tmp_byte_extract_vars[j], (int8_t)j);
-
-                    if ( loop_count > 0 )
-                    {
-                        if ( child_state->result == (int)IpsOption::NO_MATCH )
-                        {
-                            if ( child_node->option_type == RULE_OPTION_TYPE_CONTENT )
-                            {
-                                if ( !child_node->is_relative )
-                                {
-                                    // If it's a non-relative content or pcre, no reason
-                                    // to check again.  Only increment result once.
-                                    // Should hit this condition on first loop iteration.
-                                    if ( loop_count == 1 )
-                                        ++result;
-
-                                    continue;
-                                }
-                                else
-                                {
-                                    IpsOption* opt = (IpsOption*)node->option_data;
-
-                                    if ( !opt->is_buffer_setter() )
-                                    {
-                                        // Check for an unbounded relative search.  If this
-                                        // failed before, it's going to fail again so don't
-                                        // go down this path again
-                                        opt = (IpsOption*)child_node->option_data;
-                                        PatternMatchData* pmd = opt->get_pattern(0, RULE_WO_DIR);
-
-                                        if ( pmd and pmd->is_literal() and pmd->is_unbounded() and !pmd->is_negated() )
-                                        {
-                                            // Only increment result once. Should hit this
-                                            // condition on first loop iteration
-                                            if (loop_count == 1)
-                                                ++result;
-
-                                            continue;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                        else if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE )
-                            // Leaf node matched, don't eval again
-                            continue;
-
-                        else if ( child_state->result == child_node->num_children )
-                            // This branch of the tree matched or has options that
-                            // don't need to be evaluated again, so don't need to
-                            // evaluate this option again
-                            continue;
-                    }
-
-                    eval_data.buf_selector = buf_selector;
-                    child_state->result = detection_option_node_evaluate(
-                        node->children[i], eval_data, cursor);
-
-                    if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE )
-                    {
-                        // Leaf node won't have any children but will return success
-                        // or failure; regardless we must count them here
-                        result += 1;
-                    }
-                    else if (child_state->result == child_node->num_children)
-                        // Indicate that the child's tree branches are done
-                        ++result;
-
-                    if ( PacketLatency::fastpath() )
-                    {
-                        state.last_check.result = result;
-                        return result;
-                    }
-                }
+            for ( int i = 0; i < node->num_children; ++i )
+            {
+                detection_option_tree_node_t* child_node = node->children[i];
+                dot_node_state_t* child_state = child_node->state + get_instance_id();
+
+                for ( unsigned j = 0; node->num_children > 1 and j < NUM_IPS_OPTIONS_VARS; ++j )
+                    SetVarValueByIndex(tmp_byte_extract_vars[j], (int8_t)j);
+
+                if ( loop_count > 0 and skip_on_retry(node, child_node, child_state, loop_count, result) )
+                    continue;
 
-                // If all children branches matched, we don't need to reeval any of
-                // the children so don't need to reeval this content/pcre rule
-                // option at a new offset.
-                // Else, reset the DOE ptr to last eval for offset/depth,
-                // distance/within adjustments for this same content/pcre rule option.
-                // If the node and its sub-tree propagate MATCH back,
-                // then all its continuations are recalled.
-                if ( result == node->num_children )
+                eval_data.buf_selector = buf_selector;
+                child_state->result = detection_option_node_evaluate(node->children[i], eval_data, cursor);
+
+                if ( child_node->option_type == RULE_OPTION_TYPE_LEAF_NODE )
                 {
-                    continue_loop = false;
-                    Continuation::recall(state, p);
+                    // Leaf node won't have any children but will return success
+                    // or failure; regardless we must count them here
+                    result += 1;
                 }
+                else if ( child_state->result == child_node->num_children )
+                    // Indicate that the child's tree branches are done
+                    ++result;
 
-                if ( eval_data.leaf_reached and !eval_data.otn->sigInfo.file_id and
-                    node->option_type != RULE_OPTION_TYPE_LEAF_NODE and
-                    ((IpsOption*)node->option_data)->is_buffer_setter() )
+                if ( PacketLatency::fastpath() )
                 {
-                    debug_logf(detection_trace, TRACE_BUFFER, p, "Collecting \"%s\" buffer of size %u\n",
-                        cursor.get_name(), cursor.size());
-                    p->context->matched_buffers.emplace_back(cursor.get_name(), cursor.buffer(), cursor.size());
-                    pc.buf_dumps++;
+                    state.last_check.result = result;
+                    return result;
                 }
+            }
+
+            // If all children branches matched, we don't need to reeval any of
+            // the children so don't need to reeval this content/pcre rule
+            // option at a new offset.
+            // Else, reset the DOE ptr to last eval for offset/depth,
+            // distance/within adjustments for this same content/pcre rule option.
+            // If the node and its sub-tree propagate MATCH back,
+            // then all its continuations are recalled.
+            if ( result == node->num_children )
+            {
+                continue_loop = false;
+                Continuation::recall(state, p);
+            }
 
-                // Don't need to reset since it's only checked after we've gone
-                // through the loop at least once and the result will have
-                // been set again already
-                //for (i = 0; i < node->num_children; i++)
-                //    node->children[i]->result;
+            if ( eval_data.leaf_reached and !eval_data.otn->sigInfo.file_id and
+                node->option_type != RULE_OPTION_TYPE_LEAF_NODE and
+                ((IpsOption*)node->option_data)->is_buffer_setter() )
+            {
+                debug_logf(detection_trace, TRACE_BUFFER, p, "Collecting \"%s\" buffer of size %u\n",
+                    cursor.get_name(), cursor.size());
+                p->context->matched_buffers.emplace_back(cursor.get_name(), cursor.buffer(), cursor.size());
+                pc.buf_dumps++;
             }
         }
 
         // Reset the flowbit_noalert flag in eval data
         eval_data.flowbit_noalert = tmp_noalert_flag;
 
-        if ( continue_loop && rval == (int)IpsOption::MATCH && node->relative_children )
+        if ( continue_loop and rval == (int)IpsOption::MATCH and node->relative_children )
         {
             IpsOption* opt = (IpsOption*)node->option_data;
             continue_loop = opt->retry(cursor);
@@ -733,13 +673,13 @@ static void detection_option_node_update_otn_stats(detection_option_tree_node_t*
 {
     dot_node_state_t local_stats(node->state[thread_id]); /* cumulative stats for this node */
 
-    if (stats)
+    if ( stats )
     {
         local_stats.elapsed += stats->elapsed;
         local_stats.elapsed_match += stats->elapsed_match;
         local_stats.elapsed_no_match += stats->elapsed_no_match;
 
-        if (stats->checks > local_stats.checks)
+        if ( stats->checks > local_stats.checks )
             local_stats.checks = stats->checks;
 
         local_stats.latency_suspends += stats->latency_suspends;
@@ -756,7 +696,7 @@ static void detection_option_node_update_otn_stats(detection_option_tree_node_t*
         state.elapsed_match = local_stats.elapsed_match;
         state.elapsed_no_match = local_stats.elapsed_no_match;
 
-        if (local_stats.checks > state.checks)
+        if ( local_stats.checks > state.checks )
             state.checks = local_stats.checks;
 
         state.latency_timeouts = local_stats.latency_timeouts;
@@ -830,7 +770,7 @@ void free_detection_option_root(void** existing_tree)
 {
     detection_option_tree_root_t* root;
 
-    if (!existing_tree || !*existing_tree)
+    if ( !existing_tree or !*existing_tree )
         return;
 
     root = (detection_option_tree_root_t*)*existing_tree;
@@ -840,3 +780,477 @@ void free_detection_option_root(void** existing_tree)
     *existing_tree = nullptr;
 }
 
+
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+
+#ifdef UNIT_TEST
+
+#include "catch/snort_catch.h"
+
+#include "filters/sfthd.h"
+
+void set_was_evaluated(detection_option_tree_node_t& n, Packet* p,
+    dot_node_state_t& s, uint64_t& c_num)
+{
+    n.is_relative = false;
+    auto& last_check = s.last_check;
+
+    if ( !p->pkth )
+        p->pkth = new DAQ_PktHdr_t();
+
+    last_check.ts = p->pkth->ts;
+    last_check.run_num = get_run_num();
+    last_check.context_num = c_num = 2;
+    last_check.rebuild_flag = p->packet_flags = p->packet_flags | PKT_REBUILT_STREAM;
+    last_check.flowbit_failed = false;
+}
+
+TEST_CASE("Detection Engine: was_evaluated", "[de_core]")
+{
+    Packet p;
+    detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr);
+    dot_node_state_t m_state;
+    uint64_t m_context_num;
+
+    SECTION("Basic passed case")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        REQUIRE(true == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    SECTION("Failed due to relative option")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        m_node.is_relative = true;
+        REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    SECTION("Failed due to timestamp difference")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        m_state.last_check.ts.tv_sec = 1;
+        bool fail_due_last_check = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_last_check);
+
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        const_cast<DAQ_PktHdr_t* >(p.pkth)->ts.tv_sec = 1;
+        bool fail_due_packet = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_packet);
+    }
+    SECTION("Failed due to run number")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        m_state.last_check.run_num = 1;
+        bool fail_due_last_check = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_last_check);
+
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        int orig_run_num = get_run_num();
+        set_run_num(1);
+        bool fail_due_global_run_num = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_global_run_num);
+        set_run_num(orig_run_num);
+    }
+    SECTION("Failed due to context number")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        m_state.last_check.context_num = 1;
+        REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    SECTION("Failed due to not rebuilt stream packet")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        p.packet_flags = 0;
+        REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    SECTION("Failed due to multiple detection")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        p.packet_flags |= PKT_ALLOW_MULTIPLE_DETECT;
+        REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    SECTION("Failed due to failed flowbit in state.last_check")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        m_state.last_check.flowbit_failed = 1;
+        REQUIRE(false == was_evaluated(&m_node, m_state, m_context_num, &p));
+    }
+    // FIXIT-L: what is the correct name of this condition? not inner tcp evaluation?
+    SECTION("Failed due to ip layer and udp check")
+    {
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        p.packet_flags = 0;
+        bool fail_due_packet_flag = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_packet_flag);
+        set_was_evaluated(m_node, &p, m_state, m_context_num);
+        p.proto_bits |= PROTO_BIT__UDP_TUNNELED;
+        p.ptrs.udph = (const snort::udp::UDPHdr*)0x1; // This is a hack to avoid irrelevant assert in "is_udp_tunneled"
+        bool fail_due_udp_tunneled = false == was_evaluated(&m_node, m_state, m_context_num, &p);
+        REQUIRE(fail_due_udp_tunneled);
+    }
+}
+
+TEST_CASE("Detection Engine: match_rtn", "[de_core]")
+{
+    std::unique_ptr<OptTreeNode> otn(new OptTreeNode());
+    detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr);
+    m_node.otn = otn.get();
+
+    // Needed to pass protocol/service check
+    std::unique_ptr<IpsContext> context(new IpsContext(1));
+    context->set_snort_protocol_id(UNKNOWN_PROTOCOL_ID);
+    context->packet->ptrs.type = PktType::PDU; // in order to get snort_protocol_id from IpsContext;
+    Packet* p = context->packet;
+
+    RuleTreeNode rtn;
+    otn->proto_node_num = 1;
+    // will be de-allocated in ~Otn
+    otn->proto_nodes = (RuleTreeNode**)snort_calloc(1, sizeof(RuleTreeNode*));
+    otn->proto_nodes[0] = &rtn;
+
+    std::unique_ptr<IpsPolicy> ips_policy(new IpsPolicy());
+    ips_policy->policy_id = 0;
+    set_ips_policy(ips_policy.get());
+
+    // Needed to pass fp_eval_rtn
+    rtn.set_enabled();
+    std::unique_ptr<RuleFpList> fp_list(new RuleFpList);
+    rtn.rule_func = fp_list.get();
+
+    SECTION("Passed due to middle-node evaluation")
+    {
+        detection_option_tree_node_t middle_node(RULE_OPTION_TYPE_OTHER, nullptr);
+        bool non_rtn_containig_node = !middle_node.otn;
+
+        REQUIRE(non_rtn_containig_node);
+        REQUIRE(true == match_rtn(&middle_node, p));
+    }
+    SECTION("Passed due to file id rule")
+    {
+        bool rtn_containig_node = m_node.otn;
+        REQUIRE(rtn_containig_node);
+
+        const_cast<OptTreeNode*>(m_node.otn)->sigInfo.file_id = 1;
+        REQUIRE(true == match_rtn(&m_node, p));
+    }
+    SECTION("Check protocol")
+    {
+        SECTION("Failed due to no known service")
+        {
+            context->set_snort_protocol_id(SNORT_PROTO_TCP);
+            otn->sigInfo.services.push_back({"not-tcp", SNORT_PROTO_UDP});
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int
+            { assert(false); return 1; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(false == match_rtn(&m_node, p));
+        }
+        SECTION("Match due to empty service list")
+        {
+            context->set_snort_protocol_id(SNORT_PROTO_TCP);
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int
+            { return 1; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(true == match_rtn(&m_node, p));
+        }
+    }
+    SECTION("Evaluation of fp_eval_rtn")
+    {
+        SECTION("RTN matched")
+        {
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int { return 1; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(true == match_rtn(&m_node, p));
+        }
+        SECTION("RTN mismatched")
+        {
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int) -> int { return 0; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(false == match_rtn(&m_node, p));
+        }
+        SECTION("Ports are checked in case of unknown protocol_id")
+        {
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int check_ports) -> int
+            { return check_ports; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(true == match_rtn(&m_node, p));
+        }
+        SECTION("RTN user_mode priority over service-check")
+        {
+            context->set_snort_protocol_id(SNORT_PROTO_TCP);
+            otn->sigInfo.services.push_back({"tcp", SNORT_PROTO_TCP});
+            rtn.flags |= RuleTreeNode::USER_MODE;
+            auto mock_r_func = [](snort::Packet*, RuleTreeNode*, RuleFpList*, int check_ports) -> int
+            { return check_ports; };
+            rtn.rule_func->RuleHeadFunc = mock_r_func;
+
+            REQUIRE(true == match_rtn(&m_node, p));
+        }
+    }
+}
+
+TEST_CASE("Detection Engine: match_leaf", "[de_core]")
+{
+    std::unique_ptr<OptTreeNode> otn(new OptTreeNode());
+    detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, otn.get());
+    detection_option_eval_data_t m_e_data;
+    Packet p;
+    m_e_data.p = &p;
+    otn->state = new OtnState[1]();
+    set_instance_id(0);
+
+    SECTION("Detection filter passed, state.matches increased")
+    {
+        // de-allocated in ~Otn
+        otn->detection_filter = new THD_NODE();
+        detection_filter_term();    // need to make early exit from detection_filter logic
+        m_e_data.flowbit_noalert = true;   // Avoid extra work in such way
+        uint64_t curr_matches = otn->state[get_instance_id()].matches;
+
+        // Needed for detection_filter_test
+        std::unique_ptr<IpsPolicy> ips_policy(new IpsPolicy());
+        ips_policy->policy_id = 0;
+        set_ips_policy(ips_policy.get());
+
+        REQUIRE(match_leaf(&m_node, m_e_data));
+        REQUIRE(curr_matches +1 == otn->state[get_instance_id()].matches);
+    }
+    SECTION("state.matches counter is increased")
+    {
+        uint64_t curr_matches = otn->state[get_instance_id()].matches;
+        m_e_data.flowbit_noalert = true;   // Avoid extra work in such way
+
+        REQUIRE(match_leaf(&m_node, m_e_data));
+        REQUIRE(curr_matches +1 == otn->state[get_instance_id()].matches);
+    }
+    SECTION("leaf_reached flag is set")
+    {
+        char curr_flag = m_e_data.leaf_reached;
+        m_e_data.flowbit_noalert = true;   // Avoid extra work in such way
+
+        REQUIRE(match_leaf(&m_node, m_e_data));
+        REQUIRE(curr_flag != m_e_data.leaf_reached);
+        REQUIRE(m_e_data.leaf_reached);
+
+        SECTION("Verify that flag is set, not toggled")
+        {
+            m_e_data.leaf_reached = 1;
+            char reached = m_e_data.leaf_reached;
+            m_e_data.flowbit_noalert = true;   // Avoid extra work in such way
+
+            REQUIRE(match_leaf(&m_node, m_e_data));
+            REQUIRE(reached == m_e_data.leaf_reached);
+        }
+    }
+    SECTION("Alert the match")
+    {
+        std::unique_ptr<IpsContext> context(new IpsContext(1));
+        context->set_snort_protocol_id(UNKNOWN_PROTOCOL_ID);
+        m_e_data.p = context->packet;
+
+        // Forcing underlying function return nullptr since the test target not the fpAddMatch function.
+        // In such way, the fpAddMatch will return 2, which is still fine for us.
+        otn->proto_node_num = 1;
+
+        otn->proto_nodes = (RuleTreeNode**)snort_calloc(1, sizeof(RuleTreeNode*));
+        otn->proto_nodes[0] = nullptr;
+
+        std::unique_ptr<IpsPolicy> ips_policy(new IpsPolicy());
+        ips_policy->policy_id = 0;
+        set_ips_policy(ips_policy.get());
+
+        REQUIRE(match_leaf(&m_node, m_e_data));
+    }
+}
+
+class MockIpsBufSetter : public IpsOption
+{
+public:
+    MockIpsBufSetter(const char* s) : IpsOption(s)
+    { };
+
+    CursorActionType get_cursor_type() const override
+    { return CAT_SET_SUB_SECTION; };
+};
+
+class MockIpsOptRead : public IpsOption
+{
+public:
+    MockIpsOptRead(const char* s) : IpsOption(s)
+    { };
+
+    CursorActionType get_cursor_type() const override
+    { return CAT_READ; };
+};
+
+TEST_CASE("Detection Engine: match_node", "[de_core]")
+{
+    detection_option_eval_data_t m_e_data;
+    Cursor m_c;
+    detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr);
+    auto mock_eval = [](void*, class Cursor&, snort::Packet*) -> int { return 1; };
+    m_node.evaluate = mock_eval;
+    MockIpsBufSetter mock_ips_setter ("mock_ips_setter");
+    MockIpsOptRead mock_ips_read ("mock_ips_read");
+
+    SECTION("Buffer setter is set from option")
+    {
+        m_node.option_data = &mock_ips_setter;
+
+        IpsOption* buf_selector = nullptr;
+        REQUIRE(match_node(&m_node, m_e_data, m_c, buf_selector));
+        REQUIRE(buf_selector == &mock_ips_setter);
+    }
+    SECTION("Empty buffer setter")
+    {
+        m_node.option_data = &mock_ips_read;
+
+        IpsOption* buf_selector = nullptr;
+        REQUIRE(match_node(&m_node, m_e_data, m_c, buf_selector));
+        REQUIRE(buf_selector != &mock_ips_read);
+        REQUIRE_FALSE(buf_selector);
+    }
+}
+
+class MockIpsOptPMD : public IpsOption
+{
+public:
+    MockIpsOptPMD(const char* s) : IpsOption(s)
+    { pmd = new PatternMatchData(); };
+
+    ~MockIpsOptPMD()
+    { delete pmd; }
+
+    CursorActionType get_cursor_type() const override
+    { return CAT_READ; };
+
+    PatternMatchData* get_pattern(SnortProtocolId, RuleDirection) override
+    { return pmd; }
+
+private:
+    PatternMatchData* pmd;
+};
+
+TEST_CASE("Detection Engine: skip_on_retry", "[de_core]")
+{
+    Packet p;
+    detection_option_tree_node_t m_node(RULE_OPTION_TYPE_OTHER, nullptr);
+    detection_option_tree_node_t m_child_node(RULE_OPTION_TYPE_OTHER, nullptr);
+    detection_option_eval_data_t m_e_data;
+    m_e_data.p = &p;
+    dot_node_state_t m_child_state;
+    int result = 0;
+
+    SECTION("Skip child leaf node")
+    {
+        m_child_node.option_type = RULE_OPTION_TYPE_LEAF_NODE;
+        REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result));
+        REQUIRE_FALSE(result);
+    }
+    SECTION("Skip if all following options are matched")
+    {
+        m_child_state.result = m_child_node.num_children = 0;
+        REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result));
+    }
+
+    SECTION("Do not skip all non-content nodes")
+    {
+        m_child_state.result = 1; // to avoid early exit
+        REQUIRE(false == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result));
+    }
+    SECTION("Retry of IPS content logic")
+    {
+        m_child_node.children = (detection_option_tree_node_t**)snort_calloc(2, sizeof(detection_option_tree_node_t*));
+        m_child_node.num_children = 2; // to avoid early exit
+        m_child_node.option_type = RULE_OPTION_TYPE_CONTENT;
+
+        SECTION("Not skipping if matched previously")
+        {
+            m_child_state.result = 1;
+            REQUIRE(false == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result));
+        }
+        SECTION("Non-relative")
+        {
+            SECTION("Increase result on 1st loop")
+            {
+                int curr_result = result;
+                m_child_node.is_relative = false;
+
+                REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 1, result));
+                REQUIRE(curr_result +1 == result);
+            }
+            SECTION("Do not affect result on non-1st loop")
+            {
+                int curr_result = result;
+                m_child_node.is_relative = false;
+
+                REQUIRE(true == skip_on_retry(nullptr, &m_child_node, &m_child_state, 2, result));
+                REQUIRE(curr_result == result);
+            }
+        }
+
+        m_child_node.is_relative = true;
+        MockIpsBufSetter mock_ips_setter ("mock_ips_setter");
+        MockIpsOptRead mock_ips_read ("mock_ips_read");
+        MockIpsOptPMD mock_ips_pmd ("mock_ips_pmd");
+
+        SECTION("Current Ips option is buffer setter")
+        {
+
+            m_node.option_data = &mock_ips_setter;
+
+            REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+        }
+        SECTION("Child node PMD evaluation")
+        {
+            m_node.option_data = &mock_ips_read;  // to avoid early exit
+
+            m_child_node.option_data = &mock_ips_pmd;
+            PatternMatchData* pmd = mock_ips_pmd.get_pattern(0, RULE_WO_DIR);
+
+            SECTION("PMD for not exists")
+            {
+                m_child_node.option_data = &mock_ips_pmd;
+                REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+            }
+            SECTION("PMD not literal")
+            {
+                REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+            }
+            SECTION("PMD not unbounded")
+            {
+                pmd->set_literal();
+                pmd->depth = 1;
+                REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+            }
+            SECTION("PMD is negated")
+            {
+                pmd->set_literal();
+                pmd->set_negated();
+                REQUIRE(false == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+            }
+            SECTION("Skipping on the 1st loop")
+            {
+                int curr_result = result;
+                pmd->set_literal();
+
+                REQUIRE(true == skip_on_retry(&m_node, &m_child_node, &m_child_state, 1, result));
+                REQUIRE(curr_result +1 == result);
+            }
+            SECTION("Skipping on the non-1st loop")
+            {
+                int curr_result = result;
+                pmd->set_literal();
+
+                REQUIRE(true == skip_on_retry(&m_node, &m_child_node, &m_child_state, 2, result));
+                REQUIRE(curr_result == result);
+            }
+        }
+    }
+}
+
+#endif
index 2b00a8957e2225b79e0e6e44845677def3d85955..8405bd70a2ed62566f6632f84b1567f1082cbb60 100644 (file)
@@ -17,7 +17,8 @@
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-// detection_options.h author Steven Sturges <ssturges@cisco.com>
+// detection_options.cc author Steve Sturges <ssturges@cisco.com>
+// detection_options.cc author Yehor Velykozhon <yvelykoz@cisco.com>
 
 #ifndef DETECTION_OPTIONS_H
 #define DETECTION_OPTIONS_H
@@ -33,6 +34,7 @@
 
 #include <sys/time.h>
 
+#include "detection/ips_context.h"
 #include "detection/rule_option_types.h"
 #include "latency/rule_latency_state.h"
 #include "main/thread_config.h"
@@ -66,6 +68,16 @@ struct dot_node_state_t
         uint16_t run_num;
         char result;
         char flowbit_failed;
+
+        void set(const snort::Packet& p, int last_result)
+        {
+            ts = p.pkth->ts;
+            run_num = get_run_num();
+            context_num = p.context->context_num;
+            flowbit_failed = 0;
+            rebuild_flag = p.packet_flags & PKT_REBUILT_STREAM;
+            result = last_result;
+        }
     } last_check;
     void* conts;
     uint64_t context_num;
@@ -137,7 +149,7 @@ struct detection_option_tree_node_t : public detection_option_tree_bud_t
     dot_node_state_t* state;
     int is_relative;
     option_type_t option_type;
-    
+
     detection_option_tree_node_t(option_type_t type, void* data) :
         evaluate(nullptr), option_data(data), is_relative(0), option_type(type)
     {
index a91acff298152068a5e4815760ab02e9e0048583..ec70e9e36b339dee8caff39b0e7426c7e3252ef3 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "hash/xhash.h"
 #include "log/messages.h"
+#include "protocols/packet.h"
 #include "utils/util.h"
 
 #include "sfthd.h"
@@ -54,15 +55,20 @@ void DetectionFilterConfigFree(DetectionFilterConfig* config)
     snort_free(config);
 }
 
-int detection_filter_test(void* pv, const SfIp* sip, const SfIp* dip, long curtime)
+int detection_filter_test(THD_NODE* pv, const Packet* const p)
 {
+    assert(p);
     // cppcheck-suppress unreadVariable
     RuleProfile profile(detectionFilterPerfStats);
 
     if (pv == nullptr)
         return 0;
 
-    return sfthd_test_rule(detection_filter_hash, (THD_NODE*)pv,
+    const SfIp* sip = p->ptrs.ip_api.get_src();
+    const SfIp* dip = p->ptrs.ip_api.get_dst();
+    long curtime = p->pkth->ts.tv_sec;
+
+    return sfthd_test_rule(detection_filter_hash, pv,
         sip, dip, curtime, get_ips_policy()->policy_id);
 }
 
index 3fd9cc8d6698732ae315d7b33a7490b40a234ff1..74783050cdc426edeacd89a37ed39552cd3b4882 100644 (file)
@@ -37,6 +37,7 @@ namespace snort
 extern THREAD_LOCAL snort::ProfileStats detectionFilterPerfStats;
 
 struct SfIp;
+struct Packet;
 }
 
 struct DetectionFilterConfig
@@ -52,8 +53,8 @@ void DetectionFilterConfigFree(DetectionFilterConfig*);
 void detection_filter_init(DetectionFilterConfig*);
 void detection_filter_term();
 
-int detection_filter_test(void*, const snort::SfIp* sip, const snort::SfIp* dip, long curtime);
 struct THD_NODE* detection_filter_create(DetectionFilterConfig*, struct THDX_STRUCT*);
+int detection_filter_test(THD_NODE*, const snort::Packet* const p);
 
 #endif