]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/threshold: implement tracking 'by_flow'
authorVictor Julien <vjulien@oisf.net>
Tue, 27 Feb 2024 10:06:47 +0000 (11:06 +0100)
committerVictor Julien <vjulien@oisf.net>
Fri, 28 Jun 2024 05:39:48 +0000 (07:39 +0200)
Add support for 'by_flow' track option. This allows using the various
threshold options in the context of a single flow.

Example:

    alert tcp ... stream-event:pkt_broken_ack; \
        threshold:type limit, track by_flow, count 1, seconds 3600;

The example would limit the number of alerts to once per hour for
packets triggering the 'pkt_broken_ack' stream event.

Implemented as a special "flowvar" holding the threshold entries. This
means no synchronization is required, making this a cheaper option
compared to the other trackers.

Ticket: #6822.

src/detect-engine-threshold.c
src/detect-engine-threshold.h
src/detect-threshold.c
src/detect-threshold.h
src/util-var.c

index b5ac8670d0bb7a8fbb99d61ccf97eac372c8ffdb..0c85dbc6ab91fa3432bfc4d42d1ae57f1c7e325b 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2007-2021 Open Information Security Foundation
+/* Copyright (C) 2007-2024 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -69,6 +69,7 @@
 #include "tm-threads.h"
 
 #include "action-globals.h"
+#include "util-validate.h"
 
 static HostStorageId host_threshold_id = { .id = -1 };     /**< host storage id for thresholds */
 static IPPairStorageId ippair_threshold_id = { .id = -1 }; /**< ip pair storage id for thresholds */
@@ -241,6 +242,71 @@ static DetectThresholdEntry *ThresholdHostLookupEntry(Host *h,
     return e;
 }
 
+/** struct for storing per flow thresholds. This will be stored in the Flow::flowvar list, so it
+ * needs to follow the GenericVar header format. */
+typedef struct FlowVarThreshold_ {
+    uint8_t type;
+    uint8_t pad[7];
+    struct GenericVar_ *next;
+    DetectThresholdEntry *thresholds;
+} FlowVarThreshold;
+
+void FlowThresholdVarFree(void *ptr)
+{
+    FlowVarThreshold *t = ptr;
+    ThresholdListFree(t->thresholds);
+    SCFree(t);
+}
+
+static FlowVarThreshold *FlowThresholdVarGet(Flow *f)
+{
+    if (f == NULL)
+        return NULL;
+
+    for (GenericVar *gv = f->flowvar; gv != NULL; gv = gv->next) {
+        if (gv->type == DETECT_THRESHOLD)
+            return (FlowVarThreshold *)gv;
+    }
+
+    return NULL;
+}
+
+static DetectThresholdEntry *ThresholdFlowLookupEntry(Flow *f, uint32_t sid, uint32_t gid)
+{
+    FlowVarThreshold *t = FlowThresholdVarGet(f);
+    if (t == NULL)
+        return NULL;
+
+    for (DetectThresholdEntry *e = t->thresholds; e != NULL; e = e->next) {
+        if (e->sid == sid && e->gid == gid) {
+            return e;
+        }
+    }
+    return NULL;
+}
+
+static int AddEntryToFlow(Flow *f, DetectThresholdEntry *e, SCTime_t packet_time)
+{
+    DEBUG_VALIDATE_BUG_ON(e == NULL);
+
+    FlowVarThreshold *t = FlowThresholdVarGet(f);
+    if (t == NULL) {
+        t = SCCalloc(1, sizeof(*t));
+        if (t == NULL) {
+            return -1;
+        }
+        t->type = DETECT_THRESHOLD;
+        GenericVarAppend(&f->flowvar, (GenericVar *)t);
+    }
+
+    e->current_count = 1;
+    e->tv1 = packet_time;
+    e->tv_timeout = 0;
+    e->next = t->thresholds;
+    t->thresholds = e;
+    return 0;
+}
+
 static DetectThresholdEntry *ThresholdIPPairLookupEntry(IPPair *pair,
         uint32_t sid, uint32_t gid)
 {
@@ -587,6 +653,28 @@ static int ThresholdHandlePacketRule(DetectEngineCtx *de_ctx, Packet *p,
     return ret;
 }
 
+/**
+ *  \retval 2 silent match (no alert but apply actions)
+ *  \retval 1 normal match
+ *  \retval 0 no match
+ */
+static int ThresholdHandlePacketFlow(Flow *f, Packet *p, const DetectThresholdData *td,
+        uint32_t sid, uint32_t gid, PacketAlert *pa)
+{
+    int ret = 0;
+    DetectThresholdEntry *lookup_tsh = ThresholdFlowLookupEntry(f, sid, gid);
+    SCLogDebug("lookup_tsh %p sid %u gid %u", lookup_tsh, sid, gid);
+
+    DetectThresholdEntry *new_tsh = NULL;
+    ret = ThresholdHandlePacket(p, lookup_tsh, &new_tsh, td, sid, gid, pa);
+    if (new_tsh != NULL) {
+        if (AddEntryToFlow(f, new_tsh, p->ts) == -1) {
+            SCFree(new_tsh);
+        }
+    }
+    return ret;
+}
+
 /**
  * \brief Make the threshold logic for signatures
  *
@@ -633,6 +721,10 @@ int PacketAlertThreshold(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx
         SCMutexLock(&de_ctx->ths_ctx.threshold_table_lock);
         ret = ThresholdHandlePacketRule(de_ctx,p,td,s,pa);
         SCMutexUnlock(&de_ctx->ths_ctx.threshold_table_lock);
+    } else if (td->track == TRACK_FLOW) {
+        if (p->flow) {
+            ret = ThresholdHandlePacketFlow(p->flow, p, td, s->id, s->gid, pa);
+        }
     }
 
     SCReturnInt(ret);
index 1516359a4b42597151961f8940c1555418c108b1..ff76374fcaae5dccc7a47d070fe2a221fd537ee6 100644 (file)
@@ -51,4 +51,6 @@ int ThresholdHostTimeoutCheck(Host *, SCTime_t);
 int ThresholdIPPairTimeoutCheck(IPPair *, SCTime_t);
 void ThresholdListFree(void *ptr);
 
+void FlowThresholdVarFree(void *ptr);
+
 #endif /* SURICATA_DETECT_ENGINE_THRESHOLD_H */
index 98eb3ce8dc0308f5ef14b9e4ab3c24d36d2d2d3a..d952b5ec36fc1da44edcd85f666eaea1e03c39b7 100644 (file)
 #include "util-cpu.h"
 #endif
 
-#define PARSE_REGEX "^\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_both|by_rule|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_both|by_rule|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_both|by_rule|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_both|by_rule|\\d+)\\s*"
+#define PARSE_REGEX                                                                                \
+    "^\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_both|by_rule|by_"   \
+    "flow|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_dst|by_src|by_"     \
+    "both|by_rule|by_flow|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|threshold|by_"   \
+    "dst|by_src|by_both|by_rule|by_flow|\\d+)\\s*,\\s*(track|type|count|seconds)\\s+(limit|both|"  \
+    "threshold|by_dst|by_src|by_both|by_rule|by_flow|\\d+)\\s*"
 
 static DetectParseRegex parse_regex;
 
@@ -183,6 +188,8 @@ static DetectThresholdData *DetectThresholdParse(const char *rawstr)
             de->track = TRACK_BOTH;
         if (strncasecmp(args[i],"by_rule",strlen("by_rule")) == 0)
             de->track = TRACK_RULE;
+        if (strncasecmp(args[i], "by_flow", strlen("by_flow")) == 0)
+            de->track = TRACK_FLOW;
         if (strncasecmp(args[i],"count",strlen("count")) == 0)
             count_pos = i+1;
         if (strncasecmp(args[i],"seconds",strlen("seconds")) == 0)
@@ -342,6 +349,7 @@ error:
 #include "util-hashlist.h"
 #include "packet.h"
 #include "action-globals.h"
+
 /**
  * \test ThresholdTestParse01 is a test for a valid threshold options
  *
@@ -360,6 +368,18 @@ static int ThresholdTestParse01(void)
     return 0;
 }
 
+static int ThresholdTestParseByFlow01(void)
+{
+    DetectThresholdData *de = DetectThresholdParse("type limit,track by_flow,count 1,seconds 60");
+    FAIL_IF_NULL(de);
+    FAIL_IF_NOT(de->type == TYPE_LIMIT);
+    FAIL_IF_NOT(de->track == TRACK_FLOW);
+    FAIL_IF_NOT(de->count == 1);
+    FAIL_IF_NOT(de->seconds == 60);
+    DetectThresholdFree(NULL, de);
+    PASS;
+}
+
 /**
  * \test ThresholdTestParse02 is a test for a invalid threshold options
  *
@@ -1692,6 +1712,7 @@ static int DetectThresholdTestSig14(void)
 static void ThresholdRegisterTests(void)
 {
     UtRegisterTest("ThresholdTestParse01", ThresholdTestParse01);
+    UtRegisterTest("ThresholdTestParseByFlow01", ThresholdTestParseByFlow01);
     UtRegisterTest("ThresholdTestParse02", ThresholdTestParse02);
     UtRegisterTest("ThresholdTestParse03", ThresholdTestParse03);
     UtRegisterTest("ThresholdTestParse04", ThresholdTestParse04);
index 2648feb0e4ce20bf57e0d8fa36e823f57ffb5bd0..5321fd54bdac3168a59c72aeb504bafabd0ed719 100644 (file)
@@ -36,6 +36,7 @@
 #define TRACK_RULE     3
 #define TRACK_EITHER   4 /**< either src or dst: only used by suppress */
 #define TRACK_BOTH     5 /* used by rate_filter to match detections by both src and dst addresses */
+#define TRACK_FLOW     6 /**< track by flow */
 
 /* Get the new action to take */
 #define TH_ACTION_ALERT     0x01
index 37b0032eb755442c7603017d8feffa3397460fd9..6ef3fe638f10162be87e90a1cbea9079f243f5ef 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2007-2013 Open Information Security Foundation
+/* Copyright (C) 2007-2024 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -25,6 +25,7 @@
 
 #include "suricata-common.h"
 #include "detect.h"
+#include "detect-engine-threshold.h"
 
 #include "util-var.h"
 
@@ -67,6 +68,10 @@ void GenericVarFree(GenericVar *gv)
             XBitFree(fb);
             break;
         }
+        case DETECT_THRESHOLD: {
+            FlowThresholdVarFree(gv);
+            break;
+        }
         case DETECT_FLOWVAR:
         {
             FlowVar *fv = (FlowVar *)gv;