Introduce host table, make tag use it

Add a host table similar to the flow table. A hash using fine grained
locking. Flow manager for now takes care of book keeping / garbage
collecting.

Tag subsystem now uses this for host based tagging instead of the
global tag hash table. Because the latter used a global lock and the
new code uses very fine grained locking this patch should improve
scalability.
remotes/origin/HEAD
Victor Julien 14 years ago
parent db24258acf
commit a05df345de

@ -55,6 +55,8 @@ flow-bit.c flow-bit.h \
flow-alert-sid.c flow-alert-sid.h \
pkt-var.c pkt-var.h \
host.c host.h \
host-queue.c host-queue.h \
host-timeout.c host-timeout.h \
reputation.c reputation.h \
detect.c detect.h \
detect-engine-sigorder.c detect-engine-sigorder.h \

File diff suppressed because it is too large Load Diff

@ -27,10 +27,9 @@
#ifndef __DETECT_ENGINE_TAG_H__
#define __DETECT_ENGINE_TAG_H__
#include "host.h"
#include "detect.h"
#define TAG_HASH_SIZE 0xffff
/* This limit should be overwriten/predefined at the config file
* to limit the options to prevent possible DOS situations. We should also
* create a limit for bytes and a limit for number of packets */
@ -43,16 +42,18 @@
#define TAG_SIG_GEN 2
#define TAG_SIG_ID 1
void TagHashInit(DetectTagHostCtx *);
int TagHashAddTag(DetectTagHostCtx *, DetectTagDataEntry *, Packet *);
void TagContextDestroy(DetectTagHostCtx *);
void TagHandlePacket(DetectEngineCtx *, DetectEngineThreadCtx *,
Packet *);
int TagHashAddTag(DetectTagDataEntry *, Packet *);
int TagFlowAdd(Packet *, DetectTagDataEntry *);
void TagContextDestroy(void);
void TagHandlePacket(DetectEngineCtx *, DetectEngineThreadCtx *, Packet *);
void TagInitCtx(void);
void TagDestroyCtx(void);
void TagRestartCtx(void);
int TagTimeoutCheck(Host *, struct timeval *);
#endif /* __DETECT_ENGINE_TAG_H__ */

@ -1,4 +1,4 @@
/* Copyright (C) 2007-2011 Open Information Security Foundation
/* Copyright (C) 2007-2012 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
@ -50,7 +50,6 @@
#include "threads.h"
SC_ATOMIC_EXTERN(unsigned int, num_tags);
extern DetectTagHostCtx *tag_ctx;
/* format: tag: <type>, <count>, <metric>, [direction]; */
#define PARSE_REGEX "^\\s*(host|session)\\s*(,\\s*(\\d+)\\s*,\\s*(packets|bytes|seconds)\\s*(,\\s*(src|dst))?\\s*)?$"
@ -97,97 +96,6 @@ error:
return;
}
DetectTagDataEntry *DetectTagDataCopy(DetectTagDataEntry *dtd) {
DetectTagDataEntry *tde = SCMalloc(sizeof(DetectTagDataEntry));
if (tde == NULL) {
return NULL;
}
memset(tde, 0, sizeof(DetectTagDataEntry));
tde->sid = dtd->sid;
tde->gid = dtd->gid;
tde->td = dtd->td;
tde->first_ts.tv_sec = dtd->first_ts.tv_sec;
tde->first_ts.tv_usec = dtd->first_ts.tv_usec;
tde->last_ts.tv_sec = dtd->last_ts.tv_sec;
tde->last_ts.tv_usec = dtd->last_ts.tv_usec;
return tde;
}
/**
* \brief This function is used to add a tag to a session (type session)
* or update it if it's already installed. The number of times to
* allow an update is limited by DETECT_TAG_MATCH_LIMIT. This way
* repetitive matches to the same rule are limited of setting tags,
* to avoid DOS attacks
*
* \param p pointer to the current packet
* \param tde pointer to the new DetectTagDataEntry
*
* \retval 0 if the tde was added succesfuly
* \retval 1 if an entry of this sid/gid already exist and was updated
*/
int DetectTagFlowAdd(Packet *p, DetectTagDataEntry *tde) {
uint8_t updated = 0;
uint16_t num_tags = 0;
DetectTagDataEntry *iter = NULL;
if (p->flow == NULL)
return 1;
SCMutexLock(&p->flow->m);
if (p->flow->tag_list == NULL) {
p->flow->tag_list = SCMalloc(sizeof(DetectTagDataEntryList));
if (p->flow->tag_list == NULL) {
goto error;
}
memset(p->flow->tag_list, 0, sizeof(DetectTagDataEntryList));
} else {
iter = p->flow->tag_list->header_entry;
/* First iterate installed entries searching a duplicated sid/gid */
for (; iter != NULL; iter = iter->next) {
num_tags++;
if (iter->sid == tde->sid && iter->gid == tde->gid) {
iter->cnt_match++;
/* If so, update data, unless the maximum MATCH limit is
* reached. This prevents possible DOS attacks */
if (iter->cnt_match < DETECT_TAG_MATCH_LIMIT) {
/* Reset time and counters */
iter->first_ts.tv_sec = iter->last_ts.tv_sec = tde->first_ts.tv_sec;
iter->packets = 0;
iter->bytes = 0;
}
updated = 1;
break;
}
}
}
/* If there was no entry of this rule, prepend the new tde */
if (updated == 0 && num_tags < DETECT_TAG_MAX_TAGS) {
DetectTagDataEntry *new_tde = DetectTagDataCopy(tde);
if (new_tde != NULL) {
new_tde->next = p->flow->tag_list->header_entry;
p->flow->tag_list->header_entry = new_tde;
SC_ATOMIC_ADD(num_tags, 1);
}
} else if (num_tags == DETECT_TAG_MAX_TAGS) {
SCLogDebug("Max tags for sessions reached (%"PRIu16")", num_tags);
}
SCMutexUnlock(&p->flow->m);
return updated;
error:
SCMutexUnlock(&p->flow->m);
return 1;
}
/**
* \brief This function is used to setup a tag for session/host
*
@ -202,25 +110,40 @@ error:
int DetectTagMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Packet *p, Signature *s, SigMatch *m)
{
DetectTagData *td = (DetectTagData *) m->ctx;
DetectTagDataEntry tde;
memset(&tde, 0, sizeof(DetectTagDataEntry));
tde.sid = s->id;
tde.gid = s->gid;
tde.td = td;
tde.last_ts.tv_sec = tde.first_ts.tv_sec = p->ts.tv_usec;
switch (td->type) {
case DETECT_TAG_TYPE_HOST:
#ifdef DEBUG
BUG_ON(!(td->direction == DETECT_TAG_DIR_SRC || td->direction == DETECT_TAG_DIR_DST));
#endif
DetectTagDataEntry tde;
memset(&tde, 0, sizeof(DetectTagDataEntry));
tde.sid = s->id;
tde.gid = s->gid;
tde.last_ts = tde.first_ts = p->ts.tv_sec;
tde.metric = td->metric;
tde.count = td->count;
if (td->direction == DETECT_TAG_DIR_SRC)
tde.flags |= TAG_ENTRY_FLAG_DIR_SRC;
else if (td->direction == DETECT_TAG_DIR_DST)
tde.flags |= TAG_ENTRY_FLAG_DIR_DST;
SCLogDebug("Tagging Host with sid %"PRIu32":%"PRIu32"", s->id, s->gid);
TagHashAddTag(tag_ctx, &tde, p);
TagHashAddTag(&tde, p);
break;
case DETECT_TAG_TYPE_SESSION:
if (p->flow != NULL) {
/* If it already exists it will be updated */
DetectTagFlowAdd(p, &tde);
DetectTagDataEntry tde;
memset(&tde, 0, sizeof(DetectTagDataEntry));
tde.sid = s->id;
tde.gid = s->gid;
tde.last_ts = tde.first_ts = p->ts.tv_usec;
tde.metric = td->metric;
tde.count = td->count;
TagFlowAdd(p, &tde);
} else {
SCLogDebug("No flow to append the session tag");
}
@ -388,6 +311,19 @@ error:
}
/** \internal
* \brief this function will free memory associated with
* DetectTagDataEntry
*
* \param td pointer to DetectTagDataEntry
*/
static void DetectTagDataEntryFree(void *ptr) {
if (ptr != NULL) {
DetectTagDataEntry *dte = (DetectTagDataEntry *)ptr;
SCFree(dte);
}
}
/**
* \brief this function will free all the entries of a list
@ -397,8 +333,7 @@ error:
*/
void DetectTagDataListFree(void *ptr) {
if (ptr != NULL) {
DetectTagDataEntryList *list = (DetectTagDataEntryList *)ptr;
DetectTagDataEntry *entry = list->header_entry;
DetectTagDataEntry *entry = ptr;
while (entry != NULL) {
DetectTagDataEntry *next_entry = entry->next;
@ -406,19 +341,6 @@ void DetectTagDataListFree(void *ptr) {
SC_ATOMIC_SUB(num_tags, 1);
entry = next_entry;
}
SCFree(list);
}
}
/**
* \brief this function will free memory associated with
* DetectTagDataEntry
*
* \param td pointer to DetectTagDataEntry
*/
void DetectTagDataEntryFree(void *ptr) {
if (ptr != NULL) {
DetectTagDataEntry *dte = (DetectTagDataEntry *)ptr;
SCFree(dte);
}
}

@ -71,33 +71,35 @@ typedef struct DetectTagData_ {
/** This is the installed data at the session/global or host table */
typedef struct DetectTagDataEntry_ {
DetectTagData *td; /**< Pointer referencing the tag parameters */
uint8_t flags:3;
uint8_t metric:5;
uint8_t pad0;
uint16_t cnt_match; /**< number of times this tag was reset/updated */
uint32_t count; /**< count setting from rule */
uint32_t sid; /**< sid originating the tag */
uint32_t gid; /**< gid originating the tag */
uint32_t packets; /**< number of packets */
uint32_t bytes; /**< number of bytes */
struct timeval first_ts; /**< First time seen (for metric = seconds) */
struct timeval last_ts; /**< Last time seen (to prune old sessions) */
union {
uint32_t packets; /**< number of packets (metric packets) */
uint32_t bytes; /**< number of bytes (metric bytes) */
};
uint32_t first_ts; /**< First time seen (for metric = seconds) */
uint32_t last_ts; /**< Last time seen (to prune old sessions) */
#if __WORDSIZE == 64
uint32_t pad1;
#endif
struct DetectTagDataEntry_ *next; /**< Pointer to the next tag of this
* session/src_host/dst_host (if any from other rule) */
uint16_t cnt_match; /**< number of times this tag was reset/updated */
uint8_t skipped_first; /**< Used for output. The first packet write the
header with the data of the sig. The next packets use
gid/sid/rev of the tagging engine */
* session/src_host/dst_host (if any from other rule) */
} DetectTagDataEntry;
typedef struct DetectTagDataEntryList_ {
DetectTagDataEntry *header_entry;
Address addr; /**< Var used to store dst or src addr */
uint8_t ipv; /**< IP Version */
} DetectTagDataEntryList;
#define TAG_ENTRY_FLAG_DIR_SRC 0x01
#define TAG_ENTRY_FLAG_DIR_DST 0x02
#define TAG_ENTRY_FLAG_SKIPPED_FIRST 0x04
/* prototypes */
void DetectTagRegister (void);
void DetectTagDataFree(void *ptr);
void DetectTagDataEntryFree(void *ptr);
void DetectTagDataListFree(void *ptr);
DetectTagDataEntry *DetectTagDataCopy(DetectTagDataEntry *dtd);
#endif /* __DETECT_TAG_H__ */

@ -525,14 +525,6 @@ typedef struct ThresholdCtx_ {
uint32_t th_size;
} ThresholdCtx;
/** \brief tag ctx */
typedef struct DetectTagHostCtx_ {
HashListTable *tag_hash_table_ipv4; /**< Ipv4 hash table */
HashListTable *tag_hash_table_ipv6; /**< Ipv6 hash table */
SCMutex lock; /**< Mutex for the ctx */
struct timeval last_ts; /**< Last time the ctx was pruned */
} DetectTagHostCtx;
/** \brief main detection engine ctx */
typedef struct DetectEngineCtx_ {
uint8_t flags;

@ -60,6 +60,8 @@
#include "app-layer-parser.h"
#include "host-timeout.h"
/* Run mode selected at suricata.c */
extern int run_mode;
@ -378,7 +380,18 @@ void *FlowManagerThread(void *td)
struct timespec cond_time;
int flow_update_delay_sec = FLOW_NORMAL_MODE_UPDATE_DELAY_SEC;
int flow_update_delay_nsec = FLOW_NORMAL_MODE_UPDATE_DELAY_NSEC;
/* VJ leaving disabled for now, as hosts are only used by tags and the numbers
* are really low. Might confuse ppl
uint16_t flow_mgr_host_prune = SCPerfTVRegisterCounter("hosts.pruned", th_v,
SC_PERF_TYPE_UINT64,
"NULL");
uint16_t flow_mgr_host_active = SCPerfTVRegisterCounter("hosts.active", th_v,
SC_PERF_TYPE_Q_NORMAL,
"NULL");
uint16_t flow_mgr_host_spare = SCPerfTVRegisterCounter("hosts.spare", th_v,
SC_PERF_TYPE_Q_NORMAL,
"NULL");
*/
uint16_t flow_mgr_cnt_clo = SCPerfTVRegisterCounter("flow_mgr.closed_pruned", th_v,
SC_PERF_TYPE_UINT64,
"NULL");
@ -452,6 +465,16 @@ void *FlowManagerThread(void *td)
FlowTimeoutCounters counters = { 0, 0, 0, };
FlowTimeoutHash(&ts, 0 /* check all */, &counters);
//uint32_t hosts_pruned =
HostTimeoutHash(&ts);
/*
SCPerfCounterAddUI64(flow_mgr_host_prune, th_v->sc_perf_pca, (uint64_t)hosts_pruned);
uint32_t hosts_active = HostGetActiveCount();
SCPerfCounterSetUI64(flow_mgr_host_active, th_v->sc_perf_pca, (uint64_t)hosts_active);
uint32_t hosts_spare = HostGetSpareCount();
SCPerfCounterSetUI64(flow_mgr_host_spare, th_v->sc_perf_pca, (uint64_t)hosts_spare);
*/
SCPerfCounterAddUI64(flow_mgr_cnt_clo, th_v->sc_perf_pca, (uint64_t)counters.clo);
SCPerfCounterAddUI64(flow_mgr_cnt_new, th_v->sc_perf_pca, (uint64_t)counters.new);
SCPerfCounterAddUI64(flow_mgr_cnt_est, th_v->sc_perf_pca, (uint64_t)counters.est);

@ -290,7 +290,7 @@ typedef struct Flow_
struct SigGroupHead_ *sgh_toserver;
/** List of tags of this flow (from "tag" keyword of type "session") */
DetectTagDataEntryList *tag_list;
void *tag_list;
/* pointer to the var list */
GenericVar *flowvar;

@ -0,0 +1,138 @@
/* Copyright (C) 2007-2012 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>
*
* Host queue handler functions
*/
#include "suricata-common.h"
#include "threads.h"
#include "debug.h"
#include "host-queue.h"
#include "util-error.h"
#include "util-debug.h"
#include "util-print.h"
HostQueue *HostQueueInit (HostQueue *q) {
if (q != NULL) {
memset(q, 0, sizeof(HostQueue));
HQLOCK_INIT(q);
}
return q;
}
HostQueue *HostQueueNew() {
HostQueue *q = (HostQueue *)SCMalloc(sizeof(HostQueue));
if (q == NULL) {
SCLogError(SC_ERR_FATAL, "Fatal error encountered in HostQueueNew. Exiting...");
exit(EXIT_SUCCESS);
}
q = HostQueueInit(q);
return q;
}
/**
* \brief Destroy a host queue
*
* \param q the host queue to destroy
*/
void HostQueueDestroy (HostQueue *q) {
HQLOCK_DESTROY(q);
}
/**
* \brief add a host to a queue
*
* \param q queue
* \param h host
*/
void HostEnqueue (HostQueue *q, Host *h) {
#ifdef DEBUG
BUG_ON(q == NULL || h == NULL);
#endif
HQLOCK_LOCK(q);
/* more hosts in queue */
if (q->top != NULL) {
h->lnext = q->top;
q->top->lprev = h;
q->top = h;
/* only host */
} 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 a host from the queue
*
* \param q queue
*
* \retval h host or NULL if empty list.
*/
Host *HostDequeue (HostQueue *q) {
HQLOCK_LOCK(q);
Host *h = q->bot;
if (h == NULL) {
HQLOCK_UNLOCK(q);
return NULL;
}
/* more packets in queue */
if (q->bot->lprev != NULL) {
q->bot = q->bot->lprev;
q->bot->lnext = 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->lnext = NULL;
h->lprev = NULL;
HQLOCK_UNLOCK(q);
return h;
}
uint32_t HostQueueLen(HostQueue *q) {
uint32_t len;
HQLOCK_LOCK(q);
len = q->len;
HQLOCK_UNLOCK(q);
return len;
}

@ -0,0 +1,84 @@
/* Copyright (C) 2007-2012 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>
*/
#ifndef __HOST_QUEUE_H__
#define __HOST_QUEUE_H__
#include "suricata-common.h"
#include "host.h"
/** Spinlocks or Mutex for the host queues. */
//#define HQLOCK_SPIN
#define HQLOCK_MUTEX
#ifdef HQLOCK_SPIN
#ifdef HQLOCK_MUTEX
#error Cannot enable both HQLOCK_SPIN and HQLOCK_MUTEX
#endif
#endif
/* Define a queue for storing hosts */
typedef struct HostQueue_
{
Host *top;
Host *bot;
uint32_t len;
#ifdef DBG_PERF
uint32_t dbg_maxlen;
#endif /* DBG_PERF */
#ifdef HQLOCK_MUTEX
SCMutex m;
#elif defined HQLOCK_SPIN
SCSpinlock s;
#else
#error Enable HQLOCK_SPIN or HQLOCK_MUTEX
#endif
} HostQueue;
#ifdef HQLOCK_SPIN
#define HQLOCK_INIT(q) SCSpinInit(&(q)->s, 0)
#define HQLOCK_DESTROY(q) SCSpinDestroy(&(q)->s)
#define HQLOCK_LOCK(q) SCSpinLock(&(q)->s)
#define HQLOCK_TRYLOCK(q) SCSpinTrylock(&(q)->s)
#define HQLOCK_UNLOCK(q) SCSpinUnlock(&(q)->s)
#elif defined HQLOCK_MUTEX
#define HQLOCK_INIT(q) SCMutexInit(&(q)->m, NULL)
#define HQLOCK_DESTROY(q) SCMutexDestroy(&(q)->m)
#define HQLOCK_LOCK(q) SCMutexLock(&(q)->m)
#define HQLOCK_TRYLOCK(q) SCMutexTrylock(&(q)->m)
#define HQLOCK_UNLOCK(q) SCMutexUnlock(&(q)->m)
#else
#error Enable HQLOCK_SPIN or HQLOCK_MUTEX
#endif
/* prototypes */
HostQueue *HostQueueNew();
HostQueue *HostQueueInit(HostQueue *);
void HostQueueDestroy (HostQueue *);
void HostEnqueue (HostQueue *, Host *);
Host *HostDequeue (HostQueue *);
uint32_t HostQueueLen(HostQueue *);
#endif /* __HOST_QUEUE_H__ */

@ -0,0 +1,159 @@
/* Copyright (C) 2007-2012 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 "host.h"
#include "detect-engine-tag.h"
uint32_t HostGetSpareCount(void) {
return HostSpareQueueGetSize();
}
uint32_t HostGetActiveCount(void) {
return SC_ATOMIC_GET(host_counter);
}
/** \internal
* \brief See if we can really discard this host. Check use_cnt reference.
*
* \param h host
* \param ts timestamp
*
* \retval 0 not timed out just yet
* \retval 1 fully timed out, lets kill it
*/
static int HostHostTimedOut(Host *h, struct timeval *ts) {
int tags = 0;
int thresholds = 0;
/** never prune a host that is used by a packet
* we are currently processing in one of the threads */
if (h->use_cnt > 0) {
return 0;
}
if (h->tag && TagTimeoutCheck(h, ts) == 0) {
tags = 1;
}
if (h->threshold) {
// run threshold cleanup
}
if (tags || thresholds)
return 0;
return 1;
}
/**
* \internal
*
* \brief check all hosts in a hash row for timing out
*
* \param hb host hash row *LOCKED*
* \param h last host in the hash row
* \param ts timestamp
*
* \retval cnt timed out hosts
*/
static uint32_t HostHashRowTimeout(HostHashRow *hb, Host *h, struct timeval *ts)
{
uint32_t cnt = 0;
do {
if (SCMutexTrylock(&h->m) != 0) {
h = h->hprev;
continue;
}
Host *next_host = h->hprev;
/* check if the host is fully timed out and
* ready to be discarded. */
if (HostHostTimedOut(h, ts) == 1) {
/* remove from the hash */
if (h->hprev != NULL)
h->hprev->hnext = h->hnext;
if (h->hnext != NULL)
h->hnext->hprev = h->hprev;
if (hb->head == h)
hb->head = h->hnext;
if (hb->tail == h)
hb->tail = h->hprev;
h->hnext = NULL;
h->hprev = NULL;
HostClearMemory (h);
/* no one is referring to this host, use_cnt 0, removed from hash
* so we can unlock it and move it back to the spare queue. */
SCMutexUnlock(&h->m);
/* move to spare list */
HostMoveToSpare(h);
cnt++;
} else {
SCMutexUnlock(&h->m);
}
h = next_host;
} while (h != NULL);
return cnt;
}
/**
* \brief time out hosts from the hash
*
* \param ts timestamp
*
* \retval cnt number of timed out host
*/
uint32_t HostTimeoutHash(struct timeval *ts) {
uint32_t idx = 0;
uint32_t cnt = 0;
for (idx = 0; idx < host_config.hash_size; idx++) {
HostHashRow *hb = &host_hash[idx];
if (hb == NULL)
continue;
if (HRLOCK_TRYLOCK(hb) != 0)
continue;
/* host hash bucket is now locked */
if (hb->tail == NULL) {
HRLOCK_UNLOCK(hb);
continue;
}
/* we have a host, or more than one */
cnt += HostHashRowTimeout(hb, hb->tail, ts);
HRLOCK_UNLOCK(hb);
}
return cnt;
}

@ -0,0 +1,33 @@
/* Copyright (C) 2007-2012 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>
*/
#ifndef __HOST_TIMEOUT_H__
#define __HOST_TIMEOUT_H__
uint32_t HostTimeoutHash(struct timeval *ts);
uint32_t HostGetSpareCount(void);
uint32_t HostGetActiveCount(void);
#endif

@ -1,4 +1,4 @@
/* Copyright (C) 2007-2010 Open Information Security Foundation
/* Copyright (C) 2007-2012 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
@ -20,18 +20,49 @@
*
* \author Victor Julien <victor@inliniac.net>
*
* Information about hosts for ip reputation.
* Information about hosts.
*/
#include "suricata-common.h"
#include "conf.h"
#include "util-debug.h"
#include "host.h"
#include "util-random.h"
#include "util-misc.h"
#include "util-byte.h"
#include "host-queue.h"
#include "detect-tag.h"
static Host *HostGetUsedHost(void);
/** queue with spare hosts */
static HostQueue host_spare_q;
uint32_t HostSpareQueueGetSize(void) {
return HostQueueLen(&host_spare_q);
}
void HostMoveToSpare(Host *h) {
HostEnqueue(&host_spare_q, h);
SC_ATOMIC_SUB(host_counter, 1);
}
Host *HostAlloc(void) {
if ((SC_ATOMIC_GET(host_memuse) + sizeof(Host)) > host_config.memcap) {
return NULL;
}
SC_ATOMIC_ADD(host_memuse, sizeof(Host));
Host *h = SCMalloc(sizeof(Host));
if (h == NULL)
goto error;
SCMutexInit(&h->m, NULL);
return h;
error:
@ -39,7 +70,13 @@ error:
}
void HostFree(Host *h) {
SCFree(h);
if (h != NULL) {
HostClearMemory(h);
SCMutexDestroy(&h->m);
SCFree(h);
SC_ATOMIC_SUB(host_memuse, sizeof(Host));
}
}
Host *HostNew(Address *a) {
@ -48,10 +85,7 @@ Host *HostNew(Address *a) {
goto error;
/* copy address */
/* set os and reputation to 0 */
h->os = HOST_OS_UNKNOWN;
h->reputation = HOST_REPU_UNKNOWN;
COPY_ADDRESS(a, &h->a);
return h;
@ -59,3 +93,503 @@ error:
return NULL;
}
void HostClearMemory(Host *h) {
if (h->tag != NULL) {
DetectTagDataListFree(h->tag);
h->tag = NULL;
}
}
#define HOST_DEFAULT_HASHSIZE 4096
#define HOST_DEFAULT_MEMCAP 16777216
#define HOST_DEFAULT_PREALLOC 10000
/** \brief initialize the configuration
* \warning Not thread safe */
void HostInitConfig(char quiet)
{
SCLogDebug("initializing host engine...");
memset(&host_config, 0, sizeof(host_config));
//SC_ATOMIC_INIT(flow_flags);
SC_ATOMIC_INIT(host_memuse);
SC_ATOMIC_INIT(host_prune_idx);
HostQueueInit(&host_spare_q);
unsigned int seed = RandomTimePreseed();
/* set defaults */
host_config.hash_rand = (int)( HOST_DEFAULT_HASHSIZE * (rand_r(&seed) / RAND_MAX + 1.0));
host_config.hash_size = HOST_DEFAULT_HASHSIZE;
host_config.memcap = HOST_DEFAULT_MEMCAP;
host_config.prealloc = HOST_DEFAULT_PREALLOC;
/* Check if we have memcap and hash_size defined at config */
char *conf_val;
uint32_t configval = 0;
/** set config values for memcap, prealloc and hash_size */
if ((ConfGet("host.memcap", &conf_val)) == 1)
{
if (ParseSizeStringU64(conf_val, &host_config.memcap) < 0) {
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing host.memcap "
"from conf file - %s. Killing engine",
conf_val);
exit(EXIT_FAILURE);
}
}
if ((ConfGet("host.hash-size", &conf_val)) == 1)
{
if (ByteExtractStringUint32(&configval, 10, strlen(conf_val),
conf_val) > 0) {
host_config.hash_size = configval;
}
}
if ((ConfGet("host.prealloc", &conf_val)) == 1)
{
if (ByteExtractStringUint32(&configval, 10, strlen(conf_val),
conf_val) > 0) {
host_config.prealloc = configval;
}
}
SCLogDebug("Host config from suricata.yaml: memcap: %"PRIu64", hash-size: "
"%"PRIu32", prealloc: %"PRIu32, host_config.memcap,
host_config.hash_size, host_config.prealloc);
/* alloc hash memory */
host_hash = SCCalloc(host_config.hash_size, sizeof(HostHashRow));
if (host_hash == NULL) {
SCLogError(SC_ERR_FATAL, "Fatal error encountered in HostInitConfig. Exiting...");
exit(EXIT_FAILURE);
}
memset(host_hash, 0, host_config.hash_size * sizeof(HostHashRow));
uint32_t i = 0;
for (i = 0; i < host_config.hash_size; i++) {
HRLOCK_INIT(&host_hash[i]);
}
SC_ATOMIC_ADD(host_memuse, (host_config.hash_size * sizeof(HostHashRow)));
if (quiet == FALSE) {
SCLogInfo("allocated %llu bytes of memory for the host hash... "
"%" PRIu32 " buckets of size %" PRIuMAX "",
SC_ATOMIC_GET(host_memuse), host_config.hash_size,
(uintmax_t)sizeof(HostHashRow));
}
/* pre allocate hosts */
for (i = 0; i < host_config.prealloc; i++) {
if ((SC_ATOMIC_GET(host_memuse) + sizeof(Host)) > host_config.memcap) {
printf("ERROR: HostAlloc failed (max host memcap reached): %s\n", strerror(errno));
exit(1);
}
Host *h = HostAlloc();
if (h == NULL) {
printf("ERROR: HostAlloc failed: %s\n", strerror(errno));
exit(1);
}
HostEnqueue(&host_spare_q,h);
}
if (quiet == FALSE) {
SCLogInfo("preallocated %" PRIu32 " hosts of size %" PRIuMAX "",
host_spare_q.len, (uintmax_t)sizeof(Host));
SCLogInfo("host memory usage: %llu bytes, maximum: %"PRIu64,
SC_ATOMIC_GET(host_memuse), host_config.memcap);
}
return;
}
/** \brief print some host stats
* \warning Not thread safe */
static void HostPrintStats (void)
{
#ifdef HOSTBITS_STATS
SCLogInfo("hostbits added: %" PRIu32 ", removed: %" PRIu32 ", max memory usage: %" PRIu32 "",
hostbits_added, hostbits_removed, hostbits_memuse_max);
#endif /* HOSTBITS_STATS */
return;
}
/** \brief shutdown the flow engine
* \warning Not thread safe */
void HostShutdown(void)
{
Host *h;
uint32_t u;
HostPrintStats();
/* free spare queue */
while((h = HostDequeue(&host_spare_q))) {
BUG_ON(h->use_cnt > 0);
HostFree(h);
}
/* clear and free the hash */
if (host_hash != NULL) {
for (u = 0; u < host_config.hash_size; u++) {
Host *h = host_hash[u].head;
while (h) {
Host *n = h->hnext;
HostClearMemory(h);
HostFree(h);
h = n;
}
HRLOCK_DESTROY(&host_hash[u]);
}
SCFree(host_hash);
host_hash = NULL;
}
SC_ATOMIC_SUB(host_memuse, host_config.hash_size * sizeof(HostHashRow));
HostQueueDestroy(&host_spare_q);
SC_ATOMIC_DESTROY(flow_prune_idx);
SC_ATOMIC_DESTROY(flow_memuse);
//SC_ATOMIC_DESTROY(flow_flags);
return;
}
/* calculate the hash key for this packet
*
* we're using:
* hash_rand -- set at init time
* source address
*/
uint32_t HostGetKey(Address *a) {
uint32_t key;
if (a->family == AF_INET) {
key = (host_config.hash_rand + a->addr_data32[0]) % host_config.hash_size;
} else if (a->family == AF_INET6) {
key = (host_config.hash_rand + a->addr_data32[0] + \
a->addr_data32[1] + a->addr_data32[2] + \
a->addr_data32[3]) % host_config.hash_size;
} else
key = 0;
return key;
}
/* Since two or more hosts can have the same hash key, we need to compare
* the flow with the current flow key. */
#define CMP_HOST(h,a) \
(CMP_ADDR(&(h)->a, (a)))
static inline int HostCompare(Host *h, Address *a) {
return CMP_HOST(h, a);
}
/**
* \brief Get a new host
*
* Get a new host. We're checking memcap first and will try to make room
* if the memcap is reached.
*
* \retval h *LOCKED* host on succes, NULL on error.
*/
static Host *HostGetNew(Address *a) {
Host *h = NULL;
/* get a host from the spare queue */
h = HostDequeue(&host_spare_q);
if (h == NULL) {
/* If we reached the max memcap, we get a used host */
if ((SC_ATOMIC_GET(host_memuse) + sizeof(Host)) > host_config.memcap) {
/* declare state of emergency */
//if (!(SC_ATOMIC_GET(host_flags) & HOST_EMERGENCY)) {
// SC_ATOMIC_OR(host_flags, HOST_EMERGENCY);
/* under high load, waking up the flow mgr each time leads
* to high cpu usage. Flows are not timed out much faster if
* we check a 1000 times a second. */
// FlowWakeupFlowManagerThread();
//}
h = HostGetUsedHost();
if (h == NULL) {
return NULL;
}
/* freed a host, but it's unlocked */
} else {
/* now see if we can alloc a new host */
h = HostNew(a);
if (h == NULL) {
return NULL;
}
/* host is initialized but *unlocked* */
}
} else {
/* host has been recycled before it went into the spare queue */
/* host is initialized (recylced) but *unlocked* */
}
SC_ATOMIC_ADD(host_counter, 1);
SCMutexLock(&h->m);
return h;
}
#define HostIncrUsecnt(h) \
(h)->use_cnt++
#define HostDecrUsecnt(h) \
(h)->use_cnt--
void HostInit(Host *h, Address *a) {
// SCMutexLock(&h->m);
COPY_ADDRESS(a, &h->a);
HostIncrUsecnt(h);
}
void HostRelease(Host *h) {
HostDecrUsecnt(h);
SCMutexUnlock(&h->m);
}
/* HostGetHostFromHash
*
* Hash retrieval function for hosts. Looks up the hash bucket containing the
* host pointer. Then compares the packet with the found host to see if it is
* the host we need. If it isn't, walk the list until the right host is found.
*
* returns a *LOCKED* host or NULL
*/
Host *HostGetHostFromHash (Address *a)
{
Host *h = NULL;
/* get the key to our bucket */
uint32_t key = HostGetKey(a);
/* get our hash bucket and lock it */
HostHashRow *hb = &host_hash[key];
HRLOCK_LOCK(hb);
/* see if the bucket already has a host */
if (hb->head == NULL) {
h = HostGetNew(a);
if (h == NULL) {
HRLOCK_UNLOCK(hb);
return NULL;
}
/* host is locked */
hb->head = h;
hb->tail = h;
/* got one, now lock, initialize and return */
HostInit(h,a);
HRLOCK_UNLOCK(hb);
return h;
}
/* ok, we have a host in the bucket. Let's find out if it is our host */
h = hb->head;
/* see if this is the host we are looking for */
if (HostCompare(h, a) == 0) {
Host *ph = NULL; /* previous host */
while (h) {
ph = h;
h = h->hnext;
if (h == NULL) {
h = ph->hnext = HostGetNew(a);
if (h == NULL) {
HRLOCK_UNLOCK(hb);
return NULL;
}
hb->tail = h;
/* host is locked */
h->hprev = ph;
/* initialize and return */
HostInit(h,a);
HRLOCK_UNLOCK(hb);
return h;
}
if (HostCompare(h, a) != 0) {
/* we found our host, lets put it on top of the
* hash list -- this rewards active hosts */
if (h->hnext) {
h->hnext->hprev = h->hprev;
}
if (h->hprev) {
h->hprev->hnext = h->hnext;
}
if (h == hb->tail) {
hb->tail = h->hprev;
}
h->hnext = hb->head;
h->hprev = NULL;
hb->head->hprev = h;
hb->head = h;
/* found our host, lock & return */
SCMutexLock(&h->m);
HostIncrUsecnt(h);
HRLOCK_UNLOCK(hb);
return h;
}
}
}
/* lock & return */
SCMutexLock(&h->m);
HostIncrUsecnt(h);
HRLOCK_UNLOCK(hb);
return h;
}
/** \brief look up a host in the hash
*
* \param a address to look up
*
* \retval h *LOCKED* host or NULL
*/
Host *HostLookupHostFromHash (Address *a)
{
Host *h = NULL;
/* get the key to our bucket */
uint32_t key = HostGetKey(a);
/* get our hash bucket and lock it */
HostHashRow *hb = &host_hash[key];
HRLOCK_LOCK(hb);
/* see if the bucket already has a host */
if (hb->head == NULL) {
HRLOCK_UNLOCK(hb);
return h;
}
/* ok, we have a host in the bucket. Let's find out if it is our host */
h = hb->head;
/* see if this is the host we are looking for */
if (HostCompare(h, a) == 0) {
while (h) {
h = h->hnext;
if (h == NULL) {
HRLOCK_UNLOCK(hb);
return h;
}
if (HostCompare(h, a) != 0) {
/* we found our host, lets put it on top of the
* hash list -- this rewards active hosts */
if (h->hnext) {
h->hnext->hprev = h->hprev;
}
if (h->hprev) {
h->hprev->hnext = h->hnext;
}
if (h == hb->tail) {
hb->tail = h->hprev;
}
h->hnext = hb->head;
h->hprev = NULL;
hb->head->hprev = h;
hb->head = h;
/* found our host, lock & return */
SCMutexLock(&h->m);
HostIncrUsecnt(h);
HRLOCK_UNLOCK(hb);
return h;
}
}
}
/* lock & return */
SCMutexLock(&h->m);
HostIncrUsecnt(h);
HRLOCK_UNLOCK(hb);
return h;
}
/** \internal
* \brief Get a host from the hash directly.
*
* Called in conditions where the spare queue is empty and memcap is reached.
*
* Walks the hash until a host can be freed. "host_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 host or NULL
*/
static Host *HostGetUsedHost(void) {
uint32_t idx = SC_ATOMIC_GET(host_prune_idx) % host_config.hash_size;
uint32_t cnt = host_config.hash_size;
while (cnt--) {
if (idx++ >= host_config.hash_size)
idx = 0;
HostHashRow *hb = &host_hash[idx];
if (hb == NULL)
continue;
if (HRLOCK_TRYLOCK(hb) != 0)
continue;
Host *h = hb->tail;
if (h == NULL) {
HRLOCK_UNLOCK(hb);
continue;
}
if (SCMutexTrylock(&h->m) != 0) {
HRLOCK_UNLOCK(hb);
continue;
}
/** never prune a host that is used by a packets
* we are currently processing in one of the threads */
if (h->use_cnt > 0) {
HRLOCK_UNLOCK(hb);
SCMutexUnlock(&h->m);
continue;
}
/* remove from the hash */
if (h->hprev != NULL)
h->hprev->hnext = h->hnext;
if (h->hnext != NULL)
h->hnext->hprev = h->hprev;
if (hb->head == h)
hb->head = h->hnext;
if (hb->tail == h)
hb->tail = h->hprev;
h->hnext = NULL;
h->hprev = NULL;
HRLOCK_UNLOCK(hb);
HostClearMemory (h);
SCMutexUnlock(&h->m);
SC_ATOMIC_ADD(host_prune_idx, (host_config.hash_size - cnt));
return h;
}
return NULL;
}

@ -1,4 +1,4 @@
/* Copyright (C) 2007-2010 Open Information Security Foundation
/* Copyright (C) 2007-2012 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
@ -25,35 +25,91 @@
#define __HOST_H__
#include "decode.h"
#include "util-hash.h"
#include "util-bloomfilter-counting.h"
typedef struct HostTable_ {
SCMutex m;
/** Spinlocks or Mutex for the flow buckets. */
//#define HRLOCK_SPIN
#define HRLOCK_MUTEX
/* storage & lookup */
HashTable *hash;
BloomFilterCounting *bf;
#ifdef HRLOCK_SPIN
#ifdef HRLOCK_MUTEX
#error Cannot enable both HRLOCK_SPIN and HRLOCK_MUTEX
#endif
#endif
uint32_t cnt;
} HostTable;
#ifdef HRLOCK_SPIN
#define HRLOCK_TYPE SCSpinlock
#define HRLOCK_INIT(fb) SCSpinInit(&(fb)->lock, 0)
#define HRLOCK_DESTROY(fb) SCSpinDestroy(&(fb)->lock)
#define HRLOCK_LOCK(fb) SCSpinLock(&(fb)->lock)
#define HRLOCK_TRYLOCK(fb) SCSpinTrylock(&(fb)->lock)
#define HRLOCK_UNLOCK(fb) SCSpinUnlock(&(fb)->lock)
#elif defined HRLOCK_MUTEX
#define HRLOCK_TYPE SCMutex
#define HRLOCK_INIT(fb) SCMutexInit(&(fb)->lock, NULL)
#define HRLOCK_DESTROY(fb) SCMutexDestroy(&(fb)->lock)
#define HRLOCK_LOCK(fb) SCMutexLock(&(fb)->lock)
#define HRLOCK_TRYLOCK(fb) SCMutexTrylock(&(fb)->lock)
#define HRLOCK_UNLOCK(fb) SCMutexUnlock(&(fb)->lock)
#else
#error Enable HRLOCK_SPIN or HRLOCK_MUTEX
#endif
typedef struct Host_ {
/** host mutex */
SCMutex m;
Address addr;
uint8_t os;
uint8_t reputation;
/** host address -- ipv4 or ipv6 */
Address a;
/** use cnt, reference counter */
uint16_t use_cnt;
/** pointers to tag and threshold storage */
void *tag;
void *threshold;
uint64_t bytes;
uint32_t pkts;
/** hash pointers, protected by hash row mutex/spin */
struct Host_ *hnext;
struct Host_ *hprev;
/** list pointers, protected by host-queue mutex/spin */
struct Host_ *lnext;
struct Host_ *lprev;
} Host;
#define HOST_OS_UNKNOWN 0
/* XXX define more */
typedef struct HostHashRow_ {
HRLOCK_TYPE lock;
Host *head;
Host *tail;
} HostHashRow;
/** host hash table */
HostHashRow *host_hash;
#define HOST_VERBOSE 0
#define HOST_QUIET 1
typedef struct HostConfig_ {
uint64_t memcap;
uint32_t hash_rand;
uint32_t hash_size;
uint32_t prealloc;
} HostConfig;
HostConfig host_config;
SC_ATOMIC_DECLARE(unsigned long long int,host_memuse);
SC_ATOMIC_DECLARE(unsigned int,host_counter);
SC_ATOMIC_DECLARE(unsigned int,host_prune_idx);
void HostInitConfig(char quiet);
void HostShutdown(void);
#define HOST_REPU_UNKNOWN 0
/* XXX see how we deal with this */
Host *HostLookupHostFromHash (Address *);
Host *HostGetHostFromHash (Address *);
void HostRelease(Host *);
void HostClearMemory(Host *);
void HostMoveToSpare(Host *);
uint32_t HostSpareQueueGetSize(void);
#endif /* __HOST_H__ */

@ -127,6 +127,8 @@
#include "flow-alert-sid.h"
#include "pkt-var.h"
#include "host.h"
#include "app-layer-detect-proto.h"
#include "app-layer-parser.h"
#include "app-layer-smb.h"
@ -1578,6 +1580,7 @@ int main(int argc, char **argv)
SCLogInfo("preallocated %"PRIiMAX" packets. Total memory %"PRIuMAX"",
max_pending_packets, (uintmax_t)(max_pending_packets*SIZE_OF_PACKET));
HostInitConfig(HOST_VERBOSE);
FlowInitConfig(FLOW_VERBOSE);
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
@ -1794,6 +1797,7 @@ int main(int argc, char **argv)
TmThreadKillThreads();
SCPerfReleaseResources();
FlowShutdown();
HostShutdown();
StreamTcpFreeConfig(STREAM_VERBOSE);
HTPFreeConfig();
HTPAtExitPrintStats();

Loading…
Cancel
Save