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/stream-tcp.c

3955 lines
140 KiB
C

/* Copyright (c) 2008 Victor Julien <victor@inliniac.net> */
/* Copyright (c) 2009 OISF */
/** \file
*
* \author Victor Julien <victor@inliniac.net>
* \author Gurvinder Singh <gurvindersinghdahiya@gmail.com>
*
* \todo - 4WHS: what if after the 2nd SYN we turn out to be normal 3WHS anyway?
*/
#include "suricata-common.h"
#include "decode.h"
#include "debug.h"
#include "detect.h"
#include "flow.h"
#include "threads.h"
#include "conf.h"
#include "threadvars.h"
#include "tm-modules.h"
#include "util-pool.h"
#include "util-unittest.h"
#include "util-print.h"
#include "util-debug.h"
#include "stream-tcp-private.h"
#include "stream-tcp-reassemble.h"
#include "stream-tcp.h"
#include "stream.h"
#include "stream-tcp.h"
#include "app-layer-parser.h"
//#define DEBUG
typedef struct StreamTcpThread_ {
uint64_t pkts;
uint16_t counter_tcp_sessions;
TcpReassemblyThreadCtx *ra_ctx;
} StreamTcpThread;
TmEcode StreamTcp (ThreadVars *, Packet *, void *, PacketQueue *);
TmEcode StreamTcpThreadInit(ThreadVars *, void *, void **);
TmEcode StreamTcpThreadDeinit(ThreadVars *, void *);
void StreamTcpExitPrintStats(ThreadVars *, void *);
static int ValidReset(TcpSession * , Packet *);
static int StreamTcpHandleFin(StreamTcpThread *, TcpSession *, Packet *);
void StreamTcpRegisterTests (void);
void StreamTcpReturnStreamSegments (TcpStream *);
void StreamTcpInitConfig(char);
extern void StreamTcpSegmentReturntoPool(TcpSegment *);
int StreamTcpGetFlowState(void *);
static int ValidTimestamp(TcpSession * , Packet *);
#ifndef UNITTESTS
#define STREAMTCP_DEFAULT_SESSIONS 262144
#else
#define STREAMTCP_DEFAULT_SESSIONS 32768
#endif
#define STREAMTCP_DEFAULT_PREALLOC 32768
#define STREAMTCP_NEW_TIMEOUT 60
#define STREAMTCP_EST_TIMEOUT 3600
#define STREAMTCP_CLOSED_TIMEOUT 120
#define STREAMTCP_EMERG_NEW_TIMEOUT 10
#define STREAMTCP_EMERG_EST_TIMEOUT 300
#define STREAMTCP_EMERG_CLOSED_TIMEOUT 20
static Pool *ssn_pool = NULL;
static SCMutex ssn_pool_mutex;
#ifdef DEBUG
static uint64_t ssn_pool_cnt;
static SCMutex ssn_pool_cnt_mutex;
#endif
void TmModuleStreamTcpRegister (void)
{
tmm_modules[TMM_STREAMTCP].name = "StreamTcp";
tmm_modules[TMM_STREAMTCP].ThreadInit = StreamTcpThreadInit;
tmm_modules[TMM_STREAMTCP].Func = StreamTcp;
tmm_modules[TMM_STREAMTCP].ThreadExitPrintStats = StreamTcpExitPrintStats;
tmm_modules[TMM_STREAMTCP].ThreadDeinit = StreamTcpThreadDeinit;
tmm_modules[TMM_STREAMTCP].RegisterTests = StreamTcpRegisterTests;
}
void StreamTcpReturnStreamSegments (TcpStream *stream)
{
TcpSegment *seg = stream->seg_list;
TcpSegment *next_seg;
if (seg == NULL)
return;
while (seg != NULL) {
next_seg = seg->next;
StreamTcpSegmentReturntoPool(seg);
seg = next_seg;
}
stream->seg_list = NULL;
}
/** \brief Function to return the stream back to the pool. It returns the
* segments in the stream to the segment pool.
*
* This function is called when the flow is destroyed, so it should free
* *everything* related to the tcp session. So including the app layer
* data. We are guaranteed to only get here when the flow's use_cnt is 0.
*
* \param ssn Void ptr to the ssn.
*/
void StreamTcpSessionClear(void *ssnptr)
{
SCEnter();
TcpSession *ssn = (TcpSession *)ssnptr;
if (ssn == NULL)
SCReturn;
StreamTcpReturnStreamSegments(&ssn->client);
StreamTcpReturnStreamSegments(&ssn->server);
AppLayerParserCleanupState(ssn);
memset(ssn, 0, sizeof(TcpSession));
SCMutexLock(&ssn_pool_mutex);
PoolReturn(ssn_pool, ssn);
SCMutexUnlock(&ssn_pool_mutex);
#ifdef DEBUG
SCMutexLock(&ssn_pool_cnt_mutex);
ssn_pool_cnt--;
SCMutexUnlock(&ssn_pool_cnt_mutex);
#endif
SCReturn;
}
/** \brief Function to return the stream back to the pool. It returns the
* segments in the stream to the segment pool.
*
* We don't clear out the app layer storage here as that is under protection
* of the "use_cnt" reference counter in the flow. This function is called
* when the use_cnt is always at least 1 (this pkt has incremented the flow
* use_cnt itself), so we don't bother.
*
* \param p Packet used to identify the stream.
*/
static void StreamTcpSessionPktFree (Packet *p)
{
SCEnter();
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
if (ssn == NULL)
SCReturn;
StreamTcpReturnStreamSegments(&ssn->client);
StreamTcpReturnStreamSegments(&ssn->server);
/*
memset(ssn, 0, sizeof(TcpSession));
SCMutexLock(&ssn_pool_mutex);
PoolReturn(ssn_pool, p->flow->protoctx);
SCMutexUnlock(&ssn_pool_mutex);
p->flow->protoctx = NULL;
*/
#ifdef DEBUG
SCMutexLock(&ssn_pool_cnt_mutex);
ssn_pool_cnt--;
SCMutexUnlock(&ssn_pool_cnt_mutex);
#endif
SCReturn;
}
/** \brief Stream alloc function for the Pool
* \param null NULL ptr (value of null is ignored)
* \retval ptr void ptr to TcpSession structure with all vars set to 0/NULL
*/
void *StreamTcpSessionPoolAlloc(void *null)
{
void *ptr = malloc(sizeof(TcpSession));
if (ptr == NULL)
return NULL;
memset(ptr, 0, sizeof(TcpSession));
return ptr;
}
/** \brief Pool free function
* \param s Void ptr to TcpSession memory */
void StreamTcpSessionPoolFree(void *s)
{
if (s == NULL)
return;
TcpSession *ssn = (TcpSession *)s;
StreamTcpReturnStreamSegments(&ssn->client);
StreamTcpReturnStreamSegments(&ssn->server);
free(ssn);
}
/** \brief To initialize the stream global configuration data
*
* \param quiet It tells the mode of operation, if it is TRUE nothing will
* be get printed.
*/
void StreamTcpInitConfig(char quiet)
{
SCLogDebug("Initializing Stream");
memset(&stream_config, 0, sizeof(stream_config));
/** set config defaults */
if ((ConfGetBool("stream.max_sessions", &stream_config.max_sessions)) == 0)
{
stream_config.max_sessions = STREAMTCP_DEFAULT_SESSIONS;
}
if ((ConfGetBool("stream.prealloc_sessions",
&stream_config.prealloc_sessions)) == 0)
{
stream_config.prealloc_sessions = STREAMTCP_DEFAULT_PREALLOC;
}
if ((ConfGetBool("stream.midstream", &stream_config.midstream)) == 0) {
stream_config.midstream = FALSE;/*In the final patch it will be FALSE*/
}
if ((ConfGetBool("stream.async_oneside", &stream_config.async_oneside)) == 0)
{
stream_config.async_oneside = FALSE; /*In the final patch it will be FALSE*/
}
ssn_pool = PoolInit(stream_config.max_sessions,
stream_config.prealloc_sessions,
StreamTcpSessionPoolAlloc, NULL,
StreamTcpSessionPoolFree);
if (ssn_pool == NULL) {
SCLogError(SC_ERR_POOL_INIT_FAILED, "ssn_pool is not initialized");
exit(EXIT_FAILURE);
}
SCMutexInit(&ssn_pool_mutex, NULL);
StreamTcpReassembleInit(quiet);
/* set the default TCP timeout, free function and flow state function
* values. */
FlowSetProtoTimeout(IPPROTO_TCP, STREAMTCP_NEW_TIMEOUT,
STREAMTCP_EST_TIMEOUT, STREAMTCP_CLOSED_TIMEOUT);
FlowSetProtoEmergencyTimeout(IPPROTO_TCP, STREAMTCP_EMERG_NEW_TIMEOUT,
STREAMTCP_EMERG_EST_TIMEOUT,
STREAMTCP_EMERG_CLOSED_TIMEOUT);
FlowSetProtoFreeFunc(IPPROTO_TCP, StreamTcpSessionClear);
FlowSetFlowStateFunc(IPPROTO_TCP, StreamTcpGetFlowState);
}
void StreamTcpFreeConfig(char quiet)
{
StreamTcpReassembleFree(quiet);
if (ssn_pool != NULL) {
PoolFree(ssn_pool);
ssn_pool = NULL;
} else {
SCLogError(SC_ERR_POOL_EMPTY, "ssn_pool is NULL");
exit(EXIT_FAILURE);
}
SCLogDebug("ssn_pool_cnt %"PRIu64"", ssn_pool_cnt);
SCMutexDestroy(&ssn_pool_mutex);
}
/** \brief The function is used to to fetch a TCP session from the
* ssn_pool, when a TCP SYN is received.
*
* \param quiet Packet P, which has been recieved for the new TCP session.
*
* \retval TcpSession A new TCP session with field initilaized to 0/NULL.
*/
TcpSession *StreamTcpNewSession (Packet *p)
{
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
if (ssn == NULL) {
SCMutexLock(&ssn_pool_mutex);
p->flow->protoctx = PoolGet(ssn_pool);
SCMutexUnlock(&ssn_pool_mutex);
ssn = (TcpSession *)p->flow->protoctx;
if (ssn == NULL) {
SCLogError(SC_ERR_POOL_EMPTY, "ssn_pool is empty");
return NULL;
}
ssn->state = TCP_NONE;
ssn->aldata = NULL;
#ifdef DEBUG
SCMutexLock(&ssn_pool_cnt_mutex);
ssn_pool_cnt++;
SCMutexUnlock(&ssn_pool_cnt_mutex);
#endif
}
return ssn;
}
static inline void StreamTcpPacketSetState(Packet *p, TcpSession *ssn,
uint8_t state)
{
if (state == ssn->state)
return;
ssn->state = state;
FlowUpdateQueue(p->flow);
}
/**
* \brief Function to flip the direction When we missed the SYN packet,
* SYN/ACK is considered as sent by server, but our engine flagged the
* packet as from client for the host whose packet is received first in
* the session.
*
* \param ssn TcpSession to whom this packet belongs
* \param p Packet whose flag has to be changed
*/
static inline void StreamTcpPacketSwitchDir(TcpSession *ssn, Packet *p)
{
SCLogDebug("ssn %p: switching pkt direction", ssn);
if (PKT_IS_TOSERVER(p)) {
p->flowflags &= ~FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_TOCLIENT;
} else {
p->flowflags &= ~FLOW_PKT_TOCLIENT;
p->flowflags |= FLOW_PKT_TOSERVER;
}
}
/**
* \brief Function to handle the TCP_CLOSED or NONE state. The function handles
* packets while the session state is None which means a newly
* initialized structure, or a fully closed session.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
switch (p->tcph->th_flags) {
case TH_SYN:
{
if (ssn == NULL) {
ssn = StreamTcpNewSession(p);
if (ssn == NULL)
return -1;
SCPerfCounterIncr(stt->counter_tcp_sessions, tv->sc_perf_pca);
}
/* set the state */
StreamTcpPacketSetState(p, ssn, TCP_SYN_SENT);
SCLogDebug("ssn %p: =~ ssn state is now TCP_SYN_SENT", ssn);
/* set the sequence numbers and window */
ssn->client.isn = TCP_GET_SEQ(p);
ssn->client.ra_base_seq = ssn->client.isn;
ssn->client.next_seq = ssn->client.isn + 1;
/*Set the stream timestamp value, if packet has timestamp option
* enabled.*/
if (p->tcpvars.ts != NULL) {
ssn->client.last_ts = TCP_GET_TSVAL(p);
SCLogDebug("ssn %p: p->tcpvars.ts %p, %02x", ssn, p->tcpvars.ts,
ssn->client.last_ts);
if (ssn->client.last_ts == 0)
ssn->client.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
ssn->client.last_pkt_ts = p->ts.tv_sec;
ssn->client.flags |= STREAMTCP_FLAG_TIMESTAMP;
}
ssn->server.window = TCP_GET_WINDOW(p);
if (p->tcpvars.ws != NULL) {
ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
ssn->server.wscale = TCP_GET_WSCALE(p);
}
SCLogDebug("ssn %p: ssn->client.isn %" PRIu32 ", "
"ssn->client.next_seq %" PRIu32 ", ssn->client.last_ack "
"%"PRIu32"", ssn, ssn->client.isn, ssn->client.next_seq,
ssn->client.last_ack);
break;
}
case TH_SYN|TH_ACK:
if (stream_config.midstream == FALSE &&
stream_config.async_oneside == FALSE)
break;
if (ssn == NULL) {
ssn = StreamTcpNewSession(p);
if (ssn == NULL)
return -1;
SCPerfCounterIncr(stt->counter_tcp_sessions, tv->sc_perf_pca);
}
/* set the state */
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
SCLogDebug("ssn %p: =~ midstream picked ssn state is now "
"TCP_SYN_RECV", ssn);
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM;
/* Flag used to change the direct in the later stage in the session */
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM_SYNACK;
/* sequence number & window */
ssn->server.isn = TCP_GET_SEQ(p);
ssn->server.ra_base_seq = ssn->server.isn;
ssn->server.next_seq = ssn->server.isn + 1;
ssn->server.window = TCP_GET_WINDOW(p);
SCLogDebug("ssn %p: server window %u", ssn, ssn->server.window);
ssn->client.isn = TCP_GET_ACK(p) - 1;
ssn->client.ra_base_seq = ssn->client.isn;
ssn->client.next_seq = ssn->client.isn + 1;
ssn->client.last_ack = TCP_GET_ACK(p);
/** If the client has a wscale option the server had it too,
* so set the wscale for the server to max. Otherwise none
* will have the wscale opt just like it should. */
if (p->tcpvars.ws != NULL) {
ssn->client.wscale = TCP_GET_WSCALE(p);
ssn->server.wscale = TCP_WSCALE_MAX;
}
SCLogDebug("ssn %p: ssn->client.isn %"PRIu32", ssn->client.next_seq"
" %"PRIu32", ssn->client.last_ack %"PRIu32"", ssn,
ssn->client.isn, ssn->client.next_seq,
ssn->client.last_ack);
SCLogDebug("ssn %p: ssn->server.isn %"PRIu32", ssn->server.next_seq"
" %"PRIu32", ssn->server.last_ack %"PRIu32"", ssn,
ssn->server.isn, ssn->server.next_seq,
ssn->server.last_ack);
/* Set the timestamp value for both streams, if packet has timestamp
* option enabled.*/
if (p->tcpvars.ts != NULL) {
ssn->server.last_ts = TCP_GET_TSVAL(p);
ssn->client.last_ts = TCP_GET_TSECR(p);
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
"ssn->client.last_ts %" PRIu32"", ssn,
ssn->server.last_ts, ssn->client.last_ts);
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
ssn->server.last_pkt_ts = p->ts.tv_sec;
if (ssn->server.last_ts == 0)
ssn->server.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
if (ssn->client.last_ts == 0)
ssn->client.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
} else {
ssn->server.last_ts = 0;
ssn->client.last_ts = 0;
}
break;
/* Handle SYN/ACK and 3WHS shake missed together as it is almost
* similar. */
case TH_ACK:
case TH_ACK|TH_PUSH:
if (stream_config.midstream == FALSE)
break;
if (ssn == NULL) {
ssn = StreamTcpNewSession(p);
if (ssn == NULL)
return -1;
SCPerfCounterIncr(stt->counter_tcp_sessions, tv->sc_perf_pca);
}
/* set the state */
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
SCLogDebug("ssn %p: =~ midstream picked ssn state is now "
"TCP_ESTABLISHED", ssn);
ssn->flags = STREAMTCP_FLAG_MIDSTREAM;
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
/* set the sequence numbers and window */
ssn->client.isn = TCP_GET_SEQ(p) - 1;
ssn->client.ra_base_seq = ssn->client.isn;
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len;
ssn->client.window = TCP_GET_WINDOW(p);
ssn->client.last_ack = TCP_GET_SEQ(p);
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
SCLogDebug("ssn %p: ssn->client.isn %u, ssn->client.next_seq %u",
ssn, ssn->client.isn, ssn->client.next_seq);
ssn->server.isn = TCP_GET_ACK(p) - 1;
ssn->server.ra_base_seq = ssn->server.isn;
ssn->server.next_seq = ssn->server.isn + 1;
ssn->server.last_ack = TCP_GET_ACK(p);
ssn->server.next_win = ssn->server.last_ack;
SCLogDebug("ssn %p: ssn->client.next_win %"PRIu32", "
"ssn->server.next_win %"PRIu32"", ssn,
ssn->client.next_win, ssn->server.next_win);
SCLogDebug("ssn %p: ssn->client.last_ack %"PRIu32", "
"ssn->server.last_ack %"PRIu32"", ssn,
ssn->client.last_ack, ssn->server.last_ack);
/** window scaling for midstream pickups, we can't do much other
* than assume that it's set to the max value: 14 */
ssn->client.wscale = TCP_WSCALE_MAX;
ssn->server.wscale = TCP_WSCALE_MAX;
/* Set the timestamp value for both streams, if packet has timestamp
* option enabled.*/
if (p->tcpvars.ts != NULL) {
ssn->client.last_ts = TCP_GET_TSVAL(p);
ssn->server.last_ts = TCP_GET_TSECR(p);
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
"ssn->client.last_ts %" PRIu32"", ssn,
ssn->server.last_ts, ssn->client.last_ts);
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
ssn->client.last_pkt_ts = p->ts.tv_sec;
if (ssn->server.last_ts == 0)
ssn->server.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
if (ssn->client.last_ts == 0)
ssn->client.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
} else {
ssn->server.last_ts = 0;
ssn->client.last_ts = 0;
}
/* If no stream reassembly/application layer protocol inspection,
* then simple return */
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn, &ssn->client,
p);
break;
case TH_RST:
case TH_RST|TH_ACK:
case TH_RST|TH_ACK|TH_PUSH:
case TH_FIN:
case TH_FIN|TH_ACK:
case TH_FIN|TH_ACK|TH_PUSH:
BUG_ON(p->flow->protoctx != NULL);
SCLogDebug("FIN or RST packet received, no session setup");
break;
default:
SCLogDebug("default case");
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_SYN_SENT state. The function handles
* SYN, SYN/ACK, RSTpackets and correspondingly changes the connection
* state.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ?
"toclient":"toserver");
switch (p->tcph->th_flags) {
case TH_SYN:
SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent", ssn);
if (ssn->flags & STREAMTCP_FLAG_4WHS)
SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent of "
"4WHS SYN", ssn);
if (PKT_IS_TOCLIENT(p)) {
/** a SYN only packet in the opposite direction could be:
* http://www.breakingpointsystems.com/community/blog/tcp-
* portals-the-three-way-handshake-is-a-lie
*
* \todo improve resetting the session */
/* indicate that we're dealing with 4WHS here */
ssn->flags |= STREAMTCP_FLAG_4WHS;
SCLogDebug("ssn %p: STREAMTCP_FLAG_4WHS flag set", ssn);
/* set the sequence numbers and window for server
* We leave the ssn->client.isn in place as we will
* check the SYN/ACK pkt with that.
*/
ssn->server.isn = TCP_GET_SEQ(p);
ssn->server.ra_base_seq = ssn->server.isn;
ssn->server.next_seq = ssn->server.isn + 1;
/* Set the stream timestamp value, if packet has timestamp
* option enabled. */
if (p->tcpvars.ts != NULL) {
ssn->server.last_ts = TCP_GET_TSVAL(p);
SCLogDebug("ssn %p: p->tcpvars.ts %p, %02x", ssn,
p->tcpvars.ts, ssn->server.last_ts);
if (ssn->server.last_ts == 0)
ssn->server.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
ssn->server.last_pkt_ts = p->ts.tv_sec;
ssn->server.flags |= STREAMTCP_FLAG_TIMESTAMP;
}
ssn->server.window = TCP_GET_WINDOW(p);
if (p->tcpvars.ws != NULL) {
ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
ssn->server.wscale = TCP_GET_WSCALE(p);
}
SCLogDebug("ssn %p: 4WHS ssn->server.isn %" PRIu32 ", "
"ssn->server.next_seq %" PRIu32 ", "
"ssn->server.last_ack %"PRIu32"", ssn,
ssn->server.isn, ssn->server.next_seq,
ssn->server.last_ack);
SCLogDebug("ssn %p: 4WHS ssn->client.isn %" PRIu32 ", "
"ssn->client.next_seq %" PRIu32 ", "
"ssn->client.last_ack %"PRIu32"", ssn,
ssn->client.isn, ssn->client.next_seq,
ssn->client.last_ack);
}
break;
case TH_SYN|TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_4WHS && PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: SYN/ACK received on 4WHS session", ssn);
/* Check if the SYN/ACK packet ack's the earlier
* received SYN packet. */
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->server.isn + 1))) {
SCLogDebug("ssn %p: 4WHS ACK mismatch, packet ACK %"PRIu32""
" != %" PRIu32 " from stream", ssn,
TCP_GET_ACK(p), ssn->server.isn + 1);
return -1;
}
/* Check if the SYN/ACK packet SEQ's the *FIRST* received SYN
* packet. */
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
SCLogDebug("ssn %p: 4WHS SEQ mismatch, packet SEQ %"PRIu32""
" != %" PRIu32 " from *first* SYN pkt", ssn,
TCP_GET_SEQ(p), ssn->client.isn);
return -1;
}
/* update state */
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
SCLogDebug("ssn %p: =~ 4WHS ssn state is now TCP_SYN_RECV", ssn);
/* sequence number & window */
ssn->client.isn = TCP_GET_SEQ(p);
ssn->client.ra_base_seq = ssn->client.isn;
ssn->client.next_seq = ssn->client.isn + 1;
ssn->server.window = TCP_GET_WINDOW(p);
SCLogDebug("ssn %p: 4WHS window %" PRIu32 "", ssn,
ssn->client.window);
/* Set the timestamp values used to validate the timestamp of
* received packets. */
if ((p->tcpvars.ts != NULL) && (ssn->server.flags &
STREAMTCP_FLAG_TIMESTAMP))
{
ssn->client.last_ts = TCP_GET_TSVAL(p);
SCLogDebug("ssn %p: 4WHS ssn->client.last_ts %" PRIu32" "
"ssn->server.last_ts %" PRIu32"", ssn,
ssn->client.last_ts, ssn->server.last_ts);
ssn->server.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
ssn->client.last_pkt_ts = p->ts.tv_sec;
if (ssn->client.last_ts == 0)
ssn->client.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
} else {
ssn->server.last_ts = 0;
ssn->client.last_ts = 0;
ssn->server.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->server.flags &= ~STREAMTCP_FLAG_ZERO_TIMESTAMP;
}
ssn->server.last_ack = TCP_GET_ACK(p);
ssn->client.last_ack = ssn->client.isn + 1;
/** check for the presense of the ws ptr to determine if we
* support wscale at all */
if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) &&
(p->tcpvars.ws != NULL))
{
ssn->server.wscale = TCP_GET_WSCALE(p);
} else {
ssn->server.wscale = 0;
}
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
SCLogDebug("ssn %p: 4WHS ssn->client.next_win %" PRIu32 "", ssn,
ssn->client.next_win);
SCLogDebug("ssn %p: 4WHS ssn->server.next_win %" PRIu32 "", ssn,
ssn->server.next_win);
SCLogDebug("ssn %p: 4WHS ssn->client.isn %" PRIu32 ", "
"ssn->client.next_seq %" PRIu32 ", "
"ssn->client.last_ack %" PRIu32 " "
"(ssn->server.last_ack %" PRIu32 ")", ssn,
ssn->client.isn, ssn->client.next_seq,
ssn->client.last_ack, ssn->server.last_ack);
/* done here */
break;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: SYN/ACK received in the wrong direction", ssn);
return -1;
}
/* Check if the SYN/ACK packet ack's the earlier
* received SYN packet. */
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.isn + 1))) {
SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != "
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
ssn->client.isn + 1);
return -1;
}
/* update state */
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
SCLogDebug("ssn %p: =~ ssn state is now TCP_SYN_RECV", ssn);
/* sequence number & window */
ssn->server.isn = TCP_GET_SEQ(p);
ssn->server.ra_base_seq = ssn->server.isn;
ssn->server.next_seq = ssn->server.isn + 1;
ssn->client.window = TCP_GET_WINDOW(p);
SCLogDebug("ssn %p: window %" PRIu32 "", ssn, ssn->server.window);
/* Set the timestamp values used to validate the timestamp of
* received packets.*/
if ((p->tcpvars.ts != NULL) &&
(ssn->client.flags & STREAMTCP_FLAG_TIMESTAMP))
{
ssn->server.last_ts = TCP_GET_TSVAL(p);
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
"ssn->client.last_ts %" PRIu32"", ssn,
ssn->server.last_ts, ssn->client.last_ts);
ssn->client.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
ssn->server.last_pkt_ts = p->ts.tv_sec;
if (ssn->server.last_ts == 0)
ssn->server.flags |= STREAMTCP_FLAG_ZERO_TIMESTAMP;
} else {
ssn->client.last_ts = 0;
ssn->server.last_ts = 0;
ssn->client.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->client.flags &= ~STREAMTCP_FLAG_ZERO_TIMESTAMP;
}
ssn->client.last_ack = TCP_GET_ACK(p);
ssn->server.last_ack = ssn->server.isn + 1;
/** check for the presense of the ws ptr to determine if we
* support wscale at all */
if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) &&
(p->tcpvars.ws != NULL))
{
ssn->client.wscale = TCP_GET_WSCALE(p);
} else {
ssn->client.wscale = 0;
}
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 "", ssn,
ssn->server.next_win);
SCLogDebug("ssn %p: ssn->client.next_win %" PRIu32 "", ssn,
ssn->client.next_win);
SCLogDebug("ssn %p: ssn->server.isn %" PRIu32 ", "
"ssn->server.next_seq %" PRIu32 ", "
"ssn->server.last_ack %" PRIu32 " "
"(ssn->client.last_ack %" PRIu32 ")", ssn,
ssn->server.isn, ssn->server.next_seq,
ssn->server.last_ack, ssn->client.last_ack);
/* unset the 4WHS flag as we received this SYN/ACK as part of a
* (so far) valid 3WHS */
if (ssn->flags & STREAMTCP_FLAG_4WHS)
SCLogDebug("ssn %p: STREAMTCP_FLAG_4WHS unset, normal SYN/ACK"
" so considering 3WHS", ssn);
ssn->flags &=~ STREAMTCP_FLAG_4WHS;
break;
case TH_ACK:
case TH_ACK|TH_PUSH :
/* Handle the asynchronous stream, when we receive a SYN packet
and now istead of receving a SYN/ACK we receive a ACK from the
same host, which sent the SYN, this suggests the ASNYC streams.*/
if (stream_config.async_oneside == FALSE)
break;
/* Check if the ACK packet seq no is equal to the expected seq. no.*/
/*XXX GS what if we missed the initial ACK here, then we can't setup
the session. I was thinking to check the SEQ_GEQ instead of SEQ_EQ
your thoughts ?? It will quite relax then in checking.*/
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq))) {
SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != "
"%" PRIu32 " from stream",ssn, TCP_GET_SEQ(p),
ssn->client.next_seq);
return -1;
}
ssn->flags |= STREAMTCP_FLAG_ASYNC;
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
ssn->client.window = TCP_GET_WINDOW(p);
ssn->client.last_ack = TCP_GET_SEQ(p);
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
/* Set the server side parameters */
ssn->server.isn = TCP_GET_ACK(p) - 1;
ssn->server.ra_base_seq = ssn->server.isn;
ssn->server.next_seq = ssn->server.isn + 1;
ssn->server.last_ack = ssn->server.next_seq;
ssn->server.next_win = ssn->server.last_ack;
SCLogDebug("ssn %p: synsent => Asynchronous stream, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
"ssn->client.next_seq %" PRIu32 ""
,ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+ p->payload_len, ssn->client.next_seq);
ssn->client.wscale = TCP_WSCALE_MAX;
ssn->server.wscale = TCP_WSCALE_MAX;
/*Set the timestamp values used to validate the timestamp of received
packets.*/
if (p->tcpvars.ts != NULL &&
(ssn->client.flags & STREAMTCP_FLAG_TIMESTAMP))
{
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
ssn->client.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->client.last_pkt_ts = p->ts.tv_sec;
} else {
ssn->client.last_ts = 0;
ssn->client.flags &= ~STREAMTCP_FLAG_TIMESTAMP;
ssn->client.flags &= ~STREAMTCP_FLAG_ZERO_TIMESTAMP;
}
break;
case TH_RST:
case TH_RST|TH_ACK:
if(ValidReset(ssn, p)){
if(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn) &&
SEQ_EQ(TCP_GET_WINDOW(p), 0) &&
SEQ_EQ(TCP_GET_ACK(p), (ssn->client.isn + 1)))
{
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received and state changed to "
"TCP_CLOSED", ssn);
StreamTcpSessionPktFree(p);
}
} else
return -1;
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_SYN_RECV state. The function handles
* SYN, SYN/ACK, ACK, FIN, RST packets and correspondingly changes
* the connection state.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch (p->tcph->th_flags) {
case TH_SYN:
SCLogDebug("ssn %p: SYN packet on state SYN_RECV... resent", ssn);
break;
case TH_SYN|TH_ACK:
SCLogDebug("ssn %p: SYN/ACK packet on state SYN_RECV. resent", ssn);
break;
case TH_ACK:
case TH_ACK|TH_PUSH:
/* If the timestamp option is enabled for both the streams, then
* validate the received packet timestamp value against the
* stream->last_ts. If the timestamp is valid then process the
* packet normally otherwise the drop the packet (RFC 1323)*/
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (ssn->flags & STREAMTCP_FLAG_4WHS && PKT_IS_TOCLIENT(p)) {
SCLogDebug("ssn %p: ACK received on 4WHS session",ssn);
if ((SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq))) {
SCLogDebug("4WHS normal pkt");
ssn->client.last_ack = TCP_GET_ACK(p);
ssn->server.next_seq += p->payload_len;
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
ssn->client.next_win = ssn->client.last_ack +
ssn->client.window;
/* If no stream reassembly/application layer protocol
* inspection, then simple return*/
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
} else {
SCLogDebug("ssn %p: 4WHS wrong seq nr on packet", ssn);
return -1;
}
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
SCLogDebug("ssn %p: ssn->client.next_win %" PRIu32 ", "
"ssn->client.last_ack %"PRIu32"", ssn,
ssn->client.next_win, ssn->client.last_ack);
break;
}
/* Check if the ACK received is in right direction. But when we have
* picked up a mid stream session after missing the initial SYN pkt,
* in this case the ACK packet can arrive from either client (normal
* case) or from server itself (asynchronous streams). Therefore
* the check has been avoided in this case */
if (PKT_IS_TOCLIENT(p)) {
/* special case, handle 4WHS, so SYN/ACK in the opposite
* direction */
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK) {
SCLogDebug("ssn %p: ACK received on midstream SYN/ACK "
"pickup session",ssn);
/* fall through */
} else {
SCLogDebug("ssn %p: ACK received in the wrong direction",
ssn);
return -1;
}
}
if ((SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq))) {
SCLogDebug("normal pkt");
/* process the packet normal, No Async streams :) */
ssn->server.last_ack = TCP_GET_ACK(p);
ssn->client.next_seq += p->payload_len;
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
ssn->client.window = TCP_GET_WINDOW(p);
ssn->server.next_win = ssn->server.last_ack +
ssn->server.window;
/* window scaling for midstream pickups, we can't do much
* other than assume that it's set to the max value: 14 */
ssn->server.wscale = TCP_WSCALE_MAX;
ssn->client.wscale = TCP_WSCALE_MAX;
}
/* If no stream reassembly/application layer protocol inspection
* then simple return*/
if (!(ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
/* If asynchronous stream handling is allowed then set the session,
if packet's seq number is equal the expected seq no.*/
} else if (stream_config.async_oneside == TRUE &&
(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)))
{
/*set the ASYNC flag used to indicate the session as async stream
and helps in relaxing the windows checks.*/
ssn->flags |= STREAMTCP_FLAG_ASYNC;
ssn->server.next_seq += p->payload_len;
ssn->server.last_ack = TCP_GET_SEQ(p);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
ssn->client.last_ack = TCP_GET_ACK(p);
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
ssn->server.window = TCP_GET_WINDOW(p);
ssn->client.next_win = ssn->server.last_ack +
ssn->server.window;
/* window scaling for midstream pickups, we can't do much
* other than assume that it's set to the max value: 14 */
ssn->server.wscale = TCP_WSCALE_MAX;
ssn->client.wscale = TCP_WSCALE_MAX;
}
SCLogDebug("ssn %p: synrecv => Asynchronous stream, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
"ssn->server.next_seq %" PRIu32 "\n"
, ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+ p->payload_len, ssn->server.next_seq);
/* If no stream reassembly/application layer protocol inspection
* then simple return*/
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
} else {
SCLogDebug("ssn %p: wrong seq nr on packet", ssn);
return -1;
}
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ""
", ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
TCP_GET_ACK(p));
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 ", "
"ssn->server.last_ack %"PRIu32"", ssn,
ssn->server.next_win, ssn->server.last_ack);
break;
case TH_RST:
case TH_RST|TH_ACK:
if(ValidReset(ssn, p)) {
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received and state changed to "
"TCP_CLOSED", ssn);
StreamTcpSessionPktFree(p);
} else
return -1;
break;
case TH_FIN:
/*FIN is handled in the same way as in TCP_ESTABLISHED case */;
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if((StreamTcpHandleFin(stt, ssn, p)) == -1)
return -1;
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_ESTABLISHED state packets, which are
* sent by the client to server. The function handles
* ACK packets and call StreamTcpReassembleHandleSegment() to handle
* the reassembling.
*
* \param ssn Pointer to the current TCP session
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int HandleEstablishedPacketToServer(TcpSession *ssn, Packet *p,
StreamTcpThread *stt)
{
SCLogDebug("ssn %p: =+ pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ","
"ACK %" PRIu32 ", WIN %"PRIu16"", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p), TCP_GET_WINDOW(p));
if (!(SEQ_GEQ(TCP_GET_SEQ(p), ssn->client.last_ack))) {
if (ssn->flags & STREAMTCP_FLAG_ASYNC) {
SCLogDebug("ssn %p: server => Asynchrouns stream, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
" ssn->client.last_ack %" PRIu32 ", ssn->client.next_win"
"%" PRIu32"(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")",
ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win,
ssn->client.ra_base_seq);
/*update the last_ack to current seq number as the session is
async and other stream is not updating it anymore :(*/
ssn->client.last_ack = TCP_GET_SEQ(p);
} else if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p)) &&
(stream_config.async_oneside == TRUE) &&
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM)) {
SCLogDebug("ssn %p: server => Asynchronous stream, packet SEQ."
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
"%" PRIu32 "(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")"
, ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win,
ssn->client.ra_base_seq);
/*it seems we missed SYN and SYN/ACK packets of this session.
* Update the last_ack to current seq number as the session
* is async and other stream is not updating it anymore :(*/
ssn->client.last_ack = TCP_GET_SEQ(p);
ssn->flags |= STREAMTCP_FLAG_ASYNC;
} else if (SEQ_EQ(ssn->client.last_ack, (ssn->client.isn + 1)) &&
(stream_config.async_oneside == TRUE) &&
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM)) {
SCLogDebug("ssn %p: server => Asynchronous stream, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
"%" PRIu32 "(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")"
, ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win,
ssn->client.ra_base_seq);
/*it seems we missed SYN and SYN/ACK packets of this session.
* Update the last_ack to current seq number as the session
* is async and other stream is not updating it anymore :(*/
ssn->client.last_ack = TCP_GET_SEQ(p);
ssn->flags |= STREAMTCP_FLAG_ASYNC;
} else {
SCLogDebug("ssn %p: server => SEQ before last_ack, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
"%" PRIu32 "(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")"
, ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win,
ssn->client.ra_base_seq);
return -1;
}
}
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
ssn->client.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "",
ssn, ssn->client.next_seq);
}
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->client.next_win) ||
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM) ||
ssn->flags & STREAMTCP_FLAG_ASYNC)
{
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win"
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->client.next_win);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
SCLogDebug("ssn %p: ssn->server.window %"PRIu32"", ssn,
ssn->server.window);
if (SEQ_GT(TCP_GET_ACK(p), ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (SEQ_GT((ssn->server.last_ack + ssn->server.window),
ssn->server.next_win))
{
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
SCLogDebug("ssn %p: seq %"PRIu32", updated ssn->server.next_win"
"%" PRIu32 " (win %"PRIu32")", ssn, TCP_GET_SEQ(p),
ssn->server.next_win, ssn->server.window);
}
/*If no stream reassembly/application layer protocol inspection,
* then simple return*/
if (!(ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn, &ssn->client, p);
} else {
SCLogDebug("ssn %p: toserver => SEQ out of window, packet SEQ "
"%" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
"%" PRIu32 "(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")",
ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win,
ssn->client.ra_base_seq);
}
return 0;
}
/**
* \brief Function to handle the TCP_ESTABLISHED state packets, which are
* sent by the server to client. The function handles
* ACK packets and call StreamTcpReassembleHandleSegment() to handle
* the reassembling.
*
* \param ssn Pointer to the current TCP session
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int HandleEstablishedPacketToClient(TcpSession *ssn, Packet *p,
StreamTcpThread *stt)
{
SCLogDebug("ssn %p: =+ pkt (%" PRIu32 ") is to client: SEQ %" PRIu32 ","
" ACK %" PRIu32 ", WIN %"PRIu16"", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p), TCP_GET_WINDOW(p));
/* To get the server window value from the servers packet, when connection
is picked up as midstream */
if ((ssn->flags & STREAMTCP_FLAG_MIDSTREAM) &&
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED))
{
ssn->server.window = TCP_GET_WINDOW(p);
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
ssn->flags &= ~STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
SCLogDebug("ssn %p: adjusted midstream ssn->server.next_win to"
" %" PRIu32 "", ssn, ssn->server.next_win);
}
if (!(SEQ_GEQ(TCP_GET_SEQ(p), ssn->server.last_ack))) {
if (ssn->flags & STREAMTCP_FLAG_ASYNC) {
SCLogDebug("ssn %p: client => Asynchrouns stream, packet SEQ"
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
" ssn->client.last_ack %" PRIu32 ", ssn->client.next_win"
" %"PRIu32"(%"PRIu32") (ssn->client.ra_base_seq %"PRIu32")",
ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
+p->payload_len, ssn->server.last_ack, ssn->server.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->server.next_win,
ssn->server.ra_base_seq);
ssn->server.last_ack = TCP_GET_SEQ(p);
} else {
return -1;
}
}
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
ssn->server.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "",
ssn, ssn->server.next_seq);
}
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->server.next_win) ||
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM) ||
(ssn->flags & STREAMTCP_FLAG_ASYNC)) {
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win"
" %" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
SCLogDebug("ssn %p: ssn->client.window %"PRIu32"", ssn,
ssn->client.window);
if (SEQ_GT(TCP_GET_ACK(p), ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (SEQ_GT((ssn->client.last_ack + ssn->client.window),
ssn->client.next_win))
{
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
SCLogDebug("ssn %p: seq %"PRIu32", updated ssn->client.next_win"
" %" PRIu32 " (win %"PRIu32")", ssn, TCP_GET_SEQ(p),
ssn->client.next_win, ssn->client.window);
} else {
SCLogDebug("ssn %p: seq %"PRIu32", keeping ssn->client.next_win"
" %" PRIu32 " the same (win %"PRIu32")", ssn,
TCP_GET_SEQ(p),ssn->client.next_win, ssn->client.window);
}
/*If no stream reassembly/application layer protocol inspection, then
* simple return*/
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn, &ssn->server, p);
} else {
SCLogDebug("ssn %p: client => SEQ out of window, packet SEQ"
"%" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
" ssn->server.last_ack %" PRIu32 ", ssn->server.next_win "
"%" PRIu32 "(%"PRIu32") (ssn->server.ra_base_seq %"PRIu32")",
ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p) +
p->payload_len, ssn->server.last_ack, ssn->server.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->server.next_win,
ssn->server.ra_base_seq);
}
return 0;
}
/**
* \brief Function to handle the TCP_ESTABLISHED state. The function handles
* ACK, FIN, RST packets and correspondingly changes the connection
* state. The function handles the data inside packets and call
* StreamTcpReassembleHandleSegment() to handle the reassembling.
*
* \param tv Thread Variable containig input/output queue, cpu affinity etc.
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateEstablished(ThreadVars *tv, Packet *p,
StreamTcpThread *stt,
TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch (p->tcph->th_flags) {
case TH_SYN:
SCLogDebug("ssn %p: SYN packet on state ESTABLISED... resent", ssn);
break;
case TH_SYN|TH_ACK:
SCLogDebug("ssn %p: SYN/ACK packet on state ESTABLISHED... resent",
ssn);
break;
case TH_ACK:
case TH_ACK|TH_PUSH:
/* If the timestamp option is enabled for both the streams, then
* validate the received packet timestamp value against the
* stream->last_ts. If the timestamp is valid then process the
* packet normally otherwise the drop the packet (RFC 1323) */
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
/* Process the received packet to server */
HandleEstablishedPacketToServer(ssn, p, stt);
SCLogDebug("ssn %p: next SEQ %" PRIu32 ", last ACK %" PRIu32 ","
" next win %" PRIu32 ", win %" PRIu32 "", ssn,
ssn->client.next_seq, ssn->server.last_ack
,ssn->client.next_win, ssn->client.window);
} else { /* implied to client */
/* Process the received packet to client */
HandleEstablishedPacketToClient(ssn, p, stt);
SCLogDebug("ssn %p: next SEQ %" PRIu32 ", last ACK %" PRIu32 ","
" next win %" PRIu32 ", win %" PRIu32 "", ssn,
ssn->server.next_seq, ssn->client.last_ack,
ssn->server.next_win, ssn->server.window);
}
break;
case TH_FIN:
case TH_FIN|TH_ACK:
case TH_FIN|TH_ACK|TH_PUSH:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
SCLogDebug("StreamTcpPacketStateEstablished (%p): FIN received SEQ"
" %" PRIu32 ", last ACK %" PRIu32 ", next win %"PRIu32","
" win %" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack, ssn->server.next_win,
ssn->server.window);
if((StreamTcpHandleFin(stt, ssn, p)) == -1)
return -1;
break;
case TH_RST:
case TH_RST|TH_ACK:
if(ValidReset(ssn, p)) {
if(PKT_IS_TOSERVER(p)) {
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received and state changed to "
"TCP_CLOSED", ssn);
ssn->client.next_seq = TCP_GET_ACK(p);
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len + 1;
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
ssn->server.next_seq);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
StreamTcpSessionPktFree(p);
} else {
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received and state changed to "
"TCP_CLOSED", ssn);
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len + 1;
ssn->client.next_seq = TCP_GET_ACK(p);
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
ssn->server.next_seq);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
StreamTcpSessionPktFree(p);
}
} else
return -1;
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the FIN packets for states TCP_SYN_RECV and
* TCP_ESTABLISHED and changes to another TCP state as required.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpHandleFin(StreamTcpThread *stt, TcpSession *ssn, Packet *p)
{
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ","
" ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 " != "
"%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p),
ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_CLOSE_WAIT);
SCLogDebug("ssn %p: state changed to TCP_CLOSE_WAIT", ssn);
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len + 1;
ssn->server.next_seq = TCP_GET_ACK(p);
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
ssn->server.next_seq);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn, &ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK %" PRIu32 "",
ssn, ssn->client.next_seq, ssn->server.last_ack);
} else { /* implied to client */
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ %" PRIu32 ", "
"ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack +
ssn->server.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 " != "
"%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p),
ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT1);
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT1", ssn);
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len + 1;
ssn->client.next_seq = TCP_GET_ACK(p);
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
ssn->server.next_seq);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn, &ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK %" PRIu32 "",
ssn, ssn->server.next_seq, ssn->client.last_ack);
}
return 0;
}
/**
* \brief Function to handle the TCP_FIN_WAIT1 state. The function handles
* ACK, FIN, RST packets and correspondingly changes the connection
* state.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateFinWait1(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch (p->tcph->th_flags) {
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2);
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
ssn->client.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "",
ssn, ssn->client.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
} else { /* implied to client */
if (TCP_GET_SEQ(p) != ssn->server.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2);
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
ssn->server.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "",
ssn, ssn->server.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
break;
case TH_FIN:
case TH_FIN|TH_ACK:
case TH_FIN|TH_ACK|TH_PUSH:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack +
ssn->client.window))))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
ssn->client.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "",
ssn, ssn->client.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
} else { /* implied to client */
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack +
ssn->server.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
ssn->server.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "",
ssn, ssn->server.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
break;
case TH_RST:
case TH_RST|TH_ACK:
if(ValidReset(ssn, p)) {
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED",
ssn);
StreamTcpSessionPktFree(p);
}
else
return -1;
break;
default:
SCLogDebug("ssn (%p): default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_FIN_WAIT2 state. The function handles
* ACK, RST, FIN packets and correspondingly changes the connection
* state.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateFinWait2(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch (p->tcph->th_flags) {
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
ssn->client.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "",
ssn, ssn->client.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
} else { /* implied to client */
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->server.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
ssn->server.next_seq += p->payload_len;
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "",
ssn, ssn->server.next_seq);
}
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
break;
case TH_RST:
case TH_RST|TH_ACK:
if(ValidReset(ssn, p)) {
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED",
ssn);
StreamTcpSessionPktFree(p);
}
else
return -1;
break;
case TH_FIN:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack +
ssn->client.window))))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ "
"%" PRIu32 " != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
} else { /* implied to client */
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack +
ssn->server.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ "
"%" PRIu32 " != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_CLOSING state. Upon arrival of ACK
* the connection goes to TCP_TIME_WAIT state. The state has been
* reached as both end application has been closed.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateClosing(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch(p->tcph->th_flags) {
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
} else { /* implied to client */
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->server.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("StreamTcpPacketStateClosing (%p): =+ next SEQ "
"%" PRIu32 ", last ACK %" PRIu32 "", ssn,
ssn->server.next_seq, ssn->client.last_ack);
}
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_CLOSE_WAIT state. Upon arrival of FIN
* packet from server the connection goes to TCP_LAST_ACK state.
* The state is possible only for server host.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateCloseWait(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch(p->tcph->th_flags) {
case TH_FIN:
case TH_FIN|TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOCLIENT(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack +
ssn->server.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_LAST_ACK);
SCLogDebug("ssn %p: state changed to TCP_LAST_ACK", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
break;
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOCLIENT(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack +
ssn->server.window)))
{
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
}
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_LAST_ACK state. Upon arrival of ACK
* the connection goes to TCP_CLOSED state and stream memory is
* returned back to pool. The state is possible only for server host.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPakcetStateLastAck(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch(p->tcph->th_flags) {
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
StreamTcpSessionPktFree(p);
}
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/**
* \brief Function to handle the TCP_TIME_WAIT state. Upon arrival of ACK
* the connection goes to TCP_CLOSED state and stream memory is
* returned back to pool.
*
* \param tv Thread Variable containig input/output queue, cpu affinity
* \param p Packet which has to be handled in this TCP state.
* \param stt Strean Thread module registered to handle the stream handling
*/
static int StreamTcpPacketStateTimeWait(ThreadVars *tv, Packet *p,
StreamTcpThread *stt, TcpSession *ssn)
{
if (ssn == NULL)
return -1;
switch(p->tcph->th_flags) {
case TH_ACK:
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p)) {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->client.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->server.last_ack))
ssn->server.last_ack = TCP_GET_ACK(p);
if (! (ssn->flags & STREAMTCP_FLAG_NOCLIENT_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->client, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->client.next_seq,
ssn->server.last_ack);
StreamTcpSessionPktFree(p);
} else {
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
TCP_GET_SEQ(p), TCP_GET_ACK(p));
if (TCP_GET_SEQ(p) != ssn->server.next_seq) {
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
" != %" PRIu32 " from stream", ssn,
TCP_GET_SEQ(p), ssn->server.next_seq);
return -1;
}
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
if (SEQ_GT(TCP_GET_ACK(p),ssn->client.last_ack))
ssn->client.last_ack = TCP_GET_ACK(p);
if (!(ssn->flags & STREAMTCP_FLAG_NOSERVER_REASSEMBLY))
StreamTcpReassembleHandleSegment(stt->ra_ctx, ssn,
&ssn->server, p);
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
"%" PRIu32 "", ssn, ssn->server.next_seq,
ssn->client.last_ack);
StreamTcpSessionPktFree(p);
}
break;
default:
SCLogDebug("ssn %p: default case", ssn);
break;
}
return 0;
}
/* flow is and stays locked */
static int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt)
{
SCEnter();
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
if (ssn == NULL || ssn->state == TCP_NONE) {
if (StreamTcpPacketStateNone(tv, p, stt, ssn) == -1)
SCReturnInt(-1);
if (ssn != NULL)
SCLogDebug("ssn->alproto %"PRIu16"", ssn->alproto);
} else {
/* check if the packet is in right direction, when we missed the
SYN packet and picked up midstream session. */
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK)
StreamTcpPacketSwitchDir(ssn, p);
switch (ssn->state) {
case TCP_SYN_SENT:
if(StreamTcpPacketStateSynSent(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_SYN_RECV:
if(StreamTcpPacketStateSynRecv(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_ESTABLISHED:
if(StreamTcpPacketStateEstablished(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_FIN_WAIT1:
if(StreamTcpPacketStateFinWait1(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_FIN_WAIT2:
if(StreamTcpPacketStateFinWait2(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_CLOSING:
if(StreamTcpPacketStateClosing(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_CLOSE_WAIT:
if(StreamTcpPacketStateCloseWait(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_LAST_ACK:
if(StreamTcpPakcetStateLastAck(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_TIME_WAIT:
if(StreamTcpPacketStateTimeWait(tv, p, stt, ssn))
SCReturnInt(-1);
break;
case TCP_CLOSED:
SCLogDebug("packet received on closed state");
break;
default:
SCLogDebug("packet received on default state");
break;
}
}
/* Process stream smsgs we may have in queue */
if (StreamTcpReassembleProcessAppLayer(stt->ra_ctx) < 0)
SCReturnInt(-1);
SCReturnInt(0);
}
TmEcode StreamTcp (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq)
{
StreamTcpThread *stt = (StreamTcpThread *)data;
TmEcode ret = TM_ECODE_OK;
if (!(PKT_IS_TCP(p)))
return TM_ECODE_OK;
if (p->flow == NULL)
return TM_ECODE_OK;
SCMutexLock(&p->flow->m);
ret = StreamTcpPacket(tv, p, stt);
SCMutexUnlock(&p->flow->m);
//if (ret)
// return TM_ECODE_FAILED;
stt->pkts++;
return TM_ECODE_OK;
}
TmEcode StreamTcpThreadInit(ThreadVars *tv, void *initdata, void **data)
{
StreamTcpThread *stt = malloc(sizeof(StreamTcpThread));
if (stt == NULL) {
return TM_ECODE_FAILED;
}
memset(stt, 0, sizeof(StreamTcpThread));
*data = (void *)stt;
stt->counter_tcp_sessions = SCPerfTVRegisterCounter("tcp.sessions", tv,
SC_PERF_TYPE_UINT64,
"NULL");
tv->sc_perf_pca = SCPerfGetAllCountersArray(&tv->sc_perf_pctx);
SCPerfAddToClubbedTMTable(tv->name, &tv->sc_perf_pctx);
/* init reassembly ctx */
stt->ra_ctx = StreamTcpReassembleInitThreadCtx();
if (stt->ra_ctx == NULL)
return TM_ECODE_FAILED;
SCLogDebug("StreamTcp thread specific ctx online at %p, reassembly ctx %p",
stt, stt->ra_ctx);
return TM_ECODE_OK;
}
TmEcode StreamTcpThreadDeinit(ThreadVars *tv, void *data)
{
StreamTcpThread *stt = (StreamTcpThread *)data;
if (stt == NULL) {
return TM_ECODE_OK;
}
/* XXX */
/* free reassembly ctx */
/* clear memory */
memset(stt, 0, sizeof(StreamTcpThread));
free(stt);
return TM_ECODE_OK;
}
void StreamTcpExitPrintStats(ThreadVars *tv, void *data)
{
StreamTcpThread *stt = (StreamTcpThread *)data;
if (stt == NULL) {
return;
}
SCLogInfo("(%s) Packets %" PRIu64 "", tv->name, stt->pkts);
}
/**
* \brief Function to check the validity of the RST packets based on the
* target OS of the given packet.
*
* \param ssn TCP session to which the given packet belongs
* \param p Packet which has to be checked for its validity
*/
static int ValidReset(TcpSession *ssn, Packet *p)
{
uint8_t os_policy;
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
if (!ValidTimestamp(ssn, p))
return -1;
}
if (PKT_IS_TOSERVER(p))
os_policy = ssn->server.os_policy;
else
os_policy = ssn->client.os_policy;
switch (os_policy) {
case OS_POLICY_HPUX11:
if(PKT_IS_TOSERVER(p)){
if(SEQ_GEQ(TCP_GET_SEQ(p), ssn->client.next_seq)) {
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
} else {
SCLogDebug("reset is not Valid! Packet SEQ: %" PRIu32 " "
"and server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->client.next_seq);
return 0;
}
} else { /* implied to client */
if(SEQ_GEQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
} else {
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " "
"and client SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->server.next_seq);
return 0;
}
}
break;
case OS_POLICY_OLD_LINUX:
case OS_POLICY_LINUX:
case OS_POLICY_SOLARIS:
if(PKT_IS_TOSERVER(p)){
if(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len),
ssn->client.last_ack))
{ /*window base is needed !!*/
if(SEQ_LT(TCP_GET_SEQ(p),
(ssn->client.next_seq + ssn->client.window)))
{
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
}
} else {
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
" server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->client.next_seq);
return 0;
}
} else { /* implied to client */
if(SEQ_GEQ((TCP_GET_SEQ(p) + p->payload_len),
ssn->server.last_ack))
{ /*window base is needed !!*/
if(SEQ_LT(TCP_GET_SEQ(p),
(ssn->server.next_seq + ssn->server.window)))
{
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
}
} else {
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
" client SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->server.next_seq);
return 0;
}
}
break;
default:
case OS_POLICY_BSD:
case OS_POLICY_FIRST:
case OS_POLICY_HPUX10:
case OS_POLICY_IRIX:
case OS_POLICY_MACOS:
case OS_POLICY_LAST:
case OS_POLICY_WINDOWS:
case OS_POLICY_WINDOWS2K3:
case OS_POLICY_VISTA:
if(PKT_IS_TOSERVER(p)) {
if(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)) {
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
} else {
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " "
"and server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->client.next_seq);
return 0;
}
} else { /* implied to client */
if(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 "",
TCP_GET_SEQ(p));
return 1;
} else {
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
" client SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
ssn->server.next_seq);
return 0;
}
}
break;
}
return 0;
}
/**
* \brief Function to return the FLOW state depending upon the TCP session state.
*
* \param s TCP session of which the state has to be returned
* \retval The FLOW_STATE_ depends upon the TCP sesison state, default is
* FLOW_STATE_CLOSED
*/
int StreamTcpGetFlowState(void *s)
{
TcpSession *ssn = (TcpSession *)s;
if (ssn == NULL)
return FLOW_STATE_CLOSED;
switch(ssn->state) {
case TCP_NONE:
case TCP_SYN_SENT:
case TCP_SYN_RECV:
case TCP_LISTEN:
return FLOW_STATE_NEW;
case TCP_ESTABLISHED:
return FLOW_STATE_ESTABLISHED;
case TCP_FIN_WAIT1:
case TCP_FIN_WAIT2:
case TCP_CLOSING:
case TCP_LAST_ACK:
case TCP_TIME_WAIT:
case TCP_CLOSE_WAIT:
case TCP_CLOSED:
return FLOW_STATE_CLOSED;
}
return FLOW_STATE_CLOSED;
}
/**
* \brief Function to check the validity of the received timestamp based on
* the target OS of the given stream.
*
* \param ssn TCP session to which the given packet belongs
* \param p Packet which has to be checked for its validity
* \retval If timestamp is valid, function returns 1 otherwise 0
*/
static int ValidTimestamp (TcpSession *ssn, Packet *p)
{
TcpStream *sender_stream;
TcpStream *receiver_stream;
uint8_t ret = 1;
uint8_t check_ts = 1;
if (PKT_IS_TOSERVER(p)) {
sender_stream = &ssn->client;
receiver_stream = &ssn->server;
} else {
sender_stream = &ssn->server;
receiver_stream = &ssn->client;
}
if (p->tcpvars.ts != NULL) {
uint32_t ts = TCP_GET_TSVAL(p);
if (sender_stream->flags & STREAMTCP_FLAG_ZERO_TIMESTAMP) {
/*The 3whs used the timestamp with 0 value. */
switch (receiver_stream->os_policy) {
case OS_POLICY_LINUX:
case OS_POLICY_WINDOWS2K3:
/* Linux and windows 2003 does not allow the use of 0 as
* timestamp in the 3whs. */
ssn->flags &= ~STREAMTCP_FLAG_TIMESTAMP;
check_ts = 0;
break;
case OS_POLICY_OLD_LINUX:
case OS_POLICY_WINDOWS:
case OS_POLICY_VISTA:
sender_stream->flags &= ~STREAMTCP_FLAG_ZERO_TIMESTAMP;
if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) {
sender_stream->last_ts = ts;
check_ts = 0; /*next packet will be checked for validity
and stream TS has been updated with this
one.*/
}
break;
default:
break;
}
}
if (receiver_stream->os_policy == OS_POLICY_HPUX11) {
/*HPUX11 igoners the timestamp of out of order packets*/
if (!SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
check_ts = 0;
}
if (ts == 0) {
switch (receiver_stream->os_policy) {
case OS_POLICY_OLD_LINUX:
case OS_POLICY_WINDOWS:
case OS_POLICY_WINDOWS2K3:
case OS_POLICY_VISTA:
case OS_POLICY_SOLARIS:
/*Old Linux and windows allowed packet with 0 timestamp.*/
break;
default:
/* other OS simply drop the pakcet with 0 timestamp, when
* 3whs has valid timestamp*/
return 0;
}
}
if (check_ts) {
int32_t result = 0;
if (receiver_stream->os_policy == OS_POLICY_LINUX) {
/* Linux accepts TS which are off by one.*/
result = (int32_t) ((ts - sender_stream->last_ts) + 1);
} else {
result = (int32_t) (ts - sender_stream->last_ts);
}
if (sender_stream->last_pkt_ts == 0 &&
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM))
{
sender_stream->last_pkt_ts = p->ts.tv_sec;
}
if (result < 0) {
SCLogDebug("timestamp is not valid sender_stream->last_ts "
"%" PRIu32 " p->tcpvars->ts %" PRIu32 " result "
"%" PRId32 "", sender_stream->last_ts, ts, result);
ret = 0;
} else if ((sender_stream->last_ts != 0) &&
(((uint32_t) p->ts.tv_sec) >
sender_stream->last_pkt_ts + PAWS_24DAYS))
{
SCLogDebug("packet is not valid sender_stream->last_pkt_ts "
"%" PRIu32 " p->ts.tv_sec %" PRIu32 "",
sender_stream->last_pkt_ts, (uint32_t) p->ts.tv_sec);
ret = 0;
}
if (ret == 1) {
/* Update the timestamp and last seen packet time for this
* stream */
if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
sender_stream->last_ts = ts;
sender_stream->last_pkt_ts = p->ts.tv_sec;
}
if (ret == 0) {
/* if the timestamp of packet is not valid then, check if the
* current stream timestamp is not so old. if so then we need to
* accept the packet and update the stream->last_ts (RFC 1323)*/
if ((SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
&& (((uint32_t) p->ts.tv_sec >
(sender_stream->last_pkt_ts + PAWS_24DAYS))))
{
sender_stream->last_ts = ts;
sender_stream->last_pkt_ts = p->ts.tv_sec;
ret = 1;
}
}
}
} else {
/* Solaris stops using timestamps if a packet is received
without a timestamp and timestamps were used on that stream. */
if (receiver_stream->os_policy == OS_POLICY_SOLARIS)
ssn->flags &= ~STREAMTCP_FLAG_TIMESTAMP;
}
return ret;
}
/** \brief Set the No reassembly flag for the given direction in given TCP session.
*
* \param ssn TCP Session to set the flag in
* \param direction direction to set the flag in
*/
void StreamTcpSetSessionNoReassemblyFlag (TcpSession *ssn, char direction)
{
direction ? (ssn->flags |= STREAMTCP_FLAG_NOSERVER_REASSEMBLY) :
(ssn->flags |= STREAMTCP_FLAG_NOCLIENT_REASSEMBLY);
}
#ifdef UNITTESTS
/**
* \test Test the allocation of TCP session for a given packet from the
* ssn_pool.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest01 (void) {
Packet p;
Flow f;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
TcpSession *ssn = StreamTcpNewSession(&p);
if (ssn == NULL) {
printf("Session can not be allocated: ");
goto end;
}
f.protoctx = ssn;
if (ssn->aldata != NULL) {
printf("AppLayer field not set to NULL: ");
goto end;
}
if (ssn->state != 0) {
printf("TCP state field not set to 0: ");
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the deallocation of TCP session for a given packet and return
* the memory back to ssn_pool and corresponding segments to segment
* pool.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest02 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
u_int8_t payload[4];
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
tcph.th_win = htons(5480);
tcph.th_flags = TH_SYN;
p.tcph = &tcph;
p.flowflags = FLOW_PKT_TOSERVER;
int ret = 0;
StreamTcpInitConfig(TRUE);
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_ack = htonl(1);
p.tcph->th_flags = TH_SYN | TH_ACK;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_ack = htonl(1);
p.tcph->th_seq = htonl(1);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_ack = htonl(1);
p.tcph->th_seq = htonl(2);
p.tcph->th_flags = TH_PUSH | TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x41, 3); /*AAA*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_ack = htonl(1);
p.tcph->th_seq = htonl(6);
p.tcph->th_flags = TH_PUSH | TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we missed the intial
* SYN packet of the session. The session is setup only if midstream
* sessions are allowed to setup.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest03 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_SYN|TH_ACK;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(20);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(19);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.midstream != TRUE) {
ret = 1;
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED)
goto end;
if (((TcpSession *)(p.flow->protoctx))->client.next_seq != 20 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 11)
goto end;
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we missed the intial
* SYN/ACK packet of the session. The session is setup only if
* midstream sessions are allowed to setup.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest04 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_ACK;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(9);
p.tcph->th_ack = htonl(19);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.midstream != TRUE) {
ret = 1;
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED)
goto end;
if (((TcpSession *)(p.flow->protoctx))->client.next_seq != 10 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 20)
goto end;
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we missed the intial
* 3WHS packet of the session. The session is setup only if
* midstream sessions are allowed to setup.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest05 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
u_int8_t payload[4];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
/* prevent L7 from kicking in */
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOCLIENT, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_ACK|TH_PUSH;
p.tcph = &tcph;
StreamTcpCreateTestPacket(payload, 0x41, 3); /*AAA*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(20);
p.tcph->th_ack = htonl(13);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOCLIENT;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(13);
p.tcph->th_ack = htonl(23);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x43, 3); /*CCC*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(19);
p.tcph->th_ack = htonl(16);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOCLIENT;
StreamTcpCreateTestPacket(payload, 0x44, 3); /*DDD*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.midstream != TRUE) {
ret = 1;
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED)
goto end;
if (((TcpSession *)(p.flow->protoctx))->client.next_seq != 16 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 23)
goto end;
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we have seen only the
* FIN, RST packets packet of the session. The session is setup only if
* midstream sessions are allowed to setup.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest06 (void) {
Packet p;
Flow f;
TcpSession ssn;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&ssn, 0, sizeof (TcpSession));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
tcph.th_flags = TH_FIN;
p.tcph = &tcph;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *)(p.flow->protoctx)) != NULL)
goto end;
p.tcph->th_flags = TH_RST;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *)(p.flow->protoctx)) != NULL)
goto end;
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the working on PAWS. The packet will be dropped by stream, as
* its timestamp is old, although the segment is in the window.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest07 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
u_int8_t payload[1] = {0x42};
TCPVars tcpvars;
TCPOpt ts;
uint32_t data[2];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof(StreamTcpThread));
memset(&tcph, 0, sizeof(TCPHdr));
memset(&tcpvars, 0, sizeof(TCPVars));
memset(&ts, 0, sizeof(TCPOpt));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
stream_config.midstream = TRUE;
/* prevent L7 from kicking in */
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOCLIENT, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_ACK|TH_PUSH;
p.tcph = &tcph;
data[0] = htonl(10);
data[1] = htonl(11);
ts.type = TCP_OPT_TS;
ts.len = 10;
ts.data = (uint8_t *)data;
tcpvars.ts = &ts;
p.tcpvars = tcpvars;
p.payload = payload;
p.payload_len = 1;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(23);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
data[0] = htonl(2);
p.tcpc.ts1 = 0;
p.tcpc.ts2 = 0;
p.tcpvars.ts->data = (uint8_t *)data;
if (StreamTcpPacket(&tv, &p, &stt) == -1) {
if (((TcpSession *) (p.flow->protoctx))->client.next_seq != 11) {
printf("the timestamp values are client %"PRIu32" server %" PRIu32 " seq %" PRIu32 "\n", TCP_GET_TSVAL(&p), TCP_GET_TSECR(&p), ((TcpSession *) (p.flow->protoctx))->client.next_seq);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
}
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the working on PAWS. The packet will be accpeted by engine as
* the timestamp is valid and it is in window.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest08 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
u_int8_t payload[1] = {0x42};
TCPVars tcpvars;
TCPOpt ts;
uint32_t data[2];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof(StreamTcpThread));
memset(&tcph, 0, sizeof(TCPHdr));
memset(&tcpvars, 0, sizeof(TCPVars));
memset(&ts, 0, sizeof(TCPOpt));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
stream_config.midstream = TRUE;
/* prevent L7 from kicking in */
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOCLIENT, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_ACK|TH_PUSH;
p.tcph = &tcph;
data[0] = htonl(10);
data[1] = htonl(11);
ts.type = TCP_OPT_TS;
ts.len = 10;
ts.data = (uint8_t *)data;
tcpvars.ts = &ts;
p.tcpvars = tcpvars;
p.payload = payload;
p.payload_len = 1;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(23);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
data[0] = htonl(12);
p.tcpc.ts1 = 0;
p.tcpc.ts2 = 0;
p.tcpvars.ts->data = (uint8_t *)data;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *) (p.flow->protoctx))->client.next_seq != 12) {
printf("the timestamp values are client %"PRIu32" server %" PRIu32 " seq %" PRIu32 "\n", TCP_GET_TSVAL(&p), TCP_GET_TSECR(&p), ((TcpSession *) (p.flow->protoctx))->client.next_seq);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the working of No stream reassembly flag. The stream will not reassemble the
* segment if the flag is set.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest09 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
u_int8_t payload[1] = {0x42};
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof(StreamTcpThread));
memset(&tcph, 0, sizeof(TCPHdr));
p.flow = &f;
int ret = 0;
StreamTcpInitConfig(TRUE);
stream_config.midstream = TRUE;
//prevent L7 from kicking in
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinInitChunkLen(FLOW_PKT_TOCLIENT, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096);
StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(20);
tcph.th_flags = TH_ACK|TH_PUSH;
p.tcph = &tcph;
p.payload = payload;
p.payload_len = 1;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(12);
p.tcph->th_ack = htonl(23);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpSetSessionNoReassemblyFlag(((TcpSession *)(p.flow->protoctx)), 0);
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(23);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *) (p.flow->protoctx))->client.seg_list->next == NULL)
ret = 1;
StreamTcpSessionClear(p.flow->protoctx);
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we are seeing asynchronous
* stream, while we see all the packets in that stream from start.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest10 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
uint8_t payload[4];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(11);
tcph.th_flags = TH_SYN;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(6);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.async_oneside != TRUE) {
ret = 1;
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("failed in setting state\n");
goto end;
}
if (! (((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_ASYNC)) {
printf("failed in setting asynchronous session\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->client.last_ack != 6 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 11) {
printf("failed in seq %"PRIu32" match\n",((TcpSession *)(p.flow->protoctx))->client.last_ack);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we are seeing asynchronous
* stream, while we missed the SYN packet of that stream.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest11 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
uint8_t payload[4];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(1);
tcph.th_flags = TH_SYN|TH_ACK;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(1);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(1);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(2);
p.tcph->th_ack = htonl(1);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.async_oneside != TRUE) {
ret = 1;
goto end;
}
if (! (((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_ASYNC)) {
printf("failed in setting asynchronous session\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("failed in setting state\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->server.last_ack != 2 ||
((TcpSession *)(p.flow->protoctx))->client.next_seq != 1) {
printf("failed in seq %"PRIu32" match\n",((TcpSession *)(p.flow->protoctx))->server.last_ack);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we are seeing asynchronous
* stream, while we missed the SYN and SYN/ACK packets in that stream.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest12 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
uint8_t payload[4];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(11);
tcph.th_flags = TH_ACK;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(10);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(6);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.async_oneside != TRUE) {
ret = 1;
goto end;
}
if (! (((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_ASYNC)) {
printf("failed in setting asynchronous session\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("failed in setting state\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->client.last_ack != 6 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 11) {
printf("failed in seq %"PRIu32" match\n",((TcpSession *)(p.flow->protoctx))->client.last_ack);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session when we are seeing asynchronous
* stream, while we missed the SYN and SYN/ACK packets in that stream.
* Later, we start to receive the packet from other end stream too.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpTest13 (void) {
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
uint8_t payload[4];
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = htonl(11);
tcph.th_flags = TH_ACK;
p.tcph = &tcph;
int ret = 0;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(10);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(6);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOSERVER;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (stream_config.async_oneside != TRUE) {
ret = 1;
goto end;
}
if (! (((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_ASYNC)) {
printf("failed in setting asynchronous session\n");
goto end;
}
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("failed in setting state\n");
goto end;
}
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(9);
p.tcph->th_flags = TH_ACK|TH_PUSH;
p.flowflags = FLOW_PKT_TOCLIENT;
StreamTcpCreateTestPacket(payload, 0x42, 3); /*BBB*/
p.payload = payload;
p.payload_len = 3;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *)(p.flow->protoctx))->client.last_ack != 9 ||
((TcpSession *)(p.flow->protoctx))->server.next_seq != 14) {
printf("failed in seq %"PRIu32" match\n",((TcpSession *)(p.flow->protoctx))->client.last_ack);
goto end;
}
StreamTcpSessionClear(p.flow->protoctx);
ret = 1;
end:
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test Test the setting up a TCP session using the 4WHS:
* SYN, SYN, SYN/ACK, ACK
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcp4WHSTest01 (void) {
int ret = 0;
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = 0;
tcph.th_flags = TH_SYN;
p.tcph = &tcph;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(20);
p.tcph->th_ack = 0;
p.tcph->th_flags = TH_SYN;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if ((!(((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_4WHS))) {
printf("STREAMTCP_FLAG_4WHS flag not set: ");
goto end;
}
p.tcph->th_seq = htonl(10);
p.tcph->th_ack = htonl(21); /* the SYN/ACK uses the SEQ from the first SYN pkt */
p.tcph->th_flags = TH_SYN|TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(21);
p.tcph->th_ack = htonl(10);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("state is not ESTABLISHED: ");
goto end;
}
ret = 1;
end:
StreamTcpSessionClear(p.flow->protoctx);
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test set up a TCP session using the 4WHS:
* SYN, SYN, SYN/ACK, ACK, but the SYN/ACK does
* not have the right SEQ
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcp4WHSTest02 (void) {
int ret = 0;
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = 0;
tcph.th_flags = TH_SYN;
p.tcph = &tcph;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(20);
p.tcph->th_ack = 0;
p.tcph->th_flags = TH_SYN;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if ((!(((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_4WHS))) {
printf("STREAMTCP_FLAG_4WHS flag not set: ");
goto end;
}
p.tcph->th_seq = htonl(30);
p.tcph->th_ack = htonl(21); /* the SYN/ACK uses the SEQ from the first SYN pkt */
p.tcph->th_flags = TH_SYN|TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) != -1) {
printf("SYN/ACK pkt not rejected but it should have: ");
goto end;
}
ret = 1;
end:
StreamTcpSessionClear(p.flow->protoctx);
StreamTcpFreeConfig(TRUE);
return ret;
}
/**
* \test set up a TCP session using the 4WHS:
* SYN, SYN, SYN/ACK, ACK: however the SYN/ACK and ACK
* are part of a normal 3WHS
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcp4WHSTest03 (void) {
int ret = 0;
Packet p;
Flow f;
ThreadVars tv;
StreamTcpThread stt;
TCPHdr tcph;
memset (&p, 0, sizeof(Packet));
memset (&f, 0, sizeof(Flow));
memset(&tv, 0, sizeof (ThreadVars));
memset(&stt, 0, sizeof (StreamTcpThread));
memset(&tcph, 0, sizeof (TCPHdr));
p.flow = &f;
StreamTcpInitConfig(TRUE);
tcph.th_win = htons(5480);
tcph.th_seq = htonl(10);
tcph.th_ack = 0;
tcph.th_flags = TH_SYN;
p.tcph = &tcph;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(20);
p.tcph->th_ack = 0;
p.tcph->th_flags = TH_SYN;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if ((!(((TcpSession *)(p.flow->protoctx))->flags & STREAMTCP_FLAG_4WHS))) {
printf("STREAMTCP_FLAG_4WHS flag not set: ");
goto end;
}
p.tcph->th_seq = htonl(30);
p.tcph->th_ack = htonl(11);
p.tcph->th_flags = TH_SYN|TH_ACK;
p.flowflags = FLOW_PKT_TOCLIENT;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
p.tcph->th_seq = htonl(11);
p.tcph->th_ack = htonl(31);
p.tcph->th_flags = TH_ACK;
p.flowflags = FLOW_PKT_TOSERVER;
if (StreamTcpPacket(&tv, &p, &stt) == -1)
goto end;
if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) {
printf("state is not ESTABLISHED: ");
goto end;
}
ret = 1;
end:
StreamTcpSessionClear(p.flow->protoctx);
StreamTcpFreeConfig(TRUE);
return ret;
}
#endif /* UNITTESTS */
void StreamTcpRegisterTests (void) {
#ifdef UNITTESTS
UtRegisterTest("StreamTcpTest01 -- TCP session allocation", StreamTcpTest01, 1);
UtRegisterTest("StreamTcpTest02 -- TCP session deallocation", StreamTcpTest02, 1);
UtRegisterTest("StreamTcpTest03 -- SYN missed MidStream session", StreamTcpTest03, 1);
UtRegisterTest("StreamTcpTest04 -- SYN/ACK missed MidStream session", StreamTcpTest04, 1);
UtRegisterTest("StreamTcpTest05 -- 3WHS missed MidStream session", StreamTcpTest05, 1);
UtRegisterTest("StreamTcpTest06 -- FIN, RST message MidStream session", StreamTcpTest06, 1);
UtRegisterTest("StreamTcpTest07 -- PAWS invalid timestamp", StreamTcpTest07, 1);
UtRegisterTest("StreamTcpTest08 -- PAWS valid timestamp", StreamTcpTest08, 1);
UtRegisterTest("StreamTcpTest09 -- No Client Reassembly", StreamTcpTest09, 1);
UtRegisterTest("StreamTcpTest10 -- No missed packet Async stream", StreamTcpTest10, 1);
UtRegisterTest("StreamTcpTest11 -- SYN missed Async stream", StreamTcpTest11, 1);
UtRegisterTest("StreamTcpTest12 -- SYN/ACK missed Async stream", StreamTcpTest12, 1);
UtRegisterTest("StreamTcpTest13 -- opposite stream packets for Async stream", StreamTcpTest13, 1);
UtRegisterTest("StreamTcp4WHSTest01 -- 4WHS setup", StreamTcp4WHSTest01, 1);
UtRegisterTest("StreamTcp4WHSTest02 -- 4WHS invalid setup", StreamTcp4WHSTest02, 1);
UtRegisterTest("StreamTcp4WHSTest03 -- 4WHS turning out as normal 3WHS", StreamTcp4WHSTest03, 1);
/* set up the reassembly tests as well */
StreamTcpReassembleRegisterTests();
#endif /* UNITTESTS */
}