diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /src/util-thash.c | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/util-thash.c | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/src/util-thash.c b/src/util-thash.c new file mode 100644 index 0000000..6443990 --- /dev/null +++ b/src/util-thash.c @@ -0,0 +1,820 @@ +/* Copyright (C) 2007-2016 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien <victor@inliniac.net> + * + */ + +#include "suricata-common.h" +#include "conf.h" + +#include "util-debug.h" +#include "util-thash.h" + +#include "util-random.h" +#include "util-misc.h" +#include "util-byte.h" + +#include "util-hash-lookup3.h" +#include "util-validate.h" + +static THashData *THashGetUsed(THashTableContext *ctx); +static void THashDataEnqueue (THashDataQueue *q, THashData *h); + +void THashDataMoveToSpare(THashTableContext *ctx, THashData *h) +{ + THashDataEnqueue(&ctx->spare_q, h); + (void) SC_ATOMIC_SUB(ctx->counter, 1); +} + +static THashDataQueue *THashDataQueueInit (THashDataQueue *q) +{ + if (q != NULL) { + memset(q, 0, sizeof(THashDataQueue)); + HQLOCK_INIT(q); + } + return q; +} + +THashDataQueue *THashDataQueueNew(void) +{ + THashDataQueue *q = (THashDataQueue *)SCMalloc(sizeof(THashDataQueue)); + if (q == NULL) { + SCLogError("Fatal error encountered in THashDataQueueNew. Exiting..."); + exit(EXIT_SUCCESS); + } + q = THashDataQueueInit(q); + return q; +} + +/** + * \brief Destroy a queue + * + * \param q the queue to destroy + */ +static void THashDataQueueDestroy (THashDataQueue *q) +{ + HQLOCK_DESTROY(q); +} + +/** + * \brief add to queue + * + * \param q queue + * \param h data + */ +static void THashDataEnqueue (THashDataQueue *q, THashData *h) +{ +#ifdef DEBUG + BUG_ON(q == NULL || h == NULL); +#endif + + HQLOCK_LOCK(q); + + /* more data in queue */ + if (q->top != NULL) { + h->next = q->top; + q->top->prev = h; + q->top = h; + /* only data */ + } else { + q->top = h; + q->bot = h; + } + q->len++; +#ifdef DBG_PERF + if (q->len > q->dbg_maxlen) + q->dbg_maxlen = q->len; +#endif /* DBG_PERF */ + HQLOCK_UNLOCK(q); +} + +/** + * \brief remove data from the queue + * + * \param q queue + * + * \retval h data or NULL if empty list. + */ +static THashData *THashDataDequeue (THashDataQueue *q) +{ + HQLOCK_LOCK(q); + + THashData *h = q->bot; + if (h == NULL) { + HQLOCK_UNLOCK(q); + return NULL; + } + + /* more packets in queue */ + if (q->bot->prev != NULL) { + q->bot = q->bot->prev; + q->bot->next = NULL; + /* just the one we remove, so now empty */ + } else { + q->top = NULL; + q->bot = NULL; + } + +#ifdef DEBUG + BUG_ON(q->len == 0); +#endif + if (q->len > 0) + q->len--; + + h->next = NULL; + h->prev = NULL; + + HQLOCK_UNLOCK(q); + return h; +} + +#if 0 +static uint32_t THashDataQueueLen(THashDataQueue *q) +{ + uint32_t len; + HQLOCK_LOCK(q); + len = q->len; + HQLOCK_UNLOCK(q); + return len; +} +#endif + +static THashData *THashDataAlloc(THashTableContext *ctx) +{ + const size_t data_size = THASH_DATA_SIZE(ctx); + + if (!(THASH_CHECK_MEMCAP(ctx, data_size))) { + return NULL; + } + + (void) SC_ATOMIC_ADD(ctx->memuse, data_size); + + THashData *h = SCCalloc(1, data_size); + if (unlikely(h == NULL)) + goto error; + + /* points to data right after THashData block */ + h->data = (uint8_t *)h + sizeof(THashData); + +// memset(h, 0x00, data_size); + + SCMutexInit(&h->m, NULL); + SC_ATOMIC_INIT(h->use_cnt); + return h; + +error: + return NULL; +} + +static void THashDataFree(THashTableContext *ctx, THashData *h) +{ + if (h != NULL) { + DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(h->use_cnt) != 0); + + if (h->data != NULL) { + ctx->config.DataFree(h->data); + } + SCMutexDestroy(&h->m); + SCFree(h); + (void) SC_ATOMIC_SUB(ctx->memuse, THASH_DATA_SIZE(ctx)); + } +} + +#define THASH_DEFAULT_HASHSIZE 4096 +#define THASH_DEFAULT_MEMCAP 16777216 +#define THASH_DEFAULT_PREALLOC 1000 + +#define GET_VAR(prefix,name) \ + snprintf(varname, sizeof(varname), "%s.%s", (prefix), (name)) + +/** \brief initialize the configuration + * \warning Not thread safe */ +static int THashInitConfig(THashTableContext *ctx, const char *cnf_prefix) +{ + char varname[256]; + + SCLogDebug("initializing thash engine..."); + + /* Check if we have memcap and hash_size defined at config */ + const char *conf_val; + uint32_t configval = 0; + + /** set config values for memcap, prealloc and hash_size */ + GET_VAR(cnf_prefix, "memcap"); + if ((ConfGet(varname, &conf_val)) == 1) + { + if (ParseSizeStringU64(conf_val, &ctx->config.memcap) < 0) { + SCLogError("Error parsing %s " + "from conf file - %s. Killing engine", + varname, conf_val); + return -1; + } + } + GET_VAR(cnf_prefix, "hash-size"); + if ((ConfGet(varname, &conf_val)) == 1) + { + if (StringParseUint32(&configval, 10, (uint16_t)strlen(conf_val), conf_val) > 0) { + ctx->config.hash_size = configval; + } + } + + GET_VAR(cnf_prefix, "prealloc"); + if ((ConfGet(varname, &conf_val)) == 1) + { + if (StringParseUint32(&configval, 10, (uint16_t)strlen(conf_val), conf_val) > 0) { + ctx->config.prealloc = configval; + } else { + WarnInvalidConfEntry(varname, "%"PRIu32, ctx->config.prealloc); + } + } + + /* alloc hash memory */ + uint64_t hash_size = ctx->config.hash_size * sizeof(THashHashRow); + if (!(THASH_CHECK_MEMCAP(ctx, hash_size))) { + SCLogError("allocating hash failed: " + "max hash memcap is smaller than projected hash size. " + "Memcap: %" PRIu64 ", Hash table size %" PRIu64 ". Calculate " + "total hash size by multiplying \"hash-size\" with %" PRIuMAX ", " + "which is the hash bucket size.", + ctx->config.memcap, hash_size, (uintmax_t)sizeof(THashHashRow)); + return -1; + } + ctx->array = SCMallocAligned(ctx->config.hash_size * sizeof(THashHashRow), CLS); + if (unlikely(ctx->array == NULL)) { + SCLogError("Fatal error encountered in THashInitConfig. Exiting..."); + return -1; + } + memset(ctx->array, 0, ctx->config.hash_size * sizeof(THashHashRow)); + + uint32_t i = 0; + for (i = 0; i < ctx->config.hash_size; i++) { + HRLOCK_INIT(&ctx->array[i]); + } + (void) SC_ATOMIC_ADD(ctx->memuse, (ctx->config.hash_size * sizeof(THashHashRow))); + + /* pre allocate prealloc */ + for (i = 0; i < ctx->config.prealloc; i++) { + if (!(THASH_CHECK_MEMCAP(ctx, THASH_DATA_SIZE(ctx)))) { + SCLogError("preallocating data failed: " + "max thash memcap reached. Memcap %" PRIu64 ", " + "Memuse %" PRIu64 ".", + ctx->config.memcap, + ((uint64_t)SC_ATOMIC_GET(ctx->memuse) + THASH_DATA_SIZE(ctx))); + return -1; + } + + THashData *h = THashDataAlloc(ctx); + if (h == NULL) { + SCLogError("preallocating data failed: %s", strerror(errno)); + return -1; + } + THashDataEnqueue(&ctx->spare_q,h); + } + + return 0; +} + +THashTableContext *THashInit(const char *cnf_prefix, size_t data_size, + int (*DataSet)(void *, void *), void (*DataFree)(void *), uint32_t (*DataHash)(void *), + bool (*DataCompare)(void *, void *), bool reset_memcap, uint64_t memcap, uint32_t hashsize) +{ + THashTableContext *ctx = SCCalloc(1, sizeof(*ctx)); + BUG_ON(!ctx); + + ctx->config.data_size = data_size; + ctx->config.DataSet = DataSet; + ctx->config.DataFree = DataFree; + ctx->config.DataHash = DataHash; + ctx->config.DataCompare = DataCompare; + + /* set defaults */ + ctx->config.hash_rand = (uint32_t)RandomGet(); + ctx->config.hash_size = hashsize > 0 ? hashsize : THASH_DEFAULT_HASHSIZE; + /* Reset memcap in case of loading from file to the highest possible value + unless defined by the rule keyword */ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // limit memcap size to default when fuzzing + ctx->config.memcap = THASH_DEFAULT_MEMCAP; +#else + if (memcap > 0) { + ctx->config.memcap = memcap; + } else { + ctx->config.memcap = reset_memcap ? UINT64_MAX : THASH_DEFAULT_MEMCAP; + } +#endif + ctx->config.prealloc = THASH_DEFAULT_PREALLOC; + + SC_ATOMIC_INIT(ctx->counter); + SC_ATOMIC_INIT(ctx->memuse); + SC_ATOMIC_INIT(ctx->prune_idx); + THashDataQueueInit(&ctx->spare_q); + + if (THashInitConfig(ctx, cnf_prefix) < 0) { + THashShutdown(ctx); + ctx = NULL; + } + return ctx; +} + +/* \brief Set memcap to current memuse + * */ +void THashConsolidateMemcap(THashTableContext *ctx) +{ + ctx->config.memcap = MAX(SC_ATOMIC_GET(ctx->memuse), ctx->config.memcap); + SCLogDebug("memcap after load set to: %" PRIu64, ctx->config.memcap); +} + +/** \brief shutdown the flow engine + * \warning Not thread safe */ +void THashShutdown(THashTableContext *ctx) +{ + THashData *h; + + /* free spare queue */ + while ((h = THashDataDequeue(&ctx->spare_q))) { + BUG_ON(SC_ATOMIC_GET(h->use_cnt) > 0); + THashDataFree(ctx, h); + } + + /* clear and free the hash */ + if (ctx->array != NULL) { + for (uint32_t u = 0; u < ctx->config.hash_size; u++) { + h = ctx->array[u].head; + while (h) { + THashData *n = h->next; + THashDataFree(ctx, h); + h = n; + } + + HRLOCK_DESTROY(&ctx->array[u]); + } + SCFreeAligned(ctx->array); + ctx->array = NULL; + } + (void) SC_ATOMIC_SUB(ctx->memuse, ctx->config.hash_size * sizeof(THashHashRow)); + THashDataQueueDestroy(&ctx->spare_q); + SCFree(ctx); + return; +} + +/** \brief Walk the hash + * + */ +int THashWalk(THashTableContext *ctx, THashFormatFunc FormatterFunc, THashOutputFunc OutputterFunc, void *output_ctx) +{ + uint32_t u; + + if (ctx->array == NULL) + return -1; + + bool err = false; + for (u = 0; u < ctx->config.hash_size; u++) { + THashHashRow *hb = &ctx->array[u]; + HRLOCK_LOCK(hb); + THashData *h = hb->head; + while (h) { + char output_string[1024] = ""; + int size = FormatterFunc(h->data, output_string, sizeof(output_string)); + if (size > 0) { + if (OutputterFunc(output_ctx, (const uint8_t *)output_string, size) < 0) { + err = true; + break; + } + } + h = h->next; + } + HRLOCK_UNLOCK(hb); + if (err == true) + return -1; + } + return 0; +} + +/** \brief Cleanup the thash engine + * + * Cleanup the thash engine from tag and threshold. + * + */ +void THashCleanup(THashTableContext *ctx) +{ + uint32_t u; + + if (ctx->array == NULL) + return; + + for (u = 0; u < ctx->config.hash_size; u++) { + THashHashRow *hb = &ctx->array[u]; + HRLOCK_LOCK(hb); + THashData *h = hb->head; + while (h) { + if ((SC_ATOMIC_GET(h->use_cnt) > 0)) { + h = h->next; + } else { + THashData *n = h->next; + /* remove from the hash */ + if (h->prev != NULL) + h->prev->next = h->next; + if (h->next != NULL) + h->next->prev = h->prev; + if (hb->head == h) + hb->head = h->next; + if (hb->tail == h) + hb->tail = h->prev; + h->next = NULL; + h->prev = NULL; + THashDataMoveToSpare(ctx, h); + h = n; + } + } + HRLOCK_UNLOCK(hb); + } + return; +} + +/* calculate the hash key for this packet + * + * we're using: + * hash_rand -- set at init time + * source address + */ +static uint32_t THashGetKey(const THashConfig *cnf, void *data) +{ + uint32_t key; + + key = cnf->DataHash(data); + key %= cnf->hash_size; + + return key; +} + +static inline int THashCompare(const THashConfig *cnf, void *a, void *b) +{ + if (cnf->DataCompare(a, b)) + return 1; + return 0; +} + +/** + * \brief Get new data + * + * Get new data. We're checking memcap first and will try to make room + * if the memcap is reached. + * + * \retval h *LOCKED* data on succes, NULL on error. + */ +static THashData *THashDataGetNew(THashTableContext *ctx, void *data) +{ + THashData *h = NULL; + + /* get data from the spare queue */ + h = THashDataDequeue(&ctx->spare_q); + if (h == NULL) { + /* If we reached the max memcap, we get used data */ + if (!(THASH_CHECK_MEMCAP(ctx, THASH_DATA_SIZE(ctx)))) { + h = THashGetUsed(ctx); + if (h == NULL) { + return NULL; + } + + if (!SC_ATOMIC_GET(ctx->memcap_reached)) { + SC_ATOMIC_SET(ctx->memcap_reached, true); + } + + /* freed data, but it's unlocked */ + } else { + /* now see if we can alloc a new data */ + h = THashDataAlloc(ctx); + if (h == NULL) { + return NULL; + } + + /* data is initialized but *unlocked* */ + } + } else { + /* data has been recycled before it went into the spare queue */ + + /* data is initialized (recycled) but *unlocked* */ + } + + // setup the data + BUG_ON(ctx->config.DataSet(h->data, data) != 0); + + (void) SC_ATOMIC_ADD(ctx->counter, 1); + SCMutexLock(&h->m); + return h; +} + +/* + * returns a *LOCKED* data or NULL + */ + +struct THashDataGetResult +THashGetFromHash (THashTableContext *ctx, void *data) +{ + struct THashDataGetResult res = { .data = NULL, .is_new = false, }; + THashData *h = NULL; + + /* get the key to our bucket */ + uint32_t key = THashGetKey(&ctx->config, data); + /* get our hash bucket and lock it */ + THashHashRow *hb = &ctx->array[key]; + HRLOCK_LOCK(hb); + + /* see if the bucket already has data */ + if (hb->head == NULL) { + h = THashDataGetNew(ctx, data); + if (h == NULL) { + HRLOCK_UNLOCK(hb); + return res; + } + + /* data is locked */ + hb->head = h; + hb->tail = h; + + /* initialize and return */ + (void) THashIncrUsecnt(h); + + HRLOCK_UNLOCK(hb); + res.data = h; + res.is_new = true; + return res; + } + + /* ok, we have data in the bucket. Let's find out if it is our data */ + h = hb->head; + + /* see if this is the data we are looking for */ + if (THashCompare(&ctx->config, h->data, data) == 0) { + THashData *ph = NULL; /* previous data */ + + while (h) { + ph = h; + h = h->next; + + if (h == NULL) { + h = ph->next = THashDataGetNew(ctx, data); + if (h == NULL) { + HRLOCK_UNLOCK(hb); + return res; + } + hb->tail = h; + + /* data is locked */ + + h->prev = ph; + + /* initialize and return */ + (void) THashIncrUsecnt(h); + + HRLOCK_UNLOCK(hb); + res.data = h; + res.is_new = true; + return res; + } + + if (THashCompare(&ctx->config, h->data, data) != 0) { + /* we found our data, lets put it on top of the + * hash list -- this rewards active data */ + if (h->next) { + h->next->prev = h->prev; + } + if (h->prev) { + h->prev->next = h->next; + } + if (h == hb->tail) { + hb->tail = h->prev; + } + + h->next = hb->head; + h->prev = NULL; + hb->head->prev = h; + hb->head = h; + + /* found our data, lock & return */ + SCMutexLock(&h->m); + (void) THashIncrUsecnt(h); + HRLOCK_UNLOCK(hb); + res.data = h; + res.is_new = false; + /* coverity[missing_unlock : FALSE] */ + return res; + } + } + } + + /* lock & return */ + SCMutexLock(&h->m); + (void) THashIncrUsecnt(h); + HRLOCK_UNLOCK(hb); + res.data = h; + res.is_new = false; + /* coverity[missing_unlock : FALSE] */ + return res; +} + +/** \brief look up data in the hash + * + * \param data data to look up + * + * \retval h *LOCKED* data or NULL + */ +THashData *THashLookupFromHash (THashTableContext *ctx, void *data) +{ + THashData *h = NULL; + + /* get the key to our bucket */ + uint32_t key = THashGetKey(&ctx->config, data); + /* get our hash bucket and lock it */ + THashHashRow *hb = &ctx->array[key]; + HRLOCK_LOCK(hb); + + if (hb->head == NULL) { + HRLOCK_UNLOCK(hb); + return h; + } + + /* ok, we have data in the bucket. Let's find out if it is our data */ + h = hb->head; + + /* see if this is the data we are looking for */ + if (THashCompare(&ctx->config, h->data, data) == 0) { + while (h) { + h = h->next; + if (h == NULL) { + HRLOCK_UNLOCK(hb); + return h; + } + + if (THashCompare(&ctx->config, h->data, data) != 0) { + /* we found our data, lets put it on top of the + * hash list -- this rewards active data */ + if (h->next) { + h->next->prev = h->prev; + } + if (h->prev) { + h->prev->next = h->next; + } + if (h == hb->tail) { + hb->tail = h->prev; + } + + h->next = hb->head; + h->prev = NULL; + hb->head->prev = h; + hb->head = h; + + /* found our data, lock & return */ + SCMutexLock(&h->m); + (void) THashIncrUsecnt(h); + HRLOCK_UNLOCK(hb); + return h; + } + } + } + + /* lock & return */ + SCMutexLock(&h->m); + (void) THashIncrUsecnt(h); + HRLOCK_UNLOCK(hb); + return h; +} + +/** \internal + * \brief Get data from the hash directly. + * + * Called in conditions where the spare queue is empty and memcap is + * reached. + * + * Walks the hash until data can be freed. "prune_idx" atomic int makes + * sure we don't start at the top each time since that would clear the top + * of the hash leading to longer and longer search times under high + * pressure (observed). + * + * \retval h data or NULL + */ +static THashData *THashGetUsed(THashTableContext *ctx) +{ + uint32_t idx = SC_ATOMIC_GET(ctx->prune_idx) % ctx->config.hash_size; + uint32_t cnt = ctx->config.hash_size; + + while (cnt--) { + if (++idx >= ctx->config.hash_size) + idx = 0; + + THashHashRow *hb = &ctx->array[idx]; + + if (HRLOCK_TRYLOCK(hb) != 0) + continue; + + THashData *h = hb->tail; + if (h == NULL) { + HRLOCK_UNLOCK(hb); + continue; + } + + if (SCMutexTrylock(&h->m) != 0) { + HRLOCK_UNLOCK(hb); + continue; + } + + if (SC_ATOMIC_GET(h->use_cnt) > 0) { + HRLOCK_UNLOCK(hb); + SCMutexUnlock(&h->m); + continue; + } + + /* remove from the hash */ + if (h->prev != NULL) + h->prev->next = h->next; + if (h->next != NULL) + h->next->prev = h->prev; + if (hb->head == h) + hb->head = h->next; + if (hb->tail == h) + hb->tail = h->prev; + + h->next = NULL; + h->prev = NULL; + HRLOCK_UNLOCK(hb); + + if (h->data != NULL) { + ctx->config.DataFree(h->data); + } + SCMutexUnlock(&h->m); + + (void) SC_ATOMIC_ADD(ctx->prune_idx, (ctx->config.hash_size - cnt)); + return h; + } + + return NULL; +} + +/** + * \retval int -1 not found + * \retval int 0 found, but it was busy (ref cnt) + * \retval int 1 found and removed */ +int THashRemoveFromHash (THashTableContext *ctx, void *data) +{ + /* get the key to our bucket */ + uint32_t key = THashGetKey(&ctx->config, data); + /* get our hash bucket and lock it */ + THashHashRow *hb = &ctx->array[key]; + + HRLOCK_LOCK(hb); + THashData *h = hb->head; + while (h != NULL) { + /* see if this is the data we are looking for */ + if (THashCompare(&ctx->config, h->data, data) == 0) { + h = h->next; + continue; + } + + SCMutexLock(&h->m); + if (SC_ATOMIC_GET(h->use_cnt) > 0) { + SCMutexUnlock(&h->m); + HRLOCK_UNLOCK(hb); + return 0; + } + + /* remove from the hash */ + if (h->prev != NULL) + h->prev->next = h->next; + if (h->next != NULL) + h->next->prev = h->prev; + if (hb->head == h) + hb->head = h->next; + if (hb->tail == h) + hb->tail = h->prev; + + h->next = NULL; + h->prev = NULL; + SCMutexUnlock(&h->m); + HRLOCK_UNLOCK(hb); + THashDataFree(ctx, h); + SCLogDebug("found and removed"); + return 1; + } + + HRLOCK_UNLOCK(hb); + SCLogDebug("data not found"); + return -1; +} |