]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rate_filter by_both through IPPair storage 3056/head
authorRuslan Usmanov <ruslan.usmanov@threattrack.com>
Fri, 27 Oct 2017 16:50:32 +0000 (12:50 -0400)
committerRuslan Usmanov <ruslan.usmanov@threattrack.com>
Wed, 6 Dec 2017 21:49:35 +0000 (16:49 -0500)
Ticket https://redmine.openinfosecfoundation.org/issues/2127

doc/userguide/configuration/global-thresholds.rst
src/detect-engine-threshold.c
src/detect-engine-threshold.h
src/detect-threshold.h
src/host-timeout.c
src/ippair-timeout.c
src/util-threshold-config.c

index adb5cb8b47fec7317a15d07ae22e850d70bbd896..d63fa4b07adbbc56d9887fe858d7112fec4f36f5 100644 (file)
@@ -53,6 +53,8 @@ track
 Where to track the rule matches. When using by_src/by_dst the tracking is
 done per IP-address. The Host table is used for storage. When using by_rule
 it's done globally for the rule.
+Option by_both used to track per IP pair of source and destination. Packets
+going to opposite directions between same addresses tracked as the same pair.
 
 count
 ^^^^^
index e639332bb1fc05e23d8146abc054ca744b395589..18f75fe6726c1be6c9c7e8f86726940c40cf0323 100644 (file)
@@ -43,6 +43,9 @@
 #include "host.h"
 #include "host-storage.h"
 
+#include "ippair.h"
+#include "ippair-storage.h"
+
 #include "detect-parse.h"
 #include "detect-engine-sigorder.h"
 
 #include "util-var-name.h"
 #include "tm-threads.h"
 
-static int threshold_id = -1; /**< host storage id for thresholds */
+static int host_threshold_id = -1; /**< host storage id for thresholds */
+static int ippair_threshold_id = -1; /**< ip pair storage id for thresholds */
 
 int ThresholdHostStorageId(void)
 {
-    return threshold_id;
+    return host_threshold_id;
 }
 
 void ThresholdInit(void)
 {
-    threshold_id = HostStorageRegister("threshold", sizeof(void *), NULL, ThresholdListFree);
-    if (threshold_id == -1) {
+    host_threshold_id = HostStorageRegister("threshold", sizeof(void *), NULL, ThresholdListFree);
+    if (host_threshold_id == -1) {
         SCLogError(SC_ERR_HOST_INIT, "Can't initiate host storage for thresholding");
         exit(EXIT_FAILURE);
     }
+    ippair_threshold_id = IPPairStorageRegister("threshold", sizeof(void *), NULL, ThresholdListFree);
+    if (ippair_threshold_id == -1) {
+        SCLogError(SC_ERR_HOST_INIT, "Can't initiate IP pair storage for thresholding");
+        exit(EXIT_FAILURE);
+    }
 }
 
 int ThresholdHostHasThreshold(Host *host)
 {
-    return HostGetStorageById(host, threshold_id) ? 1 : 0;
+    return HostGetStorageById(host, host_threshold_id) ? 1 : 0;
+}
+
+int ThresholdIPPairHasThreshold(IPPair *pair)
+{
+    return IPPairGetStorageById(pair, ippair_threshold_id) ? 1 : 0;
 }
 
 /**
@@ -143,49 +157,61 @@ const DetectThresholdData *SigGetThresholdTypeIter(const Signature *sig,
 /**
  * \brief Remove timeout threshold hash elements
  *
- * \param de_ctx Dectection Context
+ * \param head Current head element of storage
+ * \param tv Current time
+ *
+ * \retval DetectThresholdEntry Return new head element or NULL if all expired
  *
  */
 
-int ThresholdTimeoutCheck(Host *host, struct timeval *tv)
+static DetectThresholdEntry* ThresholdTimeoutCheck(DetectThresholdEntry *head, struct timeval *tv)
 {
-    DetectThresholdEntry *tde = NULL;
-    DetectThresholdEntry *tmp = NULL;
+    DetectThresholdEntry *tmp = head;
     DetectThresholdEntry *prev = NULL;
-    int retval = 1;
-
-    tmp = HostGetStorageById(host, threshold_id);
-    if (tmp == NULL)
-         return 1;
+    DetectThresholdEntry *new_head = head;
 
-    prev = NULL;
     while (tmp != NULL) {
         if ((tv->tv_sec - tmp->tv_sec1) <= tmp->seconds) {
             prev = tmp;
             tmp = tmp->next;
-            retval = 0;
             continue;
         }
 
         /* timed out */
 
+        DetectThresholdEntry *tde = tmp;
         if (prev != NULL) {
             prev->next = tmp->next;
+        }
+        else {
+            new_head = tmp->next;
+        }
+        tmp = tde->next;
+        SCFree(tde);
+    }
 
-            tde = tmp;
-            tmp = tde->next;
-
-            SCFree(tde);
-        } else {
-            HostSetStorageById(host, threshold_id, tmp->next);
-            tde = tmp;
-            tmp = tde->next;
+    return new_head;
+}
 
-            SCFree(tde);
-        }
+int ThresholdHostTimeoutCheck(Host *host, struct timeval *tv)
+{
+    DetectThresholdEntry* head = HostGetStorageById(host, host_threshold_id);
+    DetectThresholdEntry* new_head = ThresholdTimeoutCheck(head, tv);
+    if (new_head != head) {
+        HostSetStorageById(host, host_threshold_id, new_head);
     }
+    return new_head == NULL;
+}
 
-    return retval;
+
+int ThresholdIPPairTimeoutCheck(IPPair *pair, struct timeval *tv)
+{
+    DetectThresholdEntry* head = IPPairGetStorageById(pair, ippair_threshold_id);
+    DetectThresholdEntry* new_head = ThresholdTimeoutCheck(head, tv);
+    if (new_head != head) {
+        IPPairSetStorageById(pair, ippair_threshold_id, new_head);
+    }
+    return new_head == NULL;
 }
 
 static inline DetectThresholdEntry *
@@ -213,7 +239,19 @@ static DetectThresholdEntry *ThresholdHostLookupEntry(Host *h, uint32_t sid, uin
 {
     DetectThresholdEntry *e;
 
-    for (e = HostGetStorageById(h, threshold_id); e != NULL; e = e->next) {
+    for (e = HostGetStorageById(h, host_threshold_id); e != NULL; e = e->next) {
+        if (e->sid == sid && e->gid == gid)
+            break;
+    }
+
+    return e;
+}
+
+static DetectThresholdEntry *ThresholdIPPairLookupEntry(IPPair *pair, uint32_t sid, uint32_t gid)
+{
+    DetectThresholdEntry *e;
+
+    for (e = IPPairGetStorageById(pair, ippair_threshold_id); e != NULL; e = e->next) {
         if (e->sid == sid && e->gid == gid)
             break;
     }
@@ -281,6 +319,106 @@ static inline void RateFilterSetAction(Packet *p, PacketAlert *pa, uint8_t new_a
     }
 }
 
+/**
+* \brief Check if the entry reached threshold count limit
+*
+* \param lookup_tsh Current threshold entry
+* \param td Threshold settings
+* \param packet_time used to compare against previous detection and to set timeouts
+*
+* \retval int 1 if threshold reached for this entry
+*
+*/
+static int IsThresholdReached(DetectThresholdEntry* lookup_tsh, const DetectThresholdData *td, uint32_t packet_time)
+{
+    int ret = 0;
+
+    /* Check if we have a timeout enabled, if so,
+    * we still matching (and enabling the new_action) */
+    if (lookup_tsh->tv_timeout != 0) {
+        if ((packet_time - lookup_tsh->tv_timeout) > td->timeout) {
+            /* Ok, we are done, timeout reached */
+            lookup_tsh->tv_timeout = 0;
+        }
+        else {
+            /* Already matching */
+            ret = 1;
+        } /* else - if ((packet_time - lookup_tsh->tv_timeout) > td->timeout) */
+
+    }
+    else {
+        /* Update the matching state with the timeout interval */
+        if ((packet_time - lookup_tsh->tv_sec1) < td->seconds) {
+            lookup_tsh->current_count++;
+            if (lookup_tsh->current_count > td->count) {
+                /* Then we must enable the new action by setting a
+                * timeout */
+                lookup_tsh->tv_timeout = packet_time;
+                ret = 1;
+            }
+        }
+        else {
+            lookup_tsh->tv_sec1 = packet_time;
+            lookup_tsh->current_count = 1;
+        }
+    } /* else - if (lookup_tsh->tv_timeout != 0) */
+
+    return ret;
+}
+
+static void AddEntryToHostStorage(Host *h, DetectThresholdEntry *e, uint32_t packet_time)
+{
+    if (h && e) {
+        e->current_count = 1;
+        e->tv_sec1 = packet_time;
+        e->tv_timeout = 0;
+        e->next = HostGetStorageById(h, host_threshold_id);
+        HostSetStorageById(h, host_threshold_id, e);
+    }
+}
+
+static void AddEntryToIPPairStorage(IPPair *pair, DetectThresholdEntry *e, uint32_t packet_time)
+{
+    if (pair && e) {
+        e->current_count = 1;
+        e->tv_sec1 = packet_time;
+        e->tv_timeout = 0;
+        e->next = IPPairGetStorageById(pair, ippair_threshold_id);
+        IPPairSetStorageById(pair, ippair_threshold_id, e);
+    }
+}
+
+static int ThresholdHandlePacketIPPair(IPPair *pair, Packet *p, const DetectThresholdData *td,
+    uint32_t sid, uint32_t gid, PacketAlert *pa)
+{
+    int ret = 0;
+
+    DetectThresholdEntry *lookup_tsh = ThresholdIPPairLookupEntry(pair, sid, gid);
+    SCLogDebug("ippair lookup_tsh %p sid %u gid %u", lookup_tsh, sid, gid);
+
+    switch (td->type) {
+        case TYPE_RATE:
+        {
+            SCLogDebug("rate_filter");
+            ret = 1;
+            if (lookup_tsh && IsThresholdReached(lookup_tsh, td, p->ts.tv_sec)) {
+                RateFilterSetAction(p, pa, td->new_action);
+            } else if (!lookup_tsh) {
+                DetectThresholdEntry *e = DetectThresholdEntryAlloc(td, p, sid, gid);
+                AddEntryToIPPairStorage(pair, e, p->ts.tv_sec);
+            }
+            break;
+        }
+        default:
+        {
+            SCLogError(SC_ERR_INVALID_VALUE, "type %d is not supported", td->type);
+            break;
+        }
+    }
+
+    return ret;
+}
+
 /**
  *  \retval 2 silent match (no alert but apply actions)
  *  \retval 1 normal match
@@ -325,8 +463,8 @@ static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdDa
 
                 ret = 1;
 
-                e->next = HostGetStorageById(h, threshold_id);
-                HostSetStorageById(h, threshold_id, e);
+                e->next = HostGetStorageById(h, host_threshold_id);
+                HostSetStorageById(h, host_threshold_id, e);
             }
             break;
         }
@@ -358,8 +496,8 @@ static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdDa
                     e->current_count = 1;
                     e->tv_sec1 = p->ts.tv_sec;
 
-                    e->next = HostGetStorageById(h, threshold_id);
-                    HostSetStorageById(h, threshold_id, e);
+                    e->next = HostGetStorageById(h, host_threshold_id);
+                    HostSetStorageById(h, host_threshold_id, e);
                 }
             }
             break;
@@ -398,8 +536,8 @@ static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdDa
                 e->current_count = 1;
                 e->tv_sec1 = p->ts.tv_sec;
 
-                e->next = HostGetStorageById(h, threshold_id);
-                HostSetStorageById(h, threshold_id, e);
+                e->next = HostGetStorageById(h, host_threshold_id);
+                HostSetStorageById(h, host_threshold_id, e);
 
                 /* for the first match we return 1 to
                  * indicate we should alert */
@@ -442,8 +580,8 @@ static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdDa
                 e->tv_sec1 = p->ts.tv_sec;
                 e->tv_usec1 = p->ts.tv_usec;
 
-                e->next = HostGetStorageById(h, threshold_id);
-                HostSetStorageById(h, threshold_id, e);
+                e->next = HostGetStorageById(h, host_threshold_id);
+                HostSetStorageById(h, host_threshold_id, e);
             }
             break;
         }
@@ -451,56 +589,12 @@ static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdDa
         case TYPE_RATE:
         {
             SCLogDebug("rate_filter");
-
             ret = 1;
-
-            if (lookup_tsh != NULL) {
-                /* Check if we have a timeout enabled, if so,
-                 * we still matching (and enabling the new_action) */
-                if (lookup_tsh->tv_timeout != 0) {
-                    if ((p->ts.tv_sec - lookup_tsh->tv_timeout) > td->timeout) {
-                        /* Ok, we are done, timeout reached */
-                        lookup_tsh->tv_timeout = 0;
-                    } else {
-                        /* Already matching */
-                        /* Take the action to perform */
-                        RateFilterSetAction(p, pa, td->new_action);
-                        ret = 1;
-                    } /* else - if ((p->ts.tv_sec - lookup_tsh->tv_timeout) > td->timeout) */
-
-                } else {
-                    /* Update the matching state with the timeout interval */
-                    if ( (p->ts.tv_sec - lookup_tsh->tv_sec1) < td->seconds) {
-                        lookup_tsh->current_count++;
-                        if (lookup_tsh->current_count > td->count) {
-                            /* Then we must enable the new action by setting a
-                             * timeout */
-                            lookup_tsh->tv_timeout = p->ts.tv_sec;
-                            /* Take the action to perform */
-                            RateFilterSetAction(p, pa, td->new_action);
-                            ret = 1;
-                        }
-                    } else {
-                        lookup_tsh->tv_sec1 = p->ts.tv_sec;
-                        lookup_tsh->current_count = 1;
-                    }
-                } /* else - if (lookup_tsh->tv_timeout != 0) */
-            } else {
-                if (td->count == 1) {
-                    ret = 1;
-                }
-
+            if (lookup_tsh && IsThresholdReached(lookup_tsh, td, p->ts.tv_sec)) {
+                RateFilterSetAction(p, pa, td->new_action);
+            } else if (!lookup_tsh) {
                 DetectThresholdEntry *e = DetectThresholdEntryAlloc(td, p, sid, gid);
-                if (e == NULL) {
-                    break;
-                }
-
-                e->current_count = 1;
-                e->tv_sec1 = p->ts.tv_sec;
-                e->tv_timeout = 0;
-
-                e->next = HostGetStorageById(h, threshold_id);
-                HostSetStorageById(h, threshold_id, e);
+                AddEntryToHostStorage(h, e, p->ts.tv_sec);
             }
             break;
         }
@@ -603,6 +697,12 @@ int PacketAlertThreshold(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx
             ret = ThresholdHandlePacketHost(dst,p,td,s->id,s->gid,pa);
             HostRelease(dst);
         }
+    } else if (td->track == TRACK_BOTH) {
+        IPPair *pair = IPPairGetIPPairFromHash(&p->src, &p->dst);
+        if (pair) {
+            ret = ThresholdHandlePacketIPPair(pair, p, td, s->id, s->gid, pa);
+            IPPairRelease(pair);
+        }
     } else if (td->track == TRACK_RULE) {
         SCMutexLock(&de_ctx->ths_ctx.threshold_table_lock);
         ret = ThresholdHandlePacketRule(de_ctx,p,td,s,pa);
index 76a0759d3549fbd57b62d7af130d5d1b311a81cc..93f83f039caf76f2036097a6c5965021184fdeab 100644 (file)
 
 #include "detect.h"
 #include "host.h"
+#include "ippair.h"
 
 void ThresholdInit(void);
 
 int ThresholdHostStorageId(void);
 int ThresholdHostHasThreshold(Host *);
 
+int ThresholdIPPairHasThreshold(IPPair *pair);
+
 const DetectThresholdData *SigGetThresholdTypeIter(const Signature *,
         Packet *, const SigMatchData **, int list);
 int PacketAlertThreshold(DetectEngineCtx *, DetectEngineThreadCtx *,
@@ -42,7 +45,8 @@ int PacketAlertThreshold(DetectEngineCtx *, DetectEngineThreadCtx *,
 void ThresholdHashInit(DetectEngineCtx *);
 void ThresholdContextDestroy(DetectEngineCtx *);
 
-int ThresholdTimeoutCheck(Host *, struct timeval *);
+int ThresholdHostTimeoutCheck(Host *, struct timeval *);
+int ThresholdIPPairTimeoutCheck(IPPair *, struct timeval *);
 void ThresholdListFree(void *ptr);
 
 #endif /* __DETECT_ENGINE_THRESHOLD_H__ */
index 50e1d2704857178fa8a4069158057a5a6951a401..534a6eb4aedb0b2c5a87f66c65e4cce4beb66812 100644 (file)
@@ -39,6 +39,7 @@
 #define TRACK_SRC      2
 #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 */
 
 /* Get the new action to take */
 #define TH_ACTION_ALERT     0x01
index 48efb5ab74d0a6d7f5b27f41150d3f11f2dde06d..ccb9214983c7f8a41f9c72cd9c87279251555ef4 100644 (file)
@@ -73,7 +73,7 @@ static int HostHostTimedOut(Host *h, struct timeval *ts)
     if (TagHostHasTag(h) && TagTimeoutCheck(h, ts) == 0) {
         tags = 1;
     }
-    if (ThresholdHostHasThreshold(h) && ThresholdTimeoutCheck(h, ts) == 0) {
+    if (ThresholdHostHasThreshold(h) && ThresholdHostTimeoutCheck(h, ts) == 0) {
         thresholds = 1;
     }
     if (HostHasHostBits(h) && HostBitsTimedoutCheck(h, ts) == 0) {
index 074200c8786db344a9b4ccdee6ac705f4e1f6667..47f420b17c56c15bad4033eacdc6b45d5cbd2c99 100644 (file)
@@ -25,6 +25,7 @@
 #include "ippair.h"
 #include "ippair-bit.h"
 #include "ippair-timeout.h"
+#include "detect-engine-threshold.h"
 
 uint32_t IPPairGetSpareCount(void)
 {
@@ -48,6 +49,7 @@ uint32_t IPPairGetActiveCount(void)
 static int IPPairTimedOut(IPPair *h, struct timeval *ts)
 {
     int vars = 0;
+    int thresholds = 0;
 
     /** never prune a ippair that is used by a packet
      *  we are currently processing in one of the threads */
@@ -59,7 +61,11 @@ static int IPPairTimedOut(IPPair *h, struct timeval *ts)
         vars = 1;
     }
 
-    if (vars) {
+    if (ThresholdIPPairHasThreshold(h) && ThresholdIPPairTimeoutCheck(h, ts) == 0) {
+        thresholds = 1;
+    }
+
+    if (vars || thresholds) {
         return 0;
     }
 
index da7ac5af3cf27b8ff21cbb42c7635fe36dfa55b7..370a9e476de18e69dbb431a9f1d554b71452cfba 100644 (file)
@@ -33,6 +33,7 @@
 #include "suricata-common.h"
 
 #include "host.h"
+#include "ippair.h"
 
 #include "detect.h"
 #include "detect-engine.h"
@@ -68,7 +69,7 @@ static FILE *g_ut_threshold_fp = NULL;
 #define DETECT_THRESHOLD_REGEX "^,\\s*type\\s*(limit|both|threshold)\\s*,\\s*track\\s*(by_dst|by_src)\\s*,\\s*count\\s*(\\d+)\\s*,\\s*seconds\\s*(\\d+)\\s*$"
 
 /* TODO: "apply_to" */
-#define DETECT_RATE_REGEX "^,\\s*track\\s*(by_dst|by_src|by_rule)\\s*,\\s*count\\s*(\\d+)\\s*,\\s*seconds\\s*(\\d+)\\s*,\\s*new_action\\s*(alert|drop|pass|log|sdrop|reject)\\s*,\\s*timeout\\s*(\\d+)\\s*$"
+#define DETECT_RATE_REGEX "^,\\s*track\\s*(by_dst|by_src|by_both|by_rule)\\s*,\\s*count\\s*(\\d+)\\s*,\\s*seconds\\s*(\\d+)\\s*,\\s*new_action\\s*(alert|drop|pass|log|sdrop|reject)\\s*,\\s*timeout\\s*(\\d+)\\s*$"
 
 /*
  * suppress has two form:
@@ -916,6 +917,9 @@ static int ParseThresholdRule(DetectEngineCtx *de_ctx, char *rawstr,
                 parsed_track = TRACK_DST;
             else if (strcasecmp(th_track,"by_src") == 0)
                 parsed_track = TRACK_SRC;
+            else if (strcasecmp(th_track, "by_both") == 0) {
+                parsed_track = TRACK_BOTH;
+            }
             else if (strcasecmp(th_track,"by_rule") == 0)
                 parsed_track = TRACK_RULE;
             else {
@@ -1219,7 +1223,8 @@ static FILE *SCThresholdConfGenerateValidDummyFD05(void)
     const char *buffer =
         "rate_filter gen_id 1, sig_id 10, track by_src, count 1, seconds 60, new_action drop, timeout 10\n"
         "rate_filter gen_id 1, sig_id 100, track by_dst, count 10, seconds 60, new_action pass, timeout 5\n"
-        "rate_filter gen_id 1, sig_id 1000, track by_rule, count 100, seconds 60, new_action alert, timeout 30\n";
+        "rate_filter gen_id 1, sig_id 1000, track by_rule, count 100, seconds 60, new_action alert, timeout 30\n"
+        "rate_filter gen_id 1, sig_id 10000, track by_both, count 1000, seconds 60, new_action reject, timeout 21\n";
 
     fd = SCFmemopen((void *)buffer, strlen(buffer), "r");
     if (fd == NULL)
@@ -1240,7 +1245,8 @@ static FILE *SCThresholdConfGenerateValidDummyFD06(void)
     const char *buffer =
         "rate_filter \\\ngen_id 1, sig_id 10, track by_src, count 1, seconds 60\\\n, new_action drop, timeout 10\n"
         "rate_filter gen_id 1, \\\nsig_id 100, track by_dst, \\\ncount 10, seconds 60, new_action pass, timeout 5\n"
-        "rate_filter gen_id 1, sig_id 1000, \\\ntrack by_rule, count 100, seconds 60, new_action alert, timeout 30\n";
+        "rate_filter gen_id 1, sig_id 1000, \\\ntrack by_rule, count 100, seconds 60, new_action alert, timeout 30\n"
+        "rate_filter gen_id 1, sig_id 10000, track by_both, count 1000, \\\nseconds 60, new_action reject, timeout 21\n";
 
     fd = SCFmemopen((void *)buffer, strlen(buffer), "r");
     if (fd == NULL)
@@ -2475,6 +2481,230 @@ static int SCThresholdConfTest21(void)
     PASS;
 }
 
+/**
+* \brief Creates a dummy rate_filter file, for testing rate filtering by_both source and destination
+*
+* \retval fd Pointer to file descriptor.
+*/
+static FILE *SCThresholdConfGenerateValidDummyFD22(void)
+{
+    FILE *fd = NULL;
+    const char *buffer =
+        "rate_filter gen_id 1, sig_id 10, track by_both, count 2, seconds 5, new_action drop, timeout 6\n";
+
+    fd = SCFmemopen((void *)buffer, strlen(buffer), "r");
+    if (fd == NULL)
+        SCLogDebug("Error with SCFmemopen() called by Threshold Config test code");
+
+    return fd;
+}
+
+/**
+* \test Check if the rate_filter rules work with track by_both
+*
+*  \retval 1 on succces
+*  \retval 0 on failure
+*/
+static int SCThresholdConfTest22(void)
+{
+    ThreadVars th_v;
+    memset(&th_v, 0, sizeof(th_v));
+
+    IPPairInitConfig(IPPAIR_QUIET);
+
+    struct timeval ts;
+    memset(&ts, 0, sizeof(struct timeval));
+    TimeGet(&ts);
+
+    /* This packet will cause rate_filter */
+    Packet *p1 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.10");
+    FAIL_IF_NULL(p1);
+
+    /* Should not be filtered for different destination */
+    Packet *p2 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.2");
+    FAIL_IF_NULL(p2);
+
+    /* Should not be filtered when both src and dst the same */
+    Packet *p3 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.1");
+    FAIL_IF_NULL(p3);
+
+    DetectEngineThreadCtx *det_ctx = NULL;
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+    de_ctx->flags |= DE_QUIET;
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+            "alert tcp any any -> any any (msg:\"ratefilter by_both test\"; gid:1; sid:10;)");
+    FAIL_IF_NULL(sig);
+
+    FAIL_IF_NOT_NULL(g_ut_threshold_fp);
+    g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD22();
+    FAIL_IF_NULL(g_ut_threshold_fp);
+    SCThresholdConfInitContext(de_ctx);
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
+
+    TimeGet(&p1->ts);
+    p2->ts = p3->ts = p1->ts;
+
+    /* All should be alerted, none dropped */
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
+    FAIL_IF(PACKET_TEST_ACTION(p1, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p1, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p2);
+    FAIL_IF(PACKET_TEST_ACTION(p2, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p2, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p3);
+    FAIL_IF(PACKET_TEST_ACTION(p3, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p3, 10) != 1);
+
+    p1->action = p2->action = p3->action = 0;
+
+    TimeSetIncrementTime(2);
+    TimeGet(&p1->ts);
+    p2->ts = p3->ts = p1->ts;
+
+    /* p1 still shouldn't be dropped after 2nd alert */
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
+    FAIL_IF(PACKET_TEST_ACTION(p1, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p1, 10) != 1);
+
+    p1->action = 0;
+
+    TimeSetIncrementTime(2);
+    TimeGet(&p1->ts);
+    p2->ts = p3->ts = p1->ts;
+
+    /* All should be alerted, only p1 must be dropped  due to rate_filter*/
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
+    FAIL_IF_NOT(PACKET_TEST_ACTION(p1, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p1, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p2);
+    FAIL_IF(PACKET_TEST_ACTION(p2, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p2, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p3);
+    FAIL_IF(PACKET_TEST_ACTION(p3, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p3, 10) != 1);
+
+    p1->action = p2->action = p3->action = 0;
+
+    TimeSetIncrementTime(7);
+    TimeGet(&p1->ts);
+    p2->ts = p3->ts = p1->ts;
+
+    /* All should be alerted, none dropped (because timeout expired) */
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
+    FAIL_IF(PACKET_TEST_ACTION(p1, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p1, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p2);
+    FAIL_IF(PACKET_TEST_ACTION(p2, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p2, 10) != 1);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p3);
+    FAIL_IF(PACKET_TEST_ACTION(p3, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p3, 10) != 1);
+
+    UTHFreePacket(p3);
+    UTHFreePacket(p2);
+    UTHFreePacket(p1);
+
+    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
+    DetectEngineCtxFree(de_ctx);
+    IPPairShutdown();
+    PASS;
+}
+
+/**
+* \brief Creates a dummy rate_filter file, for testing rate filtering by_both source and destination
+*
+* \retval fd Pointer to file descriptor.
+*/
+static FILE *SCThresholdConfGenerateValidDummyFD23(void)
+{
+    FILE *fd = NULL;
+    const char *buffer =
+        "rate_filter gen_id 1, sig_id 10, track by_both, count 1, seconds 5, new_action drop, timeout 6\n";
+
+    fd = SCFmemopen((void *)buffer, strlen(buffer), "r");
+    if (fd == NULL)
+        SCLogDebug("Error with SCFmemopen() called by Threshold Config test code");
+
+    return fd;
+}
+
+/**
+* \test Check if the rate_filter by_both work when similar packets
+*       going in opposite direction
+*
+*  \retval 1 on succces
+*  \retval 0 on failure
+*/
+static int SCThresholdConfTest23(void)
+{
+    ThreadVars th_v;
+    memset(&th_v, 0, sizeof(th_v));
+
+    IPPairInitConfig(IPPAIR_QUIET);
+
+    struct timeval ts;
+    memset(&ts, 0, sizeof(struct timeval));
+    TimeGet(&ts);
+
+    /* Create two packets between same addresses in opposite direction */
+    Packet *p1 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.10");
+    FAIL_IF_NULL(p1);
+
+    Packet *p2 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.10", "172.26.0.1");
+    FAIL_IF_NULL(p2);
+
+    DetectEngineThreadCtx *det_ctx = NULL;
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+    de_ctx->flags |= DE_QUIET;
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+        "alert tcp any any -> any any (msg:\"ratefilter by_both test\"; gid:1; sid:10;)");
+    FAIL_IF_NULL(sig);
+
+    FAIL_IF_NOT_NULL(g_ut_threshold_fp);
+    g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD23();
+    FAIL_IF_NULL(g_ut_threshold_fp);
+    SCThresholdConfInitContext(de_ctx);
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
+
+    TimeGet(&p1->ts);
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p1);
+    /* First packet should be alerted, not dropped */
+    FAIL_IF(PACKET_TEST_ACTION(p1, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p1, 10) != 1);
+
+    TimeSetIncrementTime(2);
+    TimeGet(&p2->ts);
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p2);
+
+    /* Second packet should be dropped because it considered as "the same pair"
+       and rate_filter count reached*/
+    FAIL_IF_NOT(PACKET_TEST_ACTION(p2, ACTION_DROP));
+    FAIL_IF(PacketAlertCheck(p2, 10) != 1);
+
+    UTHFreePacket(p2);
+    UTHFreePacket(p1);
+
+    DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
+    DetectEngineCtxFree(de_ctx);
+    IPPairShutdown();
+    PASS;
+}
 #endif /* UNITTESTS */
 
 /**
@@ -2516,6 +2746,11 @@ void SCThresholdConfRegisterTests(void)
                    SCThresholdConfTest20);
     UtRegisterTest("SCThresholdConfTest21 - suppress parsing",
                    SCThresholdConfTest21);
+    UtRegisterTest("SCThresholdConfTest22 - rate_filter by_both",
+                   SCThresholdConfTest22);
+    UtRegisterTest("SCThresholdConfTest23 - rate_filter by_both opposite",
+        SCThresholdConfTest23);
+
 #endif /* UNITTESTS */
 }