suricata
detect-engine-threshold.c
Go to the documentation of this file.
1/* Copyright (C) 2007-2024 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
19 * \defgroup threshold Thresholding
20 *
21 * This feature is used to reduce the number of logged alerts for noisy rules.
22 * This can be tuned to significantly reduce false alarms, and it can also be
23 * used to write a newer breed of rules. Thresholding commands limit the number
24 * of times a particular event is logged during a specified time interval.
25 *
26 * @{
27 */
28
29/**
30 * \file
31 *
32 * \author Breno Silva <breno.silva@gmail.com>
33 * \author Victor Julien <victor@inliniac.net>
34 *
35 * Threshold part of the detection engine.
36 */
37
38#include "suricata-common.h"
39#include "detect.h"
40#include "flow.h"
41
42#include "detect-parse.h"
43#include "detect-engine.h"
47
48#include "util-misc.h"
49#include "util-time.h"
50#include "util-error.h"
51#include "util-debug.h"
52#include "action-globals.h"
53#include "util-validate.h"
54
55#include "util-hash.h"
56#include "util-thash.h"
57#include "util-hash-lookup3.h"
58
62
63static int ThresholdsInit(struct Thresholds *t);
64static void ThresholdsDestroy(struct Thresholds *t);
65
66void ThresholdInit(void)
67{
68 ThresholdsInit(&ctx);
69}
70
72{
73 ThresholdsDestroy(&ctx);
74}
75
76#define SID 0
77#define GID 1
78#define REV 2
79#define TRACK 3
80#define TENANT 4
81
82typedef struct ThresholdEntry_ {
83 uint32_t key[5];
84
85 SCTime_t tv_timeout; /**< Timeout for new_action (for rate_filter)
86 its not "seconds", that define the time interval */
87 uint32_t seconds; /**< Event seconds */
88 uint32_t current_count; /**< Var for count control */
89
90 union {
91 struct {
92 uint32_t next_value;
94 struct {
95 SCTime_t tv1; /**< Var for time control */
96 Address addr; /* used for src/dst/either tracking */
97 Address addr2; /* used for both tracking */
98 };
99 };
100
102
103static int ThresholdEntrySet(void *dst, void *src)
104{
105 const ThresholdEntry *esrc = src;
106 ThresholdEntry *edst = dst;
107 memset(edst, 0, sizeof(*edst));
108 *edst = *esrc;
109 return 0;
110}
111
112static void ThresholdEntryFree(void *ptr)
113{
114 // nothing to free, base data is part of hash
115}
116
117static inline uint32_t HashAddress(const Address *a)
118{
119 uint32_t key;
120
121 if (a->family == AF_INET) {
122 key = a->addr_data32[0];
123 } else if (a->family == AF_INET6) {
124 key = hashword(a->addr_data32, 4, 0);
125 } else
126 key = 0;
127
128 return key;
129}
130
131static inline int CompareAddress(const Address *a, const Address *b)
132{
133 if (a->family == b->family) {
134 switch (a->family) {
135 case AF_INET:
136 return (a->addr_data32[0] == b->addr_data32[0]);
137 case AF_INET6:
138 return CMP_ADDR(a, b);
139 }
140 }
141 return 0;
142}
143
144static uint32_t ThresholdEntryHash(uint32_t seed, void *ptr)
145{
146 const ThresholdEntry *e = ptr;
147 uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), seed);
148 switch (e->key[TRACK]) {
149 case TRACK_BOTH:
150 hash += HashAddress(&e->addr2);
151 /* fallthrough */
152 case TRACK_SRC:
153 case TRACK_DST:
154 hash += HashAddress(&e->addr);
155 break;
156 }
157 return hash;
158}
159
160static bool ThresholdEntryCompare(void *a, void *b)
161{
162 const ThresholdEntry *e1 = a;
163 const ThresholdEntry *e2 = b;
164 SCLogDebug("sid1: %u sid2: %u", e1->key[SID], e2->key[SID]);
165
166 if (memcmp(e1->key, e2->key, sizeof(e1->key)) != 0)
167 return false;
168 switch (e1->key[TRACK]) {
169 case TRACK_BOTH:
170 if (!(CompareAddress(&e1->addr2, &e2->addr2)))
171 return false;
172 /* fallthrough */
173 case TRACK_SRC:
174 case TRACK_DST:
175 if (!(CompareAddress(&e1->addr, &e2->addr)))
176 return false;
177 break;
178 }
179 return true;
180}
181
182static bool ThresholdEntryExpire(void *data, const SCTime_t ts)
183{
184 const ThresholdEntry *e = data;
185 const SCTime_t entry = SCTIME_ADD_SECS(e->tv1, e->seconds);
186 if (SCTIME_CMP_GT(ts, entry)) {
187 return true;
188 }
189 return false;
190}
191
192static int ThresholdsInit(struct Thresholds *t)
193{
194 uint32_t hashsize = 16384;
195 uint64_t memcap = 16 * 1024 * 1024;
196
197 const char *str;
198 if (SCConfGet("detect.thresholds.memcap", &str) == 1) {
199 if (ParseSizeStringU64(str, &memcap) < 0) {
200 SCLogError("Error parsing detect.thresholds.memcap from conf file - %s", str);
201 return -1;
202 }
203 }
204
205 intmax_t value = 0;
206 if ((SCConfGetInt("detect.thresholds.hash-size", &value)) == 1) {
207 if (value < 256 || value > INT_MAX) {
208 SCLogError("'detect.thresholds.hash-size' value %" PRIiMAX
209 " out of range. Valid range 256-2147483647.",
210 value);
211 return -1;
212 }
213 hashsize = (uint32_t)value;
214 }
215
216 t->thash = THashInit("thresholds", sizeof(ThresholdEntry), ThresholdEntrySet,
217 ThresholdEntryFree, ThresholdEntryHash, ThresholdEntryCompare, ThresholdEntryExpire,
218 NULL, 0, memcap, hashsize);
219 if (t->thash == NULL) {
220 SCLogError("failed to initialize thresholds hash table");
221 return -1;
222 }
223 return 0;
224}
225
226static void ThresholdsDestroy(struct Thresholds *t)
227{
228 if (t->thash) {
230 }
231}
232
234{
235 return THashExpire(ctx.thash, ts);
236}
237
238#define TC_ADDRESS 0
239#define TC_SID 1
240#define TC_GID 2
241#define TC_REV 3
242#define TC_TENANT 4
243
244typedef struct ThresholdCacheItem {
245 int8_t track; // by_src/by_dst
246 int8_t ipv;
247 int8_t retval;
248 uint32_t key[5];
252
253static thread_local HashTable *threshold_cache_ht = NULL;
254
255thread_local uint64_t cache_lookup_cnt = 0;
256thread_local uint64_t cache_lookup_notinit = 0;
257thread_local uint64_t cache_lookup_nosupport = 0;
258thread_local uint64_t cache_lookup_miss_expired = 0;
259thread_local uint64_t cache_lookup_miss = 0;
260thread_local uint64_t cache_lookup_hit = 0;
261thread_local uint64_t cache_housekeeping_check = 0;
262thread_local uint64_t cache_housekeeping_expired = 0;
263
264static void DumpCacheStats(void)
265{
266 SCLogPerf("threshold thread cache stats: cnt:%" PRIu64 " notinit:%" PRIu64 " nosupport:%" PRIu64
267 " miss_expired:%" PRIu64 " miss:%" PRIu64 " hit:%" PRIu64
268 ", housekeeping: checks:%" PRIu64 ", expired:%" PRIu64,
272}
273
274/* rbtree for expiry handling */
275
276static int ThresholdCacheTreeCompareFunc(ThresholdCacheItem *a, ThresholdCacheItem *b)
277{
279 return 1;
280 } else {
281 return -1;
282 }
283}
284
285RB_HEAD(THRESHOLD_CACHE, ThresholdCacheItem);
286RB_PROTOTYPE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
287RB_GENERATE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
288thread_local struct THRESHOLD_CACHE threshold_cache_tree;
289thread_local uint64_t threshold_cache_housekeeping_ts = 0;
290
291static void ThresholdCacheExpire(SCTime_t now)
292{
293 ThresholdCacheItem *iter, *safe = NULL;
294 int cnt = 0;
296
297 RB_FOREACH_SAFE (iter, THRESHOLD_CACHE, &threshold_cache_tree, safe) {
299
300 if (SCTIME_CMP_LT(iter->expires_at, now)) {
301 THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, iter);
302 HashTableRemove(threshold_cache_ht, iter, 0);
303 SCLogDebug("iter %p expired", iter);
305 }
306
307 if (++cnt > 1)
308 break;
309 }
310}
311
312/* hash table for threshold look ups */
313
314static uint32_t ThresholdCacheHashFunc(HashTable *ht, void *data, uint16_t datalen)
315{
316 ThresholdCacheItem *e = data;
317 uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), 0) * (e->ipv + e->track);
318 hash = hash % ht->array_size;
319 return hash;
320}
321
322static char ThresholdCacheHashCompareFunc(
323 void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
324{
325 ThresholdCacheItem *tci1 = data1;
326 ThresholdCacheItem *tci2 = data2;
327 return tci1->ipv == tci2->ipv && tci1->track == tci2->track &&
328 memcmp(tci1->key, tci2->key, sizeof(tci1->key)) == 0;
329}
330
331static void ThresholdCacheHashFreeFunc(void *data)
332{
333 SCFree(data);
334}
335
336/// \brief Thread local cache
337static int SetupCache(const Packet *p, const int8_t track, const int8_t retval, const uint32_t sid,
338 const uint32_t gid, const uint32_t rev, SCTime_t expires)
339{
340 if (!threshold_cache_ht) {
341 threshold_cache_ht = HashTableInit(256, ThresholdCacheHashFunc,
342 ThresholdCacheHashCompareFunc, ThresholdCacheHashFreeFunc);
343 }
344
345 uint32_t addr;
346 if (track == TRACK_SRC) {
347 addr = p->src.addr_data32[0];
348 } else if (track == TRACK_DST) {
349 addr = p->dst.addr_data32[0];
350 } else {
351 return -1;
352 }
353
354 ThresholdCacheItem lookup = {
355 .track = track,
356 .ipv = 4,
357 .retval = retval,
358 .key[TC_ADDRESS] = addr,
359 .key[TC_SID] = sid,
360 .key[TC_GID] = gid,
361 .key[TC_REV] = rev,
362 .key[TC_TENANT] = p->tenant_id,
363 .expires_at = expires,
364 };
365 ThresholdCacheItem *found = HashTableLookup(threshold_cache_ht, &lookup, 0);
366 if (!found) {
367 ThresholdCacheItem *n = SCCalloc(1, sizeof(*n));
368 if (n) {
369 n->track = track;
370 n->ipv = 4;
371 n->retval = retval;
372 n->key[TC_ADDRESS] = addr;
373 n->key[TC_SID] = sid;
374 n->key[TC_GID] = gid;
375 n->key[TC_REV] = rev;
376 n->key[TC_TENANT] = p->tenant_id;
377 n->expires_at = expires;
378
379 if (HashTableAdd(threshold_cache_ht, n, 0) == 0) {
380 ThresholdCacheItem *r = THRESHOLD_CACHE_RB_INSERT(&threshold_cache_tree, n);
381 DEBUG_VALIDATE_BUG_ON(r != NULL); // duplicate; should be impossible
382 (void)r; // only used by DEBUG_VALIDATE_BUG_ON
383 return 1;
384 }
385 SCFree(n);
386 }
387 return -1;
388 } else {
389 found->expires_at = expires;
390 found->retval = retval;
391
392 THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, found);
393 THRESHOLD_CACHE_RB_INSERT(&threshold_cache_tree, found);
394 return 1;
395 }
396}
397
398/** \brief Check Thread local thresholding cache
399 * \note only supports IPv4
400 * \retval -1 cache miss - not found
401 * \retval -2 cache miss - found but expired
402 * \retval -3 error - cache not initialized
403 * \retval -4 error - unsupported tracker
404 * \retval ret cached return code
405 */
406static int CheckCache(const Packet *p, const int8_t track, const uint32_t sid, const uint32_t gid,
407 const uint32_t rev)
408{
410
411 if (!threshold_cache_ht) {
413 return -3; // error cache initialized
414 }
415
416 uint32_t addr;
417 if (track == TRACK_SRC) {
418 addr = p->src.addr_data32[0];
419 } else if (track == TRACK_DST) {
420 addr = p->dst.addr_data32[0];
421 } else {
423 return -4; // error tracker not unsupported
424 }
425
427 ThresholdCacheExpire(p->ts);
428 }
429
430 ThresholdCacheItem lookup = {
431 .track = track,
432 .ipv = 4,
433 .key[TC_ADDRESS] = addr,
434 .key[TC_SID] = sid,
435 .key[TC_GID] = gid,
436 .key[TC_REV] = rev,
437 .key[TC_TENANT] = p->tenant_id,
438 };
439 ThresholdCacheItem *found = HashTableLookup(threshold_cache_ht, &lookup, 0);
440 if (found) {
441 if (SCTIME_CMP_GT(p->ts, found->expires_at)) {
442 THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, found);
443 HashTableRemove(threshold_cache_ht, found, 0);
445 return -2; // cache miss - found but expired
446 }
448 return found->retval;
449 }
451 return -1; // cache miss - not found
452}
453
455{
456 if (threshold_cache_ht) {
457 HashTableFree(threshold_cache_ht);
458 threshold_cache_ht = NULL;
459 }
461 DumpCacheStats();
462}
463
464/**
465 * \brief Return next DetectThresholdData for signature
466 *
467 * \param sig Signature pointer
468 * \param psm Pointer to a Signature Match pointer
469 * \param list List to return data from
470 *
471 * \retval tsh Return the threshold data from signature or NULL if not found
472 */
474 const Signature *sig, const SigMatchData **psm, int list)
475{
476 const SigMatchData *smd = NULL;
477 const DetectThresholdData *tsh = NULL;
478
479 if (sig == NULL)
480 return NULL;
481
482 if (*psm == NULL) {
483 smd = sig->sm_arrays[list];
484 } else {
485 /* Iteration in progress, using provided value */
486 smd = *psm;
487 }
488
489 while (1) {
490 if (smd->type == DETECT_THRESHOLD || smd->type == DETECT_DETECTION_FILTER) {
491 tsh = (DetectThresholdData *)smd->ctx;
492
493 if (smd->is_last) {
494 *psm = NULL;
495 } else {
496 *psm = smd + 1;
497 }
498 return tsh;
499 }
500
501 if (smd->is_last) {
502 break;
503 }
504 smd++;
505 }
506 *psm = NULL;
507 return NULL;
508}
509
514
515static void FlowThresholdEntryListFree(FlowThresholdEntryList *list)
516{
517 for (FlowThresholdEntryList *i = list; i != NULL;) {
519 SCFree(i);
520 i = next;
521 }
522}
523
524/** struct for storing per flow thresholds. This will be stored in the Flow::flowvar list, so it
525 * needs to follow the GenericVar header format. */
532
533void FlowThresholdVarFree(void *ptr)
534{
535 FlowVarThreshold *t = ptr;
536 FlowThresholdEntryListFree(t->thresholds);
537 SCFree(t);
538}
539
540static FlowVarThreshold *FlowThresholdVarGet(Flow *f)
541{
542 if (f == NULL)
543 return NULL;
544
545 for (GenericVar *gv = f->flowvar; gv != NULL; gv = gv->next) {
546 if (gv->type == DETECT_THRESHOLD)
547 return (FlowVarThreshold *)gv;
548 }
549
550 return NULL;
551}
552
553static ThresholdEntry *ThresholdFlowLookupEntry(
554 Flow *f, uint32_t sid, uint32_t gid, uint32_t rev, uint32_t tenant_id)
555{
556 FlowVarThreshold *t = FlowThresholdVarGet(f);
557 if (t == NULL)
558 return NULL;
559
560 for (FlowThresholdEntryList *e = t->thresholds; e != NULL; e = e->next) {
561 if (e->threshold.key[SID] == sid && e->threshold.key[GID] == gid &&
562 e->threshold.key[REV] == rev && e->threshold.key[TENANT] == tenant_id) {
563 return &e->threshold;
564 }
565 }
566 return NULL;
567}
568
569static int AddEntryToFlow(Flow *f, FlowThresholdEntryList *e, SCTime_t packet_time)
570{
571 DEBUG_VALIDATE_BUG_ON(e == NULL);
572
573 FlowVarThreshold *t = FlowThresholdVarGet(f);
574 if (t == NULL) {
575 t = SCCalloc(1, sizeof(*t));
576 if (t == NULL) {
577 return -1;
578 }
581 }
582
583 e->next = t->thresholds;
584 t->thresholds = e;
585 return 0;
586}
587
588static int ThresholdHandlePacketSuppress(Packet *p,
589 const DetectThresholdData *td, uint32_t sid, uint32_t gid)
590{
591 int ret = 0;
592 DetectAddress *m = NULL;
593 switch (td->track) {
594 case TRACK_DST:
596 SCLogDebug("TRACK_DST");
597 break;
598 case TRACK_SRC:
600 SCLogDebug("TRACK_SRC");
601 break;
602 /* suppress if either src or dst is a match on the suppress
603 * address list */
604 case TRACK_EITHER:
606 if (m == NULL) {
608 }
609 break;
610 case TRACK_RULE:
611 case TRACK_FLOW:
612 default:
613 SCLogError("track mode %d is not supported", td->track);
614 break;
615 }
616 if (m == NULL)
617 ret = 1;
618 else
619 ret = 2; /* suppressed but still need actions */
620
621 return ret;
622}
623
624static inline void RateFilterSetAction(PacketAlert *pa, uint8_t new_action)
625{
626 switch (new_action) {
627 case TH_ACTION_ALERT:
629 pa->action = ACTION_ALERT;
630 break;
631 case TH_ACTION_DROP:
633 pa->action = ACTION_DROP;
634 break;
635 case TH_ACTION_REJECT:
638 break;
639 case TH_ACTION_PASS:
641 pa->action = ACTION_PASS;
642 break;
643 default:
644 /* Weird, leave the default action */
645 break;
646 }
647}
648
649/** \internal
650 * \brief Apply the multiplier and return the new value.
651 * If it would overflow the uint32_t we return UINT32_MAX.
652 */
653static uint32_t BackoffCalcNextValue(const uint32_t cur, const uint32_t m)
654{
655 /* goal is to see if cur * m would overflow uint32_t */
656 if (unlikely(UINT32_MAX / m < cur)) {
657 return UINT32_MAX;
658 }
659 return cur * m;
660}
661
662/**
663 * \retval 2 silent match (no alert but apply actions)
664 * \retval 1 normal match
665 * \retval 0 no match
666 */
667static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
668 const SCTime_t packet_time, const uint32_t sid, const uint32_t gid, const uint32_t rev,
669 const uint32_t tenant_id)
670{
671 te->key[SID] = sid;
672 te->key[GID] = gid;
673 te->key[REV] = rev;
674 te->key[TRACK] = td->track;
675 te->key[TENANT] = tenant_id;
676
677 te->seconds = td->seconds;
678 te->current_count = 1;
679
680 switch (td->type) {
681 case TYPE_BACKOFF:
682 te->backoff.next_value = td->count;
683 break;
684 default:
685 te->tv1 = packet_time;
687 break;
688 }
689
690 switch (td->type) {
691 case TYPE_LIMIT:
692 case TYPE_RATE:
693 return 1;
694 case TYPE_THRESHOLD:
695 case TYPE_BOTH:
696 if (td->count == 1)
697 return 1;
698 return 0;
699 case TYPE_BACKOFF:
700 if (td->count == 1) {
701 te->backoff.next_value =
702 BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
703 return 1;
704 }
705 return 0;
706 case TYPE_DETECTION:
707 return 0;
708 }
709 return 0;
710}
711
712static int ThresholdCheckUpdate(const DetectEngineCtx *de_ctx, const DetectThresholdData *td,
713 ThresholdEntry *te,
714 const Packet *p, // ts only? - cache too
715 const uint32_t sid, const uint32_t gid, const uint32_t rev, PacketAlert *pa)
716{
717 int ret = 0;
718 const SCTime_t packet_time = p->ts;
719 const SCTime_t entry = SCTIME_ADD_SECS(te->tv1, td->seconds);
720 switch (td->type) {
721 case TYPE_LIMIT:
722 SCLogDebug("limit");
723
724 if (SCTIME_CMP_LTE(p->ts, entry)) {
725 te->current_count++;
726
727 if (te->current_count <= td->count) {
728 ret = 1;
729 } else {
730 ret = 2;
731
732 if (PacketIsIPv4(p)) {
733 SetupCache(p, td->track, (int8_t)ret, sid, gid, rev, entry);
734 }
735 }
736 } else {
737 /* entry expired, reset */
738 te->tv1 = p->ts;
739 te->current_count = 1;
740 ret = 1;
741 }
742 break;
743 case TYPE_THRESHOLD:
744 if (SCTIME_CMP_LTE(p->ts, entry)) {
745 te->current_count++;
746
747 if (te->current_count >= td->count) {
748 ret = 1;
749 te->current_count = 0;
750 }
751 } else {
752 te->tv1 = p->ts;
753 te->current_count = 1;
754 }
755 break;
756 case TYPE_BOTH:
757 if (SCTIME_CMP_LTE(p->ts, entry)) {
758 /* within time limit */
759
760 te->current_count++;
761 if (te->current_count == td->count) {
762 ret = 1;
763 } else if (te->current_count > td->count) {
764 /* silent match */
765 ret = 2;
766
767 if (PacketIsIPv4(p)) {
768 SetupCache(p, td->track, (int8_t)ret, sid, gid, rev, entry);
769 }
770 }
771 } else {
772 /* expired, so reset */
773 te->tv1 = p->ts;
774 te->current_count = 1;
775
776 /* if we have a limit of 1, this is a match */
777 if (te->current_count == td->count) {
778 ret = 1;
779 }
780 }
781 break;
782 case TYPE_DETECTION:
783 SCLogDebug("detection_filter");
784
785 if (SCTIME_CMP_LTE(p->ts, entry)) {
786 /* within timeout */
787 te->current_count++;
788 if (te->current_count > td->count) {
789 ret = 1;
790 }
791 } else {
792 /* expired, reset */
793 te->tv1 = p->ts;
794 te->current_count = 1;
795 }
796 break;
797 case TYPE_RATE: {
798 SCLogDebug("rate_filter");
799 const uint8_t original_action = pa->action;
800 ret = 1;
801 /* Check if we have a timeout enabled, if so,
802 * we still matching (and enabling the new_action) */
804 if ((SCTIME_SECS(packet_time) - SCTIME_SECS(te->tv_timeout)) > td->timeout) {
805 /* Ok, we are done, timeout reached */
807 } else {
808 /* Already matching */
809 RateFilterSetAction(pa, td->new_action);
810 }
811 } else {
812 /* Update the matching state with the timeout interval */
813 if (SCTIME_CMP_LTE(packet_time, entry)) {
814 te->current_count++;
815 if (te->current_count > td->count) {
816 /* Then we must enable the new action by setting a
817 * timeout */
818 te->tv_timeout = packet_time;
819 RateFilterSetAction(pa, td->new_action);
820 }
821 } else {
822 te->tv1 = packet_time;
823 te->current_count = 1;
824 }
825 }
826 if (de_ctx->RateFilterCallback && original_action != pa->action) {
827 pa->action = de_ctx->RateFilterCallback(p, sid, gid, rev, original_action,
829 if (pa->action == original_action) {
830 /* Reset back to original action, clear modified flag. */
831 pa->flags &= ~PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
832 }
833 }
834 break;
835 }
836 case TYPE_BACKOFF:
837 SCLogDebug("backoff");
838
839 if (te->current_count < UINT32_MAX) {
840 te->current_count++;
841 if (te->backoff.next_value == te->current_count) {
842 te->backoff.next_value =
843 BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
844 SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
845 ret = 1;
846 } else {
847 ret = 2;
848 }
849 } else {
850 /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
851 ret = 2;
852 }
853 break;
854 }
855 return ret;
856}
857
858static int ThresholdGetFromHash(const DetectEngineCtx *de_ctx, struct Thresholds *tctx,
859 const Packet *p, const Signature *s, const DetectThresholdData *td, PacketAlert *pa)
860{
861 /* fast track for count 1 threshold */
862 if (td->count == 1 && td->type == TYPE_THRESHOLD) {
863 return 1;
864 }
865
866 ThresholdEntry lookup;
867 memset(&lookup, 0, sizeof(lookup));
868 lookup.key[SID] = s->id;
869 lookup.key[GID] = s->gid;
870 lookup.key[REV] = s->rev;
871 lookup.key[TRACK] = td->track;
872 lookup.key[TENANT] = p->tenant_id;
873 if (td->track == TRACK_SRC) {
874 COPY_ADDRESS(&p->src, &lookup.addr);
875 } else if (td->track == TRACK_DST) {
876 COPY_ADDRESS(&p->dst, &lookup.addr);
877 } else if (td->track == TRACK_BOTH) {
878 /* make sure lower ip address is first */
879 if (PacketIsIPv4(p)) {
880 if (SCNtohl(p->src.addr_data32[0]) < SCNtohl(p->dst.addr_data32[0])) {
881 COPY_ADDRESS(&p->src, &lookup.addr);
882 COPY_ADDRESS(&p->dst, &lookup.addr2);
883 } else {
884 COPY_ADDRESS(&p->dst, &lookup.addr);
885 COPY_ADDRESS(&p->src, &lookup.addr2);
886 }
887 } else {
888 if (AddressIPv6Lt(&p->src, &p->dst)) {
889 COPY_ADDRESS(&p->src, &lookup.addr);
890 COPY_ADDRESS(&p->dst, &lookup.addr2);
891 } else {
892 COPY_ADDRESS(&p->dst, &lookup.addr);
893 COPY_ADDRESS(&p->src, &lookup.addr2);
894 }
895 }
896 }
897
898 struct THashDataGetResult res = THashGetFromHash(tctx->thash, &lookup);
899 if (res.data) {
900 SCLogDebug("found %p, is_new %s", res.data, BOOL2STR(res.is_new));
901 int r;
902 ThresholdEntry *te = res.data->data;
903 if (res.is_new) {
904 // new threshold, set up
905 r = ThresholdSetup(td, te, p->ts, s->id, s->gid, s->rev, p->tenant_id);
906 } else {
907 // existing, check/update
908 r = ThresholdCheckUpdate(de_ctx, td, te, p, s->id, s->gid, s->rev, pa);
909 }
910
911 (void)THashDecrUsecnt(res.data);
912 THashDataUnlock(res.data);
913 return r;
914 }
915 return 0; // TODO error?
916}
917
918/**
919 * \retval 2 silent match (no alert but apply actions)
920 * \retval 1 normal match
921 * \retval 0 no match
922 */
923static int ThresholdHandlePacketFlow(const DetectEngineCtx *de_ctx, Flow *f, Packet *p,
924 const DetectThresholdData *td, uint32_t sid, uint32_t gid, uint32_t rev, PacketAlert *pa)
925{
926 int ret = 0;
927 ThresholdEntry *found = ThresholdFlowLookupEntry(f, sid, gid, rev, p->tenant_id);
928 SCLogDebug("found %p sid %u gid %u rev %u", found, sid, gid, rev);
929
930 if (found == NULL) {
931 FlowThresholdEntryList *new = SCCalloc(1, sizeof(*new));
932 if (new == NULL)
933 return 0;
934
935 // new threshold, set up
936 ret = ThresholdSetup(td, &new->threshold, p->ts, sid, gid, rev, p->tenant_id);
937
938 if (AddEntryToFlow(f, new, p->ts) == -1) {
939 SCFree(new);
940 return 0;
941 }
942 } else {
943 // existing, check/update
944 ret = ThresholdCheckUpdate(de_ctx, td, found, p, sid, gid, rev, pa);
945 }
946 return ret;
947}
948
949/**
950 * \brief Make the threshold logic for signatures
951 *
952 * \param de_ctx Detection Context
953 * \param tsh_ptr Threshold element
954 * \param p Packet structure
955 * \param s Signature structure
956 *
957 * \retval 2 silent match (no alert but apply actions)
958 * \retval 1 alert on this event
959 * \retval 0 do not alert on this event
960 */
962 const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa)
963{
964 SCEnter();
965
966 int ret = 0;
967 if (td == NULL) {
968 SCReturnInt(0);
969 }
970
971 if (td->type == TYPE_SUPPRESS) {
972 ret = ThresholdHandlePacketSuppress(p,td,s->id,s->gid);
973 } else if (td->track == TRACK_SRC) {
974 if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
975 int cache_ret = CheckCache(p, td->track, s->id, s->gid, s->rev);
976 if (cache_ret >= 0) {
977 SCReturnInt(cache_ret);
978 }
979 }
980
981 ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
982 } else if (td->track == TRACK_DST) {
983 if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
984 int cache_ret = CheckCache(p, td->track, s->id, s->gid, s->rev);
985 if (cache_ret >= 0) {
986 SCReturnInt(cache_ret);
987 }
988 }
989
990 ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
991 } else if (td->track == TRACK_BOTH) {
992 ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
993 } else if (td->track == TRACK_RULE) {
994 ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
995 } else if (td->track == TRACK_FLOW) {
996 if (p->flow) {
997 ret = ThresholdHandlePacketFlow(de_ctx, p->flow, p, td, s->id, s->gid, s->rev, pa);
998 }
999 }
1000
1001 SCReturnInt(ret);
1002}
1003
1004/**
1005 * @}
1006 */
#define ACTION_REJECT
#define ACTION_PASS
#define ACTION_ALERT
#define ACTION_DROP
uint16_t dst
uint16_t src
struct HtpBodyChunk_ * next
int SCConfGetInt(const char *name, intmax_t *val)
Retrieve a configuration value as an integer.
Definition conf.c:414
int SCConfGet(const char *name, const char **vptr)
Retrieve the value of a configuration node.
Definition conf.c:350
#define CMP_ADDR(a1, a2)
Definition decode.h:222
#define COPY_ADDRESS(a, b)
Definition decode.h:127
#define TRACK_SRC
#define TRACK_DST
int AddressIPv6Lt(const Address *a, const Address *b)
Compares 2 ipv6 addresses and returns if the first address(a) is less than the second address(b) or n...
DetectAddress * DetectAddressLookupInHead(const DetectAddressHead *gh, Address *a)
Find the group matching address in a group head.
@ DETECT_THRESHOLD
@ DETECT_DETECTION_FILTER
#define TYPE_SUPPRESS
#define TH_ACTION_REJECT
#define TYPE_BOTH
#define TYPE_LIMIT
#define TYPE_DETECTION
#define TH_ACTION_PASS
#define TH_ACTION_ALERT
#define TRACK_RULE
#define TYPE_RATE
#define TRACK_FLOW
#define TRACK_BOTH
#define TYPE_BACKOFF
#define TH_ACTION_DROP
#define TYPE_THRESHOLD
#define TRACK_EITHER
SCMutex m
Definition flow-hash.h:6
DetectEngineCtx * de_ctx
#define PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED
Definition decode.h:274
const DetectThresholdData * SigGetThresholdTypeIter(const Signature *sig, const SigMatchData **psm, int list)
Return next DetectThresholdData for signature.
#define TC_REV
thread_local uint64_t cache_lookup_cnt
#define REV
#define TC_ADDRESS
thread_local uint64_t cache_lookup_miss
#define TRACK
thread_local struct THRESHOLD_CACHE threshold_cache_tree
void ThresholdInit(void)
#define GID
int PacketAlertThreshold(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa)
Make the threshold logic for signatures.
thread_local uint64_t cache_lookup_nosupport
thread_local uint64_t cache_lookup_miss_expired
#define TC_TENANT
void ThresholdDestroy(void)
uint32_t ThresholdsExpire(const SCTime_t ts)
#define SID
#define TC_SID
struct FlowVarThreshold_ FlowVarThreshold
thread_local uint64_t cache_housekeeping_expired
thread_local uint64_t cache_lookup_hit
#define TENANT
#define TC_GID
struct ThresholdEntry_ ThresholdEntry
thread_local uint64_t cache_housekeeping_check
thread_local uint64_t cache_lookup_notinit
struct Thresholds ctx
struct FlowThresholdEntryList_ FlowThresholdEntryList
void ThresholdCacheThreadFree(void)
void FlowThresholdVarFree(void *ptr)
thread_local uint64_t threshold_cache_housekeeping_ts
uint64_t ts
char family
Definition decode.h:113
address structure for use in the detection engine.
Definition detect.h:168
main detection engine ctx
Definition detect.h:932
void * rate_filter_callback_arg
Definition detect.h:1150
SCDetectRateFilterFunc RateFilterCallback
Definition detect.h:1147
DetectAddressHead addrs
struct FlowThresholdEntryList_ * next
struct GenericVar_ * next
FlowThresholdEntryList * thresholds
Flow data structure.
Definition flow.h:356
GenericVar * flowvar
Definition flow.h:489
uint32_t array_size
Definition util-hash.h:37
struct HtpBodyChunk_ * next
uint8_t flags
Definition decode.h:251
uint8_t action
Definition decode.h:250
uint32_t tenant_id
Definition decode.h:665
SCTime_t ts
Definition decode.h:555
Address src
Definition decode.h:505
struct Flow_ * flow
Definition decode.h:546
Address dst
Definition decode.h:506
Data needed for Match()
Definition detect.h:365
bool is_last
Definition detect.h:367
SigMatchCtx * ctx
Definition detect.h:368
uint16_t type
Definition detect.h:366
Signature container.
Definition detect.h:668
uint32_t rev
Definition detect.h:715
uint32_t id
Definition detect.h:713
SigMatchData * sm_arrays[DETECT_SM_LIST_MAX]
Definition detect.h:731
uint32_t gid
Definition detect.h:714
THashData * data
Definition util-thash.h:192
void * data
Definition util-thash.h:92
struct ThresholdEntry_::@69::@71 backoff
THashTableContext * thash
#define str(s)
#define SCNtohl(x)
uint32_t cnt
#define RB_PROTOTYPE(name, type, field, cmp)
Definition tree.h:385
#define RB_HEAD(name, type)
Definition tree.h:300
#define RB_ENTRY(type)
Definition tree.h:314
#define RB_FOREACH_SAFE(x, name, head, y)
Definition tree.h:791
#define RB_INIT(root)
Definition tree.h:308
#define RB_GENERATE(name, type, field, cmp)
Definition tree.h:421
#define SCEnter(...)
Definition util-debug.h:277
#define BOOL2STR(b)
Definition util-debug.h:535
#define SCLogPerf(...)
Definition util-debug.h:234
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnInt(x)
Definition util-debug.h:281
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
uint32_t hashword(const uint32_t *k, size_t length, uint32_t initval)
#define hashsize(n)
int HashTableRemove(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:142
int HashTableAdd(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:104
HashTable * HashTableInit(uint32_t size, uint32_t(*Hash)(struct HashTable_ *, void *, uint16_t), char(*Compare)(void *, uint16_t, void *, uint16_t), void(*Free)(void *))
Definition util-hash.c:35
void HashTableFree(HashTable *ht)
Definition util-hash.c:78
void * HashTableLookup(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:183
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
int ParseSizeStringU64(const char *size, uint64_t *res)
Definition util-misc.c:190
#define unlikely(expr)
uint32_t THashExpire(THashTableContext *ctx, const SCTime_t ts)
expire data from the hash Walk the hash table and remove data that is exprired according to the DataE...
Definition util-thash.c:423
THashTableContext * THashInit(const char *cnf_prefix, uint32_t data_size, int(*DataSet)(void *, void *), void(*DataFree)(void *), uint32_t(*DataHash)(uint32_t, void *), bool(*DataCompare)(void *, void *), bool(*DataExpired)(void *, SCTime_t), uint32_t(*DataSize)(void *), bool reset_memcap, uint64_t memcap, uint32_t hashsize)
Definition util-thash.c:302
struct THashDataGetResult THashGetFromHash(THashTableContext *ctx, void *data)
Definition util-thash.c:618
void THashShutdown(THashTableContext *ctx)
shutdown the flow engine
Definition util-thash.c:354
#define THashDecrUsecnt(h)
Definition util-thash.h:170
#define SCTIME_CMP_NEQ(a, b)
Definition util-time.h:107
#define SCTIME_CMP_GT(a, b)
Definition util-time.h:104
#define SCTIME_CMP_LTE(a, b)
Definition util-time.h:106
#define SCTIME_SECS(t)
Definition util-time.h:57
#define SCTIME_INITIALIZER
Definition util-time.h:51
#define SCTIME_CMP_LT(a, b)
Definition util-time.h:105
#define SCTIME_CMP_GTE(a, b)
Definition util-time.h:103
#define SCTIME_ADD_SECS(ts, s)
Definition util-time.h:64
#define DEBUG_VALIDATE_BUG_ON(exp)
void GenericVarAppend(GenericVar **list, GenericVar *gv)
Definition util-var.c:98