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/flow.c

1513 lines
48 KiB
C

/* Copyright (C) 2007-2010 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>
*
* Flow implementation.
*
* \todo Maybe place the flow that we get a packet for on top of the
* list in the bucket. This rewards active flows.
*/
#include "suricata-common.h"
#include "suricata.h"
#include "decode.h"
#include "conf.h"
#include "threadvars.h"
#include "tm-modules.h"
#include "tm-threads.h"
#include "util-random.h"
#include "util-time.h"
#include "flow.h"
#include "flow-queue.h"
#include "flow-hash.h"
#include "flow-util.h"
#include "flow-var.h"
#include "flow-private.h"
#include "util-unittest.h"
#include "util-byte.h"
#include "util-debug.h"
#include "util-privs.h"
#include "detect.h"
#include "detect-engine-state.h"
//#define FLOW_DEFAULT_HASHSIZE 262144
#define FLOW_DEFAULT_HASHSIZE 65536
//#define FLOW_DEFAULT_MEMCAP 128 * 1024 * 1024 /* 128 MB */
#define FLOW_DEFAULT_MEMCAP 32 * 1024 * 1024 /* 32 MB */
#define FLOW_DEFAULT_PREALLOC 10000
void FlowRegisterTests (void);
void FlowInitFlowProto();
static int FlowUpdateSpareFlows(void);
int FlowSetProtoTimeout(uint8_t , uint32_t ,uint32_t ,uint32_t);
int FlowSetProtoEmergencyTimeout(uint8_t , uint32_t ,uint32_t ,uint32_t);
static int FlowClearMemory(Flow *,uint8_t );
int FlowSetProtoFreeFunc(uint8_t, void (*Free)(void *));
int FlowSetFlowStateFunc (uint8_t , int (*GetProtoState)(void *));
/* Run mode selected at suricata.c */
extern int run_mode;
/** \brief Update the flows position in the queue's
* \param f Flow to requeue.
* \todo if we have a flow state func rely on that soly
*
* In-use flows are in the flow_new_q, flow_est_q lists or flow_close_q lists.
*/
void FlowUpdateQueue(Flow *f)
{
if (f->flags & FLOW_NEW_LIST) {
/* in the new list -- we consider a flow no longer
* new if we have seen at least 2 pkts in both ways. */
if (f->todstpktcnt && f->tosrcpktcnt) {
FlowRequeue(f, &flow_new_q[f->protomap], &flow_est_q[f->protomap]);
f->flags |= FLOW_EST_LIST; /* transition */
f->flags &= ~FLOW_NEW_LIST;
} else {
FlowRequeue(f, &flow_new_q[f->protomap], &flow_new_q[f->protomap]);
}
} else if (f->flags & FLOW_EST_LIST) {
if (flow_proto[f->protomap].GetProtoState != NULL) {
uint8_t state = flow_proto[f->protomap].GetProtoState(f->protoctx);
if (state == FLOW_STATE_CLOSED) {
f->flags |= FLOW_CLOSED_LIST; /* transition */
f->flags &=~ FLOW_EST_LIST;
SCLogDebug("flow %p was put into closing queue ts %"PRIuMAX"", f, (uintmax_t)f->lastts.tv_sec);
FlowRequeue(f, &flow_est_q[f->protomap], &flow_close_q[f->protomap]);
} else {
/* Pull and put back -- this way the flows on
* top of the list are least recently used. */
FlowRequeue(f, &flow_est_q[f->protomap], &flow_est_q[f->protomap]);
}
} else {
/* Pull and put back -- this way the flows on
* top of the list are least recently used. */
FlowRequeue(f, &flow_est_q[f->protomap], &flow_est_q[f->protomap]);
}
} else if (f->flags & FLOW_CLOSED_LIST){
/* Pull and put back -- this way the flows on
* top of the list are least recently used. */
FlowRequeue(f, &flow_close_q[f->protomap], &flow_close_q[f->protomap]);
}
}
/** FlowPrune
*
* Inspect top (last recently used) flow from the queue and see if
* we need to prune it.
*
* Use trylock here so prevent us from blocking the packet handling.
*
* \param q flow queue to prune
* \param ts current time
* \param timeout timeout to enforce
*
* \retval 0 on error, failed block, nothing to prune
* \retval 1 on successfully pruned one
*/
static int FlowPrune (FlowQueue *q, struct timeval *ts)
{
int mr = SCMutexTrylock(&q->mutex_q);
if (mr != 0) {
SCLogDebug("trylock failed");
if (mr == EBUSY)
SCLogDebug("was locked");
if (mr == EINVAL)
SCLogDebug("bad mutex value");
return 0;
}
Flow *f = q->top;
if (f == NULL) {
SCMutexUnlock(&q->mutex_q);
SCLogDebug("top is null");
return 0;
}
if (SCMutexTrylock(&f->m) != 0) {
SCLogDebug("cant lock 1");
SCMutexUnlock(&q->mutex_q);
return 0;
}
/* unlock list */
SCMutexUnlock(&q->mutex_q);
if (SCSpinTrylock(&f->fb->s) != 0) {
SCMutexUnlock(&f->m);
SCLogDebug("cant lock 2");
return 0;
}
/*set the timeout value according to the flow operating mode, flow's state
and protocol.*/
uint32_t timeout = 0;
if (flow_flags & FLOW_EMERGENCY) {
if (flow_proto[f->protomap].GetProtoState != NULL) {
switch(flow_proto[f->protomap].GetProtoState(f->protoctx)) {
case FLOW_STATE_NEW:
timeout = flow_proto[f->protomap].emerg_new_timeout;
break;
case FLOW_STATE_ESTABLISHED:
timeout = flow_proto[f->protomap].emerg_est_timeout;
break;
case FLOW_STATE_CLOSED:
timeout = flow_proto[f->protomap].emerg_closed_timeout;
break;
}
} else {
if (f->flags & FLOW_EST_LIST)
timeout = flow_proto[f->protomap].emerg_est_timeout;
else
timeout = flow_proto[f->protomap].emerg_new_timeout;
}
} else { /* impliet not emergency */
if (flow_proto[f->protomap].GetProtoState != NULL) {
switch(flow_proto[f->protomap].GetProtoState(f->protoctx)) {
case FLOW_STATE_NEW:
timeout = flow_proto[f->protomap].new_timeout;
break;
case FLOW_STATE_ESTABLISHED:
timeout = flow_proto[f->protomap].est_timeout;
break;
case FLOW_STATE_CLOSED:
timeout = flow_proto[f->protomap].closed_timeout;
break;
}
} else {
if (f->flags & FLOW_EST_LIST)
timeout = flow_proto[f->protomap].est_timeout;
else
timeout = flow_proto[f->protomap].new_timeout;
}
}
SCLogDebug("got lock, now check: %" PRIdMAX "+%" PRIu32 "=(%" PRIdMAX ") < %" PRIdMAX "", (intmax_t)f->lastts.tv_sec,
timeout, (intmax_t)f->lastts.tv_sec + timeout, (intmax_t)ts->tv_sec);
/* do the timeout check */
if ((int32_t)(f->lastts.tv_sec + timeout) >= ts->tv_sec) {
SCSpinUnlock(&f->fb->s);
SCMutexUnlock(&f->m);
SCLogDebug("timeout check failed");
return 0;
}
/** never prune a flow that is used by a packet or stream msg
* we are currently processing in one of the threads */
if (f->use_cnt > 0) {
SCLogDebug("timed out but use_cnt > 0: %"PRIu16", %p, proto %"PRIu8"", f->use_cnt, f, f->proto);
SCSpinUnlock(&f->fb->s);
SCMutexUnlock(&f->m);
SCLogDebug("it is in one of the threads");
return 0;
}
/* remove from the hash */
if (f->hprev)
f->hprev->hnext = f->hnext;
if (f->hnext)
f->hnext->hprev = f->hprev;
if (f->fb->f == f)
f->fb->f = f->hnext;
f->hnext = NULL;
f->hprev = NULL;
SCSpinUnlock(&f->fb->s);
f->fb = NULL;
FlowClearMemory (f, f->protomap);
/* move to spare list */
FlowRequeue(f, q, &flow_spare_q);
SCMutexUnlock(&f->m);
return 1;
}
/** \brief Time out flows.
* \param q flow queue to time out flows from
* \param ts current time
* \param timeout timeout to consider
* \retval cnt number of flows that are timed out
*/
static uint32_t FlowPruneFlows(FlowQueue *q, struct timeval *ts)
{
uint32_t cnt = 0;
while(FlowPrune(q, ts)) { cnt++; }
return cnt;
}
/** \brief Make sure we have enough spare flows.
*
* Enforce the prealloc parameter, so keep at least prealloc flows in the
* spare queue and free flows going over the limit.
*
* \retval 1 if the queue was properly updated (or if it already was in good shape)
* \retval 0 otherwise.
*/
static int FlowUpdateSpareFlows(void) {
uint32_t toalloc = 0, tofree = 0, len;
SCMutexLock(&flow_spare_q.mutex_q);
len = flow_spare_q.len;
SCMutexUnlock(&flow_spare_q.mutex_q);
if (len < flow_config.prealloc) {
toalloc = flow_config.prealloc - len;
uint32_t i;
for (i = 0; i < toalloc; i++) {
Flow *f = FlowAlloc();
if (f == NULL)
return 0;
SCMutexLock(&flow_spare_q.mutex_q);
FlowEnqueue(&flow_spare_q,f);
SCMutexUnlock(&flow_spare_q.mutex_q);
}
} else if (len > flow_config.prealloc) {
tofree = len - flow_config.prealloc;
uint32_t i;
for (i = 0; i < tofree; i++) {
/* FlowDequeue locks the queue */
Flow *f = FlowDequeue(&flow_spare_q);
if (f == NULL)
return 1;
FlowFree(f);
}
}
return 1;
}
/** \brief Set the IPOnly scanned flag for 'direction'. This function
* handles the locking too.
* \param f Flow to set the flag in
* \param direction direction to set the flag in
*/
void FlowSetIPOnlyFlag(Flow *f, char direction) {
SCMutexLock(&f->m);
direction ? (f->flags |= FLOW_TOSERVER_IPONLY_SET) : (f->flags |= FLOW_TOCLIENT_IPONLY_SET);
SCMutexUnlock(&f->m);
}
/** \brief increase the use cnt of a flow
* \param tv thread vars (\todo unused?)
* \param p packet with flow to decrease use cnt for
*/
void FlowIncrUsecnt(ThreadVars *tv, Packet *p) {
if (p == NULL || p->flow == NULL)
return;
SCMutexLock(&p->flow->m);
p->flow->use_cnt++;
SCMutexUnlock(&p->flow->m);
}
/** \brief decrease the use cnt of a flow
* \param tv thread vars (\todo unused?)
* \param p packet with flow to decrease use cnt for
*/
void FlowDecrUsecnt(ThreadVars *tv, Packet *p) {
if (p == NULL || p->flow == NULL)
return;
SCMutexLock(&p->flow->m);
if (p->flow->use_cnt > 0)
p->flow->use_cnt--;
SCMutexUnlock(&p->flow->m);
}
#define TOSERVER 0
#define TOCLIENT 1
/**
* \brief determine the direction of the packet compared to the flow
* \retval 0 to_server
* \retval 1 to_client
*/
static inline int FlowGetPacketDirection(Flow *f, Packet *p) {
if (p->proto == IPPROTO_TCP || p->proto == IPPROTO_UDP) {
if (!(CMP_PORT(p->sp,p->dp))) {
/* update flags and counters */
if (CMP_PORT(f->sp,p->sp)) {
return TOSERVER;
} else {
return TOCLIENT;
}
} else {
if (CMP_ADDR(&f->src,&p->src)) {
return TOSERVER;
} else {
return TOCLIENT;
}
}
} else if (p->proto == IPPROTO_ICMP || p->proto == IPPROTO_ICMPV6) {
if (CMP_ADDR(&f->src,&p->src)) {
return TOSERVER;
} else {
return TOCLIENT;
}
}
/* default to toserver */
return TOSERVER;
}
/**
* \brief Check to update "seen" flags
*
* \param p packet
*
* \retval 1 true
* \retval 0 false
*/
static inline int FlowUpdateSeenFlag(Packet *p) {
if (PKT_IS_ICMPV4(p)) {
if (ICMPV4_IS_ERROR_MSG(p)) {
return 0;
}
}
return 1;
}
/** \brief Entry point for packet flow handling
*
* This is called for every packet.
*
* \param tv threadvars
* \param p packet to handle flow for
*/
void FlowHandlePacket (ThreadVars *tv, Packet *p)
{
/* Get this packet's flow from the hash. FlowHandlePacket() will setup
* a new flow if nescesary. If we get NULL, we're out of flow memory.
* The returned flow is locked. */
Flow *f = FlowGetFlowFromHash(p);
if (f == NULL)
return;
f->use_cnt++;
/* update the last seen timestamp of this flow */
COPY_TIMESTAMP(&p->ts, &f->lastts);
/* update flags and counters */
if (FlowGetPacketDirection(f,p) == TOSERVER) {
if (FlowUpdateSeenFlag(p)) {
f->flags |= FLOW_TO_DST_SEEN;
}
f->todstpktcnt++;
p->flowflags |= FLOW_PKT_TOSERVER;
} else {
if (FlowUpdateSeenFlag(p)) {
f->flags |= FLOW_TO_SRC_SEEN;
}
f->tosrcpktcnt++;
p->flowflags |= FLOW_PKT_TOCLIENT;
}
f->bytecnt += p->pktlen;
if (f->flags & FLOW_TO_DST_SEEN && f->flags & FLOW_TO_SRC_SEEN) {
p->flowflags |= FLOW_PKT_ESTABLISHED;
}
/* update queue positions */
FlowUpdateQueue(f);
/* set the iponly stuff */
if (f->flags & FLOW_TOCLIENT_IPONLY_SET)
p->flowflags |= FLOW_PKT_TOCLIENT_IPONLY_SET;
if (f->flags & FLOW_TOSERVER_IPONLY_SET)
p->flowflags |= FLOW_PKT_TOSERVER_IPONLY_SET;
/*set the detection bypass flags*/
if (f->flags & FLOW_NOPACKET_INSPECTION) {
SCLogDebug("setting FLOW_NOPACKET_INSPECTION flag on flow %p", f);
DecodeSetNoPacketInspectionFlag(p);
}
if (f->flags & FLOW_NOPAYLOAD_INSPECTION) {
SCLogDebug("setting FLOW_NOPAYLOAD_INSPECTION flag on flow %p", f);
DecodeSetNoPayloadInspectionFlag(p);
}
/* set the flow in the packet */
p->flow = f;
SCMutexUnlock(&f->m);
}
/** \brief initialize the configuration
* \warning Not thread safe */
void FlowInitConfig (char quiet)
{
if (quiet == FALSE)
SCLogInfo("initializing flow engine...");
memset(&flow_config, 0, sizeof(flow_config));
flow_memuse = 0;
int ifq = 0;
FlowQueueInit(&flow_spare_q);
for (ifq = 0; ifq < FLOW_PROTO_MAX; ifq++) {
FlowQueueInit(&flow_new_q[ifq]);
FlowQueueInit(&flow_est_q[ifq]);
FlowQueueInit(&flow_close_q[ifq]);
}
SCMutexInit(&flow_memuse_mutex, NULL);
unsigned int seed = RandomTimePreseed();
/* set defaults */
flow_config.hash_rand = (int)( FLOW_DEFAULT_HASHSIZE * (rand_r(&seed) / RAND_MAX + 1.0));
flow_config.hash_size = FLOW_DEFAULT_HASHSIZE;
flow_config.memcap = FLOW_DEFAULT_MEMCAP;
flow_config.prealloc = FLOW_DEFAULT_PREALLOC;
/* If we have specific config, overwrite the defaults with them,
* otherwise, leave the default values */
/* 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("flow.memcap", &conf_val)) == 1)
{
if (ByteExtractStringUint32(&configval, 10, strlen(conf_val),
conf_val) > 0)
flow_config.memcap = configval;
}
if ((ConfGet("flow.hash_size", &conf_val)) == 1)
{
if (ByteExtractStringUint32(&configval, 10, strlen(conf_val),
conf_val) > 0)
flow_config.hash_size = configval;
}
if ((ConfGet("flow.prealloc", &conf_val)) == 1)
{
if (ByteExtractStringUint32(&configval, 10, strlen(conf_val),
conf_val) > 0)
flow_config.prealloc = configval;
}
SCLogDebug("Flow config from suricata.yaml: memcap: %"PRIu32", hash_size: "
"%"PRIu32", prealloc: %"PRIu32, flow_config.memcap,
flow_config.hash_size, flow_config.prealloc);
/* alloc hash memory */
flow_hash = SCCalloc(flow_config.hash_size, sizeof(FlowBucket));
if (flow_hash == NULL) {
SCLogError(SC_ERR_FATAL, "Fatal error encountered in FlowInitConfig. Exiting...");
exit(EXIT_FAILURE);
}
uint32_t i = 0;
memset(flow_hash, 0, flow_config.hash_size * sizeof(FlowBucket));
for (i = 0; i < flow_config.hash_size; i++)
SCSpinInit(&flow_hash[i].s, 0);
flow_memuse += (flow_config.hash_size * sizeof(FlowBucket));
if (quiet == FALSE)
SCLogInfo("allocated %" PRIu32 " bytes of memory for the flow hash... "
"%" PRIu32 " buckets of size %" PRIuMAX "",
flow_memuse, flow_config.hash_size,
(uintmax_t)sizeof(FlowBucket));
/* pre allocate flows */
for (i = 0; i < flow_config.prealloc; i++) {
Flow *f = FlowAlloc();
if (f == NULL) {
printf("ERROR: FlowAlloc failed: %s\n", strerror(errno));
exit(1);
}
FlowEnqueue(&flow_spare_q,f);
}
if (quiet == FALSE) {
SCLogInfo("preallocated %" PRIu32 " flows of size %" PRIuMAX "",
flow_spare_q.len, (uintmax_t)sizeof(Flow));
SCLogInfo("flow memory usage: %" PRIu32 " bytes, maximum: %" PRIu32 "",
flow_memuse, flow_config.memcap);
}
FlowInitFlowProto();
}
/** \brief print some flow stats
* \warning Not thread safe */
void FlowPrintQueueInfo (void)
{
int i;
SCLogDebug("flow queue info:");
SCLogDebug("spare flow queue %" PRIu32 "", flow_spare_q.len);
#ifdef DBG_PERF
SCLogDebug("flow_spare_q.dbg_maxlen %" PRIu32 "", flow_spare_q.dbg_maxlen);
#endif
for (i = 0; i < FLOW_PROTO_MAX; i++) {
SCLogDebug("proto [%"PRId32"] new flow queue %" PRIu32 " "
#ifdef DBG_PERF
" - flow_new_q.dbg_maxlen %" PRIu32 ""
#endif
,i,flow_new_q[i].len
#ifdef DBG_PERF
,flow_new_q[i].dbg_maxlen
#endif
);
SCLogDebug("proto [%"PRId32"] establised flow queue %" PRIu32 " "
#ifdef DBG_PERF
" - flow_est_q.dbg_maxlen %" PRIu32 ""
#endif
,i,flow_est_q[i].len
#ifdef DBG_PERF
,flow_est_q[i].dbg_maxlen
#endif
);
SCLogDebug("proto [%"PRId32"] closing flow queue %" PRIu32 " "
#ifdef DBG_PERF
" - flow_closing_q.dbg_maxlen %" PRIu32 ""
#endif
,i,flow_close_q[i].len
#ifdef DBG_PERF
,flow_close_q[i].dbg_maxlen
#endif
);
}
#ifdef FLOWBITS_STATS
SCLogInfo("flowbits added: %" PRIu32 ", removed: %" PRIu32 ", max memory usage: %" PRIu32 "",
flowbits_added, flowbits_removed, flowbits_memuse_max);
#endif /* FLOWBITS_STATS */
}
/** \brief shutdown the flow engine
* \warning Not thread safe */
void FlowShutdown(void) {
Flow *f;
int i;
uint32_t u;
while((f = FlowDequeue(&flow_spare_q))) {
FlowFree(f);
}
for (i = 0; i < FLOW_PROTO_MAX; i++) {
while((f = FlowDequeue(&flow_new_q[i]))) {
uint8_t proto_map = FlowGetProtoMapping(f->proto);
FlowClearMemory(f, proto_map);
FlowFree(f);
}
while((f = FlowDequeue(&flow_est_q[i]))) {
uint8_t proto_map = FlowGetProtoMapping(f->proto);
FlowClearMemory(f, proto_map);
FlowFree(f);
}
while((f = FlowDequeue(&flow_close_q[i]))) {
uint8_t proto_map = FlowGetProtoMapping(f->proto);
FlowClearMemory(f, proto_map);
FlowFree(f);
}
}
if (flow_hash != NULL) {
/* clean up flow mutexes */
for (u = 0; u < flow_config.hash_size; u++) {
SCSpinDestroy(&flow_hash[u].s);
}
SCFree(flow_hash);
flow_hash = NULL;
}
flow_memuse -= flow_config.hash_size * sizeof(FlowBucket);
int ifq = 0;
FlowQueueDestroy(&flow_spare_q);
for (ifq = 0; ifq < FLOW_PROTO_MAX; ifq++) {
FlowQueueDestroy(&flow_new_q[ifq]);
FlowQueueDestroy(&flow_est_q[ifq]);
FlowQueueDestroy(&flow_close_q[ifq]);
}
SCMutexDestroy(&flow_memuse_mutex);
}
/** \brief Thread that manages the various queue's and removes timed out flows.
* \param td ThreadVars casted to void ptr
*
* IDEAS/TODO
* Create a 'emergency mode' in which flow handling threads can indicate
* we are/seem to be under attack..... maybe this thread should check
* key indicators for that like:
* - number of flows created in the last x time
* - avg number of pkts per flow (how?)
* - avg flow age
*
* Keep an eye on the spare list, alloc flows if needed...
*/
void *FlowManagerThread(void *td)
{
ThreadVars *th_v = (ThreadVars *)td;
struct timeval ts;
struct timeval tsdiff;
uint32_t established_cnt = 0, new_cnt = 0, closing_cnt = 0, nowcnt;
uint32_t sleeping = 0;
uint8_t emerg = FALSE;
uint32_t last_sec = 0;
memset(&ts, 0, sizeof(ts));
/* set the thread name */
SCSetThreadName(th_v->name);
SCLogDebug("%s started...", th_v->name);
/* Set the threads capability */
th_v->cap_flags = 0;
SCDropCaps(th_v);
FlowHashDebugInit();
TmThreadsSetFlag(th_v, THV_INIT_DONE);
while (1)
{
TmThreadTestThreadUnPaused(th_v);
if (sleeping >= 100 || flow_flags & FLOW_EMERGENCY)
{
if (flow_flags & FLOW_EMERGENCY) {
emerg = TRUE;
SCLogDebug("Flow emergency mode entered...");
}
/* Get the time */
memset(&ts, 0, sizeof(ts));
TimeGet(&ts);
SCLogDebug("ts %" PRIdMAX "", (intmax_t)ts.tv_sec);
if (((uint32_t)ts.tv_sec - last_sec) > 600) {
FlowHashDebugPrint((uint32_t)ts.tv_sec);
last_sec = (uint32_t)ts.tv_sec;
}
/* see if we still have enough spare flows */
FlowUpdateSpareFlows();
int i;
for (i = 0; i < FLOW_PROTO_MAX; i++) {
/* prune closing list */
nowcnt = FlowPruneFlows(&flow_close_q[i], &ts);
if (nowcnt) {
SCLogDebug("Pruned %" PRIu32 " closing flows...", nowcnt);
closing_cnt += nowcnt;
}
/* prune new list */
nowcnt = FlowPruneFlows(&flow_new_q[i], &ts);
if (nowcnt) {
SCLogDebug("Pruned %" PRIu32 " new flows...", nowcnt);
new_cnt += nowcnt;
}
/* prune established list */
nowcnt = FlowPruneFlows(&flow_est_q[i], &ts);
if (nowcnt) {
SCLogDebug("Pruned %" PRIu32 " established flows...", nowcnt);
established_cnt += nowcnt;
}
}
sleeping = 0;
/* Don't fear, FlowManagerThread is here...
* clear emergency bit. */
if (emerg == TRUE) {
flow_flags &= ~FLOW_EMERGENCY;
emerg = FALSE;
SCLogDebug("Flow emergency mode over, back to normal...");
}
}
if (TmThreadsCheckFlag(th_v, THV_KILL)) {
SCPerfUpdateCounterArray(th_v->sc_perf_pca, &th_v->sc_perf_pctx, 0);
break;
}
if (run_mode != MODE_PCAP_FILE) {
usleep(10);
sleeping += 10;
} else {
/* If we are reading a pcap, how long the pcap timestamps
* says that has passed */
memset(&tsdiff, 0, sizeof(tsdiff));
TimeGet(&tsdiff);
if (tsdiff.tv_sec == ts.tv_sec &&
tsdiff.tv_usec > ts.tv_usec &&
tsdiff.tv_usec - ts.tv_usec < 10) {
/* if it has passed less than 10 usec, sleep that usecs */
sleeping += tsdiff.tv_usec - ts.tv_usec;
usleep(tsdiff.tv_usec - ts.tv_usec);
} else {
/* Else update the sleeping var but don't sleep so long */
if (tsdiff.tv_sec == ts.tv_sec && tsdiff.tv_usec > ts.tv_usec)
sleeping += tsdiff.tv_usec - ts.tv_usec;
else if (tsdiff.tv_sec == ts.tv_sec + 1)
sleeping += tsdiff.tv_usec + (1000000 - ts.tv_usec);
else
sleeping += 100;
usleep(1);
}
}
}
FlowHashDebugDeinit();
SCLogInfo("%" PRIu32 " new flows, %" PRIu32 " established flows were "
"timed out, %"PRIu32" flows in closed state", new_cnt,
established_cnt, closing_cnt);
pthread_exit((void *) 0);
}
/** \brief spawn the flow manager thread */
void FlowManagerThreadSpawn()
{
ThreadVars *tv_flowmgr = NULL;
tv_flowmgr = TmThreadCreateMgmtThread("FlowManagerThread", FlowManagerThread, 0);
if (tv_flowmgr == NULL) {
printf("ERROR: TmThreadsCreate failed\n");
exit(1);
}
if (TmThreadSpawn(tv_flowmgr) != TM_ECODE_OK) {
printf("ERROR: TmThreadSpawn failed\n");
exit(1);
}
return;
}
/**
* \brief Function to set the default timeout, free function and flow state
* function for all supported flow_proto.
*/
void FlowInitFlowProto(void) {
/*Default*/
flow_proto[FLOW_PROTO_DEFAULT].new_timeout = FLOW_DEFAULT_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].est_timeout = FLOW_DEFAULT_EST_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].closed_timeout = FLOW_DEFAULT_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].emerg_new_timeout = FLOW_DEFAULT_EMERG_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].emerg_est_timeout = FLOW_DEFAULT_EMERG_EST_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].emerg_closed_timeout = FLOW_DEFAULT_EMERG_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_DEFAULT].Freefunc = NULL;
flow_proto[FLOW_PROTO_DEFAULT].GetProtoState = NULL;
/*TCP*/
flow_proto[FLOW_PROTO_TCP].new_timeout = FLOW_IPPROTO_TCP_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].est_timeout = FLOW_IPPROTO_TCP_EST_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].closed_timeout = FLOW_DEFAULT_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].emerg_new_timeout = FLOW_IPPROTO_TCP_EMERG_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].emerg_est_timeout = FLOW_IPPROTO_TCP_EMERG_EST_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].emerg_closed_timeout = FLOW_DEFAULT_EMERG_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_TCP].Freefunc = NULL;
flow_proto[FLOW_PROTO_TCP].GetProtoState = NULL;
/*UDP*/
flow_proto[FLOW_PROTO_UDP].new_timeout = FLOW_IPPROTO_UDP_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].est_timeout = FLOW_IPPROTO_UDP_EST_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].closed_timeout = FLOW_DEFAULT_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].emerg_new_timeout = FLOW_IPPROTO_UDP_EMERG_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].emerg_est_timeout = FLOW_IPPROTO_UDP_EMERG_EST_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].emerg_closed_timeout = FLOW_DEFAULT_EMERG_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_UDP].Freefunc = NULL;
flow_proto[FLOW_PROTO_UDP].GetProtoState = NULL;
/*ICMP*/
flow_proto[FLOW_PROTO_ICMP].new_timeout = FLOW_IPPROTO_ICMP_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].est_timeout = FLOW_IPPROTO_ICMP_EST_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].closed_timeout = FLOW_DEFAULT_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].emerg_new_timeout = FLOW_IPPROTO_ICMP_EMERG_NEW_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].emerg_est_timeout = FLOW_IPPROTO_ICMP_EMERG_EST_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].emerg_closed_timeout = FLOW_DEFAULT_EMERG_CLOSED_TIMEOUT;
flow_proto[FLOW_PROTO_ICMP].Freefunc = NULL;
flow_proto[FLOW_PROTO_ICMP].GetProtoState = NULL;
/* Let's see if we have custom timeouts defined from config */
const char *new = NULL;
const char *established = NULL;
const char *closed = NULL;
const char *emergency_new = NULL;
const char *emergency_established = NULL;
const char *emergency_closed = NULL;
ConfNode *flow_timeouts = ConfGetNode("flow-timeouts");
if (flow_timeouts != NULL) {
ConfNode *proto = NULL;
uint32_t configval = 0;
/* Defaults. */
proto = ConfNodeLookupChild(flow_timeouts, "default");
if (proto != NULL) {
new = ConfNodeLookupChildValue(proto, "new");
established = ConfNodeLookupChildValue(proto, "established");
closed = ConfNodeLookupChildValue(proto, "closed");
emergency_new = ConfNodeLookupChildValue(proto, "emergency_new");
emergency_established = ConfNodeLookupChildValue(proto,
"emergency_established");
emergency_closed = ConfNodeLookupChildValue(proto,
"emergency_closed");
if (new != NULL && ByteExtractStringUint32(&configval, 10,
strlen(new), new) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].new_timeout = configval;
}
if (established != NULL && ByteExtractStringUint32(&configval,
10, strlen(established), established) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].est_timeout = configval;
}
if (closed != NULL && ByteExtractStringUint32(&configval, 10,
strlen(closed), closed) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].closed_timeout = configval;
}
if (emergency_new != NULL && ByteExtractStringUint32(&configval,
10, strlen(emergency_new), emergency_new) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].emerg_new_timeout = configval;
}
if (emergency_established != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_established), emergency_established) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].emerg_est_timeout= configval;
}
if (emergency_closed != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_closed), emergency_closed) > 0) {
flow_proto[FLOW_PROTO_DEFAULT].emerg_closed_timeout = configval;
}
}
/* TCP. */
proto = ConfNodeLookupChild(flow_timeouts, "tcp");
if (proto != NULL) {
new = ConfNodeLookupChildValue(proto, "new");
established = ConfNodeLookupChildValue(proto, "established");
closed = ConfNodeLookupChildValue(proto, "closed");
emergency_new = ConfNodeLookupChildValue(proto, "emergency_new");
emergency_established = ConfNodeLookupChildValue(proto,
"emergency_established");
emergency_closed = ConfNodeLookupChildValue(proto,
"emergency_closed");
if (new != NULL && ByteExtractStringUint32(&configval, 10,
strlen(new), new) > 0) {
flow_proto[FLOW_PROTO_TCP].new_timeout = configval;
}
if (established != NULL && ByteExtractStringUint32(&configval,
10, strlen(established), established) > 0) {
flow_proto[FLOW_PROTO_TCP].est_timeout = configval;
}
if (closed != NULL && ByteExtractStringUint32(&configval, 10,
strlen(closed), closed) > 0) {
flow_proto[FLOW_PROTO_TCP].closed_timeout = configval;
}
if (emergency_new != NULL && ByteExtractStringUint32(&configval,
10, strlen(emergency_new), emergency_new) > 0) {
flow_proto[FLOW_PROTO_TCP].emerg_new_timeout = configval;
}
if (emergency_established != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_established), emergency_established) > 0) {
flow_proto[FLOW_PROTO_TCP].emerg_est_timeout = configval;
}
if (emergency_closed != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_closed), emergency_closed) > 0) {
flow_proto[FLOW_PROTO_TCP].emerg_closed_timeout = configval;
}
}
/* UDP. */
proto = ConfNodeLookupChild(flow_timeouts, "udp");
if (proto != NULL) {
new = ConfNodeLookupChildValue(proto, "new");
established = ConfNodeLookupChildValue(proto, "established");
emergency_new = ConfNodeLookupChildValue(proto, "emergency_new");
emergency_established = ConfNodeLookupChildValue(proto,
"emergency_established");
if (new != NULL && ByteExtractStringUint32(&configval, 10,
strlen(new), new) > 0) {
flow_proto[FLOW_PROTO_UDP].new_timeout = configval;
}
if (established != NULL && ByteExtractStringUint32(&configval,
10, strlen(established), established) > 0) {
flow_proto[FLOW_PROTO_UDP].est_timeout = configval;
}
if (emergency_new != NULL && ByteExtractStringUint32(&configval,
10, strlen(emergency_new), emergency_new) > 0) {
flow_proto[FLOW_PROTO_UDP].emerg_new_timeout = configval;
}
if (emergency_established != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_established), emergency_established) > 0) {
flow_proto[FLOW_PROTO_UDP].emerg_est_timeout = configval;
}
}
/* ICMP. */
proto = ConfNodeLookupChild(flow_timeouts, "icmp");
if (proto != NULL) {
new = ConfNodeLookupChildValue(proto, "new");
established = ConfNodeLookupChildValue(proto, "established");
emergency_new = ConfNodeLookupChildValue(proto, "emergency_new");
emergency_established = ConfNodeLookupChildValue(proto,
"emergency_established");
if (new != NULL && ByteExtractStringUint32(&configval, 10,
strlen(new), new) > 0) {
flow_proto[FLOW_PROTO_ICMP].new_timeout = configval;
}
if (established != NULL && ByteExtractStringUint32(&configval,
10, strlen(established), established) > 0) {
flow_proto[FLOW_PROTO_ICMP].est_timeout = configval;
}
if (emergency_new != NULL && ByteExtractStringUint32(&configval,
10, strlen(emergency_new), emergency_new) > 0) {
flow_proto[FLOW_PROTO_ICMP].emerg_new_timeout = configval;
}
if (emergency_established != NULL &&
ByteExtractStringUint32(&configval, 10,
strlen(emergency_established), emergency_established) > 0) {
flow_proto[FLOW_PROTO_ICMP].emerg_est_timeout = configval;
}
}
}
}
/**
* \brief Function clear the flow memory before queueing it to spare flow
* queue.
*
* \param f pointer to the flow needed to be cleared.
* \param proto_map mapped value of the protocol to FLOW_PROTO's.
*/
static int FlowClearMemory(Flow* f, uint8_t proto_map) {
SCEnter();
/* call the protocol specific free function if we have one */
if (flow_proto[proto_map].Freefunc != NULL) {
flow_proto[proto_map].Freefunc(f->protoctx);
}
f->protoctx = NULL;
CLEAR_FLOW(f);
SCReturnInt(1);
}
/**
* \brief Function to set the function to get protocol specific flow state.
*
* \param proto protocol of which function is needed to be set.
* \param Free Function pointer which will be called to free the protocol
* specific memory.
*/
int FlowSetProtoFreeFunc (uint8_t proto, void (*Free)(void *)) {
uint8_t proto_map;
proto_map = FlowGetProtoMapping(proto);
flow_proto[proto_map].Freefunc = Free;
return 1;
}
/**
* \brief Function to set the function to get protocol specific flow state.
*
* \param proto protocol of which function is needed to be set.
* \param GetFlowState Function pointer which will be called to get state.
*/
int FlowSetFlowStateFunc (uint8_t proto, int (*GetProtoState)(void *)) {
uint8_t proto_map;
proto_map = FlowGetProtoMapping(proto);
flow_proto[proto_map].GetProtoState = GetProtoState;
return 1;
}
/**
* \brief Function to set the timeout values for the specified protocol.
*
* \param proto protocol of which timeout value is needed to be set.
* \param new_timeout timeout value for the new flows.
* \param est_timeout timeout value for the established flows.
* \param closed_timeout timeout value for the closed flows.
*/
int FlowSetProtoTimeout(uint8_t proto, uint32_t new_timeout, uint32_t est_timeout, uint32_t closed_timeout) {
uint8_t proto_map;
proto_map = FlowGetProtoMapping(proto);
flow_proto[proto_map].new_timeout = new_timeout;
flow_proto[proto_map].est_timeout = est_timeout;
flow_proto[proto_map].closed_timeout = closed_timeout;
return 1;
}
/**
* \brief Function to set the emergency timeout values for the specified
* protocol.
*
* \param proto protocol of which timeout value is needed to be set.
* \param emerg_new_timeout timeout value for the new flows.
* \param emerg_est_timeout timeout value for the established flows.
* \param emerg_closed_timeout timeout value for the closed flows.
*/
int FlowSetProtoEmergencyTimeout(uint8_t proto, uint32_t emerg_new_timeout, uint32_t emerg_est_timeout, uint32_t emerg_closed_timeout) {
uint8_t proto_map;
proto_map = FlowGetProtoMapping(proto);
flow_proto[proto_map].emerg_new_timeout = emerg_new_timeout;
flow_proto[proto_map].emerg_est_timeout = emerg_est_timeout;
flow_proto[proto_map].emerg_closed_timeout = emerg_closed_timeout;
return 1;
}
/** \brief Set the No Packet Inspection Flag after locking the flow.
*
* \param f Flow to set the flag in
*/
void FlowLockSetNoPacketInspectionFlag(Flow *f) {
SCEnter();
SCLogDebug("flow %p", f);
SCMutexLock(&f->m);
f->flags |= FLOW_NOPACKET_INSPECTION;
SCMutexUnlock(&f->m);
SCReturn;
}
/** \brief Set the No Packet Inspection Flag without locking the flow.
*
* \param f Flow to set the flag in
*/
void FlowSetNoPacketInspectionFlag(Flow *f) {
SCEnter();
SCLogDebug("flow %p", f);
f->flags |= FLOW_NOPACKET_INSPECTION;
SCReturn;
}
/** \brief Set the No payload inspection Flag after locking the flow.
*
* \param f Flow to set the flag in
*/
void FlowLockSetNoPayloadInspectionFlag(Flow *f) {
SCEnter();
SCLogDebug("flow %p", f);
SCMutexLock(&f->m);
f->flags |= FLOW_NOPAYLOAD_INSPECTION;
SCMutexUnlock(&f->m);
SCReturn;
}
/** \brief Set the No payload inspection Flag without locking the flow.
*
* \param f Flow to set the flag in
*/
void FlowSetNoPayloadInspectionFlag(Flow *f) {
SCEnter();
SCLogDebug("flow %p", f);
f->flags |= FLOW_NOPAYLOAD_INSPECTION;
SCReturn;
}
#ifdef UNITTESTS
#include "stream-tcp-private.h"
#include "threads.h"
/**
* \test Test the setting of the per protocol timeouts.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest01 (void) {
uint8_t proto_map;
FlowInitFlowProto();
proto_map = FlowGetProtoMapping(IPPROTO_TCP);
if ((flow_proto[proto_map].new_timeout != FLOW_IPPROTO_TCP_NEW_TIMEOUT) && (flow_proto[proto_map].est_timeout != FLOW_IPPROTO_TCP_EST_TIMEOUT)
&& (flow_proto[proto_map].emerg_new_timeout != FLOW_IPPROTO_TCP_EMERG_NEW_TIMEOUT) && (flow_proto[proto_map].emerg_est_timeout != FLOW_IPPROTO_TCP_EMERG_EST_TIMEOUT)){
printf ("failed in setting TCP flow timeout");
return 0;
}
proto_map = FlowGetProtoMapping(IPPROTO_UDP);
if ((flow_proto[proto_map].new_timeout != FLOW_IPPROTO_UDP_NEW_TIMEOUT) && (flow_proto[proto_map].est_timeout != FLOW_IPPROTO_UDP_EST_TIMEOUT)
&& (flow_proto[proto_map].emerg_new_timeout != FLOW_IPPROTO_UDP_EMERG_NEW_TIMEOUT) && (flow_proto[proto_map].emerg_est_timeout != FLOW_IPPROTO_UDP_EMERG_EST_TIMEOUT)){
printf ("failed in setting UDP flow timeout");
return 0;
}
proto_map = FlowGetProtoMapping(IPPROTO_ICMP);
if ((flow_proto[proto_map].new_timeout != FLOW_IPPROTO_ICMP_NEW_TIMEOUT) && (flow_proto[proto_map].est_timeout != FLOW_IPPROTO_ICMP_EST_TIMEOUT)
&& (flow_proto[proto_map].emerg_new_timeout != FLOW_IPPROTO_ICMP_EMERG_NEW_TIMEOUT) && (flow_proto[proto_map].emerg_est_timeout != FLOW_IPPROTO_ICMP_EMERG_EST_TIMEOUT)){
printf ("failed in setting ICMP flow timeout");
return 0;
}
proto_map = FlowGetProtoMapping(IPPROTO_DCCP);
if ((flow_proto[proto_map].new_timeout != FLOW_DEFAULT_NEW_TIMEOUT) && (flow_proto[proto_map].est_timeout != FLOW_DEFAULT_EST_TIMEOUT)
&& (flow_proto[proto_map].emerg_new_timeout != FLOW_DEFAULT_EMERG_NEW_TIMEOUT) && (flow_proto[proto_map].emerg_est_timeout != FLOW_DEFAULT_EMERG_EST_TIMEOUT)){
printf ("failed in setting default flow timeout");
return 0;
}
return 1;
}
/*Test function for the unit test FlowTest02*/
void test(void *f){}
/**
* \test Test the setting of the per protocol free function to free the
* protocol specific memory.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest02 (void) {
FlowSetProtoFreeFunc(IPPROTO_DCCP, test);
FlowSetProtoFreeFunc(IPPROTO_TCP, test);
FlowSetProtoFreeFunc(IPPROTO_UDP, test);
FlowSetProtoFreeFunc(IPPROTO_ICMP, test);
if (flow_proto[FLOW_PROTO_DEFAULT].Freefunc != test) {
printf("Failed in setting default free function\n");
return 0;
}
if (flow_proto[FLOW_PROTO_TCP].Freefunc != test) {
printf("Failed in setting TCP free function\n");
return 0;
}
if (flow_proto[FLOW_PROTO_UDP].Freefunc != test) {
printf("Failed in setting UDP free function\n");
return 0;
}
if (flow_proto[FLOW_PROTO_ICMP].Freefunc != test) {
printf("Failed in setting ICMP free function\n");
return 0;
}
return 1;
}
/**
* \brief Function to test the prunning of the flow in different flow modes.
*
* \param f Pointer to the flow to be prunned
* \param ts time value against which the flow will be checked
*
* \retval on success returns 1 and on failure 0
*/
static int FlowTestPrune(Flow *f, struct timeval *ts) {
FlowQueue *q = FlowQueueNew();
if (q == NULL) {
goto error;
}
q->top = NULL;
FlowEnqueue(q, f);
if (q->len != 1) {
printf("Failed in enqueue the flow in flowqueue: ");
goto error;
}
FlowPrune(q, ts);
if (q->len != 0) {
printf("Failed in prunning the flow: ");
goto error;
}
if (f->protoctx != NULL){
printf("Failed in freeing the TcpSession: ");
goto error;
}
return 1;
error:
if (q != NULL) {
FlowQueueDestroy(q);
}
return 0;
}
/**
* \test Test the timing out of a flow with a fresh TcpSession
* (just initialized, no data segments) in normal mode.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest03 (void) {
TcpSession ssn;
Flow f;
FlowBucket fb;
struct timeval ts;
memset(&ssn, 0, sizeof(TcpSession));
memset(&f, 0, sizeof(Flow));
memset(&ts, 0, sizeof(ts));
memset(&fb, 0, sizeof(FlowBucket));
SCSpinInit(&fb.s, 0);
SCMutexInit(&f.m, NULL);
TimeGet(&ts);
f.lastts.tv_sec = ts.tv_sec - 5000;
f.protoctx = &ssn;
f.fb = &fb;
f.proto = IPPROTO_TCP;
if (FlowTestPrune(&f, &ts) != 1) {
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 0;
}
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 1;
}
/**
* \test Test the timing out of a flow with a TcpSession
* (with data segments) in normal mode.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest04 (void) {
TcpSession ssn;
Flow f;
FlowBucket fb;
struct timeval ts;
TcpSegment seg;
TcpStream client;
uint8_t payload[3] = {0x41, 0x41, 0x41};
memset(&ssn, 0, sizeof(TcpSession));
memset(&f, 0, sizeof(Flow));
memset(&fb, 0, sizeof(FlowBucket));
memset(&ts, 0, sizeof(ts));
memset(&seg, 0, sizeof(TcpSegment));
memset(&client, 0, sizeof(TcpSegment));
SCSpinInit(&fb.s, 0);
SCMutexInit(&f.m, NULL);
TimeGet(&ts);
seg.payload = payload;
seg.payload_len = 3;
seg.next = NULL;
seg.prev = NULL;
client.seg_list = &seg;
ssn.client = client;
ssn.server = client;
ssn.state = TCP_ESTABLISHED;
f.lastts.tv_sec = ts.tv_sec - 5000;
f.protoctx = &ssn;
f.fb = &fb;
f.proto = IPPROTO_TCP;
if (FlowTestPrune(&f, &ts) != 1) {
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 0;
}
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 1;
}
/**
* \test Test the timing out of a flow with a fresh TcpSession
* (just initialized, no data segments) in emergency mode.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest05 (void) {
TcpSession ssn;
Flow f;
FlowBucket fb;
struct timeval ts;
memset(&ssn, 0, sizeof(TcpSession));
memset(&f, 0, sizeof(Flow));
memset(&ts, 0, sizeof(ts));
memset(&fb, 0, sizeof(FlowBucket));
SCSpinInit(&fb.s, 0);
SCMutexInit(&f.m, NULL);
TimeGet(&ts);
ssn.state = TCP_SYN_SENT;
f.lastts.tv_sec = ts.tv_sec - 300;
f.protoctx = &ssn;
f.fb = &fb;
f.proto = IPPROTO_TCP;
f.flags = FLOW_EMERGENCY;
if (FlowTestPrune(&f, &ts) != 1) {
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 0;
}
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 1;
}
/**
* \test Test the timing out of a flow with a TcpSession
* (with data segments) in emergency mode.
*
* \retval On success it returns 1 and on failure 0.
*/
static int FlowTest06 (void) {
TcpSession ssn;
Flow f;
FlowBucket fb;
struct timeval ts;
TcpSegment seg;
TcpStream client;
uint8_t payload[3] = {0x41, 0x41, 0x41};
memset(&ssn, 0, sizeof(TcpSession));
memset(&f, 0, sizeof(Flow));
memset(&fb, 0, sizeof(FlowBucket));
memset(&ts, 0, sizeof(ts));
memset(&seg, 0, sizeof(TcpSegment));
memset(&client, 0, sizeof(TcpSegment));
SCSpinInit(&fb.s, 0);
SCMutexInit(&f.m, NULL);
TimeGet(&ts);
seg.payload = payload;
seg.payload_len = 3;
seg.next = NULL;
seg.prev = NULL;
client.seg_list = &seg;
ssn.client = client;
ssn.server = client;
ssn.state = TCP_ESTABLISHED;
f.lastts.tv_sec = ts.tv_sec - 5000;
f.protoctx = &ssn;
f.fb = &fb;
f.proto = IPPROTO_TCP;
f.flags = FLOW_EMERGENCY;
if (FlowTestPrune(&f, &ts) != 1) {
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 0;
}
SCSpinDestroy(&fb.s);
SCMutexDestroy(&f.m);
return 1;
}
#endif /* UNITTESTS */
/**
* \brief Function to register the Flow Unitests.
*/
void FlowRegisterTests (void) {
#ifdef UNITTESTS
UtRegisterTest("FlowTest01 -- Protocol Specific Timeouts", FlowTest01, 1);
UtRegisterTest("FlowTest02 -- Setting Protocol Specific Free Function", FlowTest02, 1);
UtRegisterTest("FlowTest03 -- Timeout a flow having fresh TcpSession", FlowTest03, 1);
UtRegisterTest("FlowTest04 -- Timeout a flow having TcpSession with segments", FlowTest04, 1);
UtRegisterTest("FlowTest05 -- Timeout a flow in emergency having fresh TcpSession", FlowTest05, 1);
UtRegisterTest("FlowTest06 -- Timeout a flow in emergency having TcpSession with segments", FlowTest06, 1);
#endif /* UNITTESTS */
}