You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/util-thash.c

820 lines
21 KiB
C

/* 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"
static THashData *THashGetUsed(THashTableContext *ctx);
static void THashDataEnqueue (THashDataQueue *q, THashData *h);
static 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(SC_ERR_FATAL, "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) {
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(SC_ERR_SIZE_PARSE, "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, 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, 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(SC_ERR_THASH_INIT, "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(SC_ERR_THASH_INIT, "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(SC_ERR_THASH_INIT, "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(SC_ERR_THASH_INIT, "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), THASH_DEFAULT_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;
uint32_t u;
/* 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 (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) == TRUE)
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 (recylced) 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;
}