Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/detect-engine-threshold.c
Line
Count
Source
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
#include "util-random.h"
60
61
#include "thread-storage.h"
62
63
static SC_ATOMIC_DECLARE(uint64_t, threshold_cache_memuse);
64
65
static void ThresholdCacheInit(void);
66
67
struct Thresholds {
68
    THashTableContext *thash;
69
} ctx;
70
71
static int ThresholdsInit(struct Thresholds *t);
72
static void ThresholdsDestroy(struct Thresholds *t);
73
74
static uint64_t ThresholdCacheMemuseCounter(void)
75
0
{
76
0
    return SC_ATOMIC_GET(threshold_cache_memuse);
77
0
}
78
79
void ThresholdInit(void)
80
78
{
81
78
    SC_ATOMIC_INIT(threshold_cache_memuse);
82
83
78
    if (ThresholdsInit(&ctx) < 0) {
84
0
        FatalError("Failed to initialize threshold table");
85
0
    }
86
78
    ThresholdCacheInit();
87
78
}
88
89
void ThresholdRegisterGlobalCounters(void)
90
78
{
91
78
    StatsRegisterGlobalCounter("detect.thresholds.cache.memuse", ThresholdCacheMemuseCounter);
92
78
}
93
94
void ThresholdDestroy(void)
95
0
{
96
0
    ThresholdsDestroy(&ctx);
97
0
}
98
99
7.40k
#define SID    0
100
7.35k
#define GID    1
101
7.35k
#define REV    2
102
21.8k
#define TRACK  3
103
7.35k
#define TENANT 4
104
105
typedef struct ThresholdEntry_ {
106
    uint32_t key[5];
107
108
    SCTime_t tv_timeout;    /**< Timeout for new_action (for rate_filter)
109
                                 its not "seconds", that define the time interval */
110
    uint32_t seconds;       /**< Event seconds */
111
    uint32_t current_count; /**< Var for count control */
112
113
    union {
114
        struct {
115
            uint32_t next_value;
116
        } backoff;
117
        struct {
118
            SCTime_t tv1;  /**< Var for time control */
119
            Address addr;  /* used for src/dst/either tracking */
120
            Address addr2; /* used for both tracking */
121
        };
122
    };
123
124
} ThresholdEntry;
125
126
static int ThresholdEntrySet(void *dst, void *src)
127
30
{
128
30
    const ThresholdEntry *esrc = src;
129
30
    ThresholdEntry *edst = dst;
130
30
    memset(edst, 0, sizeof(*edst));
131
30
    *edst = *esrc;
132
30
    return 0;
133
30
}
134
135
static void ThresholdEntryFree(void *ptr)
136
0
{
137
    // nothing to free, base data is part of hash
138
0
}
139
140
static inline uint32_t HashAddress(const Address *a, const uint32_t seed)
141
1.88k
{
142
1.88k
    uint32_t key;
143
144
1.88k
    if (a->family == AF_INET) {
145
1.87k
        key = hashword(a->addr_data32, 1, seed);
146
1.87k
    } else if (a->family == AF_INET6) {
147
7
        key = hashword(a->addr_data32, 4, seed);
148
7
    } else
149
0
        key = 0;
150
151
1.88k
    return key;
152
1.88k
}
153
154
static inline int CompareAddress(const Address *a, const Address *b)
155
1.85k
{
156
1.85k
    if (a->family == b->family) {
157
1.85k
        switch (a->family) {
158
1.85k
            case AF_INET:
159
1.85k
                return (a->addr_data32[0] == b->addr_data32[0]);
160
5
            case AF_INET6:
161
5
                return CMP_ADDR(a, b);
162
1.85k
        }
163
1.85k
    }
164
0
    return 0;
165
1.85k
}
166
167
static uint32_t ThresholdEntryHash(const uint32_t seed, void *ptr)
168
7.30k
{
169
7.30k
    const ThresholdEntry *e = ptr;
170
7.30k
    uint32_t hash = hashword(e->key, sizeof(e->key) / sizeof(uint32_t), seed);
171
7.30k
    switch (e->key[TRACK]) {
172
0
        case TRACK_BOTH:
173
0
            hash += HashAddress(&e->addr2, seed);
174
            /* fallthrough */
175
1.87k
        case TRACK_SRC:
176
1.88k
        case TRACK_DST:
177
1.88k
            hash += HashAddress(&e->addr, seed);
178
1.88k
            break;
179
7.30k
    }
180
7.30k
    return hash;
181
7.30k
}
182
183
static bool ThresholdEntryCompare(void *a, void *b)
184
7.27k
{
185
7.27k
    const ThresholdEntry *e1 = a;
186
7.27k
    const ThresholdEntry *e2 = b;
187
7.27k
    SCLogDebug("sid1: %u sid2: %u", e1->key[SID], e2->key[SID]);
188
189
7.27k
    if (memcmp(e1->key, e2->key, sizeof(e1->key)) != 0)
190
0
        return false;
191
7.27k
    switch (e1->key[TRACK]) {
192
0
        case TRACK_BOTH:
193
0
            if (!(CompareAddress(&e1->addr2, &e2->addr2)))
194
0
                return false;
195
            /* fallthrough */
196
1.84k
        case TRACK_SRC:
197
1.85k
        case TRACK_DST:
198
1.85k
            if (!(CompareAddress(&e1->addr, &e2->addr)))
199
0
                return false;
200
1.85k
            break;
201
7.27k
    }
202
7.27k
    return true;
203
7.27k
}
204
205
static bool ThresholdEntryExpire(void *data, const SCTime_t ts)
206
0
{
207
0
    const ThresholdEntry *e = data;
208
0
    const SCTime_t entry = SCTIME_ADD_SECS(e->tv1, e->seconds);
209
0
    if (SCTIME_CMP_GT(ts, entry)) {
210
0
        return true;
211
0
    }
212
0
    return false;
213
0
}
214
215
static int ThresholdsInit(struct Thresholds *t)
216
78
{
217
78
    uint32_t hashsize = 16384;
218
78
    uint64_t memcap = 16 * 1024 * 1024;
219
220
78
    const char *str;
221
78
    if (SCConfGetNonNull("detect.thresholds.memcap", &str) == 1) {
222
0
        if (ParseSizeStringU64(str, &memcap) < 0) {
223
0
            SCLogError("Error parsing detect.thresholds.memcap from conf file - %s", str);
224
0
            return -1;
225
0
        }
226
0
    }
227
228
78
    intmax_t value = 0;
229
78
    if ((SCConfGetInt("detect.thresholds.hash-size", &value)) == 1) {
230
0
        if (value < 256 || value > INT_MAX) {
231
0
            SCLogError("'detect.thresholds.hash-size' value %" PRIiMAX
232
0
                       " out of range. Valid range 256-2147483647.",
233
0
                    value);
234
0
            return -1;
235
0
        }
236
0
        hashsize = (uint32_t)value;
237
0
    }
238
239
78
    t->thash = THashInit("thresholds", sizeof(ThresholdEntry), ThresholdEntrySet,
240
78
            ThresholdEntryFree, ThresholdEntryHash, ThresholdEntryCompare, ThresholdEntryExpire,
241
78
            NULL, 0, memcap, hashsize);
242
78
    if (t->thash == NULL) {
243
0
        SCLogError("failed to initialize thresholds hash table");
244
0
        return -1;
245
0
    }
246
78
    return 0;
247
78
}
248
249
static void ThresholdsDestroy(struct Thresholds *t)
250
0
{
251
0
    if (t->thash) {
252
0
        THashShutdown(t->thash);
253
0
    }
254
0
}
255
256
uint32_t ThresholdsExpire(const SCTime_t ts)
257
0
{
258
0
    return THashExpire(ctx.thash, ts);
259
0
}
260
261
0
#define TC_ADDRESS 0
262
0
#define TC_SID     1
263
0
#define TC_GID     2
264
0
#define TC_REV     3
265
0
#define TC_TENANT  4
266
267
typedef struct ThresholdCacheItem {
268
    int8_t track; // by_src/by_dst
269
    int8_t ipv;
270
    int8_t retval;
271
    uint32_t key[5];
272
    SCTime_t expires_at;
273
    RB_ENTRY(ThresholdCacheItem) rb;
274
} ThresholdCacheItem;
275
276
/* rbtree for expiry handling */
277
278
static int ThresholdCacheTreeCompareFunc(ThresholdCacheItem *a, ThresholdCacheItem *b)
279
0
{
280
0
    if (SCTIME_CMP_GTE(a->expires_at, b->expires_at)) {
281
0
        return 1;
282
0
    } else {
283
0
        return -1;
284
0
    }
285
0
}
286
287
RB_HEAD(THRESHOLD_CACHE, ThresholdCacheItem);
288
RB_PROTOTYPE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
289
0
RB_GENERATE(THRESHOLD_CACHE, ThresholdCacheItem, rb, ThresholdCacheTreeCompareFunc);
Unexecuted instantiation: THRESHOLD_CACHE_RB_INSERT_COLOR
Unexecuted instantiation: THRESHOLD_CACHE_RB_REMOVE_COLOR
Unexecuted instantiation: THRESHOLD_CACHE_RB_INSERT
Unexecuted instantiation: THRESHOLD_CACHE_RB_REMOVE
Unexecuted instantiation: THRESHOLD_CACHE_RB_FIND
Unexecuted instantiation: THRESHOLD_CACHE_RB_NFIND
Unexecuted instantiation: THRESHOLD_CACHE_RB_MINMAX
290
0
291
0
struct ThresholdCacheThreadCtx {
292
0
    HashTable *ht;
293
0
    struct THRESHOLD_CACHE tree;
294
0
    uint64_t housekeeping_ts;
295
0
    uint32_t entries;  /* number of entries in ht/tree, <= cache_max_entries */
296
0
    uint64_t init_mem; /* charged fixed per-thread memory (see ThresholdCacheThreadInit) */
297
0
298
0
    uint64_t lookup_cnt;
299
0
    uint64_t lookup_nosupport;
300
0
    uint64_t lookup_miss_expired;
301
0
    uint64_t lookup_miss;
302
0
    uint64_t lookup_hit;
303
0
    uint64_t housekeeping_check;
304
0
    uint64_t housekeeping_expired;
305
0
};
306
0
307
0
/* per-thread cap on the number of decision cache entries, configured via
308
0
 * detect.thresholds.cache.max-entries */
309
0
#define THRESHOLD_CACHE_MAX_ENTRIES_DEFAULT 256
310
0
#define THRESHOLD_CACHE_MAX_ENTRIES_MIN     256
311
0
#define THRESHOLD_CACHE_MAX_ENTRIES_MAX     1048576
312
static uint32_t cache_max_entries = THRESHOLD_CACHE_MAX_ENTRIES_DEFAULT;
313
314
/* bytes charged to the cache memory counter per entry: the item plus the
315
 * hash-table bucket allocated for it */
316
#define THRESHOLD_CACHE_ENTRY_MEM (sizeof(ThresholdCacheItem) + sizeof(HashTableBucket))
317
318
static ThreadStorageId thread_storage_id = { .id = -1 };
319
320
static void DumpCacheStats(struct ThresholdCacheThreadCtx *tctx)
321
0
{
322
0
    SCLogPerf("threshold thread cache stats: cnt:%" PRIu64 " nosupport:%" PRIu64
323
0
              " miss_expired:%" PRIu64 " miss:%" PRIu64 " hit:%" PRIu64 ", entries:%" PRIu32
324
0
              ", housekeeping: checks:%" PRIu64 ", expired:%" PRIu64,
325
0
            tctx->lookup_cnt, tctx->lookup_nosupport, tctx->lookup_miss_expired, tctx->lookup_miss,
326
0
            tctx->lookup_hit, tctx->entries, tctx->housekeeping_check, tctx->housekeeping_expired);
327
0
}
328
329
static inline struct ThresholdCacheThreadCtx *GetThreadCtx(DetectEngineThreadCtx *det_ctx)
330
4.31k
{
331
4.31k
    if (unlikely(det_ctx->tv == NULL || thread_storage_id.id < 0)) {
332
0
        return NULL;
333
0
    }
334
4.31k
    return ThreadGetStorageById(det_ctx->tv, thread_storage_id);
335
4.31k
}
336
337
static void ThresholdCacheExpire(DetectEngineThreadCtx *det_ctx, SCTime_t now)
338
0
{
339
0
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
340
0
    if (tctx == NULL)
341
0
        return;
342
0
    tctx->housekeeping_ts = SCTIME_SECS(now);
343
344
0
    ThresholdCacheItem *iter, *safe = NULL;
345
0
    int cnt = 0;
346
0
    RB_FOREACH_SAFE (iter, THRESHOLD_CACHE, &tctx->tree, safe) {
347
0
        tctx->housekeeping_check++;
348
349
0
        if (SCTIME_CMP_LT(iter->expires_at, now)) {
350
0
            THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, iter);
351
0
            HashTableRemove(tctx->ht, iter, 0);
352
0
            SCLogDebug("iter %p expired", iter);
353
0
            tctx->housekeeping_expired++;
354
0
            tctx->entries--;
355
0
            (void)SC_ATOMIC_SUB(threshold_cache_memuse, THRESHOLD_CACHE_ENTRY_MEM);
356
0
        }
357
358
0
        if (++cnt > 1)
359
0
            break;
360
0
    }
361
0
}
362
363
/* hash table for threshold look ups */
364
365
static uint32_t ThresholdCacheHashFunc(HashTable *ht, void *data, uint16_t datalen)
366
0
{
367
0
    ThresholdCacheItem *e = data;
368
0
    uint32_t hash =
369
0
            hashword(e->key, sizeof(e->key) / sizeof(uint32_t), ht->seed) * (e->ipv + e->track);
370
0
    hash = hash % ht->array_size;
371
0
    return hash;
372
0
}
373
374
static char ThresholdCacheHashCompareFunc(
375
        void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
376
0
{
377
0
    ThresholdCacheItem *tci1 = data1;
378
0
    ThresholdCacheItem *tci2 = data2;
379
0
    return tci1->ipv == tci2->ipv && tci1->track == tci2->track &&
380
0
           memcmp(tci1->key, tci2->key, sizeof(tci1->key)) == 0;
381
0
}
382
383
static void ThresholdCacheHashFreeFunc(void *data)
384
0
{
385
0
    SCFree(data);
386
0
}
387
388
/// \brief Thread local cache
389
static int SetupCache(DetectEngineThreadCtx *det_ctx, const Packet *p, const int8_t track,
390
        const int8_t retval, const uint32_t sid, const uint32_t gid, const uint32_t rev,
391
        SCTime_t expires)
392
4.31k
{
393
4.31k
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
394
4.31k
    if (!tctx) {
395
0
        return -1;
396
0
    }
397
398
4.31k
    uint32_t addr;
399
4.31k
    if (track == TRACK_SRC) {
400
0
        addr = p->src.addr_data32[0];
401
4.31k
    } else if (track == TRACK_DST) {
402
0
        addr = p->dst.addr_data32[0];
403
4.31k
    } else {
404
4.31k
        return -1;
405
4.31k
    }
406
407
0
    ThresholdCacheItem lookup = {
408
0
        .track = track,
409
0
        .ipv = 4,
410
0
        .retval = retval,
411
0
        .key[TC_ADDRESS] = addr,
412
0
        .key[TC_SID] = sid,
413
0
        .key[TC_GID] = gid,
414
0
        .key[TC_REV] = rev,
415
0
        .key[TC_TENANT] = p->tenant_id,
416
0
        .expires_at = expires,
417
0
    };
418
0
    ThresholdCacheItem *found = HashTableLookup(tctx->ht, &lookup, 0);
419
0
    if (!found) {
420
        /* the cache is bounded by cache_max_entries entries: evict the
421
         * entry with the earliest expiry (head of the tree) to make room
422
         * for the new one. */
423
0
        if (tctx->entries >= cache_max_entries) {
424
0
            ThresholdCacheItem *victim = THRESHOLD_CACHE_RB_MINMAX(&tctx->tree, RB_NEGINF);
425
0
            if (victim == NULL) {
426
                /* defensive: cannot happen while entries > 0 */
427
0
                DEBUG_VALIDATE_BUG_ON(1);
428
0
                return -1;
429
0
            }
430
0
            THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, victim);
431
0
            HashTableRemove(tctx->ht, victim, 0);
432
0
            tctx->entries--;
433
0
            (void)SC_ATOMIC_SUB(threshold_cache_memuse, THRESHOLD_CACHE_ENTRY_MEM);
434
0
        }
435
436
0
        ThresholdCacheItem *n = SCCalloc(1, sizeof(*n));
437
0
        if (n) {
438
0
            n->track = track;
439
0
            n->ipv = 4;
440
0
            n->retval = retval;
441
0
            n->key[TC_ADDRESS] = addr;
442
0
            n->key[TC_SID] = sid;
443
0
            n->key[TC_GID] = gid;
444
0
            n->key[TC_REV] = rev;
445
0
            n->key[TC_TENANT] = p->tenant_id;
446
0
            n->expires_at = expires;
447
448
0
            if (HashTableAdd(tctx->ht, n, 0) == 0) {
449
0
                ThresholdCacheItem *r = THRESHOLD_CACHE_RB_INSERT(&tctx->tree, n);
450
0
                DEBUG_VALIDATE_BUG_ON(r != NULL); // duplicate; should be impossible
451
0
                (void)r;                          // only used by DEBUG_VALIDATE_BUG_ON
452
0
                tctx->entries++;
453
0
                (void)SC_ATOMIC_ADD(threshold_cache_memuse, THRESHOLD_CACHE_ENTRY_MEM);
454
0
                return 1;
455
0
            }
456
0
            SCFree(n);
457
0
        }
458
0
        return -1;
459
0
    } else {
460
0
        found->expires_at = expires;
461
0
        found->retval = retval;
462
463
0
        THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, found);
464
0
        THRESHOLD_CACHE_RB_INSERT(&tctx->tree, found);
465
0
        return 1;
466
0
    }
467
0
}
468
469
/** \brief Check Thread local thresholding cache
470
 *  \note only supports IPv4
471
 *  \retval -1 cache miss - not found
472
 *  \retval -2 cache miss - found but expired
473
 *  \retval -3 error - cache not initialized
474
 *  \retval -4 error - unsupported tracker
475
 *  \retval ret cached return code
476
 */
477
static int CheckCache(DetectEngineThreadCtx *det_ctx, const Packet *p, const int8_t track,
478
        const uint32_t sid, const uint32_t gid, const uint32_t rev)
479
0
{
480
0
    struct ThresholdCacheThreadCtx *tctx = GetThreadCtx(det_ctx);
481
0
    if (!tctx) {
482
0
        return -3;
483
0
    }
484
485
0
    tctx->lookup_cnt++;
486
487
0
    uint32_t addr;
488
0
    if (track == TRACK_SRC) {
489
0
        addr = p->src.addr_data32[0];
490
0
    } else if (track == TRACK_DST) {
491
0
        addr = p->dst.addr_data32[0];
492
0
    } else {
493
0
        tctx->lookup_nosupport++;
494
0
        return -4; // error tracker not unsupported
495
0
    }
496
497
0
    if (SCTIME_SECS(p->ts) > tctx->housekeeping_ts) {
498
0
        ThresholdCacheExpire(det_ctx, p->ts);
499
0
    }
500
501
0
    ThresholdCacheItem lookup = {
502
0
        .track = track,
503
0
        .ipv = 4,
504
0
        .key[TC_ADDRESS] = addr,
505
0
        .key[TC_SID] = sid,
506
0
        .key[TC_GID] = gid,
507
0
        .key[TC_REV] = rev,
508
0
        .key[TC_TENANT] = p->tenant_id,
509
0
    };
510
0
    ThresholdCacheItem *found = HashTableLookup(tctx->ht, &lookup, 0);
511
0
    if (found) {
512
0
        if (SCTIME_CMP_GT(p->ts, found->expires_at)) {
513
0
            THRESHOLD_CACHE_RB_REMOVE(&tctx->tree, found);
514
0
            HashTableRemove(tctx->ht, found, 0);
515
0
            tctx->lookup_miss_expired++;
516
0
            tctx->entries--;
517
0
            (void)SC_ATOMIC_SUB(threshold_cache_memuse, THRESHOLD_CACHE_ENTRY_MEM);
518
0
            return -2; // cache miss - found but expired
519
0
        }
520
0
        tctx->lookup_hit++;
521
0
        return found->retval;
522
0
    }
523
0
    tctx->lookup_miss++;
524
0
    return -1; // cache miss - not found
525
0
}
526
527
static void ThresholdCacheThreadFree(void *ptr)
528
0
{
529
0
    if (ptr != NULL) {
530
0
        struct ThresholdCacheThreadCtx *tctx = ptr;
531
0
        DumpCacheStats(tctx);
532
0
        (void)SC_ATOMIC_SUB(threshold_cache_memuse,
533
0
                (uint64_t)tctx->entries * THRESHOLD_CACHE_ENTRY_MEM + tctx->init_mem);
534
0
        HashTableFree(tctx->ht);
535
0
        SCFree(tctx);
536
0
    }
537
0
}
538
539
static void ThresholdCacheInit(void)
540
78
{
541
#ifdef UNITTESTS
542
    /* many tests don't manage the thread storage correctly, so skip the cache in unittests */
543
    if (!(RunmodeIsUnittests())) {
544
#endif
545
78
        intmax_t value = 0;
546
78
        if (SCConfGetInt("detect.thresholds.cache.max-entries", &value) == 1) {
547
0
            if (value < THRESHOLD_CACHE_MAX_ENTRIES_MIN ||
548
0
                    value > THRESHOLD_CACHE_MAX_ENTRIES_MAX) {
549
0
                SCLogError("'detect.thresholds.cache.max-entries' value %" PRIdMAX
550
0
                           " out of range. Valid range %d-%d.",
551
0
                        value, THRESHOLD_CACHE_MAX_ENTRIES_MIN, THRESHOLD_CACHE_MAX_ENTRIES_MAX);
552
0
                FatalError("Invalid value for detect.thresholds.cache.max-entries");
553
0
            }
554
0
            cache_max_entries = (uint32_t)value;
555
0
        }
556
557
        /* Register thread storage. */
558
78
        thread_storage_id = ThreadStorageRegister(
559
78
                "threshold_cache", sizeof(void *), NULL, ThresholdCacheThreadFree);
560
78
        if (thread_storage_id.id < 0) {
561
0
            FatalError("Failed to register threshold_cache thread storage");
562
0
        }
563
#ifdef UNITTESTS
564
    }
565
#endif
566
78
}
567
568
int ThresholdCacheThreadInit(DetectEngineThreadCtx *det_ctx)
569
100k
{
570
100k
    if (thread_storage_id.id < 0)
571
0
        return 0;
572
    /* we can get called more than once per thread for MT */
573
100k
    if (ThreadGetStorageById(det_ctx->tv, thread_storage_id) != NULL)
574
100k
        return 0;
575
576
5
    struct ThresholdCacheThreadCtx *tctx = SCCalloc(1, sizeof(*tctx));
577
5
    if (tctx == NULL)
578
0
        return -1;
579
580
5
    uint32_t seed = (uint32_t)RandomGet();
581
582
5
    uint32_t hashsize = cache_max_entries;
583
5
    DEBUG_VALIDATE_BUG_ON(hashsize < 256);
584
5
    uint32_t hashpow = 1;
585
45
    while (hashpow < hashsize)
586
40
        hashpow <<= 1;
587
588
5
    tctx->ht = HashTableInitWithSeed(hashpow, ThresholdCacheHashFunc, ThresholdCacheHashCompareFunc,
589
5
            ThresholdCacheHashFreeFunc, seed);
590
5
    if (tctx->ht == NULL) {
591
0
        SCFree(tctx);
592
0
        return -1;
593
0
    }
594
595
5
    RB_INIT(&tctx->tree);
596
    /* charge the fixed per-thread baseline (thread context, hash table
597
     * struct and the eagerly allocated bucket pointer array) so the memuse
598
     * gauge reflects all cache memory in use from the moment the cache is
599
     * set up, not just the entries; released symmetrically in
600
     * ThresholdCacheThreadFree */
601
5
    tctx->init_mem =
602
5
            sizeof(*tctx) + sizeof(*tctx->ht) + (uint64_t)hashpow * sizeof(HashTableBucket *);
603
5
    (void)SC_ATOMIC_ADD(threshold_cache_memuse, tctx->init_mem);
604
5
    ThreadSetStorageById(det_ctx->tv, thread_storage_id, tctx);
605
5
    return 0;
606
5
}
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
7.38k
{
620
7.38k
    const SigMatchData *smd = NULL;
621
7.38k
    const DetectThresholdData *tsh = NULL;
622
623
7.38k
    if (sig == NULL)
624
0
        return NULL;
625
626
7.38k
    if (*psm == NULL) {
627
7.38k
        smd = sig->sm_arrays[list];
628
7.38k
    } else {
629
        /* Iteration in progress, using provided value */
630
0
        smd = *psm;
631
0
    }
632
633
7.38k
    while (1) {
634
7.38k
        if (smd->type == DETECT_THRESHOLD || smd->type == DETECT_DETECTION_FILTER) {
635
7.38k
            tsh = (DetectThresholdData *)smd->ctx;
636
637
7.38k
            if (smd->is_last) {
638
7.38k
                *psm = NULL;
639
7.38k
            } else {
640
0
                *psm = smd + 1;
641
0
            }
642
7.38k
            return tsh;
643
7.38k
        }
644
645
0
        if (smd->is_last) {
646
0
            break;
647
0
        }
648
0
        smd++;
649
0
    }
650
0
    *psm = NULL;
651
0
    return NULL;
652
7.38k
}
653
654
typedef struct FlowThresholdEntryList_ {
655
    struct FlowThresholdEntryList_ *next;
656
    ThresholdEntry threshold;
657
} FlowThresholdEntryList;
658
659
static void FlowThresholdEntryListFree(FlowThresholdEntryList *list)
660
19
{
661
55
    for (FlowThresholdEntryList *i = list; i != NULL;) {
662
36
        FlowThresholdEntryList *next = i->next;
663
36
        SCFree(i);
664
36
        i = next;
665
36
    }
666
19
}
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
19
{
679
19
    FlowVarThreshold *t = ptr;
680
19
    FlowThresholdEntryListFree(t->thresholds);
681
19
    SCFree(t);
682
19
}
683
684
static FlowVarThreshold *FlowThresholdVarGet(Flow *f)
685
116
{
686
116
    if (f == NULL)
687
0
        return NULL;
688
689
124
    for (GenericVar *gv = f->flowvar; gv != NULL; gv = gv->next) {
690
86
        if (gv->type == DETECT_THRESHOLD)
691
78
            return (FlowVarThreshold *)gv;
692
86
    }
693
694
38
    return NULL;
695
116
}
696
697
static ThresholdEntry *ThresholdFlowLookupEntry(
698
        Flow *f, uint32_t sid, uint32_t gid, uint32_t rev, uint32_t tenant_id)
699
80
{
700
80
    FlowVarThreshold *t = FlowThresholdVarGet(f);
701
80
    if (t == NULL)
702
19
        return NULL;
703
704
110
    for (FlowThresholdEntryList *e = t->thresholds; e != NULL; e = e->next) {
705
93
        if (e->threshold.key[SID] == sid && e->threshold.key[GID] == gid &&
706
44
                e->threshold.key[REV] == rev && e->threshold.key[TENANT] == tenant_id) {
707
44
            return &e->threshold;
708
44
        }
709
93
    }
710
17
    return NULL;
711
61
}
712
713
static int AddEntryToFlow(Flow *f, FlowThresholdEntryList *e, SCTime_t packet_time)
714
36
{
715
36
    DEBUG_VALIDATE_BUG_ON(e == NULL);
716
717
36
    FlowVarThreshold *t = FlowThresholdVarGet(f);
718
36
    if (t == NULL) {
719
19
        t = SCCalloc(1, sizeof(*t));
720
19
        if (t == NULL) {
721
0
            return -1;
722
0
        }
723
19
        t->type = DETECT_THRESHOLD;
724
19
        GenericVarAppend(&f->flowvar, (GenericVar *)t);
725
19
    }
726
727
36
    e->next = t->thresholds;
728
36
    t->thresholds = e;
729
36
    return 0;
730
36
}
731
732
static int ThresholdHandlePacketSuppress(Packet *p,
733
        const DetectThresholdData *td, uint32_t sid, uint32_t gid)
734
0
{
735
0
    int ret = 0;
736
0
    DetectAddress *m = NULL;
737
0
    switch (td->track) {
738
0
        case TRACK_DST:
739
0
            m = DetectAddressLookupInHead(&td->addrs, &p->dst);
740
0
            SCLogDebug("TRACK_DST");
741
0
            break;
742
0
        case TRACK_SRC:
743
0
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
744
0
            SCLogDebug("TRACK_SRC");
745
0
            break;
746
        /* suppress if either src or dst is a match on the suppress
747
         * address list */
748
0
        case TRACK_EITHER:
749
0
            m = DetectAddressLookupInHead(&td->addrs, &p->src);
750
0
            if (m == NULL) {
751
0
                m = DetectAddressLookupInHead(&td->addrs, &p->dst);
752
0
            }
753
0
            break;
754
0
        case TRACK_RULE:
755
0
        case TRACK_FLOW:
756
0
        default:
757
0
            SCLogError("track mode %d is not supported", td->track);
758
0
            break;
759
0
    }
760
0
    if (m == NULL)
761
0
        ret = 1;
762
0
    else
763
0
        ret = 2; /* suppressed but still need actions */
764
765
0
    return ret;
766
0
}
767
768
static inline void RateFilterSetAction(PacketAlert *pa, uint8_t new_action)
769
0
{
770
0
    switch (new_action) {
771
0
        case TH_ACTION_ALERT:
772
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
773
0
            pa->action = ACTION_ALERT;
774
0
            break;
775
0
        case TH_ACTION_DROP:
776
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
777
0
            pa->action = ACTION_DROP;
778
0
            break;
779
0
        case TH_ACTION_REJECT:
780
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
781
0
            pa->action = (ACTION_REJECT | ACTION_DROP);
782
0
            break;
783
0
        case TH_ACTION_PASS:
784
0
            pa->flags |= PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
785
0
            pa->action = ACTION_PASS;
786
0
            break;
787
0
        default:
788
            /* Weird, leave the default action */
789
0
            break;
790
0
    }
791
0
}
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
0
{
799
    /* goal is to see if cur * m would overflow uint32_t */
800
0
    if (unlikely(UINT32_MAX / m < cur)) {
801
0
        return UINT32_MAX;
802
0
    }
803
0
    return cur * m;
804
0
}
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,
812
        const SCTime_t packet_time, const uint32_t sid, const uint32_t gid, const uint32_t rev,
813
        const uint32_t tenant_id)
814
1
{
815
1
    te->key[SID] = sid;
816
1
    te->key[GID] = gid;
817
1
    te->key[REV] = rev;
818
1
    te->key[TRACK] = td->track;
819
1
    te->key[TENANT] = tenant_id;
820
821
1
    te->seconds = td->seconds;
822
1
    te->current_count = 1;
823
824
1
    switch (td->type) {
825
0
        case TYPE_BACKOFF:
826
0
            te->backoff.next_value = td->count;
827
0
            break;
828
1
        default:
829
1
            te->tv1 = packet_time;
830
1
            te->tv_timeout = SCTIME_INITIALIZER;
831
1
            break;
832
1
    }
833
834
1
    switch (td->type) {
835
1
        case TYPE_LIMIT:
836
1
        case TYPE_RATE:
837
1
            return 1;
838
0
        case TYPE_THRESHOLD:
839
0
        case TYPE_BOTH:
840
0
            if (td->count == 1)
841
0
                return 1;
842
0
            return 0;
843
0
        case TYPE_BACKOFF:
844
0
            if (td->count == 1) {
845
0
                te->backoff.next_value =
846
0
                        BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
847
0
                return 1;
848
0
            }
849
0
            return 0;
850
0
        case TYPE_DETECTION:
851
0
            return 0;
852
1
    }
853
0
    return 0;
854
1
}
855
856
static int ThresholdCheckUpdate(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
857
        const DetectThresholdData *td, ThresholdEntry *te,
858
        const Packet *p, // ts only? - cache too
859
        const uint32_t sid, const uint32_t gid, const uint32_t rev, PacketAlert *pa)
860
110
{
861
110
    int ret = 0;
862
110
    const SCTime_t packet_time = p->ts;
863
110
    const SCTime_t entry = SCTIME_ADD_SECS(te->tv1, td->seconds);
864
110
    switch (td->type) {
865
110
        case TYPE_LIMIT:
866
110
            SCLogDebug("limit");
867
868
110
            if (SCTIME_CMP_LTE(p->ts, entry)) {
869
110
                te->current_count++;
870
871
110
                if (te->current_count <= td->count) {
872
4
                    ret = 1;
873
106
                } else {
874
106
                    ret = 2;
875
876
106
                    if (PacketIsIPv4(p)) {
877
0
                        SetupCache(det_ctx, p, td->track, (int8_t)ret, sid, gid, rev, entry);
878
0
                    }
879
106
                }
880
110
            } else {
881
                /* entry expired, reset */
882
0
                te->tv1 = p->ts;
883
0
                te->current_count = 1;
884
0
                ret = 1;
885
0
            }
886
110
            break;
887
0
        case TYPE_THRESHOLD:
888
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
889
0
                te->current_count++;
890
891
0
                if (te->current_count >= td->count) {
892
0
                    ret = 1;
893
0
                    te->current_count = 0;
894
0
                }
895
0
            } else {
896
0
                te->tv1 = p->ts;
897
0
                te->current_count = 1;
898
0
            }
899
0
            break;
900
0
        case TYPE_BOTH:
901
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
902
                /* within time limit */
903
904
0
                te->current_count++;
905
0
                if (te->current_count == td->count) {
906
0
                    ret = 1;
907
0
                } else if (te->current_count > td->count) {
908
                    /* silent match */
909
0
                    ret = 2;
910
911
0
                    if (PacketIsIPv4(p)) {
912
0
                        SetupCache(det_ctx, p, td->track, (int8_t)ret, sid, gid, rev, entry);
913
0
                    }
914
0
                }
915
0
            } else {
916
                /* expired, so reset */
917
0
                te->tv1 = p->ts;
918
0
                te->current_count = 1;
919
920
                /* if we have a limit of 1, this is a match */
921
0
                if (te->current_count == td->count) {
922
0
                    ret = 1;
923
0
                }
924
0
            }
925
0
            break;
926
0
        case TYPE_DETECTION:
927
0
            SCLogDebug("detection_filter");
928
929
0
            if (SCTIME_CMP_LTE(p->ts, entry)) {
930
                /* within timeout */
931
0
                te->current_count++;
932
0
                if (te->current_count > td->count) {
933
0
                    ret = 1;
934
0
                }
935
0
            } else {
936
                /* expired, reset */
937
0
                te->tv1 = p->ts;
938
0
                te->current_count = 1;
939
0
            }
940
0
            break;
941
0
        case TYPE_RATE: {
942
0
            SCLogDebug("rate_filter");
943
0
            const uint8_t original_action = pa->action;
944
0
            ret = 1;
945
            /* Check if we have a timeout enabled, if so,
946
             * we still matching (and enabling the new_action) */
947
0
            if (SCTIME_CMP_NEQ(te->tv_timeout, SCTIME_INITIALIZER)) {
948
0
                if ((SCTIME_SECS(packet_time) - SCTIME_SECS(te->tv_timeout)) > td->timeout) {
949
                    /* Ok, we are done, timeout reached */
950
0
                    te->tv_timeout = SCTIME_INITIALIZER;
951
0
                } else {
952
                    /* Already matching */
953
0
                    RateFilterSetAction(pa, td->new_action);
954
0
                }
955
0
            } else {
956
                /* Update the matching state with the timeout interval */
957
0
                if (SCTIME_CMP_LTE(packet_time, entry)) {
958
0
                    te->current_count++;
959
0
                    if (te->current_count > td->count) {
960
                        /* Then we must enable the new action by setting a
961
                         * timeout */
962
0
                        te->tv_timeout = packet_time;
963
0
                        RateFilterSetAction(pa, td->new_action);
964
0
                    }
965
0
                } else {
966
0
                    te->tv1 = packet_time;
967
0
                    te->current_count = 1;
968
0
                }
969
0
            }
970
0
            if (de_ctx->RateFilterCallback && original_action != pa->action) {
971
0
                pa->action = de_ctx->RateFilterCallback(p, sid, gid, rev, original_action,
972
0
                        pa->action, de_ctx->rate_filter_callback_arg);
973
0
                if (pa->action == original_action) {
974
                    /* Reset back to original action, clear modified flag. */
975
0
                    pa->flags &= ~PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED;
976
0
                }
977
0
            }
978
0
            break;
979
0
        }
980
0
        case TYPE_BACKOFF:
981
0
            SCLogDebug("backoff");
982
983
0
            if (te->current_count < UINT32_MAX) {
984
0
                te->current_count++;
985
0
                if (te->backoff.next_value == te->current_count) {
986
0
                    te->backoff.next_value =
987
0
                            BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
988
0
                    SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
989
0
                    ret = 1;
990
0
                } else {
991
0
                    ret = 2;
992
0
                }
993
0
            } else {
994
                /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
995
0
                ret = 2;
996
0
            }
997
0
            break;
998
110
    }
999
110
    return ret;
1000
110
}
1001
1002
static int ThresholdGetFromHash(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
1003
        struct Thresholds *tctx, const Packet *p, const Signature *s, const DetectThresholdData *td,
1004
        PacketAlert *pa)
1005
7.30k
{
1006
    /* fast track for count 1 threshold */
1007
7.30k
    if (td->count == 1 && td->type == TYPE_THRESHOLD) {
1008
0
        return 1;
1009
0
    }
1010
1011
7.30k
    ThresholdEntry lookup;
1012
7.30k
    memset(&lookup, 0, sizeof(lookup));
1013
7.30k
    lookup.key[SID] = s->id;
1014
7.30k
    lookup.key[GID] = s->gid;
1015
7.30k
    lookup.key[REV] = s->rev;
1016
7.30k
    lookup.key[TRACK] = td->track;
1017
7.30k
    lookup.key[TENANT] = p->tenant_id;
1018
7.30k
    if (td->track == TRACK_SRC) {
1019
1.87k
        COPY_ADDRESS(&p->src, &lookup.addr);
1020
5.43k
    } else if (td->track == TRACK_DST) {
1021
12
        COPY_ADDRESS(&p->dst, &lookup.addr);
1022
5.42k
    } else if (td->track == TRACK_BOTH) {
1023
        /* make sure lower ip address is first */
1024
0
        if (PacketIsIPv4(p)) {
1025
0
            if (SCNtohl(p->src.addr_data32[0]) < SCNtohl(p->dst.addr_data32[0])) {
1026
0
                COPY_ADDRESS(&p->src, &lookup.addr);
1027
0
                COPY_ADDRESS(&p->dst, &lookup.addr2);
1028
0
            } else {
1029
0
                COPY_ADDRESS(&p->dst, &lookup.addr);
1030
0
                COPY_ADDRESS(&p->src, &lookup.addr2);
1031
0
            }
1032
0
        } else {
1033
0
            if (AddressIPv6Lt(&p->src, &p->dst)) {
1034
0
                COPY_ADDRESS(&p->src, &lookup.addr);
1035
0
                COPY_ADDRESS(&p->dst, &lookup.addr2);
1036
0
            } else {
1037
0
                COPY_ADDRESS(&p->dst, &lookup.addr);
1038
0
                COPY_ADDRESS(&p->src, &lookup.addr2);
1039
0
            }
1040
0
        }
1041
0
    }
1042
1043
7.30k
    struct THashDataGetResult res = THashGetFromHash(tctx->thash, &lookup);
1044
7.30k
    if (res.data) {
1045
7.30k
        SCLogDebug("found %p, is_new %s", res.data, BOOL2STR(res.is_new));
1046
7.30k
        int r;
1047
7.30k
        ThresholdEntry *te = res.data->data;
1048
7.30k
        if (res.is_new) {
1049
            // new threshold, set up
1050
30
            r = ThresholdSetup(td, te, p->ts, s->id, s->gid, s->rev, p->tenant_id);
1051
7.27k
        } else {
1052
            // existing, check/update
1053
7.27k
            r = ThresholdCheckUpdate(de_ctx, det_ctx, td, te, p, s->id, s->gid, s->rev, pa);
1054
7.27k
        }
1055
1056
7.30k
        (void)THashDecrUsecnt(res.data);
1057
7.30k
        THashDataUnlock(res.data);
1058
7.30k
        return r;
1059
7.30k
    }
1060
0
    return 0; // TODO error?
1061
7.30k
}
1062
1063
/**
1064
 *  \retval 2 silent match (no alert but apply actions)
1065
 *  \retval 1 normal match
1066
 *  \retval 0 no match
1067
 */
1068
static int ThresholdHandlePacketFlow(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
1069
        Flow *f, Packet *p, const DetectThresholdData *td, uint32_t sid, uint32_t gid, uint32_t rev,
1070
        PacketAlert *pa)
1071
80
{
1072
80
    int ret = 0;
1073
80
    ThresholdEntry *found = ThresholdFlowLookupEntry(f, sid, gid, rev, p->tenant_id);
1074
80
    SCLogDebug("found %p sid %u gid %u rev %u", found, sid, gid, rev);
1075
1076
80
    if (found == NULL) {
1077
36
        FlowThresholdEntryList *new = SCCalloc(1, sizeof(*new));
1078
36
        if (new == NULL)
1079
0
            return 0;
1080
1081
        // new threshold, set up
1082
36
        ret = ThresholdSetup(td, &new->threshold, p->ts, sid, gid, rev, p->tenant_id);
1083
1084
36
        if (AddEntryToFlow(f, new, p->ts) == -1) {
1085
0
            SCFree(new);
1086
0
            return 0;
1087
0
        }
1088
44
    } else {
1089
        // existing, check/update
1090
44
        ret = ThresholdCheckUpdate(de_ctx, det_ctx, td, found, p, sid, gid, rev, pa);
1091
44
    }
1092
80
    return ret;
1093
80
}
1094
1095
/**
1096
 * \brief Make the threshold logic for signatures
1097
 *
1098
 * \param de_ctx Detection Context
1099
 * \param tsh_ptr Threshold element
1100
 * \param p Packet structure
1101
 * \param s Signature structure
1102
 *
1103
 * \retval 2 silent match (no alert but apply actions)
1104
 * \retval 1 alert on this event
1105
 * \retval 0 do not alert on this event
1106
 */
1107
int PacketAlertThreshold(const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
1108
        const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa)
1109
7.38k
{
1110
7.38k
    SCEnter();
1111
1112
7.38k
    int ret = 0;
1113
7.38k
    if (td == NULL) {
1114
0
        SCReturnInt(0);
1115
0
    }
1116
1117
7.38k
    if (td->type == TYPE_SUPPRESS) {
1118
0
        ret = ThresholdHandlePacketSuppress(p,td,s->id,s->gid);
1119
7.38k
    } else if (td->track == TRACK_SRC) {
1120
1.87k
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
1121
0
            int cache_ret = CheckCache(det_ctx, p, td->track, s->id, s->gid, s->rev);
1122
0
            if (cache_ret >= 0) {
1123
0
                SCReturnInt(cache_ret);
1124
0
            }
1125
0
        }
1126
1127
1.87k
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1128
5.51k
    } else if (td->track == TRACK_DST) {
1129
12
        if (PacketIsIPv4(p) && (td->type == TYPE_LIMIT || td->type == TYPE_BOTH)) {
1130
0
            int cache_ret = CheckCache(det_ctx, p, td->track, s->id, s->gid, s->rev);
1131
0
            if (cache_ret >= 0) {
1132
0
                SCReturnInt(cache_ret);
1133
0
            }
1134
0
        }
1135
1136
12
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1137
5.50k
    } else if (td->track == TRACK_BOTH) {
1138
0
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1139
5.50k
    } else if (td->track == TRACK_RULE) {
1140
5.42k
        ret = ThresholdGetFromHash(de_ctx, det_ctx, &ctx, p, s, td, pa);
1141
5.42k
    } else if (td->track == TRACK_FLOW) {
1142
80
        if (p->flow) {
1143
80
            ret = ThresholdHandlePacketFlow(
1144
80
                    de_ctx, det_ctx, p->flow, p, td, s->id, s->gid, s->rev, pa);
1145
80
        }
1146
80
    }
1147
1148
7.38k
    SCReturnInt(ret);
1149
7.38k
}
1150
1151
/**
1152
 * @}
1153
 */