• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 22712336922

05 Mar 2026 09:55AM UTC coverage: 66.796% (-12.5%) from 79.283%
22712336922

Pull #14946

github

web-flow
Merge 91559149c into 7e97dfd52
Pull Request #14946: Stack 8001 v15

14 of 19 new or added lines in 7 files covered. (73.68%)

11298 existing lines in 298 files now uncovered.

155282 of 232473 relevant lines covered (66.8%)

5923441.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.72
/src/detect-engine-threshold.c
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"
44
#include "detect-engine-threshold.h"
45
#include "detect-engine-address.h"
46
#include "detect-engine-address-ipv6.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
#include "counters.h"
59

60
static SC_ATOMIC_DECLARE(uint64_t, threshold_bitmap_alloc_fail);
61
static SC_ATOMIC_DECLARE(uint64_t, threshold_bitmap_memuse);
62

63
/* UNITTESTS-only test seam to force allocation failure and query counters */
64
#ifdef UNITTESTS
65
void ThresholdForceAllocFail(int v);
66
uint64_t ThresholdGetBitmapMemuse(void);
67
uint64_t ThresholdGetBitmapAllocFail(void);
68

69
static int g_threshold_force_alloc_fail = 0;
70

71
void ThresholdForceAllocFail(int v)
72
{
73
    g_threshold_force_alloc_fail = v;
74
}
75

76
uint64_t ThresholdGetBitmapMemuse(void)
77
{
78
    return SC_ATOMIC_GET(threshold_bitmap_memuse);
79
}
80

81
uint64_t ThresholdGetBitmapAllocFail(void)
82
{
83
    return SC_ATOMIC_GET(threshold_bitmap_alloc_fail);
84
}
85
#endif
86

87
/* bitmap settings for exact distinct counting of 16-bit ports */
88
#define DF_PORT_BITMAP_SIZE (65536u / 8u)
8✔
89
#define DF_PORT_BYTE_IDX(p) ((uint32_t)((p) >> 3))
10✔
90
#define DF_PORT_BIT_MASK(p) ((uint8_t)(1u << ((p)&7u)))
10✔
91

92
struct Thresholds {
93
    THashTableContext *thash;
94
} ctx;
95

96
static int ThresholdsInit(struct Thresholds *t);
97
static void ThresholdsDestroy(struct Thresholds *t);
98

99
static uint64_t ThresholdBitmapAllocFailCounter(void)
100
{
2,950✔
101
    return SC_ATOMIC_GET(threshold_bitmap_alloc_fail);
2,950✔
102
}
2,950✔
103

104
static uint64_t ThresholdBitmapMemuseCounter(void)
105
{
2,950✔
106
    return SC_ATOMIC_GET(threshold_bitmap_memuse);
2,950✔
107
}
2,950✔
108

109
static uint64_t ThresholdMemuseCounter(void)
110
{
2,950✔
111
    if (ctx.thash == NULL)
2,950✔
112
        return 0;
×
113
    return SC_ATOMIC_GET(ctx.thash->memuse);
2,950✔
114
}
2,950✔
115

116
static uint64_t ThresholdMemcapCounter(void)
117
{
2,950✔
118
    if (ctx.thash == NULL)
2,950✔
119
        return 0;
×
120
    return SC_ATOMIC_GET(ctx.thash->config.memcap);
2,950✔
121
}
2,950✔
122

123
void ThresholdInit(void)
124
{
2,178✔
125
    SC_ATOMIC_INIT(threshold_bitmap_alloc_fail);
2,178✔
126
    SC_ATOMIC_INIT(threshold_bitmap_memuse);
2,178✔
127
    ThresholdsInit(&ctx);
2,178✔
128
}
2,178✔
129

130
void ThresholdRegisterGlobalCounters(void)
131
{
3,554✔
132
    StatsRegisterGlobalCounter("detect.thresholds.memuse", ThresholdMemuseCounter);
3,554✔
133
    StatsRegisterGlobalCounter("detect.thresholds.memcap", ThresholdMemcapCounter);
3,554✔
134
    StatsRegisterGlobalCounter("detect.thresholds.bitmap_memuse", ThresholdBitmapMemuseCounter);
3,554✔
135
    StatsRegisterGlobalCounter(
3,554✔
136
            "detect.thresholds.bitmap_alloc_fail", ThresholdBitmapAllocFailCounter);
3,554✔
137
}
3,554✔
138

139
void ThresholdDestroy(void)
140
{
2,104✔
141
    ThresholdsDestroy(&ctx);
2,104✔
142
}
2,104✔
143

144
#define SID    0
51,291✔
145
#define GID    1
25,977✔
146
#define REV    2
25,978✔
147
#define TRACK  3
2,109✔
148
#define TENANT 4
25,978✔
149

150
typedef struct ThresholdEntry_ {
151
    uint32_t key[5];
152

153
    SCTime_t tv_timeout;    /**< Timeout for new_action (for rate_filter)
154
                                 its not "seconds", that define the time interval */
155
    uint32_t seconds;       /**< Event seconds */
156
    uint32_t current_count; /**< Var for count control */
157

158
    union {
159
        struct {
160
            uint32_t next_value;
161
        } backoff;
162
        struct {
163
            SCTime_t tv1;  /**< Var for time control */
164
            Address addr;  /* used for src/dst/either tracking */
165
            Address addr2; /* used for both tracking */
166
            /* distinct counting state (for detection_filter unique_on ports) */
167
            uint8_t *distinct_bitmap_union; /* 8192 bytes (65536 bits) */
168
        };
169
    };
170

171
} ThresholdEntry;
172

173
static int ThresholdEntrySet(void *dst, void *src)
174
{
116✔
175
    const ThresholdEntry *esrc = src;
116✔
176
    ThresholdEntry *edst = dst;
116✔
177
    memset(edst, 0, sizeof(*edst));
116✔
178
    *edst = *esrc;
116✔
179
    return 0;
116✔
180
}
116✔
181

182
static void ThresholdDistinctInit(ThresholdEntry *te, const DetectThresholdData *td)
183
{
131✔
184
    if (td->type != TYPE_DETECTION || td->unique_on == DF_UNIQUE_NONE) {
131✔
185
        return;
127✔
186
    }
127✔
187
    DEBUG_VALIDATE_BUG_ON(td->seconds == 0);
4✔
188

189
    const uint32_t bitmap_size = DF_PORT_BITMAP_SIZE;
4✔
190
    te->current_count = 0;
4✔
191
#ifdef UNITTESTS
192
    if (g_threshold_force_alloc_fail) {
193
        SC_ATOMIC_ADD(threshold_bitmap_alloc_fail, 1);
194
        te->distinct_bitmap_union = NULL;
195
        return;
196
    }
197
#endif
198
    /* Check memcap before allocating bitmap.
199
     * Bitmap memory is bounded by detect.thresholds.memcap via thash.
200
     * Note: if ctx.thash is NULL (e.g. init failed or unittests), we bypass
201
     * the memcap check but still attempt allocation unless forced to fail. */
202
    if (ctx.thash != NULL && !THASH_CHECK_MEMCAP(ctx.thash, bitmap_size)) {
4✔
203
        SC_ATOMIC_ADD(threshold_bitmap_alloc_fail, 1);
×
204
        te->distinct_bitmap_union = NULL;
×
205
        return;
×
206
    }
×
207

208
    te->distinct_bitmap_union = SCCalloc(1, bitmap_size);
4✔
209
    if (te->distinct_bitmap_union == NULL) {
4✔
210
        SC_ATOMIC_ADD(threshold_bitmap_alloc_fail, 1);
×
211
    } else {
4✔
212
        /* Track bitmap memory in thash memuse for proper accounting */
213
        if (ctx.thash != NULL) {
4✔
214
            (void)SC_ATOMIC_ADD(ctx.thash->memuse, bitmap_size);
4✔
215
        }
4✔
216
        SC_ATOMIC_ADD(threshold_bitmap_memuse, bitmap_size);
4✔
217
    }
4✔
218
}
4✔
219

220
static void ThresholdDistinctReset(ThresholdEntry *te)
UNCOV
221
{
×
UNCOV
222
    const uint32_t bitmap_size = DF_PORT_BITMAP_SIZE;
×
UNCOV
223
    if (te->distinct_bitmap_union) {
×
UNCOV
224
        memset(te->distinct_bitmap_union, 0x00, bitmap_size);
×
UNCOV
225
    }
×
UNCOV
226
    te->current_count = 0;
×
UNCOV
227
}
×
228

229
static inline void ThresholdDistinctAddPort(ThresholdEntry *te, uint16_t port)
230
{
10✔
231
    const uint32_t byte_index = DF_PORT_BYTE_IDX(port);
10✔
232
    const uint8_t bit_mask = DF_PORT_BIT_MASK(port);
10✔
233
    if (te->distinct_bitmap_union) {
10✔
234
        bool already = (te->distinct_bitmap_union[byte_index] & bit_mask);
10✔
235
        if (!already) {
10✔
236
            te->distinct_bitmap_union[byte_index] =
8✔
237
                    (uint8_t)(te->distinct_bitmap_union[byte_index] | bit_mask);
8✔
238
            te->current_count++;
8✔
239
        }
8✔
240
    }
10✔
241
}
10✔
242

243
static void ThresholdEntryFree(void *ptr)
244
{
2,104,077✔
245
    if (ptr == NULL)
2,104,077✔
246
        return;
×
247

248
    ThresholdEntry *e = ptr;
2,104,077✔
249
    if (e->distinct_bitmap_union) {
2,104,077✔
250
        const uint32_t bitmap_size = DF_PORT_BITMAP_SIZE;
4✔
251
        /* Decrement bitmap memory from thash memuse */
252
        if (ctx.thash != NULL) {
4✔
253
            (void)SC_ATOMIC_SUB(ctx.thash->memuse, bitmap_size);
4✔
254
        }
4✔
255
        SC_ATOMIC_SUB(threshold_bitmap_memuse, bitmap_size);
4✔
256
        SCFree(e->distinct_bitmap_union);
4✔
257
        e->distinct_bitmap_union = NULL;
4✔
258
    }
4✔
259
}
2,104,077✔
260

261
static inline uint32_t HashAddress(const Address *a)
262
{
429✔
263
    uint32_t key;
429✔
264

265
    if (a->family == AF_INET) {
429✔
266
        key = a->addr_data32[0];
428✔
267
    } else if (a->family == AF_INET6) {
429✔
268
        key = hashword(a->addr_data32, 4, 0);
1✔
269
    } else
1✔
UNCOV
270
        key = 0;
×
271

272
    return key;
429✔
273
}
429✔
274

275
static inline int CompareAddress(const Address *a, const Address *b)
276
{
319✔
277
    if (a->family == b->family) {
319✔
278
        switch (a->family) {
319✔
279
            case AF_INET:
319✔
280
                return (a->addr_data32[0] == b->addr_data32[0]);
319✔
281
            case AF_INET6:
×
282
                return CMP_ADDR(a, b);
×
283
        }
319✔
284
    }
319✔
285
    return 0;
×
286
}
319✔
287

288
static uint32_t ThresholdEntryHash(uint32_t seed, void *ptr)
289
{
479✔
290
    const ThresholdEntry *e = ptr;
479✔
291
    uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), seed);
479✔
292
    switch (e->key[TRACK]) {
479✔
293
        case TRACK_BOTH:
25✔
294
            hash += HashAddress(&e->addr2);
25✔
295
            /* fallthrough */
296
        case TRACK_SRC:
362✔
297
        case TRACK_DST:
404✔
298
            hash += HashAddress(&e->addr);
404✔
299
            break;
404✔
300
    }
479✔
301
    return hash;
479✔
302
}
479✔
303

304
static bool ThresholdEntryCompare(void *a, void *b)
305
{
366✔
306
    const ThresholdEntry *e1 = a;
366✔
307
    const ThresholdEntry *e2 = b;
366✔
308
    SCLogDebug("sid1: %u sid2: %u", e1->key[SID], e2->key[SID]);
366✔
309

310
    if (memcmp(e1->key, e2->key, sizeof(e1->key)) != 0)
366✔
311
        return false;
×
312
    switch (e1->key[TRACK]) {
366✔
313
        case TRACK_BOTH:
21✔
314
            if (!(CompareAddress(&e1->addr2, &e2->addr2)))
21✔
UNCOV
315
                return false;
×
316
            /* fallthrough */
317
        case TRACK_SRC:
268✔
318
        case TRACK_DST:
298✔
319
            if (!(CompareAddress(&e1->addr, &e2->addr)))
298✔
320
                return false;
3✔
321
            break;
295✔
322
    }
366✔
323
    return true;
363✔
324
}
366✔
325

326
static bool ThresholdEntryExpire(void *data, const SCTime_t ts)
327
{
2,370✔
328
    const ThresholdEntry *e = data;
2,370✔
329
    const SCTime_t entry = SCTIME_ADD_SECS(e->tv1, e->seconds);
2,370✔
330
    if (SCTIME_CMP_GT(ts, entry)) {
2,370✔
331
        return true;
77✔
332
    }
77✔
333
    return false;
2,293✔
334
}
2,370✔
335

336
static int ThresholdsInit(struct Thresholds *t)
337
{
2,178✔
338
    uint32_t hashsize = 16384;
2,178✔
339
    uint64_t memcap = 16 * 1024 * 1024;
2,178✔
340

341
    const char *str;
2,178✔
342
    if (SCConfGet("detect.thresholds.memcap", &str) == 1) {
2,178✔
343
        if (ParseSizeStringU64(str, &memcap) < 0) {
1,461✔
344
            SCLogError("Error parsing detect.thresholds.memcap from conf file - %s", str);
×
345
            return -1;
×
346
        }
×
347
    }
1,461✔
348

349
    intmax_t value = 0;
2,178✔
350
    if ((SCConfGetInt("detect.thresholds.hash-size", &value)) == 1) {
2,178✔
351
        if (value < 256 || value > INT_MAX) {
1,461✔
352
            SCLogError("'detect.thresholds.hash-size' value %" PRIiMAX
×
353
                       " out of range. Valid range 256-2147483647.",
×
354
                    value);
×
355
            return -1;
×
356
        }
×
357
        hashsize = (uint32_t)value;
1,461✔
358
    }
1,461✔
359

360
    t->thash = THashInit("thresholds", sizeof(ThresholdEntry), ThresholdEntrySet,
2,178✔
361
            ThresholdEntryFree, ThresholdEntryHash, ThresholdEntryCompare, ThresholdEntryExpire,
2,178✔
362
            NULL, 0, memcap, hashsize);
2,178✔
363
    if (t->thash == NULL) {
2,178✔
364
        SCLogError("failed to initialize thresholds hash table");
×
365
        return -1;
×
366
    }
×
367
    return 0;
2,178✔
368
}
2,178✔
369

370
static void ThresholdsDestroy(struct Thresholds *t)
371
{
2,104✔
372
    if (t->thash) {
2,104✔
373
        THashShutdown(t->thash);
2,104✔
374
    }
2,104✔
375
}
2,104✔
376

377
uint32_t ThresholdsExpire(const SCTime_t ts)
378
{
4,503✔
379
    return THashExpire(ctx.thash, ts);
4,503✔
380
}
4,503✔
381

382
#define TC_ADDRESS 0
18✔
383
#define TC_SID     1
18✔
384
#define TC_GID     2
18✔
385
#define TC_REV     3
18✔
386
#define TC_TENANT  4
18✔
387

388
typedef struct ThresholdCacheItem {
389
    int8_t track; // by_src/by_dst
390
    int8_t ipv;
391
    int8_t retval;
392
    uint32_t key[5];
393
    SCTime_t expires_at;
394
    RB_ENTRY(ThresholdCacheItem) rb;
395
} ThresholdCacheItem;
396

397
static thread_local HashTable *threshold_cache_ht = NULL;
398

399
thread_local uint64_t cache_lookup_cnt = 0;
400
thread_local uint64_t cache_lookup_notinit = 0;
401
thread_local uint64_t cache_lookup_nosupport = 0;
402
thread_local uint64_t cache_lookup_miss_expired = 0;
403
thread_local uint64_t cache_lookup_miss = 0;
404
thread_local uint64_t cache_lookup_hit = 0;
405
thread_local uint64_t cache_housekeeping_check = 0;
406
thread_local uint64_t cache_housekeeping_expired = 0;
407

408
static void DumpCacheStats(void)
409
{
47,467✔
410
    SCLogPerf("threshold thread cache stats: cnt:%" PRIu64 " notinit:%" PRIu64 " nosupport:%" PRIu64
47,467✔
411
              " miss_expired:%" PRIu64 " miss:%" PRIu64 " hit:%" PRIu64
47,467✔
412
              ", housekeeping: checks:%" PRIu64 ", expired:%" PRIu64,
47,467✔
413
            cache_lookup_cnt, cache_lookup_notinit, cache_lookup_nosupport,
47,467✔
414
            cache_lookup_miss_expired, cache_lookup_miss, cache_lookup_hit,
47,467✔
415
            cache_housekeeping_check, cache_housekeeping_expired);
47,467✔
416
}
47,467✔
417

418
/* rbtree for expiry handling */
419

420
static int ThresholdCacheTreeCompareFunc(ThresholdCacheItem *a, ThresholdCacheItem *b)
421
{
12✔
422
    if (SCTIME_CMP_GTE(a->expires_at, b->expires_at)) {
12✔
423
        return 1;
12✔
424
    } else {
12✔
UNCOV
425
        return -1;
×
UNCOV
426
    }
×
427
}
12✔
428

429
RB_HEAD(THRESHOLD_CACHE, ThresholdCacheItem);
430
RB_PROTOTYPE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
431
RB_GENERATE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
192✔
432
thread_local struct THRESHOLD_CACHE threshold_cache_tree;
192✔
433
thread_local uint64_t threshold_cache_housekeeping_ts = 0;
192✔
434

192✔
435
static void ThresholdCacheExpire(SCTime_t now)
192✔
436
{
192✔
437
    ThresholdCacheItem *iter, *safe = NULL;
49✔
438
    int cnt = 0;
49✔
439
    threshold_cache_housekeeping_ts = SCTIME_SECS(now);
49✔
440

441
    RB_FOREACH_SAFE (iter, THRESHOLD_CACHE, &threshold_cache_tree, safe) {
63✔
442
        cache_housekeeping_check++;
63✔
443

444
        if (SCTIME_CMP_LT(iter->expires_at, now)) {
63✔
445
            THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, iter);
9✔
446
            HashTableRemove(threshold_cache_ht, iter, 0);
9✔
447
            SCLogDebug("iter %p expired", iter);
9✔
448
            cache_housekeeping_expired++;
9✔
449
        }
9✔
450

451
        if (++cnt > 1)
63✔
452
            break;
19✔
453
    }
63✔
454
}
49✔
455

456
/* hash table for threshold look ups */
457

458
static uint32_t ThresholdCacheHashFunc(HashTable *ht, void *data, uint16_t datalen)
459
{
872✔
460
    ThresholdCacheItem *e = data;
872✔
461
    uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), 0) * (e->ipv + e->track);
872✔
462
    hash = hash % ht->array_size;
872✔
463
    return hash;
872✔
464
}
872✔
465

466
static char ThresholdCacheHashCompareFunc(
467
        void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
468
{
792✔
469
    ThresholdCacheItem *tci1 = data1;
792✔
470
    ThresholdCacheItem *tci2 = data2;
792✔
471
    return tci1->ipv == tci2->ipv && tci1->track == tci2->track &&
792✔
472
           memcmp(tci1->key, tci2->key, sizeof(tci1->key)) == 0;
792✔
473
}
792✔
474

475
static void ThresholdCacheHashFreeFunc(void *data)
476
{
18✔
477
    SCFree(data);
18✔
478
}
18✔
479

480
/// \brief Thread local cache
481
static int SetupCache(const Packet *p, const int8_t track, const int8_t retval, const uint32_t sid,
482
        const uint32_t gid, const uint32_t rev, SCTime_t expires)
483
{
126✔
484
    if (!threshold_cache_ht) {
126✔
485
        threshold_cache_ht = HashTableInit(256, ThresholdCacheHashFunc,
21✔
486
                ThresholdCacheHashCompareFunc, ThresholdCacheHashFreeFunc);
21✔
487
    }
21✔
488

489
    uint32_t addr;
126✔
490
    if (track == TRACK_SRC) {
126✔
491
        addr = p->src.addr_data32[0];
18✔
492
    } else if (track == TRACK_DST) {
126✔
UNCOV
493
        addr = p->dst.addr_data32[0];
×
494
    } else {
108✔
495
        return -1;
108✔
496
    }
108✔
497

498
    ThresholdCacheItem lookup = {
18✔
499
        .track = track,
18✔
500
        .ipv = 4,
18✔
501
        .retval = retval,
18✔
502
        .key[TC_ADDRESS] = addr,
18✔
503
        .key[TC_SID] = sid,
18✔
504
        .key[TC_GID] = gid,
18✔
505
        .key[TC_REV] = rev,
18✔
506
        .key[TC_TENANT] = p->tenant_id,
18✔
507
        .expires_at = expires,
18✔
508
    };
18✔
509
    ThresholdCacheItem *found = HashTableLookup(threshold_cache_ht, &lookup, 0);
18✔
510
    if (!found) {
18✔
511
        ThresholdCacheItem *n = SCCalloc(1, sizeof(*n));
18✔
512
        if (n) {
18✔
513
            n->track = track;
18✔
514
            n->ipv = 4;
18✔
515
            n->retval = retval;
18✔
516
            n->key[TC_ADDRESS] = addr;
18✔
517
            n->key[TC_SID] = sid;
18✔
518
            n->key[TC_GID] = gid;
18✔
519
            n->key[TC_REV] = rev;
18✔
520
            n->key[TC_TENANT] = p->tenant_id;
18✔
521
            n->expires_at = expires;
18✔
522

523
            if (HashTableAdd(threshold_cache_ht, n, 0) == 0) {
18✔
524
                ThresholdCacheItem *r = THRESHOLD_CACHE_RB_INSERT(&threshold_cache_tree, n);
18✔
525
                DEBUG_VALIDATE_BUG_ON(r != NULL); // duplicate; should be impossible
18✔
526
                (void)r;                          // only used by DEBUG_VALIDATE_BUG_ON
18✔
527
                return 1;
18✔
528
            }
18✔
529
            SCFree(n);
×
530
        }
×
531
        return -1;
×
532
    } else {
18✔
533
        found->expires_at = expires;
×
534
        found->retval = retval;
×
535

536
        THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, found);
×
537
        THRESHOLD_CACHE_RB_INSERT(&threshold_cache_tree, found);
×
538
        return 1;
×
539
    }
×
540
}
18✔
541

542
/** \brief Check Thread local thresholding cache
543
 *  \note only supports IPv4
544
 *  \retval -1 cache miss - not found
545
 *  \retval -2 cache miss - found but expired
546
 *  \retval -3 error - cache not initialized
547
 *  \retval -4 error - unsupported tracker
548
 *  \retval ret cached return code
549
 */
550
static int CheckCache(const Packet *p, const int8_t track, const uint32_t sid, const uint32_t gid,
551
        const uint32_t rev)
552
{
1,031✔
553
    cache_lookup_cnt++;
1,031✔
554

555
    if (!threshold_cache_ht) {
1,031✔
556
        cache_lookup_notinit++;
204✔
557
        return -3; // error cache initialized
204✔
558
    }
204✔
559

560
    uint32_t addr;
827✔
561
    if (track == TRACK_SRC) {
827✔
562
        addr = p->src.addr_data32[0];
827✔
563
    } else if (track == TRACK_DST) {
827✔
UNCOV
564
        addr = p->dst.addr_data32[0];
×
UNCOV
565
    } else {
×
566
        cache_lookup_nosupport++;
×
567
        return -4; // error tracker not unsupported
×
568
    }
×
569

570
    if (SCTIME_SECS(p->ts) > threshold_cache_housekeeping_ts) {
827✔
571
        ThresholdCacheExpire(p->ts);
49✔
572
    }
49✔
573

574
    ThresholdCacheItem lookup = {
827✔
575
        .track = track,
827✔
576
        .ipv = 4,
827✔
577
        .key[TC_ADDRESS] = addr,
827✔
578
        .key[TC_SID] = sid,
827✔
579
        .key[TC_GID] = gid,
827✔
580
        .key[TC_REV] = rev,
827✔
581
        .key[TC_TENANT] = p->tenant_id,
827✔
582
    };
827✔
583
    ThresholdCacheItem *found = HashTableLookup(threshold_cache_ht, &lookup, 0);
827✔
584
    if (found) {
827✔
585
        if (SCTIME_CMP_GT(p->ts, found->expires_at)) {
783✔
UNCOV
586
            THRESHOLD_CACHE_RB_REMOVE(&threshold_cache_tree, found);
×
UNCOV
587
            HashTableRemove(threshold_cache_ht, found, 0);
×
UNCOV
588
            cache_lookup_miss_expired++;
×
UNCOV
589
            return -2; // cache miss - found but expired
×
UNCOV
590
        }
×
591
        cache_lookup_hit++;
783✔
592
        return found->retval;
783✔
593
    }
783✔
594
    cache_lookup_miss++;
44✔
595
    return -1; // cache miss - not found
44✔
596
}
827✔
597

598
void ThresholdCacheThreadFree(void)
599
{
47,467✔
600
    if (threshold_cache_ht) {
47,467✔
601
        HashTableFree(threshold_cache_ht);
21✔
602
        threshold_cache_ht = NULL;
21✔
603
    }
21✔
604
    RB_INIT(&threshold_cache_tree);
47,467✔
605
    DumpCacheStats();
47,467✔
606
}
47,467✔
607

608
/**
609
 * \brief Return next DetectThresholdData for signature
610
 *
611
 * \param sig  Signature pointer
612
 * \param psm  Pointer to a Signature Match pointer
613
 * \param list List to return data from
614
 *
615
 * \retval tsh Return the threshold data from signature or NULL if not found
616
 */
617
const DetectThresholdData *SigGetThresholdTypeIter(
618
        const Signature *sig, const SigMatchData **psm, int list)
619
{
26,802✔
620
    const SigMatchData *smd = NULL;
26,802✔
621
    const DetectThresholdData *tsh = NULL;
26,802✔
622

623
    if (sig == NULL)
26,802✔
624
        return NULL;
×
625

626
    if (*psm == NULL) {
26,804✔
627
        smd = sig->sm_arrays[list];
26,804✔
628
    } else {
2,147,484,747✔
629
        /* Iteration in progress, using provided value */
630
        smd = *psm;
2,147,483,647✔
631
    }
2,147,483,647✔
632

633
    while (1) {
26,805✔
634
        if (smd->type == DETECT_THRESHOLD || smd->type == DETECT_DETECTION_FILTER) {
26,811✔
635
            tsh = (DetectThresholdData *)smd->ctx;
26,811✔
636

637
            if (smd->is_last) {
26,811✔
638
                *psm = NULL;
26,810✔
639
            } else {
26,810✔
640
                *psm = smd + 1;
1✔
641
            }
1✔
642
            return tsh;
26,811✔
643
        }
26,811✔
644

645
        if (smd->is_last) {
2,147,483,647✔
646
            break;
×
647
        }
×
648
        smd++;
2,147,483,647✔
649
    }
2,147,483,647✔
650
    *psm = NULL;
2,147,483,647✔
651
    return NULL;
2,147,483,647✔
652
}
26,802✔
653

654
typedef struct FlowThresholdEntryList_ {
655
    struct FlowThresholdEntryList_ *next;
656
    ThresholdEntry threshold;
657
} FlowThresholdEntryList;
658

659
static void FlowThresholdEntryListFree(FlowThresholdEntryList *list)
660
{
227✔
661
    for (FlowThresholdEntryList *i = list; i != NULL;) {
896✔
662
        FlowThresholdEntryList *next = i->next;
669✔
663
        SCFree(i);
669✔
664
        i = next;
669✔
665
    }
669✔
666
}
227✔
667

668
/** struct for storing per flow thresholds. This will be stored in the Flow::flowvar list, so it
669
 * needs to follow the GenericVar header format. */
670
typedef struct FlowVarThreshold_ {
671
    uint16_t type;
672
    uint8_t pad[6];
673
    struct GenericVar_ *next;
674
    FlowThresholdEntryList *thresholds;
675
} FlowVarThreshold;
676

677
void FlowThresholdVarFree(void *ptr)
678
{
227✔
679
    FlowVarThreshold *t = ptr;
227✔
680
    FlowThresholdEntryListFree(t->thresholds);
227✔
681
    SCFree(t);
227✔
682
}
227✔
683

684
static FlowVarThreshold *FlowThresholdVarGet(Flow *f)
685
{
26,045✔
686
    if (f == NULL)
26,045✔
687
        return NULL;
×
688

689
    for (GenericVar *gv = f->flowvar; gv != NULL; gv = gv->next) {
2,147,483,676✔
690
        if (gv->type == DETECT_THRESHOLD)
25,594✔
691
            return (FlowVarThreshold *)gv;
25,596✔
692
    }
25,594✔
693

694
    return NULL;
449✔
695
}
26,045✔
696

697
static ThresholdEntry *ThresholdFlowLookupEntry(
698
        Flow *f, uint32_t sid, uint32_t gid, uint32_t rev, uint32_t tenant_id)
699
{
25,378✔
700
    FlowVarThreshold *t = FlowThresholdVarGet(f);
25,378✔
701
    if (t == NULL)
25,378✔
702
        return NULL;
227✔
703

704
    for (FlowThresholdEntryList *e = t->thresholds; e != NULL; e = e->next) {
50,463✔
705
        if (e->threshold.key[SID] == sid && e->threshold.key[GID] == gid &&
50,027✔
706
                e->threshold.key[REV] == rev && e->threshold.key[TENANT] == tenant_id) {
50,027✔
707
            return &e->threshold;
24,715✔
708
        }
24,715✔
709
    }
50,027✔
710
    return NULL;
436✔
711
}
25,151✔
712

713
static int AddEntryToFlow(Flow *f, FlowThresholdEntryList *e, SCTime_t packet_time)
714
{
669✔
715
    DEBUG_VALIDATE_BUG_ON(e == NULL);
669✔
716

717
    FlowVarThreshold *t = FlowThresholdVarGet(f);
669✔
718
    if (t == NULL) {
669✔
719
        t = SCCalloc(1, sizeof(*t));
227✔
720
        if (t == NULL) {
227✔
721
            return -1;
×
722
        }
×
723
        t->type = DETECT_THRESHOLD;
227✔
724
        GenericVarAppend(&f->flowvar, (GenericVar *)t);
227✔
725
    }
227✔
726

727
    e->next = t->thresholds;
669✔
728
    t->thresholds = e;
669✔
729
    return 0;
669✔
730
}
669✔
731

732
static int ThresholdHandlePacketSuppress(
733
        Packet *p, const DetectThresholdData *td, uint32_t sid, uint32_t gid)
734
{
171✔
735
    int ret = 0;
171✔
736
    DetectAddress *m = NULL;
171✔
737
    switch (td->track) {
171✔
738
        case TRACK_DST:
57✔
739
            m = DetectAddressLookupInHead(&td->addrs, &p->dst);
57✔
740
            SCLogDebug("TRACK_DST");
57✔
741
            break;
57✔
742
        case TRACK_SRC:
57✔
743
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
57✔
744
            SCLogDebug("TRACK_SRC");
57✔
745
            break;
57✔
746
        /* suppress if either src or dst is a match on the suppress
747
         * address list */
748
        case TRACK_EITHER:
57✔
749
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
57✔
750
            if (m == NULL) {
57✔
751
                m = DetectAddressLookupInHead(&td->addrs, &p->dst);
12✔
752
            }
12✔
753
            break;
57✔
754
        case TRACK_RULE:
×
755
        case TRACK_FLOW:
×
756
        default:
×
757
            SCLogError("track mode %d is not supported", td->track);
×
758
            break;
×
759
    }
171✔
760
    if (m == NULL)
171✔
761
        ret = 1;
57✔
762
    else
114✔
763
        ret = 2; /* suppressed but still need actions */
114✔
764

765
    return ret;
171✔
766
}
171✔
767

768
static inline void RateFilterSetAction(PacketAlert *pa, uint8_t new_action)
769
{
98✔
770
    switch (new_action) {
98✔
771
        case TH_ACTION_ALERT:
86✔
772
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
86✔
773
            pa->action = ACTION_ALERT;
86✔
774
            break;
86✔
775
        case TH_ACTION_DROP:
4✔
776
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
4✔
777
            pa->action = (ACTION_DROP | ACTION_ALERT);
4✔
778
            break;
4✔
779
        case TH_ACTION_REJECT:
4✔
780
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
4✔
781
            pa->action = (ACTION_REJECT | ACTION_DROP | ACTION_ALERT);
4✔
782
            break;
4✔
783
        case TH_ACTION_PASS:
4✔
784
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
4✔
785
            pa->action = ACTION_PASS;
4✔
786
            break;
4✔
787
        default:
×
788
            /* Weird, leave the default action */
789
            break;
×
790
    }
98✔
791
}
98✔
792

793
/** \internal
794
 *  \brief Apply the multiplier and return the new value.
795
 *  If it would overflow the uint32_t we return UINT32_MAX.
796
 */
797
static uint32_t BackoffCalcNextValue(const uint32_t cur, const uint32_t m)
798
{
1,335✔
799
    /* goal is to see if cur * m would overflow uint32_t */
800
    if (unlikely(UINT32_MAX / m < cur)) {
1,335✔
801
        return UINT32_MAX;
×
802
    }
×
803
    return cur * m;
1,335✔
804
}
1,335✔
805

806
/**
807
 *  \retval 2 silent match (no alert but apply actions)
808
 *  \retval 1 normal match
809
 *  \retval 0 no match
810
 */
811
static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te, const Packet *p,
812
        const uint32_t sid, const uint32_t gid, const uint32_t rev)
813
{
785✔
814
    te->key[SID] = sid;
785✔
815
    te->key[GID] = gid;
785✔
816
    te->key[REV] = rev;
785✔
817
    te->key[TRACK] = td->track;
785✔
818
    te->key[TENANT] = p->tenant_id;
785✔
819

820
    te->seconds = td->seconds;
785✔
821
    te->current_count = 1;
785✔
822

823
    switch (td->type) {
785✔
824
        case TYPE_BACKOFF:
654✔
825
            te->backoff.next_value = td->count;
654✔
826
            break;
654✔
827
        default:
131✔
828
            te->tv1 = p->ts;
131✔
829
            te->tv_timeout = SCTIME_INITIALIZER;
131✔
830
            ThresholdDistinctInit(te, td);
131✔
831
            /* If unique_on is enabled, we must add the current packet's port to the bitmap.
832
             * ThresholdDistinctInit resets current_count to 0, so we must add the port
833
             * or restore the count if allocation failed. */
834
            if (td->type == TYPE_DETECTION && td->unique_on != DF_UNIQUE_NONE) {
131✔
835
                if (te->distinct_bitmap_union) {
4✔
836
                    uint16_t port = (td->unique_on == DF_UNIQUE_SRC_PORT) ? p->sp : p->dp;
4✔
837
                    ThresholdDistinctAddPort(te, port);
4✔
838
                } else {
4✔
839
                    /* Allocation failed (or test mode), fallback to classic counting.
840
                     * We must set current_count to 1 for this first packet. */
UNCOV
841
                    te->current_count = 1;
×
UNCOV
842
                }
×
843
            }
4✔
844
            break;
131✔
845
    }
785✔
846

847
    switch (td->type) {
785✔
848
        case TYPE_LIMIT:
20✔
849
        case TYPE_RATE:
45✔
850
            return 1;
45✔
851
        case TYPE_THRESHOLD:
14✔
852
        case TYPE_BOTH:
72✔
853
            if (td->count == 1)
72✔
854
                return 1;
×
855
            return 0;
72✔
856
        case TYPE_BACKOFF:
654✔
857
            if (td->count == 1) {
654✔
858
                te->backoff.next_value =
434✔
859
                        BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
434✔
860
                return 1;
434✔
861
            }
434✔
862
            return 0;
220✔
863
        case TYPE_DETECTION:
14✔
864
            return 0;
14✔
865
    }
785✔
866
    return 0;
×
867
}
785✔
868

869
static int ThresholdCheckUpdate(const DetectEngineCtx *de_ctx, const DetectThresholdData *td,
870
        ThresholdEntry *te,
871
        const Packet *p, // ts only? - cache too
872
        const uint32_t sid, const uint32_t gid, const uint32_t rev, PacketAlert *pa)
873
{
25,077✔
874
    int ret = 0;
25,077✔
875
    const SCTime_t packet_time = p->ts;
25,077✔
876
    const SCTime_t entry = SCTIME_ADD_SECS(te->tv1, td->seconds);
25,077✔
877
    switch (td->type) {
25,077✔
878
        case TYPE_LIMIT:
110✔
879
            SCLogDebug("limit");
110✔
880

881
            if (SCTIME_CMP_LTE(p->ts, entry)) {
110✔
882
                te->current_count++;
109✔
883

884
                if (te->current_count <= td->count) {
109✔
885
                    ret = 1;
12✔
886
                } else {
97✔
887
                    ret = 2;
97✔
888

889
                    if (PacketIsIPv4(p)) {
97✔
890
                        SetupCache(p, td->track, (int8_t)ret, sid, gid, rev, entry);
97✔
891
                    }
97✔
892
                }
97✔
893
            } else {
109✔
894
                /* entry expired, reset */
895
                te->tv1 = p->ts;
1✔
896
                te->current_count = 1;
1✔
897
                ret = 1;
1✔
898
            }
1✔
899
            break;
110✔
900
        case TYPE_THRESHOLD:
30✔
901
            if (SCTIME_CMP_LTE(p->ts, entry)) {
30✔
902
                te->current_count++;
30✔
903

904
                if (te->current_count >= td->count) {
30✔
905
                    ret = 1;
4✔
906
                    te->current_count = 0;
4✔
907
                }
4✔
908
            } else {
30✔
UNCOV
909
                te->tv1 = p->ts;
×
UNCOV
910
                te->current_count = 1;
×
UNCOV
911
            }
×
912
            break;
30✔
913
        case TYPE_BOTH:
196✔
914
            if (SCTIME_CMP_LTE(p->ts, entry)) {
196✔
915
                /* within time limit */
916

917
                te->current_count++;
176✔
918
                if (te->current_count == td->count) {
176✔
919
                    ret = 1;
5✔
920
                } else if (te->current_count > td->count) {
171✔
921
                    /* silent match */
922
                    ret = 2;
29✔
923

924
                    if (PacketIsIPv4(p)) {
29✔
925
                        SetupCache(p, td->track, (int8_t)ret, sid, gid, rev, entry);
29✔
926
                    }
29✔
927
                }
29✔
928
            } else {
176✔
929
                /* expired, so reset */
930
                te->tv1 = p->ts;
20✔
931
                te->current_count = 1;
20✔
932

933
                /* if we have a limit of 1, this is a match */
934
                if (te->current_count == td->count) {
20✔
935
                    ret = 1;
×
936
                }
×
937
            }
20✔
938
            break;
196✔
939
        case TYPE_DETECTION: {
76✔
940
            SCLogDebug("detection_filter");
76✔
941

942
            if (SCTIME_CMP_LTE(p->ts, entry)) {
76✔
943
                /* within timeout */
944
                if (td->unique_on != DF_UNIQUE_NONE && te->distinct_bitmap_union) {
76✔
945
                    uint16_t port = (td->unique_on == DF_UNIQUE_SRC_PORT) ? p->sp : p->dp;
6✔
946
                    ThresholdDistinctAddPort(te, port);
6✔
947
                    if (te->current_count > td->count) {
6✔
948
                        ret = 1;
2✔
949
                    }
2✔
950
                } else {
70✔
951
                    te->current_count++;
70✔
952
                    if (te->current_count > td->count) {
70✔
953
                        ret = 1;
36✔
954
                    }
36✔
955
                }
70✔
956
            } else {
76✔
957
                /* expired, reset to new window starting now */
UNCOV
958
                te->tv1 = p->ts;
×
UNCOV
959
                ThresholdDistinctReset(te);
×
960

961
                /* record current packet's distinct port as the first in the new window */
UNCOV
962
                if (td->unique_on != DF_UNIQUE_NONE && te->distinct_bitmap_union) {
×
UNCOV
963
                    uint16_t port = (td->unique_on == DF_UNIQUE_SRC_PORT) ? p->sp : p->dp;
×
UNCOV
964
                    ThresholdDistinctAddPort(te, port);
×
UNCOV
965
                } else {
×
UNCOV
966
                    te->current_count = 1;
×
UNCOV
967
                }
×
UNCOV
968
            }
×
969
            break;
76✔
970
        }
×
971
        case TYPE_RATE: {
100✔
972
            SCLogDebug("rate_filter");
100✔
973
            const uint8_t original_action = pa->action;
100✔
974
            ret = 1;
100✔
975
            /* Check if we have a timeout enabled, if so,
976
             * we still matching (and enabling the new_action) */
977
            if (SCTIME_CMP_NEQ(te->tv_timeout, SCTIME_INITIALIZER)) {
100✔
978
                if ((SCTIME_SECS(packet_time) - SCTIME_SECS(te->tv_timeout)) > td->timeout) {
79✔
979
                    /* Ok, we are done, timeout reached */
UNCOV
980
                    te->tv_timeout = SCTIME_INITIALIZER;
×
981
                } else {
79✔
982
                    /* Already matching */
983
                    RateFilterSetAction(pa, td->new_action);
79✔
984
                }
79✔
985
            } else {
79✔
986
                /* Update the matching state with the timeout interval */
987
                if (SCTIME_CMP_LTE(packet_time, entry)) {
21✔
988
                    te->current_count++;
21✔
989
                    if (te->current_count > td->count) {
21✔
990
                        /* Then we must enable the new action by setting a
991
                         * timeout */
992
                        te->tv_timeout = packet_time;
19✔
993
                        RateFilterSetAction(pa, td->new_action);
19✔
994
                    }
19✔
995
                } else {
21✔
UNCOV
996
                    te->tv1 = packet_time;
×
UNCOV
997
                    te->current_count = 1;
×
UNCOV
998
                }
×
999
            }
21✔
1000
            if (de_ctx->RateFilterCallback && original_action != pa->action) {
100✔
1001
                pa->action = de_ctx->RateFilterCallback(p, sid, gid, rev, original_action,
×
1002
                        pa->action, de_ctx->rate_filter_callback_arg);
×
1003
                if (pa->action == original_action) {
×
1004
                    /* Reset back to original action, clear modified flag. */
1005
                    pa->flags &= ~PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
×
1006
                }
×
1007
            }
×
1008
            break;
100✔
1009
        }
×
1010
        case TYPE_BACKOFF:
24,566✔
1011
            SCLogDebug("backoff");
24,566✔
1012

1013
            if (te->current_count < UINT32_MAX) {
24,566✔
1014
                te->current_count++;
24,565✔
1015
                if (te->backoff.next_value == te->current_count) {
24,565✔
1016
                    te->backoff.next_value =
901✔
1017
                            BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
901✔
1018
                    SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
901✔
1019
                    ret = 1;
901✔
1020
                } else {
23,664✔
1021
                    ret = 2;
23,664✔
1022
                }
23,664✔
1023
            } else {
24,565✔
1024
                /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
1025
                ret = 2;
1✔
1026
            }
1✔
1027
            break;
24,566✔
1028
    }
25,077✔
1029
    return ret;
25,074✔
1030
}
25,077✔
1031

1032
static int ThresholdGetFromHash(const DetectEngineCtx *de_ctx, struct Thresholds *tctx,
1033
        const Packet *p, const Signature *s, const DetectThresholdData *td, PacketAlert *pa)
1034
{
479✔
1035
    /* fast track for count 1 threshold */
1036
    if (td->count == 1 && td->type == TYPE_THRESHOLD) {
479✔
1037
        return 1;
×
1038
    }
×
1039

1040
    ThresholdEntry lookup;
479✔
1041
    memset(&lookup, 0, sizeof(lookup));
479✔
1042
    lookup.key[SID] = s->id;
479✔
1043
    lookup.key[GID] = s->gid;
479✔
1044
    lookup.key[REV] = s->rev;
479✔
1045
    lookup.key[TRACK] = td->track;
479✔
1046
    lookup.key[TENANT] = p->tenant_id;
479✔
1047
    if (td->track == TRACK_SRC) {
479✔
1048
        COPY_ADDRESS(&p->src, &lookup.addr);
337✔
1049
    } else if (td->track == TRACK_DST) {
396✔
1050
        COPY_ADDRESS(&p->dst, &lookup.addr);
42✔
1051
    } else if (td->track == TRACK_BOTH) {
109✔
1052
        /* make sure lower ip address is first */
1053
        if (PacketIsIPv4(p)) {
25✔
1054
            if (SCNtohl(p->src.addr_data32[0]) < SCNtohl(p->dst.addr_data32[0])) {
25✔
1055
                COPY_ADDRESS(&p->src, &lookup.addr);
7✔
1056
                COPY_ADDRESS(&p->dst, &lookup.addr2);
7✔
1057
            } else {
18✔
1058
                COPY_ADDRESS(&p->dst, &lookup.addr);
18✔
1059
                COPY_ADDRESS(&p->src, &lookup.addr2);
18✔
1060
            }
18✔
1061
        } else {
25✔
1062
            if (AddressIPv6Lt(&p->src, &p->dst)) {
×
1063
                COPY_ADDRESS(&p->src, &lookup.addr);
×
1064
                COPY_ADDRESS(&p->dst, &lookup.addr2);
×
1065
            } else {
×
1066
                COPY_ADDRESS(&p->dst, &lookup.addr);
×
1067
                COPY_ADDRESS(&p->src, &lookup.addr2);
×
1068
            }
×
1069
        }
×
1070
    }
25✔
1071

1072
    struct THashDataGetResult res = THashGetFromHash(tctx->thash, &lookup);
479✔
1073
    if (res.data) {
479✔
1074
        SCLogDebug("found %p, is_new %s", res.data, BOOL2STR(res.is_new));
479✔
1075
        int r;
479✔
1076
        ThresholdEntry *te = res.data->data;
479✔
1077
        if (res.is_new) {
479✔
1078
            // new threshold, set up
1079
            r = ThresholdSetup(td, te, p, s->id, s->gid, s->rev);
116✔
1080
        } else {
363✔
1081
            // existing, check/update
1082
            r = ThresholdCheckUpdate(de_ctx, td, te, p, s->id, s->gid, s->rev, pa);
363✔
1083
        }
363✔
1084

1085
        (void)THashDecrUsecnt(res.data);
479✔
1086
        THashDataUnlock(res.data);
479✔
1087
        return r;
479✔
1088
    }
479✔
UNCOV
1089
    return 0; // TODO error?
×
1090
}
479✔
1091

1092
/**
1093
 *  \retval 2 silent match (no alert but apply actions)
1094
 *  \retval 1 normal match
1095
 *  \retval 0 no match
1096
 */
1097
static int ThresholdHandlePacketFlow(const DetectEngineCtx *de_ctx, Flow *f, Packet *p,
1098
        const DetectThresholdData *td, uint32_t sid, uint32_t gid, uint32_t rev, PacketAlert *pa)
1099
{
25,376✔
1100
    int ret = 0;
25,376✔
1101
    ThresholdEntry *found = ThresholdFlowLookupEntry(f, sid, gid, rev, p->tenant_id);
25,376✔
1102
    SCLogDebug("found %p sid %u gid %u rev %u", found, sid, gid, rev);
25,376✔
1103

1104
    if (found == NULL) {
25,376✔
1105
        FlowThresholdEntryList *new = SCCalloc(1, sizeof(*new));
669✔
1106
        if (new == NULL)
669✔
1107
            return 0;
×
1108

1109
        // new threshold, set up
1110
        ret = ThresholdSetup(td, &new->threshold, p, sid, gid, rev);
669✔
1111

1112
        if (AddEntryToFlow(f, new, p->ts) == -1) {
669✔
1113
            SCFree(new);
×
1114
            return 0;
×
1115
        }
×
1116
    } else {
24,707✔
1117
        // existing, check/update
1118
        ret = ThresholdCheckUpdate(de_ctx, td, found, p, sid, gid, rev, pa);
24,707✔
1119
    }
24,707✔
1120
    return ret;
25,376✔
1121
}
25,376✔
1122

1123
/**
1124
 * \brief Make the threshold logic for signatures
1125
 *
1126
 * \param de_ctx Detection Context
1127
 * \param tsh_ptr Threshold element
1128
 * \param p Packet structure
1129
 * \param s Signature structure
1130
 *
1131
 * \retval 2 silent match (no alert but apply actions)
1132
 * \retval 1 alert on this event
1133
 * \retval 0 do not alert on this event
1134
 */
1135
int PacketAlertThreshold(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
1136
        const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa)
1137
{
26,811✔
1138
    SCEnter();
26,811✔
1139

1140
    int ret = 0;
26,811✔
1141
    if (td == NULL) {
26,811✔
1142
        SCReturnInt(0);
×
1143
    }
×
1144

1145
    if (td->type == TYPE_SUPPRESS) {
26,811✔
1146
        ret = ThresholdHandlePacketSuppress(p, td, s->id, s->gid);
171✔
1147
    } else if (td->track == TRACK_SRC) {
26,640✔
1148
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
1,120✔
1149
            int cache_ret = CheckCache(p, td->track, s->id, s->gid, s->rev);
1,022✔
1150
            if (cache_ret >= 0) {
1,022✔
1151
                SCReturnInt(cache_ret);
783✔
1152
            }
783✔
1153
        }
1,022✔
1154

1155
        ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
337✔
1156
    } else if (td->track == TRACK_DST) {
25,774✔
1157
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
42✔
1158
            int cache_ret = CheckCache(p, td->track, s->id, s->gid, s->rev);
9✔
1159
            if (cache_ret >= 0) {
9✔
UNCOV
1160
                SCReturnInt(cache_ret);
×
UNCOV
1161
            }
×
1162
        }
9✔
1163

1164
        ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
42✔
1165
    } else if (td->track == TRACK_BOTH) {
25,487✔
1166
        ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
25✔
1167
    } else if (td->track == TRACK_RULE) {
25,453✔
1168
        ret = ThresholdGetFromHash(de_ctx, &ctx, p, s, td, pa);
75✔
1169
    } else if (td->track == TRACK_FLOW) {
25,378✔
1170
        if (p->flow) {
25,377✔
1171
            ret = ThresholdHandlePacketFlow(de_ctx, p->flow, p, td, s->id, s->gid, s->rev, pa);
25,377✔
1172
        }
25,377✔
1173
    }
25,375✔
1174

1175
    SCReturnInt(ret);
26,028✔
1176
}
26,811✔
1177

1178
/**
1179
 * @}
1180
 */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc