diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/stream-tcp.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/stream-tcp.c')
-rw-r--r-- | framework/src/suricata/src/stream-tcp.c | 10772 |
1 files changed, 10772 insertions, 0 deletions
diff --git a/framework/src/suricata/src/stream-tcp.c b/framework/src/suricata/src/stream-tcp.c new file mode 100644 index 00000000..6cde8651 --- /dev/null +++ b/framework/src/suricata/src/stream-tcp.c @@ -0,0 +1,10772 @@ +/* Copyright (C) 2007-2013 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Victor Julien <victor@inliniac.net> + * \author Gurvinder Singh <gurvindersinghdahiya@gmail.com> + * + * TCP stream tracking and reassembly engine. + * + * \todo - 4WHS: what if after the 2nd SYN we turn out to be normal 3WHS anyway? + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "decode.h" +#include "debug.h" +#include "detect.h" + +#include "flow.h" +#include "flow-util.h" + +#include "conf.h" +#include "conf-yaml-loader.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-pool.h" +#include "util-pool-thread.h" +#include "util-checksum.h" +#include "util-unittest.h" +#include "util-print.h" +#include "util-debug.h" +#include "util-device.h" + +#include "stream-tcp-private.h" +#include "stream-tcp-reassemble.h" +#include "stream-tcp.h" +#include "stream-tcp-inline.h" +#include "stream-tcp-sack.h" +#include "stream-tcp-util.h" +#include "stream.h" + +#include "pkt-var.h" +#include "host.h" + +#include "app-layer.h" +#include "app-layer-parser.h" +#include "app-layer-protos.h" +#include "app-layer-htp-mem.h" + +#include "util-host-os-info.h" +#include "util-privs.h" +#include "util-profiling.h" +#include "util-misc.h" +#include "util-validate.h" +#include "util-runmodes.h" + +#include "source-pcap-file.h" + +//#define DEBUG + +#define STREAMTCP_DEFAULT_PREALLOC 2048 +#define STREAMTCP_DEFAULT_MEMCAP (32 * 1024 * 1024) /* 32mb */ +#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP (64 * 1024 * 1024) /* 64mb */ +#define STREAMTCP_DEFAULT_TOSERVER_CHUNK_SIZE 2560 +#define STREAMTCP_DEFAULT_TOCLIENT_CHUNK_SIZE 2560 +#define STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED 5 + +#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 + +TmEcode StreamTcp (ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *); +TmEcode StreamTcpThreadInit(ThreadVars *, void *, void **); +TmEcode StreamTcpThreadDeinit(ThreadVars *, void *); +void StreamTcpExitPrintStats(ThreadVars *, void *); +static int StreamTcpHandleFin(ThreadVars *tv, StreamTcpThread *, TcpSession *, Packet *, PacketQueue *); +void StreamTcpRegisterTests (void); +void StreamTcpReturnStreamSegments (TcpStream *); +void StreamTcpInitConfig(char); +int StreamTcpGetFlowState(void *); +void StreamTcpSetOSPolicy(TcpStream*, Packet*); +void StreamTcpPseudoPacketCreateStreamEndPacket(ThreadVars *tv, StreamTcpThread *stt, Packet *p, TcpSession *ssn, PacketQueue *pq); + +static int StreamTcpValidateTimestamp(TcpSession * , Packet *); +static int StreamTcpHandleTimestamp(TcpSession * , Packet *); +static int StreamTcpValidateRst(TcpSession * , Packet *); +static inline int StreamTcpValidateAck(TcpSession *ssn, TcpStream *, Packet *); + +static PoolThread *ssn_pool = NULL; +static SCMutex ssn_pool_mutex = SCMUTEX_INITIALIZER; /**< init only, protect initializing and growing pool */ +#ifdef DEBUG +static uint64_t ssn_pool_cnt = 0; /** counts ssns, protected by ssn_pool_mutex */ +#endif + +uint64_t StreamTcpReassembleMemuseGlobalCounter(void); +SC_ATOMIC_DECLARE(uint64_t, st_memuse); + +/* stream engine running in "inline" mode. */ +int stream_inline = 0; + +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; + tmm_modules[TMM_STREAMTCP].cap_flags = 0; + tmm_modules[TMM_STREAMTCP].flags = TM_FLAG_STREAM_TM; +} + +void StreamTcpIncrMemuse(uint64_t size) +{ + (void) SC_ATOMIC_ADD(st_memuse, size); + return; +} + +void StreamTcpDecrMemuse(uint64_t size) +{ + (void) SC_ATOMIC_SUB(st_memuse, size); + return; +} + +uint64_t StreamTcpMemuseCounter(void) +{ + uint64_t memusecopy = SC_ATOMIC_GET(st_memuse); + return memusecopy; +} + +/** + * \brief Check if alloc'ing "size" would mean we're over memcap + * + * \retval 1 if in bounds + * \retval 0 if not in bounds + */ +int StreamTcpCheckMemcap(uint64_t size) +{ + if (stream_config.memcap == 0 || size + SC_ATOMIC_GET(st_memuse) <= stream_config.memcap) + return 1; + return 0; +} + +/** + * \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(); + StreamMsg *smsg = NULL; + TcpStateQueue *q, *q_next; + + TcpSession *ssn = (TcpSession *)ssnptr; + if (ssn == NULL) + SCReturn; + + StreamTcpReturnStreamSegments(&ssn->client); + StreamTcpReturnStreamSegments(&ssn->server); + + //AppLayerParserCleanupState(ssn); + + StreamTcpSackFreeList(&ssn->client); + StreamTcpSackFreeList(&ssn->server); + + /* if we have (a) smsg(s), return to the pool */ + smsg = ssn->toserver_smsg_head; + while(smsg != NULL) { + StreamMsg *smsg_next = smsg->next; + SCLogDebug("returning smsg %p to pool", smsg); + smsg->next = NULL; + smsg->prev = NULL; + StreamMsgReturnToPool(smsg); + smsg = smsg_next; + } + ssn->toserver_smsg_head = NULL; + + smsg = ssn->toclient_smsg_head; + while(smsg != NULL) { + StreamMsg *smsg_next = smsg->next; + SCLogDebug("returning smsg %p to pool", smsg); + smsg->next = NULL; + smsg->prev = NULL; + StreamMsgReturnToPool(smsg); + smsg = smsg_next; + } + ssn->toclient_smsg_head = NULL; + + q = ssn->queue; + while (q != NULL) { + q_next = q->next; + SCFree(q); + q = q_next; + StreamTcpDecrMemuse((uint64_t)sizeof(TcpStateQueue)); + } + ssn->queue = NULL; + ssn->queue_len = 0; + + memset(ssn, 0, sizeof(TcpSession)); + PoolThreadReturn(ssn_pool, ssn); +#ifdef DEBUG + SCMutexLock(&ssn_pool_mutex); + ssn_pool_cnt--; + SCMutexUnlock(&ssn_pool_mutex); +#endif + + SCReturn; +} + +/** + * \brief Function to return the stream segments back to the 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. + */ +void StreamTcpSessionPktFree (Packet *p) +{ + SCEnter(); + + TcpSession *ssn = (TcpSession *)p->flow->protoctx; + if (ssn == NULL) + SCReturn; + + StreamTcpReturnStreamSegments(&ssn->client); + StreamTcpReturnStreamSegments(&ssn->server); + + SCReturn; +} + +/** \brief Stream alloc function for the Pool + * \retval ptr void ptr to TcpSession structure with all vars set to 0/NULL + */ +void *StreamTcpSessionPoolAlloc() +{ + void *ptr = NULL; + + if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpSession)) == 0) + return NULL; + + ptr = SCMalloc(sizeof(TcpSession)); + if (unlikely(ptr == NULL)) + return NULL; + + return ptr; +} + +int StreamTcpSessionPoolInit(void *data, void* initdata) +{ + memset(data, 0, sizeof(TcpSession)); + StreamTcpIncrMemuse((uint64_t)sizeof(TcpSession)); + + return 1; +} + +/** \brief Pool free function + * \param s Void ptr to TcpSession memory */ +void StreamTcpSessionPoolCleanup(void *s) +{ + StreamMsg *smsg = NULL; + TcpStateQueue *q, *q_next; + + if (s == NULL) + return; + + TcpSession *ssn = (TcpSession *)s; + + StreamTcpReturnStreamSegments(&ssn->client); + StreamTcpReturnStreamSegments(&ssn->server); + + /* if we have (a) smsg(s), return to the pool */ + smsg = ssn->toserver_smsg_head; + while(smsg != NULL) { + StreamMsg *smsg_next = smsg->next; + SCLogDebug("returning smsg %p to pool", smsg); + smsg->next = NULL; + smsg->prev = NULL; + StreamMsgReturnToPool(smsg); + smsg = smsg_next; + } + ssn->toserver_smsg_head = NULL; + + smsg = ssn->toclient_smsg_head; + while(smsg != NULL) { + StreamMsg *smsg_next = smsg->next; + SCLogDebug("returning smsg %p to pool", smsg); + smsg->next = NULL; + smsg->prev = NULL; + StreamMsgReturnToPool(smsg); + smsg = smsg_next; + } + ssn->toclient_smsg_head = NULL; + + q = ssn->queue; + while (q != NULL) { + q_next = q->next; + SCFree(q); + q = q_next; + StreamTcpDecrMemuse((uint64_t)sizeof(TcpStateQueue)); + } + ssn->queue = NULL; + ssn->queue_len = 0; + + StreamTcpDecrMemuse((uint64_t)sizeof(TcpSession)); +} + +/** \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) +{ + intmax_t value = 0; + uint16_t rdrange = 10; + + SCLogDebug("Initializing Stream"); + + memset(&stream_config, 0, sizeof(stream_config)); + + if ((ConfGetInt("stream.max-sessions", &value)) == 1) { + SCLogWarning(SC_WARN_OPTION_OBSOLETE, "max-sessions is obsolete. " + "Number of concurrent sessions is now only limited by Flow and " + "TCP stream engine memcaps."); + } + + if ((ConfGetInt("stream.prealloc-sessions", &value)) == 1) { + stream_config.prealloc_sessions = (uint32_t)value; + } else { + if (RunmodeIsUnittests()) { + stream_config.prealloc_sessions = 128; + } else { + stream_config.prealloc_sessions = STREAMTCP_DEFAULT_PREALLOC; + if (ConfGetNode("stream.prealloc-sessions") != NULL) { + WarnInvalidConfEntry("stream.prealloc_sessions", + "%"PRIu32, + stream_config.prealloc_sessions); + } + } + } + if (!quiet) { + SCLogInfo("stream \"prealloc-sessions\": %"PRIu32" (per thread)", + stream_config.prealloc_sessions); + } + + char *temp_stream_memcap_str; + if (ConfGet("stream.memcap", &temp_stream_memcap_str) == 1) { + if (ParseSizeStringU64(temp_stream_memcap_str, &stream_config.memcap) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing stream.memcap " + "from conf file - %s. Killing engine", + temp_stream_memcap_str); + exit(EXIT_FAILURE); + } + } else { + stream_config.memcap = STREAMTCP_DEFAULT_MEMCAP; + } + + if (!quiet) { + SCLogInfo("stream \"memcap\": %"PRIu64, stream_config.memcap); + } + + ConfGetBool("stream.midstream", &stream_config.midstream); + + if (!quiet) { + SCLogInfo("stream \"midstream\" session pickups: %s", stream_config.midstream ? "enabled" : "disabled"); + } + + ConfGetBool("stream.async-oneside", &stream_config.async_oneside); + + if (!quiet) { + SCLogInfo("stream \"async-oneside\": %s", stream_config.async_oneside ? "enabled" : "disabled"); + } + + int csum = 0; + + if ((ConfGetBool("stream.checksum-validation", &csum)) == 1) { + if (csum == 1) { + stream_config.flags |= STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION; + } + /* Default is that we validate the checksum of all the packets */ + } else { + stream_config.flags |= STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION; + } + + if (!quiet) { + SCLogInfo("stream \"checksum-validation\": %s", + stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION ? + "enabled" : "disabled"); + } + + int inl = 0; + + + char *temp_stream_inline_str; + if (ConfGet("stream.inline", &temp_stream_inline_str) == 1) { + /* checking for "auto" and falling back to boolean to provide + * backward compatibility */ + if (strcmp(temp_stream_inline_str, "auto") == 0) { + if (EngineModeIsIPS()) { + stream_inline = 1; + } else { + stream_inline = 0; + } + } else if (ConfGetBool("stream.inline", &inl) == 1) { + stream_inline = inl; + } + } + + if (!quiet) { + SCLogInfo("stream.\"inline\": %s", stream_inline ? "enabled" : "disabled"); + } + + if ((ConfGetInt("stream.max-synack-queued", &value)) == 1) { + if (value >= 0 && value <= 255) { + stream_config.max_synack_queued = (uint8_t)value; + } else { + stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED; + } + } else { + stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED; + } + if (!quiet) { + SCLogInfo("stream \"max-synack-queued\": %"PRIu8, stream_config.max_synack_queued); + } + + char *temp_stream_reassembly_memcap_str; + if (ConfGet("stream.reassembly.memcap", &temp_stream_reassembly_memcap_str) == 1) { + if (ParseSizeStringU64(temp_stream_reassembly_memcap_str, + &stream_config.reassembly_memcap) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing " + "stream.reassembly.memcap " + "from conf file - %s. Killing engine", + temp_stream_reassembly_memcap_str); + exit(EXIT_FAILURE); + } + } else { + stream_config.reassembly_memcap = STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP; + } + + if (!quiet) { + SCLogInfo("stream.reassembly \"memcap\": %"PRIu64"", stream_config.reassembly_memcap); + } + + char *temp_stream_reassembly_depth_str; + if (ConfGet("stream.reassembly.depth", &temp_stream_reassembly_depth_str) == 1) { + if (ParseSizeStringU32(temp_stream_reassembly_depth_str, + &stream_config.reassembly_depth) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing " + "stream.reassembly.depth " + "from conf file - %s. Killing engine", + temp_stream_reassembly_depth_str); + exit(EXIT_FAILURE); + } + } else { + stream_config.reassembly_depth = 0; + } + + if (!quiet) { + SCLogInfo("stream.reassembly \"depth\": %"PRIu32"", stream_config.reassembly_depth); + } + + int randomize = 0; + if ((ConfGetBool("stream.reassembly.randomize-chunk-size", &randomize)) == 0) { + /* randomize by default if value not set + * In ut mode we disable, to get predictible test results */ + if (!(RunmodeIsUnittests())) + randomize = 1; + } + + if (randomize) { + char *temp_rdrange; + if (ConfGet("stream.reassembly.randomize-chunk-range", + &temp_rdrange) == 1) { + if (ParseSizeStringU16(temp_rdrange, &rdrange) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing " + "stream.reassembly.randomize-chunk-range " + "from conf file - %s. Killing engine", + temp_rdrange); + exit(EXIT_FAILURE); + } else if (rdrange >= 100) { + SCLogError(SC_ERR_INVALID_VALUE, + "stream.reassembly.randomize-chunk-range " + "must be lower than 100"); + exit(EXIT_FAILURE); + } + } + /* set a "random" seed */ + srandom(time(0)); + } + + char *temp_stream_reassembly_toserver_chunk_size_str; + if (ConfGet("stream.reassembly.toserver-chunk-size", + &temp_stream_reassembly_toserver_chunk_size_str) == 1) { + if (ParseSizeStringU16(temp_stream_reassembly_toserver_chunk_size_str, + &stream_config.reassembly_toserver_chunk_size) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing " + "stream.reassembly.toserver-chunk-size " + "from conf file - %s. Killing engine", + temp_stream_reassembly_toserver_chunk_size_str); + exit(EXIT_FAILURE); + } + } else { + stream_config.reassembly_toserver_chunk_size = + STREAMTCP_DEFAULT_TOSERVER_CHUNK_SIZE; + } + + if (randomize) { + stream_config.reassembly_toserver_chunk_size += + (int) (stream_config.reassembly_toserver_chunk_size * + (random() * 1.0 / RAND_MAX - 0.5) * rdrange / 100); + } + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, + stream_config.reassembly_toserver_chunk_size); + + char *temp_stream_reassembly_toclient_chunk_size_str; + if (ConfGet("stream.reassembly.toclient-chunk-size", + &temp_stream_reassembly_toclient_chunk_size_str) == 1) { + if (ParseSizeStringU16(temp_stream_reassembly_toclient_chunk_size_str, + &stream_config.reassembly_toclient_chunk_size) < 0) { + SCLogError(SC_ERR_SIZE_PARSE, "Error parsing " + "stream.reassembly.toclient-chunk-size " + "from conf file - %s. Killing engine", + temp_stream_reassembly_toclient_chunk_size_str); + exit(EXIT_FAILURE); + } + } else { + stream_config.reassembly_toclient_chunk_size = + STREAMTCP_DEFAULT_TOCLIENT_CHUNK_SIZE; + } + + if (randomize) { + stream_config.reassembly_toclient_chunk_size += + (int) (stream_config.reassembly_toclient_chunk_size * + (random() * 1.0 / RAND_MAX - 0.5) * rdrange / 100); + } + + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, + stream_config.reassembly_toclient_chunk_size); + + if (!quiet) { + SCLogInfo("stream.reassembly \"toserver-chunk-size\": %"PRIu16, + stream_config.reassembly_toserver_chunk_size); + SCLogInfo("stream.reassembly \"toclient-chunk-size\": %"PRIu16, + stream_config.reassembly_toclient_chunk_size); + } + + int enable_raw = 1; + if (ConfGetBool("stream.reassembly.raw", &enable_raw) == 1) { + if (!enable_raw) { + stream_config.ssn_init_flags = STREAMTCP_FLAG_DISABLE_RAW; + stream_config.segment_init_flags = SEGMENTTCP_FLAG_RAW_PROCESSED; + } + } else { + enable_raw = 1; + } + if (!quiet) + SCLogInfo("stream.reassembly.raw: %s", enable_raw ? "enabled" : "disabled"); + + /* init the memcap/use tracking */ + SC_ATOMIC_INIT(st_memuse); + StatsRegisterGlobalCounter("tcp.memuse", StreamTcpMemuseCounter); + + StreamTcpReassembleInit(quiet); + + /* set the default free function and flow state function + * values. */ + FlowSetProtoFreeFunc(IPPROTO_TCP, StreamTcpSessionClear); + +#ifdef UNITTESTS + if (RunmodeIsUnittests()) { + SCMutexLock(&ssn_pool_mutex); + if (ssn_pool == NULL) { + ssn_pool = PoolThreadInit(1, /* thread */ + 0, /* unlimited */ + stream_config.prealloc_sessions, + sizeof(TcpSession), + StreamTcpSessionPoolAlloc, + StreamTcpSessionPoolInit, NULL, + StreamTcpSessionPoolCleanup, NULL); + } + SCMutexUnlock(&ssn_pool_mutex); + } +#endif +} + +void StreamTcpFreeConfig(char quiet) +{ + StreamTcpReassembleFree(quiet); + + SCMutexLock(&ssn_pool_mutex); + if (ssn_pool != NULL) { + PoolThreadFree(ssn_pool); + ssn_pool = NULL; + } + SCMutexUnlock(&ssn_pool_mutex); + SCMutexDestroy(&ssn_pool_mutex); + + SCLogDebug("ssn_pool_cnt %"PRIu64"", ssn_pool_cnt); +} + +/** \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, int id) +{ + TcpSession *ssn = (TcpSession *)p->flow->protoctx; + + if (ssn == NULL) { + p->flow->protoctx = PoolThreadGetById(ssn_pool, id); +#ifdef DEBUG + SCMutexLock(&ssn_pool_mutex); + if (p->flow->protoctx != NULL) + ssn_pool_cnt++; + SCMutexUnlock(&ssn_pool_mutex); +#endif + + ssn = (TcpSession *)p->flow->protoctx; + if (ssn == NULL) { + SCLogDebug("ssn_pool is empty"); + return NULL; + } + + ssn->state = TCP_NONE; + ssn->flags = stream_config.ssn_init_flags; + ssn->tcp_packet_flags = p->tcph ? p->tcph->th_flags : 0; + + if (PKT_IS_TOSERVER(p)) { + ssn->client.tcp_flags = p->tcph ? p->tcph->th_flags : 0; + ssn->server.tcp_flags = 0; + } else if (PKT_IS_TOCLIENT(p)) { + ssn->server.tcp_flags = p->tcph ? p->tcph->th_flags : 0; + ssn->client.tcp_flags = 0; + } + } + + return ssn; +} + +static void StreamTcpPacketSetState(Packet *p, TcpSession *ssn, + uint8_t state) +{ + if (state == ssn->state || PKT_IS_PSEUDOPKT(p)) + return; + + ssn->state = state; + + /* update the flow state */ + switch(ssn->state) { + case TCP_ESTABLISHED: + case TCP_FIN_WAIT1: + case TCP_FIN_WAIT2: + case TCP_CLOSING: + case TCP_CLOSE_WAIT: + SC_ATOMIC_SET(p->flow->flow_state, FLOW_STATE_ESTABLISHED); + break; + case TCP_LAST_ACK: + case TCP_TIME_WAIT: + case TCP_CLOSED: + SC_ATOMIC_SET(p->flow->flow_state, FLOW_STATE_CLOSED); + break; + } +} + +/** + * \brief Function to set the OS policy for the given stream based on the + * destination of the received packet. + * + * \param stream TcpStream of which os_policy needs to set + * \param p Packet which is used to set the os policy + */ +void StreamTcpSetOSPolicy(TcpStream *stream, Packet *p) +{ + int ret = 0; + + if (PKT_IS_IPV4(p)) { + /* Get the OS policy based on destination IP address, as destination + OS will decide how to react on the anomalies of newly received + packets */ + ret = SCHInfoGetIPv4HostOSFlavour((uint8_t *)GET_IPV4_DST_ADDR_PTR(p)); + if (ret > 0) + stream->os_policy = ret; + else + stream->os_policy = OS_POLICY_DEFAULT; + + } else if (PKT_IS_IPV6(p)) { + /* Get the OS policy based on destination IP address, as destination + OS will decide how to react on the anomalies of newly received + packets */ + ret = SCHInfoGetIPv6HostOSFlavour((uint8_t *)GET_IPV6_DST_ADDR(p)); + if (ret > 0) + stream->os_policy = ret; + else + stream->os_policy = OS_POLICY_DEFAULT; + } + + if (stream->os_policy == OS_POLICY_BSD_RIGHT) + stream->os_policy = OS_POLICY_BSD; + else if (stream->os_policy == OS_POLICY_OLD_SOLARIS) + stream->os_policy = OS_POLICY_SOLARIS; + + SCLogDebug("Policy is %"PRIu8"", stream->os_policy); + +} + +/** + * \brief get the size of a stream + * + * \note this just calculates the diff between isn and last_ack + * and will not consider sequence wrap arounds (streams + * bigger than 4gb). + * + * \retval size stream size + */ +uint32_t StreamTcpGetStreamSize(TcpStream *stream) +{ + return (stream->last_ack - stream->isn - 1); +} + +/** + * \brief macro to update last_ack only if the new value is higher + * + * \param ssn session + * \param stream stream to update + * \param ack ACK value to test and set + */ +#define StreamTcpUpdateLastAck(ssn, stream, ack) { \ + if (SEQ_GT((ack), (stream)->last_ack)) \ + { \ + (stream)->last_ack = (ack); \ + SCLogDebug("ssn %p: last_ack set to %"PRIu32, (ssn), (stream)->last_ack); \ + StreamTcpSackPruneList((stream)); \ + } else { \ + SCLogDebug("ssn %p: no update: ack %u, last_ack %"PRIu32", next_seq %u (state %u)", \ + (ssn), (ack), (stream)->last_ack, (stream)->next_seq, (ssn)->state); \ + }\ +} + +/** + * \brief macro to update next_win only if the new value is higher + * + * \param ssn session + * \param stream stream to update + * \param win window value to test and set + */ +#define StreamTcpUpdateNextWin(ssn, stream, win) { \ + uint32_t sacked_size__ = StreamTcpSackedSize((stream)); \ + if (SEQ_GT(((win) + sacked_size__), (stream)->next_win)) { \ + (stream)->next_win = ((win) + sacked_size__); \ + SCLogDebug("ssn %p: next_win set to %"PRIu32, (ssn), (stream)->next_win); \ + } \ +} + +static int StreamTcpPacketIsRetransmission(TcpStream *stream, Packet *p) +{ + if (p->payload_len == 0) + SCReturnInt(0); + + /* retransmission of already ack'd data */ + if (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), stream->last_ack)) { + StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION); + SCReturnInt(1); + } + + /* retransmission of in flight data */ + if (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), stream->next_seq)) { + StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION); + SCReturnInt(2); + } + + SCLogDebug("seq %u payload_len %u => %u, last_ack %u", TCP_GET_SEQ(p), + p->payload_len, (TCP_GET_SEQ(p) + p->payload_len), stream->last_ack); + SCReturnInt(0); +} + +/** + * \internal + * \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 + * + * \retval 0 ok + * \retval -1 error + */ +static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (p->tcph->th_flags & TH_RST) { + StreamTcpSetEvent(p, STREAM_RST_BUT_NO_SESSION); + SCLogDebug("RST packet received, no session setup"); + return -1; + + } else if (p->tcph->th_flags & TH_FIN) { + StreamTcpSetEvent(p, STREAM_FIN_BUT_NO_SESSION); + SCLogDebug("FIN packet received, no session setup"); + return -1; + + /* SYN/ACK */ + } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) { + if (stream_config.midstream == FALSE && + stream_config.async_oneside == FALSE) + return 0; + + if (ssn == NULL) { + ssn = StreamTcpNewSession(p, stt->ssn_pool_id); + if (ssn == NULL) { + StatsIncr(tv, stt->counter_tcp_ssn_memcap); + return -1; + } + StatsIncr(tv, stt->counter_tcp_sessions); + } + /* 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); + STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, 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; + STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn); + ssn->client.next_seq = ssn->client.isn + 1; + + ssn->client.last_ack = TCP_GET_ACK(p); + ssn->server.last_ack = TCP_GET_SEQ(p); + + ssn->server.next_win = ssn->server.last_ack + ssn->server.window; + + /** 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_STREAM_FLAG_ZERO_TIMESTAMP; + if (ssn->client.last_ts == 0) + ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + + } else { + ssn->server.last_ts = 0; + ssn->client.last_ts = 0; + } + + if (TCP_GET_SACKOK(p) == 1) { + ssn->flags |= STREAMTCP_FLAG_SACKOK; + SCLogDebug("ssn %p: SYN/ACK with SACK permitted, assuming " + "SACK permitted for both sides", ssn); + } + + /* packet thinks it is in the wrong direction, flip it */ + StreamTcpPacketSwitchDir(ssn, p); + + } else if (p->tcph->th_flags & TH_SYN) { + if (ssn == NULL) { + ssn = StreamTcpNewSession(p, stt->ssn_pool_id); + if (ssn == NULL) { + StatsIncr(tv, stt->counter_tcp_ssn_memcap); + return -1; + } + + StatsIncr(tv, stt->counter_tcp_sessions); + } + + /* 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); + STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, 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_STREAM_FLAG_ZERO_TIMESTAMP; + + ssn->client.last_pkt_ts = p->ts.tv_sec; + ssn->client.flags |= STREAMTCP_STREAM_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); + } + + if (TCP_GET_SACKOK(p) == 1) { + ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK; + SCLogDebug("ssn %p: SACK permited on SYN packet", ssn); + } + + 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); + + } else if (p->tcph->th_flags & TH_ACK) { + if (stream_config.midstream == FALSE) + return 0; + + if (ssn == NULL) { + ssn = StreamTcpNewSession(p, stt->ssn_pool_id); + if (ssn == NULL) { + StatsIncr(tv, stt->counter_tcp_ssn_memcap); + return -1; + } + StatsIncr(tv, stt->counter_tcp_sessions); + } + /* 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; + + /** 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 sequence numbers and window */ + ssn->client.isn = TCP_GET_SEQ(p) - 1; + STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn); + ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + 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; + STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, 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); + + /* 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_STREAM_FLAG_ZERO_TIMESTAMP; + if (ssn->client.last_ts == 0) + ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + + } else { + ssn->server.last_ts = 0; + ssn->client.last_ts = 0; + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq); + + ssn->flags |= STREAMTCP_FLAG_SACKOK; + SCLogDebug("ssn %p: assuming SACK permitted for both sides", ssn); + + } else { + SCLogDebug("default case"); + } + + return 0; +} + +/** \internal + * \brief Setup TcpStateQueue based on SYN/ACK packet + */ +static inline void StreamTcp3whsSynAckToStateQueue(Packet *p, TcpStateQueue *q) +{ + q->flags = 0; + q->wscale = 0; + q->ts = 0; + q->win = TCP_GET_WINDOW(p); + q->seq = TCP_GET_SEQ(p); + q->ack = TCP_GET_ACK(p); + q->pkt_ts = p->ts.tv_sec; + + if (TCP_GET_SACKOK(p) == 1) + q->flags |= STREAMTCP_QUEUE_FLAG_SACK; + + if (p->tcpvars.ws != NULL) { + q->flags |= STREAMTCP_QUEUE_FLAG_WS; + q->wscale = TCP_GET_WSCALE(p); + } + if (p->tcpvars.ts != NULL) { + q->flags |= STREAMTCP_QUEUE_FLAG_TS; + q->ts = TCP_GET_TSVAL(p); + } +} + +/** \internal + * \brief Find the Queued SYN/ACK that is the same as this SYN/ACK + * \retval q or NULL */ +TcpStateQueue *StreamTcp3whsFindSynAckBySynAck(TcpSession *ssn, Packet *p) +{ + TcpStateQueue *q = ssn->queue; + TcpStateQueue search; + + StreamTcp3whsSynAckToStateQueue(p, &search); + + while (q != NULL) { + if (search.flags == q->flags && + search.wscale == q->wscale && + search.win == q->win && + search.seq == q->seq && + search.ack == q->ack && + search.ts == q->ts) { + return q; + } + + q = q->next; + } + + return q; +} + +int StreamTcp3whsQueueSynAck(TcpSession *ssn, Packet *p) +{ + /* first see if this is already in our list */ + if (StreamTcp3whsFindSynAckBySynAck(ssn, p) != NULL) + return 0; + + if (ssn->queue_len == stream_config.max_synack_queued) { + SCLogDebug("ssn %p: =~ SYN/ACK queue limit reached", ssn); + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_FLOOD); + return -1; + } + + if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpStateQueue)) == 0) { + SCLogDebug("ssn %p: =~ SYN/ACK queue failed: stream memcap reached", ssn); + return -1; + } + + TcpStateQueue *q = SCMalloc(sizeof(*q)); + if (unlikely(q == NULL)) { + SCLogDebug("ssn %p: =~ SYN/ACK queue failed: alloc failed", ssn); + return -1; + } + memset(q, 0x00, sizeof(*q)); + StreamTcpIncrMemuse((uint64_t)sizeof(TcpStateQueue)); + + StreamTcp3whsSynAckToStateQueue(p, q); + + /* put in list */ + q->next = ssn->queue; + ssn->queue = q; + ssn->queue_len++; + return 0; +} + +/** \internal + * \brief Find the Queued SYN/ACK that goes with this ACK + * \retval q or NULL */ +TcpStateQueue *StreamTcp3whsFindSynAckByAck(TcpSession *ssn, Packet *p) +{ + uint32_t ack = TCP_GET_SEQ(p); + uint32_t seq = TCP_GET_ACK(p) - 1; + TcpStateQueue *q = ssn->queue; + + while (q != NULL) { + if (seq == q->seq && + ack == q->ack) { + return q; + } + + q = q->next; + } + + return NULL; +} + +/** \internal + * \brief Update SSN after receiving a valid SYN/ACK + * + * Normally we update the SSN from the SYN/ACK packet. But in case + * of queued SYN/ACKs, we can use one of those. + * + * \param ssn TCP session + * \param p Packet + * \param q queued state if used, NULL otherwise + * + * To make sure all SYN/ACK based state updates are in one place, + * this function can updated based on Packet or TcpStateQueue, where + * the latter takes precedence. + */ +static void StreamTcp3whsSynAckUpdate(TcpSession *ssn, Packet *p, TcpStateQueue *q) +{ + TcpStateQueue update; + if (likely(q == NULL)) { + StreamTcp3whsSynAckToStateQueue(p, &update); + q = &update; + } + + if (ssn->state != TCP_SYN_RECV) { + /* 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 = q->seq; + STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn); + ssn->server.next_seq = ssn->server.isn + 1; + + ssn->client.window = q->win; + SCLogDebug("ssn %p: window %" PRIu32 "", ssn, ssn->server.window); + + /* Set the timestamp values used to validate the timestamp of + * received packets.*/ + if ((q->flags & STREAMTCP_QUEUE_FLAG_TS) && + (ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP)) + { + ssn->server.last_ts = q->ts; + 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 = q->pkt_ts; + if (ssn->server.last_ts == 0) + ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + } else { + ssn->client.last_ts = 0; + ssn->server.last_ts = 0; + ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + } + + ssn->client.last_ack = q->ack; + 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) && + (q->flags & STREAMTCP_QUEUE_FLAG_WS)) + { + ssn->client.wscale = q->wscale; + } else { + ssn->client.wscale = 0; + } + + if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) && + (q->flags & STREAMTCP_QUEUE_FLAG_SACK)) { + ssn->flags |= STREAMTCP_FLAG_SACKOK; + SCLogDebug("ssn %p: SACK permitted for session", ssn); + } else { + ssn->flags &= ~STREAMTCP_FLAG_SACKOK; + } + + 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; +} + +/** + * \brief Function to handle the TCP_SYN_SENT state. The function handles + * SYN, SYN/ACK, 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 StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ? + "toclient":"toserver"); + + /* RST */ + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + if (PKT_IS_TOSERVER(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); + } + } else { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received and state changed to " + "TCP_CLOSED", ssn); + } + + /* FIN */ + } else if (p->tcph->th_flags & TH_FIN) { + /** \todo */ + + /* SYN/ACK */ + } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (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))) { + StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_ACK); + + 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))) { + StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_SYN); + + 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); + STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, 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_STREAM_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->flags |= STREAMTCP_FLAG_TIMESTAMP; + ssn->client.last_pkt_ts = p->ts.tv_sec; + if (ssn->client.last_ts == 0) + ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + } else { + ssn->server.last_ts = 0; + ssn->client.last_ts = 0; + ssn->server.flags &= ~STREAMTCP_STREAM_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; + } + + if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) && + TCP_GET_SACKOK(p) == 1) { + ssn->flags |= STREAMTCP_FLAG_SACKOK; + SCLogDebug("ssn %p: SACK permitted for 4WHS session", ssn); + } + + 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 */ + return 0; + } + + if (PKT_IS_TOSERVER(p)) { + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_IN_WRONG_DIRECTION); + 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))) { + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_WITH_WRONG_ACK); + SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != " + "%" PRIu32 " from stream", ssn, TCP_GET_ACK(p), + ssn->client.isn + 1); + return -1; + } + + StreamTcp3whsSynAckUpdate(ssn, p, /* no queue override */NULL); + + } else if (p->tcph->th_flags & 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); + STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, 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_STREAM_FLAG_ZERO_TIMESTAMP; + ssn->server.last_pkt_ts = p->ts.tv_sec; + ssn->server.flags |= STREAMTCP_STREAM_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); + } else { + ssn->flags &= ~STREAMTCP_FLAG_SERVER_WSCALE; + ssn->server.wscale = 0; + } + + if (TCP_GET_SACKOK(p) == 1) { + ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK; + } else { + ssn->flags &= ~STREAMTCP_FLAG_CLIENT_SACKOK; + } + + 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); + } + + /** \todo check if it's correct or set event */ + + } else if (p->tcph->th_flags & TH_ACK) { + /* 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) + return 0; + + /* we are in AYNC (one side) mode now. */ + + /* one side async means we won't see a SYN/ACK, so we can + * only check the SYN. */ + if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq))) { + StreamTcpSetEvent(p, STREAM_3WHS_ASYNC_WRONG_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; + STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, 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); + + /* if SYN had wscale, assume it to be supported. Otherwise + * we know it not to be supported. */ + if (ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) { + ssn->client.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_STREAM_FLAG_TIMESTAMP)) + { + ssn->flags |= STREAMTCP_FLAG_TIMESTAMP; + ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_TIMESTAMP; + ssn->client.last_pkt_ts = p->ts.tv_sec; + } else { + ssn->client.last_ts = 0; + ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; + } + + if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) { + ssn->flags |= STREAMTCP_FLAG_SACKOK; + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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 + * + * \retval 0 ok + * \retval -1 error + */ + +static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + uint8_t reset = TRUE; + /* After receiveing the RST in SYN_RECV state and if detection + evasion flags has been set, then the following operating + systems will not closed the connection. As they consider the + packet as stray packet and not belonging to the current + session, for more information check + http://www.packetstan.com/2010/06/recently-ive-been-on-campaign-to-make.html */ + if (ssn->flags & STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT) { + if (PKT_IS_TOSERVER(p)) { + if ((ssn->server.os_policy == OS_POLICY_LINUX) || + (ssn->server.os_policy == OS_POLICY_OLD_LINUX) || + (ssn->server.os_policy == OS_POLICY_SOLARIS)) + { + reset = FALSE; + SCLogDebug("Detection evasion has been attempted, so" + " not resetting the connection !!"); + } + } else { + if ((ssn->client.os_policy == OS_POLICY_LINUX) || + (ssn->client.os_policy == OS_POLICY_OLD_LINUX) || + (ssn->client.os_policy == OS_POLICY_SOLARIS)) + { + reset = FALSE; + SCLogDebug("Detection evasion has been attempted, so" + " not resetting the connection !!"); + } + } + } + + if (reset == TRUE) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received and state changed to " + "TCP_CLOSED", ssn); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + } + + } else if (p->tcph->th_flags & TH_FIN) { + /* FIN is handled in the same way as in TCP_ESTABLISHED case */; + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(ssn, p)) + return -1; + } + + if ((StreamTcpHandleFin(tv, stt, ssn, p, pq)) == -1) + return -1; + + /* SYN/ACK */ + } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) { + SCLogDebug("ssn %p: SYN/ACK packet on state SYN_RECV. resent", ssn); + + if (PKT_IS_TOSERVER(p)) { + SCLogDebug("ssn %p: SYN/ACK-pkt to server in SYN_RECV state", ssn); + + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_TOSERVER_ON_SYN_RECV); + return -1; + } + + /* Check if the SYN/ACK packets ACK matches the earlier + * received SYN/ACK packet. */ + if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack))) { + SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != " + "%" PRIu32 " from stream", ssn, TCP_GET_ACK(p), + ssn->client.isn + 1); + + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_RESEND_WITH_DIFFERENT_ACK); + return -1; + } + + /* Check if the SYN/ACK packet SEQ the earlier + * received SYN/ACK packet, server resend with different ISN. */ + if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.isn))) { + SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != " + "%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p), + ssn->client.isn); + + if (StreamTcp3whsQueueSynAck(ssn, p) == -1) + return -1; + SCLogDebug("ssn %p: queued different SYN/ACK", ssn); + } + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn %p: SYN packet on state SYN_RECV... resent", ssn); + + if (PKT_IS_TOCLIENT(p)) { + SCLogDebug("ssn %p: SYN-pkt to client in SYN_RECV state", ssn); + + StreamTcpSetEvent(p, STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV); + return -1; + } + + if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) { + SCLogDebug("ssn %p: SYN with different SEQ on SYN_RECV state", ssn); + + StreamTcpSetEvent(p, STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV); + return -1; + } + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->queue_len) { + SCLogDebug("ssn %p: checking ACK against queued SYN/ACKs", ssn); + TcpStateQueue *q = StreamTcp3whsFindSynAckByAck(ssn, p); + if (q != NULL) { + SCLogDebug("ssn %p: here we update state against queued SYN/ACK", ssn); + StreamTcp3whsSynAckUpdate(ssn, p, /* using queue to update state */q); + } else { + SCLogDebug("ssn %p: none found, now checking ACK against original SYN/ACK (state)", ssn); + } + } + + + /* 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 (!(StreamTcpValidateTimestamp(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("ssn %p: 4WHS wrong seq nr on packet", ssn); + StreamTcpSetEvent(p, STREAM_4WHS_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: 4WHS invalid ack nr on packet", ssn); + StreamTcpSetEvent(p, STREAM_4WHS_INVALID_ACK); + return -1; + } + + SCLogDebug("4WHS normal pkt"); + 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 (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, 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; + + StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED); + SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + + SCLogDebug("ssn %p: ssn->client.next_win %" PRIu32 ", " + "ssn->client.last_ack %"PRIu32"", ssn, + ssn->client.next_win, ssn->client.last_ack); + return 0; + } + + /* 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); + + StreamTcpSetEvent(p, STREAM_3WHS_ACK_IN_WRONG_DIR); + 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)); + + /* Check both seq and ack number before accepting the packet and + changing to ESTABLISHED state */ + if ((SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)) && + SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) { + SCLogDebug("normal pkt"); + + /* process the packet normal, No Async streams :) */ + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, 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->client.wscale; + ssn->client.next_win = ssn->client.last_ack + ssn->client.window; + ssn->server.next_win = ssn->server.last_ack + + ssn->server.window; + if (!(ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK)) { + /* 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; + ssn->flags |= STREAMTCP_FLAG_SACKOK; + } + } + + StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED); + SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + /* 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_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, 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; + ssn->flags |= STREAMTCP_FLAG_SACKOK; + } + + 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); + + StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED); + SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + /* Upon receiving the packet with correct seq number and wrong + ACK number, it causes the other end to send RST. But some target + system (Linux & solaris) does not RST the connection, so it is + likely to avoid the detection */ + } else if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)){ + ssn->flags |= STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT; + SCLogDebug("ssn %p: wrong ack nr on packet, possible evasion!!", + ssn); + + StreamTcpSetEvent(p, STREAM_3WHS_RIGHT_SEQ_WRONG_ACK_EVASION); + return -1; + + /* if we get a packet with a proper ack, but a seq that is beyond + * next_seq but in-window, we probably missed some packets */ + } else if (SEQ_GT(TCP_GET_SEQ(p), ssn->client.next_seq) && + SEQ_LEQ(TCP_GET_SEQ(p),ssn->client.next_win) && + SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) + { + SCLogDebug("ssn %p: ACK for missing data", ssn); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; + SCLogDebug("ssn %p: ACK for missing data: ssn->client.next_seq %u", ssn, ssn->client.next_seq); + 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; + ssn->flags |= STREAMTCP_FLAG_SACKOK; + } + + StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED); + SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + SCLogDebug("ssn %p: wrong seq nr on packet", ssn); + + StreamTcpSetEvent(p, STREAM_3WHS_WRONG_SEQ_WRONG_ACK); + return -1; + } + + SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 ", " + "ssn->server.last_ack %"PRIu32"", ssn, + ssn->server.next_win, ssn->server.last_ack); + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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 reassembly. + * + * Timestamp has already been checked at this point. + * + * \param tv Thread Variable containig input/output queue, cpu affinity etc. + * \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(ThreadVars *tv, TcpSession *ssn, Packet *p, + StreamTcpThread *stt, PacketQueue *pq) +{ + 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 (StreamTcpValidateAck(ssn, &(ssn->server), p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_EST_INVALID_ACK); + return -1; + } + + /* check for Keep Alive */ + if ((p->payload_len == 0 || p->payload_len == 1) && + (TCP_GET_SEQ(p) == (ssn->client.next_seq - 1))) { + SCLogDebug("ssn %p: pkt is keep alive", ssn); + + /* normal pkt */ + } else if (!(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len), 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, 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); + + /* update the last_ack to current seq number as the session is + * async and other stream is not updating it anymore :( */ + StreamTcpUpdateLastAck(ssn, &ssn->client, 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, 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); + + /* 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 :( */ + StreamTcpUpdateLastAck(ssn, &ssn->client, 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, 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); + + /* 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 :(*/ + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_SEQ(p)); + ssn->flags |= STREAMTCP_FLAG_ASYNC; + + /* if last ack is beyond next_seq, we have accepted ack's for missing data. + * In this case we do accept the data before last_ack if it is (partly) + * beyond next seq */ + } else if (SEQ_GT(ssn->client.last_ack, ssn->client.next_seq) && + SEQ_GT((TCP_GET_SEQ(p)+p->payload_len),ssn->client.next_seq)) + { + SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16 + " before last_ack %"PRIu32", after next_seq %"PRIu32":" + " acked data that we haven't seen before", + ssn, TCP_GET_SEQ(p), p->payload_len, ssn->client.last_ack, ssn->client.next_seq); + if (SEQ_EQ(TCP_GET_SEQ(p),ssn->client.next_seq)) { + ssn->client.next_seq += p->payload_len; + } + } 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, 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); + + SCLogDebug("ssn %p: rejecting because pkt before last_ack", ssn); + StreamTcpSetEvent(p, STREAM_EST_PKT_BEFORE_LAST_ACK); + return -1; + } + } + + int zerowindowprobe = 0; + /* zero window probe */ + if (p->payload_len == 1 && TCP_GET_SEQ(p) == ssn->client.next_seq && ssn->client.window == 0) { + SCLogDebug("ssn %p: zero window probe", ssn); + zerowindowprobe = 1; + + /* expected packet */ + } else 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); + /* not completely as expected, but valid */ + } else if (SEQ_LT(TCP_GET_SEQ(p),ssn->client.next_seq) && + SEQ_GT((TCP_GET_SEQ(p)+p->payload_len), ssn->client.next_seq)) + { + ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; + SCLogDebug("ssn %p: ssn->client.next_seq %"PRIu32 + " (started before next_seq, ended after)", + ssn, ssn->client.next_seq); + } + + /* in window check */ + if (zerowindowprobe) { + SCLogDebug("ssn %p: zero window probe, skipping oow check", ssn); + } else 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); + + /* Check if the ACK value is sane and inside the window limit */ + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + SCLogDebug("ack %u last_ack %u next_seq %u", TCP_GET_ACK(p), ssn->server.last_ack, ssn->server.next_seq); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpSackUpdatePacket(&ssn->server, p); + + /* update next_win */ + StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window)); + + /* handle data (if any) */ + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq); + } 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, 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); + SCLogDebug("ssn %p: window %u sacked %u", ssn, ssn->client.window, + StreamTcpSackedSize(&ssn->client)); + StreamTcpSetEvent(p, STREAM_EST_PACKET_OUT_OF_WINDOW); + return -1; + } + 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 reassembly + * + * Timestamp has already been checked at this point. + * + * \param tv Thread Variable containig input/output queue, cpu affinity etc. + * \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(ThreadVars *tv, TcpSession *ssn, Packet *p, + StreamTcpThread *stt, PacketQueue *pq) +{ + 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)); + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_EST_INVALID_ACK); + return -1; + } + + /* 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); + } + + /* check for Keep Alive */ + if ((p->payload_len == 0 || p->payload_len == 1) && + (TCP_GET_SEQ(p) == (ssn->server.next_seq - 1))) { + SCLogDebug("ssn %p: pkt is keep alive", ssn); + + /* normal pkt */ + } else if (!(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len), 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, 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.last_ack = TCP_GET_SEQ(p); + + /* if last ack is beyond next_seq, we have accepted ack's for missing data. + * In this case we do accept the data before last_ack if it is (partly) + * beyond next seq */ + } else if (SEQ_GT(ssn->server.last_ack, ssn->server.next_seq) && + SEQ_GT((TCP_GET_SEQ(p)+p->payload_len),ssn->server.next_seq)) + { + SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16 + " before last_ack %"PRIu32", after next_seq %"PRIu32":" + " acked data that we haven't seen before", + ssn, TCP_GET_SEQ(p), p->payload_len, ssn->server.last_ack, ssn->server.next_seq); + if (SEQ_EQ(TCP_GET_SEQ(p),ssn->server.next_seq)) { + ssn->server.next_seq += p->payload_len; + } + } else { + SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16 + " before last_ack %"PRIu32". next_seq %"PRIu32, + ssn, TCP_GET_SEQ(p), p->payload_len, ssn->server.last_ack, ssn->server.next_seq); + StreamTcpSetEvent(p, STREAM_EST_PKT_BEFORE_LAST_ACK); + return -1; + } + } + + int zerowindowprobe = 0; + /* zero window probe */ + if (p->payload_len == 1 && TCP_GET_SEQ(p) == ssn->server.next_seq && ssn->server.window == 0) { + SCLogDebug("ssn %p: zero window probe", ssn); + zerowindowprobe = 1; + + /* expected packet */ + } else 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); + /* not completely as expected, but valid */ + } else if (SEQ_LT(TCP_GET_SEQ(p),ssn->server.next_seq) && + SEQ_GT((TCP_GET_SEQ(p)+p->payload_len), ssn->server.next_seq)) + { + ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len; + SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 + " (started before next_seq, ended after)", + ssn, ssn->server.next_seq); + } + + if (zerowindowprobe) { + SCLogDebug("ssn %p: zero window probe, skipping oow check", ssn); + } else 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); + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpSackUpdatePacket(&ssn->client, p); + + StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window)); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq); + } 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, 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); + StreamTcpSetEvent(p, STREAM_EST_PACKET_OUT_OF_WINDOW); + return -1; + } + return 0; +} + +/** + * \internal + * + * \brief Find the highest sequence number needed to consider all segments as ACK'd + * + * Used to treat all segments as ACK'd upon receiving a valid RST. + * + * \param stream stream to inspect the segments from + * \param seq sequence number to check against + * + * \retval ack highest ack we need to set + */ +static inline uint32_t StreamTcpResetGetMaxAck(TcpStream *stream, uint32_t seq) +{ + uint32_t ack = seq; + + if (stream->seg_list_tail != NULL) { + if (SEQ_GT((stream->seg_list_tail->seq + stream->seg_list_tail->payload_len), ack)) + { + ack = stream->seg_list_tail->seq + stream->seg_list_tail->payload_len; + } + } + + SCReturnUInt(ack); +} + +/** + * \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(tv, ) 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, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received and state changed to " + "TCP_CLOSED", ssn); + + ssn->server.next_seq = TCP_GET_ACK(p); + ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; + SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn, + ssn->server.next_seq); + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->client.next_seq, + ssn->server.last_ack); + + /* don't return packets to pools here just yet, the pseudo + * packet will take care, otherwise the normal session + * cleanup. */ + } 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; + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + + /* don't return packets to pools here just yet, the pseudo + * packet will take care, otherwise the normal session + * cleanup. */ + } + + } else if (p->tcph->th_flags & TH_FIN) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(ssn, p)) + return -1; + } + + SCLogDebug("ssn (%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(tv, stt, ssn, p, pq)) == -1) + return -1; + + /* SYN/ACK */ + } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) { + SCLogDebug("ssn %p: SYN/ACK packet on state ESTABLISHED... resent", + ssn); + + if (PKT_IS_TOSERVER(p)) { + SCLogDebug("ssn %p: SYN/ACK-pkt to server in ESTABLISHED state", ssn); + + StreamTcpSetEvent(p, STREAM_EST_SYNACK_TOSERVER); + return -1; + } + + /* Check if the SYN/ACK packets ACK matches the earlier + * received SYN/ACK packet. */ + if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack))) { + SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != " + "%" PRIu32 " from stream", ssn, TCP_GET_ACK(p), + ssn->client.isn + 1); + + StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND_WITH_DIFFERENT_ACK); + return -1; + } + + /* Check if the SYN/ACK packet SEQ the earlier + * received SYN packet. */ + if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.isn))) { + SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != " + "%" PRIu32 " from stream", ssn, TCP_GET_ACK(p), + ssn->client.isn + 1); + + StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND_WITH_DIFF_SEQ); + return -1; + } + + if (ssn->flags & STREAMTCP_FLAG_3WHS_CONFIRMED) { + /* a resend of a SYN while we are established already -- fishy */ + StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND); + return -1; + } + + SCLogDebug("ssn %p: SYN/ACK packet on state ESTABLISHED... resent. " + "Likely due server not receiving final ACK in 3whs", ssn); + + /* resetting state to TCP_SYN_RECV as we should get another ACK now */ + StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV); + SCLogDebug("ssn %p: =~ ssn state is now reset to TCP_SYN_RECV", ssn); + return 0; + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn %p: SYN packet on state ESTABLISED... resent", ssn); + if (PKT_IS_TOCLIENT(p)) { + SCLogDebug("ssn %p: SYN-pkt to client in EST state", ssn); + + StreamTcpSetEvent(p, STREAM_EST_SYN_TOCLIENT); + return -1; + } + + if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) { + SCLogDebug("ssn %p: SYN with different SEQ on SYN_RECV state", ssn); + + StreamTcpSetEvent(p, STREAM_EST_SYN_RESEND_DIFF_SEQ); + return -1; + } + + /* a resend of a SYN while we are established already -- fishy */ + StreamTcpSetEvent(p, STREAM_EST_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + /* Urgent pointer size can be more than the payload size, as it tells + * the future coming data from the sender will be handled urgently + * until data of size equal to urgent offset has been processed + * (RFC 2147) */ + + /* 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 (!StreamTcpValidateTimestamp(ssn, p)) + return -1; + } + + if (PKT_IS_TOSERVER(p)) { + /* Process the received packet to server */ + HandleEstablishedPacketToServer(tv, ssn, p, stt, pq); + + 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 */ + if (!(ssn->flags & STREAMTCP_FLAG_3WHS_CONFIRMED)) { + ssn->flags |= STREAMTCP_FLAG_3WHS_CONFIRMED; + SCLogDebug("3whs is now confirmed by server"); + } + + /* Process the received packet to client */ + HandleEstablishedPacketToClient(tv, ssn, p, stt, pq); + + 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); + } + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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 + * + * \retval 0 success + * \retval -1 something wrong with the packet + */ + +static int StreamTcpHandleFin(ThreadVars *tv, StreamTcpThread *stt, + TcpSession *ssn, Packet *p, PacketQueue *pq) +{ + 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 (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN_INVALID_ACK); + return -1; + } + + 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); + + StreamTcpSetEvent(p, STREAM_FIN_OUT_OF_WINDOW); + return -1; + } + + StreamTcpPacketSetState(p, ssn, TCP_CLOSE_WAIT); + SCLogDebug("ssn %p: state changed to TCP_CLOSE_WAIT", ssn); + + if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)) + ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; + + SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "", ssn, + ssn->client.next_seq); + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client packet + and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq); + + 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 (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN_INVALID_ACK); + return -1; + } + + 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); + + StreamTcpSetEvent(p, STREAM_FIN_OUT_OF_WINDOW); + return -1; + } + + StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT1); + SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT1", ssn); + + if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) + ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len; + + SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn, + ssn->server.next_seq); + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client packet + and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq); + + 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 + * + * \retval 0 success + * \retval -1 something wrong with the packet + */ + +static int StreamTcpPacketStateFinWait1(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if ((p->tcph->th_flags & (TH_FIN|TH_ACK)) == (TH_FIN|TH_ACK)) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(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); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + 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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(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); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + + } else if (p->tcph->th_flags & TH_FIN) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSING); + SCLogDebug("ssn %p: state changed to TCP_CLOSING", ssn); + + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(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); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + 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)); + + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSING); + SCLogDebug("ssn %p: state changed to TCP_CLOSING", ssn); + + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(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); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on FinWait1", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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); + + if (TCP_GET_SEQ(p) == ssn->client.next_seq) { + StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2); + SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn); + } + } else { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->client.next_seq); + + StreamTcpSetEvent(p, STREAM_FIN1_ACK_WRONG_SEQ); + return -1; + } + + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(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); + } + + StreamTcpSackUpdatePacket(&ssn->server, p); + + /* update next_win */ + StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window)); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + 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)); + + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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); + + if (TCP_GET_SEQ(p) == ssn->server.next_seq) { + StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2); + SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn); + } + } else { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->server.next_seq); + StreamTcpSetEvent(p, STREAM_FIN1_ACK_WRONG_SEQ); + return -1; + } + + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(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); + } + + StreamTcpSackUpdatePacket(&ssn->client, p); + + /* update next_win */ + StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window)); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + } else { + SCLogDebug("ssn (%p): default case", ssn); + } + + 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, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if (p->tcph->th_flags & TH_FIN) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + + if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq - 1) && + SEQ_EQ(TCP_GET_ACK(p), ssn->server.last_ack)) { + SCLogDebug("ssn %p: retransmission", ssn); + retransmission = 1; + } else if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN2_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + 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)); + int retransmission = 0; + + if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq - 1) && + SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack)) { + SCLogDebug("ssn %p: retransmission", ssn); + retransmission = 1; + } else if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else 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); + StreamTcpSetEvent(p, STREAM_FIN2_FIN_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on FinWait2", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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); + + } else { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->client.next_seq); + StreamTcpSetEvent(p, STREAM_FIN2_ACK_WRONG_SEQ); + return -1; + } + + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, 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); + } + + StreamTcpSackUpdatePacket(&ssn->server, p); + + /* update next_win */ + StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window)); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + + 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)); + int retransmission = 0; + + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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); + } else { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->server.next_seq); + StreamTcpSetEvent(p, STREAM_FIN2_ACK_WRONG_SEQ); + return -1; + } + + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, 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); + } + + StreamTcpSackUpdatePacket(&ssn->client, p); + + /* update next_win */ + StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window)); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on Closing", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + 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); + StreamTcpSetEvent(p, STREAM_CLOSING_ACK_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSING_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + 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)); + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + 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); + StreamTcpSetEvent(p, STREAM_CLOSING_ACK_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSING_INVALID_ACK); + return -1; + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("StreamTcpPacketStateClosing (%p): =+ next SEQ " + "%" PRIu32 ", last ACK %" PRIu32 "", ssn, + ssn->server.next_seq, ssn->client.last_ack); + } + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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, PacketQueue *pq) +{ + SCEnter(); + + if (ssn == NULL) { + SCReturnInt(-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)); + } else { + 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 (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if (p->tcph->th_flags & TH_FIN) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(ssn, p)) + SCReturnInt(-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)); + + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (!retransmission) { + 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); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_FIN_OUT_OF_WINDOW); + SCReturnInt(-1); + } + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + /* don't update to LAST_ACK here as we want a toclient FIN for that */ + + if (!retransmission) + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->client.next_seq, + ssn->server.last_ack); + } 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)); + + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (!retransmission) { + 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); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_FIN_OUT_OF_WINDOW); + SCReturnInt(-1); + } + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + 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; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on CloseWait", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + SCReturnInt(-1); + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(ssn, p)) + SCReturnInt(-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)); + + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (p->payload_len > 0 && (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), ssn->client.last_ack))) { + SCLogDebug("ssn %p: -> retransmission", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_PKT_BEFORE_LAST_ACK); + SCReturnInt(-1); + + } else if (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); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_ACK_OUT_OF_WINDOW); + SCReturnInt(-1); + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + if (SEQ_EQ(TCP_GET_SEQ(p),ssn->client.next_seq)) + ssn->client.next_seq += p->payload_len; + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->client.next_seq, + ssn->server.last_ack); + } 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)); + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (p->payload_len > 0 && (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), ssn->server.last_ack))) { + SCLogDebug("ssn %p: -> retransmission", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_PKT_BEFORE_LAST_ACK); + SCReturnInt(-1); + + } else if (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); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_ACK_OUT_OF_WINDOW); + SCReturnInt(-1); + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + if (SEQ_EQ(TCP_GET_SEQ(p),ssn->server.next_seq)) + ssn->server.next_seq += p->payload_len; + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + } + + } else { + SCLogDebug("ssn %p: default case", ssn); + } + SCReturnInt(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 StreamTcpPacketStateLastAck(ThreadVars *tv, Packet *p, + StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if (p->tcph->th_flags & TH_FIN) { + /** \todo */ + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on LastAck", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } + + if (TCP_GET_SEQ(p) != ssn->client.next_seq && TCP_GET_SEQ(p) != ssn->client.next_seq + 1) { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->client.next_seq); + StreamTcpSetEvent(p, STREAM_LASTACK_ACK_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_LASTACK_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn); + + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->client.next_seq, + ssn->server.last_ack); + } + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + 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, PacketQueue *pq) +{ + if (ssn == NULL) + return -1; + + if (p->tcph->th_flags & TH_RST) { + if (!StreamTcpValidateRst(ssn, p)) + return -1; + + /* force both streams to reassemble, if necessary */ + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: Reset received state changed to TCP_CLOSED", + ssn); + + if (PKT_IS_TOSERVER(p)) { + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } else { + StreamTcpUpdateLastAck(ssn, &ssn->client, + StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p))); + + StreamTcpUpdateLastAck(ssn, &ssn->server, + StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p))); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } + + } else if (p->tcph->th_flags & TH_FIN) { + /** \todo */ + + } else if (p->tcph->th_flags & TH_SYN) { + SCLogDebug("ssn (%p): SYN pkt on TimeWait", ssn); + StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND); + return -1; + + } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(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)); + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->client, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + + } else if (TCP_GET_SEQ(p) != ssn->client.next_seq && TCP_GET_SEQ(p) != ssn->client.next_seq+1) { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->client.next_seq); + StreamTcpSetEvent(p, STREAM_TIMEWAIT_ACK_WRONG_SEQ); + return -1; + } + + if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_TIMEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn); + + ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p))) + ssn->server.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->client.next_seq, + ssn->server.last_ack); + + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + } 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)); + int retransmission = 0; + if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; + } else if (TCP_GET_SEQ(p) != ssn->server.next_seq && TCP_GET_SEQ(p) != ssn->server.next_seq+1) { + if (p->payload_len > 0 && TCP_GET_SEQ(p) == ssn->server.last_ack) { + SCLogDebug("ssn %p: -> retransmission", ssn); + SCReturnInt(0); + } else { + SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" + " != %" PRIu32 " from stream", ssn, + TCP_GET_SEQ(p), ssn->server.next_seq); + StreamTcpSetEvent(p, STREAM_TIMEWAIT_ACK_WRONG_SEQ); + return -1; + } + } + + if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_TIMEWAIT_INVALID_ACK); + SCReturnInt(-1); + } + + if (!retransmission) { + StreamTcpPacketSetState(p, ssn, TCP_CLOSED); + SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn); + + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + /* Update the next_seq, in case if we have missed the client + packet and server has already received and acked it */ + if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p))) + ssn->client.next_seq = TCP_GET_ACK(p); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK " + "%" PRIu32 "", ssn, ssn->server.next_seq, + ssn->client.last_ack); + + StreamTcpPseudoPacketCreateStreamEndPacket(tv, stt, p, ssn, pq); + } + + } else { + SCLogDebug("ssn %p: default case", ssn); + } + + return 0; +} + +/** + * \retval 1 packet is a keep alive pkt + * \retval 0 packet is not a keep alive pkt + */ +static int StreamTcpPacketIsKeepAlive(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + + /* + rfc 1122: + An implementation SHOULD send a keep-alive segment with no + data; however, it MAY be configurable to send a keep-alive + segment containing one garbage octet, for compatibility with + erroneous TCP implementations. + */ + if (p->payload_len > 1) + return 0; + + if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) { + return 0; + } + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + if (ack == ostream->last_ack && seq == (stream->next_seq - 1)) { + SCLogDebug("packet is TCP keep-alive: %"PRIu64, p->pcap_cnt); + stream->flags |= STREAMTCP_STREAM_FLAG_KEEPALIVE; + return 1; + } + SCLogDebug("seq %u (%u), ack %u (%u)", seq, (stream->next_seq - 1), ack, ostream->last_ack); + return 0; +} + +/** + * \retval 1 packet is a keep alive ACK pkt + * \retval 0 packet is not a keep alive ACK pkt + */ +static int StreamTcpPacketIsKeepAliveACK(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + uint32_t pkt_win; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + /* should get a normal ACK to a Keep Alive */ + if (p->payload_len > 0) + return 0; + + if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) + return 0; + + if (TCP_GET_WINDOW(p) == 0) + return 0; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + pkt_win = TCP_GET_WINDOW(p) << ostream->wscale; + if (pkt_win != ostream->window) + return 0; + + if ((ostream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE) && ack == ostream->last_ack && seq == stream->next_seq) { + SCLogDebug("packet is TCP keep-aliveACK: %"PRIu64, p->pcap_cnt); + ostream->flags &= ~STREAMTCP_STREAM_FLAG_KEEPALIVE; + return 1; + } + SCLogDebug("seq %u (%u), ack %u (%u) FLAG_KEEPALIVE: %s", seq, stream->next_seq, ack, ostream->last_ack, + ostream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE ? "set" : "not set"); + return 0; +} + +static void StreamTcpClearKeepAliveFlag(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + } else { + stream = &ssn->server; + } + + if (stream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE) { + stream->flags &= ~STREAMTCP_STREAM_FLAG_KEEPALIVE; + SCLogDebug("FLAG_KEEPALIVE cleared"); + } +} + +/** + * \retval 1 packet is a window update pkt + * \retval 0 packet is not a window update pkt + */ +static int StreamTcpPacketIsWindowUpdate(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + uint32_t pkt_win; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + + if (ssn->state < TCP_ESTABLISHED) + return 0; + + if (p->payload_len > 0) + return 0; + + if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) + return 0; + + if (TCP_GET_WINDOW(p) == 0) + return 0; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + pkt_win = TCP_GET_WINDOW(p) << ostream->wscale; + if (pkt_win == ostream->window) + return 0; + + if (ack == ostream->last_ack && seq == stream->next_seq) { + SCLogDebug("packet is TCP window update: %"PRIu64, p->pcap_cnt); + return 1; + } + SCLogDebug("seq %u (%u), ack %u (%u)", seq, stream->next_seq, ack, ostream->last_ack); + return 0; +} + +/** + * Try to detect whether a packet is a valid FIN 4whs final ack. + * + */ +static int StreamTcpPacketIsFinShutdownAck(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + if (!(ssn->state == TCP_TIME_WAIT || ssn->state == TCP_CLOSE_WAIT || ssn->state == TCP_LAST_ACK)) + return 0; + if (p->tcph->th_flags != TH_ACK) + return 0; + if (p->payload_len != 0) + return 0; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + SCLogDebug("%"PRIu64", seq %u ack %u stream->next_seq %u ostream->next_seq %u", + p->pcap_cnt, seq, ack, stream->next_seq, ostream->next_seq); + + if (SEQ_EQ(stream->next_seq + 1, seq) && SEQ_EQ(ack, ostream->next_seq + 1)) { + return 1; + } + return 0; +} + +/** + * Try to detect packets doing bad window updates + * + * See bug 1238. + * + * Find packets that are unexpected, and shrink the window to the point + * where the packets we do expect are rejected for being out of window. + * + * The logic we use here is: + * - packet seq > next_seq + * - packet ack > next_seq (packet acks unseen data) + * - packet shrinks window more than it's own data size + * - packet shrinks window more than the diff between it's ack and the + * last_ack value + * + * Packets coming in after packet loss can look quite a bit like this. + */ +static int StreamTcpPacketIsBadWindowUpdate(TcpSession *ssn, Packet *p) +{ + TcpStream *stream = NULL, *ostream = NULL; + uint32_t seq; + uint32_t ack; + uint32_t pkt_win; + + if (p->flags & PKT_PSEUDO_STREAM_END) + return 0; + + if (ssn->state < TCP_ESTABLISHED || ssn->state == TCP_CLOSED) + return 0; + + if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) + return 0; + + if (PKT_IS_TOSERVER(p)) { + stream = &ssn->client; + ostream = &ssn->server; + } else { + stream = &ssn->server; + ostream = &ssn->client; + } + + seq = TCP_GET_SEQ(p); + ack = TCP_GET_ACK(p); + + pkt_win = TCP_GET_WINDOW(p) << ostream->wscale; + + if (pkt_win < ostream->window) { + uint32_t diff = ostream->window - pkt_win; + if (diff > p->payload_len && + SEQ_GT(ack, ostream->next_seq) && + SEQ_GT(seq, stream->next_seq)) + { + SCLogDebug("%"PRIu64", pkt_win %u, stream win %u, diff %u, dsize %u", + p->pcap_cnt, pkt_win, ostream->window, diff, p->payload_len); + SCLogDebug("%"PRIu64", pkt_win %u, stream win %u", + p->pcap_cnt, pkt_win, ostream->window); + SCLogDebug("%"PRIu64", seq %u ack %u ostream->next_seq %u ostream->last_ack %u, ostream->next_win %u, diff %u (%u)", + p->pcap_cnt, seq, ack, ostream->next_seq, ostream->last_ack, ostream->next_win, + ostream->next_seq - ostream->last_ack, stream->next_seq - stream->last_ack); + + /* get the expected window shrinking from looking at ack vs last_ack. + * Observed a lot of just a little overrunning that value. So added some + * margin that is still ok. To make sure this isn't a loophole to still + * close the window, this is limited to windows above 1024. Both values + * are rather arbitrary. */ + uint32_t adiff = ack - ostream->last_ack; + if (((pkt_win > 1024) && (diff > (adiff + 32))) || + ((pkt_win <= 1024) && (diff > adiff))) + { + SCLogDebug("pkt ACK %u is %u bytes beyond last_ack %u, shrinks window by %u " + "(allowing 32 bytes extra): pkt WIN %u", ack, adiff, ostream->last_ack, diff, pkt_win); + SCLogDebug("%u - %u = %u (state %u)", diff, adiff, diff - adiff, ssn->state); + StreamTcpSetEvent(p, STREAM_PKT_BAD_WINDOW_UPDATE); + return 1; + } + } + + } + SCLogDebug("seq %u (%u), ack %u (%u)", seq, stream->next_seq, ack, ostream->last_ack); + return 0; +} + +/* flow is and stays locked */ +int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt, + PacketQueue *pq) +{ + SCEnter(); + + DEBUG_ASSERT_FLOW_LOCKED(p->flow); + + SCLogDebug("p->pcap_cnt %"PRIu64, p->pcap_cnt); + + /* assign the thread id to the flow */ + if (unlikely(p->flow->thread_id == 0)) { + p->flow->thread_id = (FlowThreadId)tv->id; +#ifdef DEBUG + } else if (unlikely((FlowThreadId)tv->id != p->flow->thread_id)) { + SCLogDebug("wrong thread: flow has %u, we are %d", p->flow->thread_id, tv->id); +#endif + } + + TcpSession *ssn = (TcpSession *)p->flow->protoctx; + + /* track TCP flags */ + if (ssn != NULL) { + ssn->tcp_packet_flags |= p->tcph->th_flags; + if (PKT_IS_TOSERVER(p)) + ssn->client.tcp_flags |= p->tcph->th_flags; + else if (PKT_IS_TOCLIENT(p)) + ssn->server.tcp_flags |= p->tcph->th_flags; + } + + /* update counters */ + if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) { + StatsIncr(tv, stt->counter_tcp_synack); + } else if (p->tcph->th_flags & (TH_SYN)) { + StatsIncr(tv, stt->counter_tcp_syn); + } + if (p->tcph->th_flags & (TH_RST)) { + StatsIncr(tv, stt->counter_tcp_rst); + } + + /* broken TCP http://ask.wireshark.org/questions/3183/acknowledgment-number-broken-tcp-the-acknowledge-field-is-nonzero-while-the-ack-flag-is-not-set */ + if (!(p->tcph->th_flags & TH_ACK) && TCP_GET_ACK(p) != 0) { + StreamTcpSetEvent(p, STREAM_PKT_BROKEN_ACK); + } + + /* If we are on IPS mode, and got a drop action triggered from + * the IP only module, or from a reassembled msg and/or from an + * applayer detection, then drop the rest of the packets of the + * same stream and avoid inspecting it any further */ + if (StreamTcpCheckFlowDrops(p) == 1) { + SCLogDebug("This flow/stream triggered a drop rule"); + FlowSetNoPacketInspectionFlag(p->flow); + DecodeSetNoPacketInspectionFlag(p); + StreamTcpDisableAppLayer(p->flow); + PACKET_DROP(p); + /* return the segments to the pool */ + StreamTcpSessionPktFree(p); + SCReturnInt(0); + } + + if (ssn == NULL || ssn->state == TCP_NONE) { + if (StreamTcpPacketStateNone(tv, p, stt, ssn, &stt->pseudo_queue) == -1) { + goto error; + } + + if (ssn != NULL) + SCLogDebug("ssn->alproto %"PRIu16"", p->flow->alproto); + } else { + /* special case for PKT_PSEUDO_STREAM_END packets: + * bypass the state handling and various packet checks, + * we care about reassembly here. */ + if (p->flags & PKT_PSEUDO_STREAM_END) { + if (PKT_IS_TOCLIENT(p)) { + ssn->client.last_ack = TCP_GET_ACK(p); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, p, pq); + } else { + ssn->server.last_ack = TCP_GET_ACK(p); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, p, pq); + } + /* straight to 'skip' as we already handled reassembly */ + goto skip; + } + + /* 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); + + if (StreamTcpPacketIsKeepAlive(ssn, p) == 1) { + goto skip; + } + if (StreamTcpPacketIsKeepAliveACK(ssn, p) == 1) { + StreamTcpClearKeepAliveFlag(ssn, p); + goto skip; + } + StreamTcpClearKeepAliveFlag(ssn, p); + + /* if packet is not a valid window update, check if it is perhaps + * a bad window update that we should ignore (and alert on) */ + if (StreamTcpPacketIsFinShutdownAck(ssn, p) == 0) + if (StreamTcpPacketIsWindowUpdate(ssn, p) == 0) + if (StreamTcpPacketIsBadWindowUpdate(ssn,p)) + goto skip; + + switch (ssn->state) { + case TCP_SYN_SENT: + if(StreamTcpPacketStateSynSent(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_SYN_RECV: + if(StreamTcpPacketStateSynRecv(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_ESTABLISHED: + if(StreamTcpPacketStateEstablished(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_FIN_WAIT1: + if(StreamTcpPacketStateFinWait1(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_FIN_WAIT2: + if(StreamTcpPacketStateFinWait2(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_CLOSING: + if(StreamTcpPacketStateClosing(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_CLOSE_WAIT: + if(StreamTcpPacketStateCloseWait(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_LAST_ACK: + if(StreamTcpPacketStateLastAck(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_TIME_WAIT: + if(StreamTcpPacketStateTimeWait(tv, p, stt, ssn, &stt->pseudo_queue)) { + goto error; + } + break; + case TCP_CLOSED: + /* TCP session memory is not returned to pool until timeout. */ + SCLogDebug("packet received on closed state"); + break; + default: + SCLogDebug("packet received on default state"); + break; + } + skip: + + if (ssn->state >= TCP_ESTABLISHED) { + p->flags |= PKT_STREAM_EST; + } + } + + /* deal with a pseudo packet that is created upon receiving a RST + * segment. To be sure we process both sides of the connection, we + * inject a fake packet into the system, forcing reassembly of the + * opposing direction. + * There should be only one, but to be sure we do a while loop. */ + if (ssn != NULL) { + while (stt->pseudo_queue.len > 0) { + SCLogDebug("processing pseudo packet / stream end"); + Packet *np = PacketDequeue(&stt->pseudo_queue); + if (np != NULL) { + /* process the opposing direction of the original packet */ + if (PKT_IS_TOSERVER(np)) { + SCLogDebug("pseudo packet is to server"); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->client, np, NULL); + } else { + SCLogDebug("pseudo packet is to client"); + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, + &ssn->server, np, NULL); + } + + /* enqueue this packet so we inspect it in detect etc */ + PacketEnqueue(pq, np); + } + SCLogDebug("processing pseudo packet / stream end done"); + } + + /* recalc the csum on the packet if it was modified */ + if (p->flags & PKT_STREAM_MODIFIED) { + ReCalculateChecksum(p); + } + + /* check for conditions that may make us not want to log this packet */ + + /* streams that hit depth */ + if ((ssn->client.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) || + (ssn->server.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED)) + { + p->flags |= PKT_STREAM_NOPCAPLOG; + } + + /* encrypted packets */ + if ((PKT_IS_TOSERVER(p) && (ssn->client.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) || + (PKT_IS_TOCLIENT(p) && (ssn->server.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY))) + { + p->flags |= PKT_STREAM_NOPCAPLOG; + } + } + + SCReturnInt(0); + +error: + /* make sure we don't leave packets in our pseudo queue */ + while (stt->pseudo_queue.len > 0) { + Packet *np = PacketDequeue(&stt->pseudo_queue); + if (np != NULL) { + PacketEnqueue(pq, np); + } + } + + /* recalc the csum on the packet if it was modified */ + if (p->flags & PKT_STREAM_MODIFIED) { + ReCalculateChecksum(p); + } + + if (StreamTcpInlineMode()) { + PACKET_DROP(p); + } + SCReturnInt(-1); +} + +/** + * \brief Function to validate the checksum of the received packet. If the + * checksum is invalid, packet will be dropped, as the end system will + * also drop the packet. + * + * \param p Packet of which checksum has to be validated + * \retval 1 if the checksum is valid, otherwise 0 + */ +static inline int StreamTcpValidateChecksum(Packet *p) +{ + int ret = 1; + + if (p->flags & PKT_IGNORE_CHECKSUM) + return ret; + + if (p->level4_comp_csum == -1) { + if (PKT_IS_IPV4(p)) { + p->level4_comp_csum = TCPCalculateChecksum(p->ip4h->s_ip_addrs, + (uint16_t *)p->tcph, + (p->payload_len + + TCP_GET_HLEN(p))); + } else if (PKT_IS_IPV6(p)) { + p->level4_comp_csum = TCPV6CalculateChecksum(p->ip6h->s_ip6_addrs, + (uint16_t *)p->tcph, + (p->payload_len + + TCP_GET_HLEN(p))); + } + } + + if (p->level4_comp_csum != p->tcph->th_sum) { + ret = 0; + SCLogDebug("Checksum of received packet %p is invalid",p); + if (p->livedev) { + (void) SC_ATOMIC_ADD(p->livedev->invalid_checksums, 1); + } else if (p->pcap_cnt) { + PcapIncreaseInvalidChecksum(); + } + } + + return ret; +} + +/** \internal + * \brief check if a packet is a valid stream started + * \retval bool true/false */ +static int TcpSessionPacketIsStreamStarter(const Packet *p) +{ + if (p->tcph->th_flags == TH_SYN) { + SCLogDebug("packet %"PRIu64" is a stream starter: %02x", p->pcap_cnt, p->tcph->th_flags); + return 1; + } + + if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) { + if (p->tcph->th_flags == (TH_SYN|TH_ACK)) { + SCLogDebug("packet %"PRIu64" is a midstream stream starter: %02x", p->pcap_cnt, p->tcph->th_flags); + return 1; + } + } + return 0; +} + +/** \internal + * \brief Check if Flow and TCP SSN allow this flow/tuple to be reused + * \retval bool true yes reuse, false no keep tracking old ssn */ +static int TcpSessionReuseDoneEnoughSyn(const Packet *p, const Flow *f, const TcpSession *ssn) +{ + if (FlowGetPacketDirection(f, p) == TOSERVER) { + if (ssn == NULL) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p null. No reuse.", p->pcap_cnt, ssn); + return 0; + } + if (SEQ_EQ(ssn->client.isn, TCP_GET_SEQ(p))) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p. Packet SEQ == Stream ISN. Retransmission. Don't reuse.", p->pcap_cnt, ssn); + return 0; + } + if (ssn->state >= TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state == TCP_NONE) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state < TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state); + return 0; + } + + } else { + if (ssn == NULL) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p null. Reuse.", p->pcap_cnt, ssn); + return 1; + } + if (ssn->state >= TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state == TCP_NONE) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state < TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state); + return 0; + } + } + + SCLogDebug("default: how did we get here?"); + return 0; +} + +/** \internal + * \brief check if ssn is done enough for reuse by syn/ack + * \note should only be called if midstream is enabled + */ +static int TcpSessionReuseDoneEnoughSynAck(const Packet *p, const Flow *f, const TcpSession *ssn) +{ + if (FlowGetPacketDirection(f, p) == TOCLIENT) { + if (ssn == NULL) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p null. No reuse.", p->pcap_cnt, ssn); + return 0; + } + if (SEQ_EQ(ssn->server.isn, TCP_GET_SEQ(p))) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p. Packet SEQ == Stream ISN. Retransmission. Don't reuse.", p->pcap_cnt, ssn); + return 0; + } + if (ssn->state >= TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state == TCP_NONE) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state < TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state); + return 0; + } + + } else { + if (ssn == NULL) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p null. Reuse.", p->pcap_cnt, ssn); + return 1; + } + if (ssn->state >= TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state == TCP_NONE) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state); + return 1; + } + if (ssn->state < TCP_LAST_ACK) { + SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state); + return 0; + } + } + + SCLogDebug("default: how did we get here?"); + return 0; +} + +/** \brief Check if SSN is done enough for reuse + * + * Reuse means a new TCP session reuses the tuple (flow in suri) + * + * \retval bool true if ssn can be reused, false if not */ +int TcpSessionReuseDoneEnough(const Packet *p, const Flow *f, const TcpSession *ssn) +{ + if (p->tcph->th_flags == TH_SYN) { + return TcpSessionReuseDoneEnoughSyn(p, f, ssn); + } + + if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) { + if (p->tcph->th_flags == (TH_SYN|TH_ACK)) { + return TcpSessionReuseDoneEnoughSynAck(p, f, ssn); + } + } + + return 0; +} + +int TcpSessionPacketSsnReuse(const Packet *p, const Flow *f, const void *tcp_ssn) +{ + if (p->proto == IPPROTO_TCP && p->tcph != NULL) { + if (TcpSessionPacketIsStreamStarter(p) == 1) { + if (TcpSessionReuseDoneEnough(p, f, tcp_ssn) == 1) { + return 1; + } + } + } + return 0; +} + +/** \brief Handle TCP reuse of tuple + * + * Logic: + * 1. see if packet could trigger a new session + * 2. see if the flow/ssn is in a state where we want to support the reuse + * 3. disconnect packet from the old flow + * -> at this point new packets can still find the old flow + * -> as the flow's reference count != 0, it can't disappear + * 4. setup a new flow unconditionally + * 5. attach packet to new flow + * 6. tag old flow as FLOW_TCP_REUSED + * -> NEW packets won't find it + * -> existing packets in our queues may still reference it + * 7. dereference the old flow (reference cnt *may* now be 0, + * if no other packets reference it) + * + * The packets that still hold a reference to the old flow are updated + * by HandleFlowReuseApplyToPacket() + */ +static void TcpSessionReuseHandle(Packet *p) { + if (likely(TcpSessionPacketIsStreamStarter(p) == 0)) + return; + + int reuse = 0; + FLOWLOCK_RDLOCK(p->flow); + reuse = TcpSessionReuseDoneEnough(p, p->flow, p->flow->protoctx); + if (!reuse) { + SCLogDebug("steam starter packet %"PRIu64", but state not " + "ready to be reused", p->pcap_cnt); + FLOWLOCK_UNLOCK(p->flow); + return; + } + + SCLogDebug("steam starter packet %"PRIu64", and state " + "ready to be reused", p->pcap_cnt); + + /* ok, this packet needs a new flow */ + + /* first, get a reference to the old flow */ + Flow *old_f = NULL; + FlowReference(&old_f, p->flow); + + /* get some settings that we move over to the new flow */ + FlowThreadId thread_id = old_f->thread_id; + int autofp_tmqh_flow_qid = SC_ATOMIC_GET(old_f->autofp_tmqh_flow_qid); + + /* disconnect the packet from the old flow */ + FlowHandlePacketUpdateRemove(p->flow, p); + FLOWLOCK_UNLOCK(p->flow); + FlowDeReference(&p->flow); // < can't disappear while usecnt >0 + + /* Can't tag flow as reused yet, would be a race condition: + * new packets will not get old flow because of FLOW_TCP_REUSED, + * so new flow may be created. This new flow could be handled in + * a different thread. */ + + /* Get a flow. It will be either a locked flow or NULL */ + Flow *new_f = FlowGetFlowFromHashByPacket(p); + if (new_f == NULL) { + FlowDeReference(&old_f); // < can't disappear while usecnt >0 + return; + } + + /* update flow and packet */ + FlowHandlePacketUpdate(new_f, p); + BUG_ON(new_f != p->flow); + + /* copy flow balancing settings */ + new_f->thread_id = thread_id; + SC_ATOMIC_SET(new_f->autofp_tmqh_flow_qid, autofp_tmqh_flow_qid); + + FLOWLOCK_UNLOCK(new_f); + + /* tag original flow that it's now unused */ + FLOWLOCK_WRLOCK(old_f); + SCLogDebug("old flow %p tagged with FLOW_TCP_REUSED by packet %"PRIu64"!", old_f, p->pcap_cnt); + old_f->flags |= FLOW_TCP_REUSED; + FLOWLOCK_UNLOCK(old_f); + FlowDeReference(&old_f); // < can't disappear while usecnt >0 + + SCLogDebug("new flow %p set up for packet %"PRIu64"!", p->flow, p->pcap_cnt); +} + +/** \brief Handle packets that reference the wrong flow because of TCP reuse + * + * In the case of TCP reuse we can have many packets that were assigned + * a flow by the capture/decode threads before the stream engine decided + * that a new flow was needed for these packets. + * When HandleFlowReuse creates a new flow, the packets already processed + * by the flow engine will still reference the old flow. + * + * This function detects this case and replaces the flow for those packets. + * It's a fairly expensive operation, but it should be rare as it's only + * done for packets that were already in the engine when the TCP reuse + * case was handled. New packets are assigned the correct flow by the + * flow engine. + */ +static void TcpSessionReuseHandleApplyToPacket(Packet *p) +{ + int need_flow_replace = 0; + + FLOWLOCK_WRLOCK(p->flow); + if (p->flow->flags & FLOW_TCP_REUSED) { + SCLogDebug("packet %"PRIu64" attached to outdated flow and ssn", p->pcap_cnt); + need_flow_replace = 1; + } + + if (likely(need_flow_replace == 0)) { + /* Work around a race condition: if HandleFlowReuse has inserted a new flow, + * it will not have seen both sides of the session yet. The packet we have here + * may be the first that got the flow directly from the hash right after the + * flow was added. In this case it won't have FLOW_PKT_ESTABLISHED flag set. */ + if ((p->flow->flags & FLOW_TO_DST_SEEN) && (p->flow->flags & FLOW_TO_SRC_SEEN)) { + p->flowflags |= FLOW_PKT_ESTABLISHED; + SCLogDebug("packet %"PRIu64" / flow %p: p->flowflags |= FLOW_PKT_ESTABLISHED (%u/%u)", p->pcap_cnt, p->flow, p->flow->todstpktcnt, p->flow->tosrcpktcnt); + } else { + SCLogDebug("packet %"PRIu64" / flow %p: p->flowflags NOT FLOW_PKT_ESTABLISHED (%u/%u)", p->pcap_cnt, p->flow, p->flow->todstpktcnt, p->flow->tosrcpktcnt); + } + SCLogDebug("packet %"PRIu64" attached to regular flow %p and ssn", p->pcap_cnt, p->flow); + FLOWLOCK_UNLOCK(p->flow); + return; + } + + /* disconnect packet from old flow */ + FlowHandlePacketUpdateRemove(p->flow, p); + FLOWLOCK_UNLOCK(p->flow); + FlowDeReference(&p->flow); // < can't disappear while usecnt >0 + + /* find the new flow that does belong to this packet */ + Flow *new_f = FlowLookupFlowFromHash(p); + if (new_f == NULL) { + // TODO reset packet flag wrt flow: direction, HAS_FLOW etc + p->flags &= ~PKT_HAS_FLOW; + return; + } + FlowHandlePacketUpdate(new_f, p); + BUG_ON(new_f != p->flow); + FLOWLOCK_UNLOCK(new_f); + SCLogDebug("packet %"PRIu64" switched over to new flow %p!", p->pcap_cnt, p->flow); +} + +TmEcode StreamTcp (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq) +{ + StreamTcpThread *stt = (StreamTcpThread *)data; + TmEcode ret = TM_ECODE_OK; + + if (!(PKT_IS_TCP(p))) + return TM_ECODE_OK; + + if (p->flow == NULL) { + StatsIncr(tv, stt->counter_tcp_no_flow); + return TM_ECODE_OK; + } + + if (stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION) { + if (StreamTcpValidateChecksum(p) == 0) { + StatsIncr(tv, stt->counter_tcp_invalid_checksum); + return TM_ECODE_OK; + } + } else { + p->flags |= PKT_IGNORE_CHECKSUM; + } + + if (stt->runmode_flow_stream_async) { + /* "autofp" handling of TCP session/flow reuse */ + if (!(p->flags & PKT_PSEUDO_STREAM_END)) { + /* apply previous reuses to this packet */ + TcpSessionReuseHandleApplyToPacket(p); + if (p->flow == NULL) + return ret; + + if (!(p->flowflags & FLOW_PKT_TOSERVER_FIRST)) { + /* after that, check for 'new' reuse */ + TcpSessionReuseHandle(p); + if (p->flow == NULL) + return ret; + } + } + } + AppLayerProfilingReset(stt->ra_ctx->app_tctx); + + FLOWLOCK_WRLOCK(p->flow); + ret = StreamTcpPacket(tv, p, stt, pq); + FLOWLOCK_UNLOCK(p->flow); + + //if (ret) + // return TM_ECODE_FAILED; + + stt->pkts++; + return ret; +} + +TmEcode StreamTcpThreadInit(ThreadVars *tv, void *initdata, void **data) +{ + SCEnter(); + StreamTcpThread *stt = SCMalloc(sizeof(StreamTcpThread)); + if (unlikely(stt == NULL)) + SCReturnInt(TM_ECODE_FAILED); + memset(stt, 0, sizeof(StreamTcpThread)); + stt->ssn_pool_id = -1; + + *data = (void *)stt; + + stt->counter_tcp_sessions = StatsRegisterCounter("tcp.sessions", tv); + stt->counter_tcp_ssn_memcap = StatsRegisterCounter("tcp.ssn_memcap_drop", tv); + stt->counter_tcp_pseudo = StatsRegisterCounter("tcp.pseudo", tv); + stt->counter_tcp_pseudo_failed = StatsRegisterCounter("tcp.pseudo_failed", tv); + stt->counter_tcp_invalid_checksum = StatsRegisterCounter("tcp.invalid_checksum", tv); + stt->counter_tcp_no_flow = StatsRegisterCounter("tcp.no_flow", tv); + stt->counter_tcp_syn = StatsRegisterCounter("tcp.syn", tv); + stt->counter_tcp_synack = StatsRegisterCounter("tcp.synack", tv); + stt->counter_tcp_rst = StatsRegisterCounter("tcp.rst", tv); + + /* init reassembly ctx */ + stt->ra_ctx = StreamTcpReassembleInitThreadCtx(tv); + if (stt->ra_ctx == NULL) + SCReturnInt(TM_ECODE_FAILED); + + stt->ra_ctx->counter_tcp_segment_memcap = StatsRegisterCounter("tcp.segment_memcap_drop", tv); + stt->ra_ctx->counter_tcp_stream_depth = StatsRegisterCounter("tcp.stream_depth_reached", tv); + stt->ra_ctx->counter_tcp_reass_gap = StatsRegisterCounter("tcp.reassembly_gap", tv); + + SCLogDebug("StreamTcp thread specific ctx online at %p, reassembly ctx %p", + stt, stt->ra_ctx); + + SCMutexLock(&ssn_pool_mutex); + if (ssn_pool == NULL) { + ssn_pool = PoolThreadInit(1, /* thread */ + 0, /* unlimited */ + stream_config.prealloc_sessions, + sizeof(TcpSession), + StreamTcpSessionPoolAlloc, + StreamTcpSessionPoolInit, NULL, + StreamTcpSessionPoolCleanup, NULL); + stt->ssn_pool_id = 0; + SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id); + } else { + /* grow ssn_pool until we have a element for our thread id */ + stt->ssn_pool_id = PoolThreadGrow(ssn_pool, + 0, /* unlimited */ + stream_config.prealloc_sessions, + sizeof(TcpSession), + StreamTcpSessionPoolAlloc, + StreamTcpSessionPoolInit, NULL, + StreamTcpSessionPoolCleanup, NULL); + SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id); + } + SCMutexUnlock(&ssn_pool_mutex); + if (stt->ssn_pool_id < 0 || ssn_pool == NULL) + SCReturnInt(TM_ECODE_FAILED); + + /* see if need to enable the TCP reuse handling in the stream engine */ + stt->runmode_flow_stream_async = RunmodeGetFlowStreamAsync(); + SCLogDebug("Flow and Stream engine run %s", + stt->runmode_flow_stream_async ? "asynchronous" : "synchronous"); + + SCReturnInt(TM_ECODE_OK); +} + +TmEcode StreamTcpThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + StreamTcpThread *stt = (StreamTcpThread *)data; + if (stt == NULL) { + return TM_ECODE_OK; + } + + /* XXX */ + + /* free reassembly ctx */ + StreamTcpReassembleFreeThreadCtx(stt->ra_ctx); + + /* clear memory */ + memset(stt, 0, sizeof(StreamTcpThread)); + + SCFree(stt); + SCReturnInt(TM_ECODE_OK); +} + +void StreamTcpExitPrintStats(ThreadVars *tv, void *data) +{ + StreamTcpThread *stt = (StreamTcpThread *)data; + if (stt == NULL) { + return; + } + + SCLogInfo("Stream TCP processed %" PRIu64 " TCP packets", 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 + * + * \retval 0 unacceptable RST + * \retval 1 acceptable RST + * + * WebSense sends RST packets that are: + * - RST flag, win 0, ack 0, seq = nextseq + * + */ + +static int StreamTcpValidateRst(TcpSession *ssn, Packet *p) +{ + + uint8_t os_policy; + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + if (!StreamTcpValidateTimestamp(ssn, p)) { + SCReturnInt(0); + } + } + + /* Set up the os_policy to be used in validating the RST packets based on + target system */ + if (PKT_IS_TOSERVER(p)) { + if (ssn->server.os_policy == 0) + StreamTcpSetOSPolicy(&ssn->server, p); + + os_policy = ssn->server.os_policy; + + if (p->tcph->th_flags & TH_ACK && + TCP_GET_ACK(p) && StreamTcpValidateAck(ssn, &ssn->server, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_RST_INVALID_ACK); + SCReturnInt(0); + } + + } else { + if (ssn->client.os_policy == 0) + StreamTcpSetOSPolicy(&ssn->client, p); + + os_policy = ssn->client.os_policy; + + if (p->tcph->th_flags & TH_ACK && + TCP_GET_ACK(p) && StreamTcpValidateAck(ssn, &ssn->client, p) == -1) { + SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn); + StreamTcpSetEvent(p, STREAM_RST_INVALID_ACK); + SCReturnInt(0); + } + } + + 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 check the validity of the received timestamp based on + * the target OS of the given stream. + * + * It's passive except for: + * 1. it sets the os policy on the stream if necessary + * 2. it sets an event in the packet if necessary + * + * \param ssn TCP session to which the given packet belongs + * \param p Packet which has to be checked for its validity + * + * \retval 1 if the timestamp is valid + * \retval 0 if the timestamp is invalid + */ +static int StreamTcpValidateTimestamp (TcpSession *ssn, Packet *p) +{ + SCEnter(); + + 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; + } + + /* Set up the os_policy to be used in validating the timestamps based on + the target system */ + if (receiver_stream->os_policy == 0) { + StreamTcpSetOSPolicy(receiver_stream, p); + } + + if (p->tcpvars.ts != NULL) { + uint32_t ts = TCP_GET_TSVAL(p); + uint32_t last_pkt_ts = sender_stream->last_pkt_ts; + uint32_t last_ts = sender_stream->last_ts; + + if (sender_stream->flags & STREAMTCP_STREAM_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. */ + check_ts = 0; + break; + + case OS_POLICY_OLD_LINUX: + case OS_POLICY_WINDOWS: + case OS_POLICY_VISTA: + if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) { + last_ts = ts; + check_ts = 0; /*next packet will be checked for validity + and stream TS has been updated with this + one.*/ + } + 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*/ + goto invalid; + } + } + + if (check_ts) { + int32_t result = 0; + + SCLogDebug("ts %"PRIu32", last_ts %"PRIu32"", ts, last_ts); + + if (receiver_stream->os_policy == OS_POLICY_LINUX) { + /* Linux accepts TS which are off by one.*/ + result = (int32_t) ((ts - last_ts) + 1); + } else { + result = (int32_t) (ts - last_ts); + } + + SCLogDebug("result %"PRIi32", p->ts.tv_sec %"PRIuMAX"", result, (uintmax_t)p->ts.tv_sec); + + if (last_pkt_ts == 0 && + (ssn->flags & STREAMTCP_FLAG_MIDSTREAM)) + { + last_pkt_ts = p->ts.tv_sec; + } + + if (result < 0) { + SCLogDebug("timestamp is not valid last_ts " + "%" PRIu32 " p->tcpvars->ts %" PRIu32 " result " + "%" PRId32 "", last_ts, ts, result); + /* candidate for rejection */ + ret = 0; + } else if ((sender_stream->last_ts != 0) && + (((uint32_t) p->ts.tv_sec) > + last_pkt_ts + PAWS_24DAYS)) + { + SCLogDebug("packet is not valid last_pkt_ts " + "%" PRIu32 " p->ts.tv_sec %" PRIu32 "", + last_pkt_ts, (uint32_t) p->ts.tv_sec); + /* candidate for rejection */ + ret = 0; + } + + 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 > (last_pkt_ts + PAWS_24DAYS)))) + { + SCLogDebug("timestamp considered valid anyway"); + } else { + goto invalid; + } + } + } + } + + SCReturnInt(1); + +invalid: + StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP); + SCReturnInt(0); +} + +/** + * \brief Function to check the validity of the received timestamp based on + * the target OS of the given stream and update the session. + * + * \param ssn TCP session to which the given packet belongs + * \param p Packet which has to be checked for its validity + * + * \retval 1 if the timestamp is valid + * \retval 0 if the timestamp is invalid + */ +static int StreamTcpHandleTimestamp (TcpSession *ssn, Packet *p) +{ + SCEnter(); + + 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; + } + + /* Set up the os_policy to be used in validating the timestamps based on + the target system */ + if (receiver_stream->os_policy == 0) { + StreamTcpSetOSPolicy(receiver_stream, p); + } + + if (p->tcpvars.ts != NULL) { + uint32_t ts = TCP_GET_TSVAL(p); + + if (sender_stream->flags & STREAMTCP_STREAM_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_STREAM_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*/ + goto invalid; + } + } + + if (check_ts) { + int32_t result = 0; + + SCLogDebug("ts %"PRIu32", last_ts %"PRIu32"", ts, sender_stream->last_ts); + + 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); + } + + SCLogDebug("result %"PRIi32", p->ts.tv_sec %"PRIuMAX"", result, (uintmax_t)p->ts.tv_sec); + + 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); + /* candidate for rejection */ + 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); + /* candidate for rejection */ + 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; + + } else 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; + + SCLogDebug("timestamp considered valid anyway"); + } else { + goto invalid; + } + } + } + } 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; + } + + SCReturnInt(1); + +invalid: + StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP); + SCReturnInt(0); +} + +/** + * \brief Function to test the received ACK values against the stream window + * and previous ack value. ACK values should be higher than previous + * ACK value and less than the next_win value. + * + * \param ssn TcpSession for state access + * \param stream TcpStream of which last_ack needs to be tested + * \param p Packet which is used to test the last_ack + * + * \retval 0 ACK is valid, last_ack is updated if ACK was higher + * \retval -1 ACK is invalid + */ +static inline int StreamTcpValidateAck(TcpSession *ssn, TcpStream *stream, Packet *p) +{ + SCEnter(); + + uint32_t ack = TCP_GET_ACK(p); + + /* fast track */ + if (SEQ_GT(ack, stream->last_ack) && SEQ_LEQ(ack, stream->next_win)) + { + SCLogDebug("ACK in bounds"); + SCReturnInt(0); + } + /* fast track */ + else if (SEQ_EQ(ack, stream->last_ack)) { + SCLogDebug("pkt ACK %"PRIu32" == stream last ACK %"PRIu32, TCP_GET_ACK(p), stream->last_ack); + SCReturnInt(0); + } + + /* exception handling */ + if (SEQ_LT(ack, stream->last_ack)) { + SCLogDebug("pkt ACK %"PRIu32" < stream last ACK %"PRIu32, TCP_GET_ACK(p), stream->last_ack); + + /* This is an attempt to get a 'left edge' value that we can check against. + * It doesn't work when the window is 0, need to think of a better way. */ + + if (stream->window != 0 && SEQ_LT(ack, (stream->last_ack - stream->window))) { + SCLogDebug("ACK %"PRIu32" is before last_ack %"PRIu32" - window " + "%"PRIu32" = %"PRIu32, ack, stream->last_ack, + stream->window, stream->last_ack - stream->window); + goto invalid; + } + + SCReturnInt(0); + } + + if (ssn->state > TCP_SYN_SENT && SEQ_GT(ack, stream->next_win)) { + SCLogDebug("ACK %"PRIu32" is after next_win %"PRIu32, ack, stream->next_win); + goto invalid; + /* a toclient RST as a reponse to SYN, next_win is 0, ack will be isn+1, just like + * the syn ack */ + } else if (ssn->state == TCP_SYN_SENT && PKT_IS_TOCLIENT(p) && + p->tcph->th_flags & TH_RST && + SEQ_EQ(ack, stream->isn + 1)) { + SCReturnInt(0); + } + + SCLogDebug("default path leading to invalid: ACK %"PRIu32", last_ack %"PRIu32 + " next_win %"PRIu32, ack, stream->last_ack, stream->next_win); +invalid: + StreamTcpSetEvent(p, STREAM_PKT_INVALID_ACK); + SCReturnInt(-1); +} + +/** \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: 0 toserver, 1 toclient + */ +void StreamTcpSetSessionNoReassemblyFlag (TcpSession *ssn, char direction) +{ + direction ? (ssn->server.flags |= STREAMTCP_STREAM_FLAG_NOREASSEMBLY) : + (ssn->client.flags |= STREAMTCP_STREAM_FLAG_NOREASSEMBLY); +} + +/** \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: 0 toserver, 1 toclient + */ +void StreamTcpSetDisableRawReassemblyFlag (TcpSession *ssn, char direction) +{ + direction ? (ssn->server.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED) : + (ssn->client.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED); +} + +#define PSEUDO_PKT_SET_IPV4HDR(nipv4h,ipv4h) do { \ + IPV4_SET_RAW_VER(nipv4h, IPV4_GET_RAW_VER(ipv4h)); \ + IPV4_SET_RAW_HLEN(nipv4h, IPV4_GET_RAW_HLEN(ipv4h)); \ + IPV4_SET_RAW_IPLEN(nipv4h, IPV4_GET_RAW_IPLEN(ipv4h)); \ + IPV4_SET_RAW_IPTOS(nipv4h, IPV4_GET_RAW_IPTOS(ipv4h)); \ + IPV4_SET_RAW_IPPROTO(nipv4h, IPV4_GET_RAW_IPPROTO(ipv4h)); \ + (nipv4h)->s_ip_src = IPV4_GET_RAW_IPDST(ipv4h); \ + (nipv4h)->s_ip_dst = IPV4_GET_RAW_IPSRC(ipv4h); \ + } while (0) + +#define PSEUDO_PKT_SET_IPV6HDR(nipv6h,ipv6h) do { \ + (nipv6h)->s_ip6_src[0] = (ipv6h)->s_ip6_dst[0]; \ + (nipv6h)->s_ip6_src[1] = (ipv6h)->s_ip6_dst[1]; \ + (nipv6h)->s_ip6_src[2] = (ipv6h)->s_ip6_dst[2]; \ + (nipv6h)->s_ip6_src[3] = (ipv6h)->s_ip6_dst[3]; \ + (nipv6h)->s_ip6_dst[0] = (ipv6h)->s_ip6_src[0]; \ + (nipv6h)->s_ip6_dst[1] = (ipv6h)->s_ip6_src[1]; \ + (nipv6h)->s_ip6_dst[2] = (ipv6h)->s_ip6_src[2]; \ + (nipv6h)->s_ip6_dst[3] = (ipv6h)->s_ip6_src[3]; \ + IPV6_SET_RAW_NH(nipv6h, IPV6_GET_RAW_NH(ipv6h)); \ + } while (0) + +#define PSEUDO_PKT_SET_TCPHDR(ntcph,tcph) do { \ + COPY_PORT((tcph)->th_dport, (ntcph)->th_sport); \ + COPY_PORT((tcph)->th_sport, (ntcph)->th_dport); \ + (ntcph)->th_seq = (tcph)->th_ack; \ + (ntcph)->th_ack = (tcph)->th_seq; \ + } while (0) + +/** + * \brief Function to fetch a packet from the packet allocation queue for + * creation of the pseudo packet from the reassembled stream. + * + * @param parent Pointer to the parent of the pseudo packet + * @param pkt pointer to the raw packet of the parent + * @param len length of the packet + * @return upon success returns the pointer to the new pseudo packet + * otherwise NULL + */ +Packet *StreamTcpPseudoSetup(Packet *parent, uint8_t *pkt, uint32_t len) +{ + SCEnter(); + + if (len == 0) { + SCReturnPtr(NULL, "Packet"); + } + + Packet *p = PacketGetFromQueueOrAlloc(); + if (p == NULL) { + SCReturnPtr(NULL, "Packet"); + } + + /* set the root ptr to the lowest layer */ + if (parent->root != NULL) + p->root = parent->root; + else + p->root = parent; + + /* copy packet and set lenght, proto */ + p->proto = parent->proto; + p->datalink = parent->datalink; + + PacketCopyData(p, pkt, len); + p->recursion_level = parent->recursion_level + 1; + p->ts.tv_sec = parent->ts.tv_sec; + p->ts.tv_usec = parent->ts.tv_usec; + + FlowReference(&p->flow, parent->flow); + /* set tunnel flags */ + + /* tell new packet it's part of a tunnel */ + SET_TUNNEL_PKT(p); + /* tell parent packet it's part of a tunnel */ + SET_TUNNEL_PKT(parent); + + /* increment tunnel packet refcnt in the root packet */ + TUNNEL_INCR_PKT_TPR(p); + + return p; +} + +/** + * \brief Function to setup the IP and TCP header of the pseudo packet from + * the newly copied raw packet contents of the parent. + * + * @param np pointer to the pseudo packet + * @param p pointer to the original packet + */ +static void StreamTcpPseudoPacketSetupHeader(Packet *np, Packet *p) +{ + /* Setup the IP header */ + if (PKT_IS_IPV4(p)) { + np->ip4h = (IPV4Hdr *)((uint8_t *)GET_PKT_DATA(np) + (GET_PKT_LEN(np) - IPV4_GET_IPLEN(p))); + PSEUDO_PKT_SET_IPV4HDR(np->ip4h, p->ip4h); + + /* Similarly setup the TCP header with ports in opposite direction */ + np->tcph = (TCPHdr *)((uint8_t *)np->ip4h + IPV4_GET_HLEN(np)); + + PSEUDO_PKT_SET_TCPHDR(np->tcph, p->tcph); + + /* Setup the adress and port details */ + SET_IPV4_SRC_ADDR(p, &np->dst); + SET_IPV4_DST_ADDR(p, &np->src); + SET_TCP_SRC_PORT(p, &np->dp); + SET_TCP_DST_PORT(p, &np->sp); + + } else if (PKT_IS_IPV6(p)) { + np->ip6h = (IPV6Hdr *)((uint8_t *)GET_PKT_DATA(np) + (GET_PKT_LEN(np) - IPV6_GET_PLEN(p) - IPV6_HEADER_LEN)); + PSEUDO_PKT_SET_IPV6HDR(np->ip6h, p->ip6h); + + /* Similarly setup the TCP header with ports in opposite direction */ + np->tcph = (TCPHdr *)((uint8_t *)np->ip6h + IPV6_HEADER_LEN); + PSEUDO_PKT_SET_TCPHDR(np->tcph, p->tcph); + + /* Setup the adress and port details */ + SET_IPV6_SRC_ADDR(p, &np->dst); + SET_IPV6_DST_ADDR(p, &np->src); + SET_TCP_SRC_PORT(p, &np->dp); + SET_TCP_DST_PORT(p, &np->sp); + } + + /* we don't need a payload (if any) */ + np->payload = NULL; + np->payload_len = 0; +} + +/** \brief Create a pseudo packet injected into the engine to signal the + * opposing direction of this stream to wrap up stream reassembly. + * + * \param p real packet + * \param pq packet queue to store the new pseudo packet in + */ +void StreamTcpPseudoPacketCreateStreamEndPacket(ThreadVars *tv, StreamTcpThread *stt, Packet *p, TcpSession *ssn, PacketQueue *pq) +{ + SCEnter(); + + if (p->flags & PKT_PSEUDO_STREAM_END) { + SCReturn; + } + + /* no need for a pseudo packet if there is nothing left to reassemble */ + if (ssn->server.seg_list == NULL && ssn->client.seg_list == NULL) { + SCReturn; + } + + Packet *np = StreamTcpPseudoSetup(p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + if (np == NULL) { + SCLogDebug("The packet received from packet allocation is NULL"); + StatsIncr(tv, stt->counter_tcp_pseudo_failed); + SCReturn; + } + PKT_SET_SRC(np, PKT_SRC_STREAM_TCP_STREAM_END_PSEUDO); + + /* Setup the IP and TCP headers */ + StreamTcpPseudoPacketSetupHeader(np,p); + + np->tenant_id = p->flow->tenant_id; + + np->flowflags = p->flowflags; + + np->flags |= PKT_STREAM_EST; + np->flags |= PKT_STREAM_EOF; + np->flags |= PKT_HAS_FLOW; + np->flags |= PKT_PSEUDO_STREAM_END; + + if (p->flags & PKT_NOPACKET_INSPECTION) { + DecodeSetNoPacketInspectionFlag(np); + } + if (p->flags & PKT_NOPAYLOAD_INSPECTION) { + DecodeSetNoPayloadInspectionFlag(np); + } + + if (PKT_IS_TOSERVER(p)) { + SCLogDebug("original is to_server, so pseudo is to_client"); + np->flowflags &= ~FLOW_PKT_TOSERVER; + np->flowflags |= FLOW_PKT_TOCLIENT; +#ifdef DEBUG + BUG_ON(!(PKT_IS_TOCLIENT(np))); + BUG_ON((PKT_IS_TOSERVER(np))); +#endif + } else if (PKT_IS_TOCLIENT(p)) { + SCLogDebug("original is to_client, so pseudo is to_server"); + np->flowflags &= ~FLOW_PKT_TOCLIENT; + np->flowflags |= FLOW_PKT_TOSERVER; +#ifdef DEBUG + BUG_ON(!(PKT_IS_TOSERVER(np))); + BUG_ON((PKT_IS_TOCLIENT(np))); +#endif + } + + PacketEnqueue(pq, np); + + StatsIncr(tv, stt->counter_tcp_pseudo); + SCReturn; +} + +/** + * \brief Run callback function on each TCP segment + * + * This function is used by StreamMsgForEach() which + * should be used directly. + * + * \return -1 in case of error, the number of segment in case of success + * + */ +int StreamTcpSegmentForEach(const Packet *p, uint8_t flag, StreamSegmentCallback CallbackFunc, void *data) +{ + TcpSession *ssn = NULL; + TcpStream *stream = NULL; + int ret = 0; + int cnt = 0; + + if (p->flow == NULL) + return 0; + + FLOWLOCK_RDLOCK(p->flow); + ssn = (TcpSession *)p->flow->protoctx; + + if (ssn == NULL) { + FLOWLOCK_UNLOCK(p->flow); + return 0; + } + + if (flag & FLOW_PKT_TOSERVER) { + stream = &(ssn->server); + } else { + stream = &(ssn->client); + } + TcpSegment *seg = stream->seg_list; + for (; seg != NULL && SEQ_LT(seg->seq, stream->last_ack);) { + ret = CallbackFunc(p, data, seg->payload, seg->payload_len); + if (ret != 1) { + SCLogDebug("Callback function has failed"); + FLOWLOCK_UNLOCK(p->flow); + return -1; + } + seg = seg->next; + cnt++; + } + FLOWLOCK_UNLOCK(p->flow); + return cnt; +} + +#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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + TcpSession *ssn = StreamTcpNewSession(p, 0); + if (ssn == NULL) { + printf("Session can not be allocated: "); + goto end; + } + f.protoctx = ssn; + + if (f.alparser != 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); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx ra_ctx; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset(&ra_ctx, 0, sizeof(TcpReassemblyThreadCtx)); + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + int ret = 0; + stt.ra_ctx = &ra_ctx; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) == -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, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + /* prevent L7 from kicking in */ + 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, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + TcpSession ssn; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + 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)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + tcph.th_flags = TH_FIN; + p->tcph = &tcph; + + SCMutexLock(&f.m); + /* StreamTcpPacket returns -1 on unsolicited FIN */ + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) { + printf("StreamTcpPacket failed: "); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx)) != NULL) { + printf("we have a ssn while we shouldn't: "); + goto end; + } + + p->tcph->th_flags = TH_RST; + /* StreamTcpPacket returns -1 on unsolicited RST */ + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) { + printf("StreamTcpPacket failed (2): "); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx)) != NULL) { + printf("we have a ssn while we shouldn't (2): "); + goto end; + } + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[1] = {0x42}; + TCPVars tcpvars; + TCPOpt ts; + uint32_t data[2]; + PacketQueue pq; + + memset(p, 0, SIZE_OF_PACKET); + memset(&pq,0,sizeof(PacketQueue)); + 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)); + + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + stream_config.midstream = TRUE; + + /* prevent L7 from kicking in */ + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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->tcpvars.ts->data = (uint8_t *)data; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[1] = {0x42}; + TCPVars tcpvars; + TCPOpt ts; + uint32_t data[2]; + + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + 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)); + + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + stream_config.midstream = TRUE; + + /* prevent L7 from kicking in */ + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(11); + p->tcph->th_ack = htonl(20); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + data[0] = htonl(12); + p->tcpvars.ts->data = (uint8_t *)data; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[1] = {0x42}; + + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof(StreamTcpThread)); + memset(&tcph, 0, sizeof(TCPHdr)); + + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + stream_config.midstream = TRUE; + + //prevent L7 from kicking in + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) == -1) + goto end; + + if (((TcpSession *) (p->flow->protoctx))->client.seg_list->next == NULL) + ret = 1; + + StreamTcpSessionClear(p->flow->protoctx); +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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; + } + + StreamTcpSessionPktFree(p); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/* Dummy conf string to setup the OS policy for unit testing */ +static const char *dummy_conf_string = + "%YAML 1.1\n" + "---\n" + "\n" + "default-log-dir: /var/log/eidps\n" + "\n" + "logging:\n" + "\n" + " default-log-level: debug\n" + "\n" + " default-format: \"<%t> - <%l>\"\n" + "\n" + " default-startup-message: Your IDS has started.\n" + "\n" + " default-output-filter:\n" + "\n" + "host-os-policy:\n" + "\n" + " windows: 192.168.0.1\n" + "\n" + " linux: 192.168.0.2\n" + "\n"; +/* Dummy conf string to setup the OS policy for unit testing */ +static const char *dummy_conf_string1 = + "%YAML 1.1\n" + "---\n" + "\n" + "default-log-dir: /var/log/eidps\n" + "\n" + "logging:\n" + "\n" + " default-log-level: debug\n" + "\n" + " default-format: \"<%t> - <%l>\"\n" + "\n" + " default-startup-message: Your IDS has started.\n" + "\n" + " default-output-filter:\n" + "\n" + "host-os-policy:\n" + "\n" + " windows: 192.168.0.0/24," "192.168.1.1\n" + "\n" + " linux: 192.168.1.0/24," "192.168.0.1\n" + "\n"; + +/** + * \brief Function to parse the dummy conf string and get the value of IP + * address for the corresponding OS policy type. + * + * \param conf_val_name Name of the OS policy type + * \retval returns IP address as string on success and NULL on failure + */ +char *StreamTcpParseOSPolicy (char *conf_var_name) +{ + SCEnter(); + char conf_var_type_name[15] = "host-os-policy"; + char *conf_var_full_name = NULL; + char *conf_var_value = NULL; + + if (conf_var_name == NULL) + goto end; + + /* the + 2 is for the '.' and the string termination character '\0' */ + conf_var_full_name = (char *)SCMalloc(strlen(conf_var_type_name) + + strlen(conf_var_name) + 2); + if (conf_var_full_name == NULL) + goto end; + + if (snprintf(conf_var_full_name, + strlen(conf_var_type_name) + strlen(conf_var_name) + 2, "%s.%s", + conf_var_type_name, conf_var_name) < 0) { + SCLogError(SC_ERR_INVALID_VALUE, "Error in making the conf full name"); + goto end; + } + + if (ConfGet(conf_var_full_name, &conf_var_value) != 1) { + SCLogError(SC_ERR_UNKNOWN_VALUE, "Error in getting conf value for conf name %s", + conf_var_full_name); + goto end; + } + + SCLogDebug("Value obtained from the yaml conf file, for the var " + "\"%s\" is \"%s\"", conf_var_name, conf_var_value); + + end: + if (conf_var_full_name != NULL) + SCFree(conf_var_full_name); + SCReturnCharPtr(conf_var_value); + + +} +/** + * \test Test the setting up a OS policy. Te OS policy values are defined in + * the config string "dummy_conf_string" + * + * \retval On success it returns 1 and on failure 0 + */ + +static int StreamTcpTest14 (void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + struct in_addr addr; + IPV4Hdr ipv4h; + char os_policy_name[10] = "windows"; + char *ip_addr; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&addr, 0, sizeof(addr)); + memset(&ipv4h, 0, sizeof(ipv4h)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string, strlen(dummy_conf_string)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + strlcpy(os_policy_name, "linux\0", sizeof(os_policy_name)); + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + addr.s_addr = inet_addr("192.168.0.1"); + 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->dst.family = AF_INET; + p->dst.address.address_un_data32[0] = addr.s_addr; + p->ip4h = &ipv4h; + + StreamTcpCreateTestPacket(payload, 0x41, 3, sizeof(payload)); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, sizeof(payload)); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(15); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(14); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + addr.s_addr = inet_addr("192.168.0.2"); + p->tcph->th_seq = htonl(25); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + p->dst.address.address_un_data32[0] = addr.s_addr; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(24); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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 != 13 && + ((TcpSession *)(p->flow->protoctx))->server.next_seq != 23) { + printf("failed in next_seq match client.next_seq %"PRIu32"" + " server.next_seq %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->client.next_seq, + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->client.os_policy != + OS_POLICY_WINDOWS && ((TcpSession *) + (p->flow->protoctx))->server.os_policy != OS_POLICY_LINUX) + { + printf("failed in setting up OS policy, client.os_policy: %"PRIu8"" + " should be %"PRIu8" and server.os_policy: %"PRIu8"" + " should be %"PRIu8"\n", ((TcpSession *) + (p->flow->protoctx))->client.os_policy, OS_POLICY_WINDOWS, + ((TcpSession *)(p->flow->protoctx))->server.os_policy, + OS_POLICY_LINUX); + goto end; + } + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) == -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, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) != -1) { + printf("SYN/ACK pkt not rejected but it should have: "); + goto end; + } + + ret = 1; +end: + StreamTcpSessionClear(p->flow->protoctx); + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + 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 = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + memset(p, 0, SIZE_OF_PACKET); + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + 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; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -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, &pq) == -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, &pq) == -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); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the setting up a OS policy. Te OS policy values are defined in + * the config string "dummy_conf_string1" + * + * \retval On success it returns 1 and on failure 0 + */ + +static int StreamTcpTest15 (void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + struct in_addr addr; + IPV4Hdr ipv4h; + char os_policy_name[10] = "windows"; + char *ip_addr; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&addr, 0, sizeof(addr)); + memset(&ipv4h, 0, sizeof(ipv4h)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + strlcpy(os_policy_name, "linux\0", sizeof(os_policy_name)); + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + addr.s_addr = inet_addr("192.168.0.20"); + 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->dst.family = AF_INET; + p->dst.address.address_un_data32[0] = addr.s_addr; + p->ip4h = &ipv4h; + + StreamTcpCreateTestPacket(payload, 0x41, 3, sizeof(payload)); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, sizeof(payload)); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(15); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(14); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + addr.s_addr = inet_addr("192.168.1.20"); + p->tcph->th_seq = htonl(25); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + p->dst.address.address_un_data32[0] = addr.s_addr; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(24); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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 != 13 && + ((TcpSession *)(p->flow->protoctx))->server.next_seq != 23) { + printf("failed in next_seq match client.next_seq %"PRIu32"" + " server.next_seq %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->client.next_seq, + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->client.os_policy != + OS_POLICY_WINDOWS && ((TcpSession *) + (p->flow->protoctx))->server.os_policy != OS_POLICY_LINUX) + { + printf("failed in setting up OS policy, client.os_policy: %"PRIu8"" + " should be %"PRIu8" and server.os_policy: %"PRIu8"" + " should be %"PRIu8"\n", ((TcpSession *) + (p->flow->protoctx))->client.os_policy, OS_POLICY_WINDOWS, + ((TcpSession *)(p->flow->protoctx))->server.os_policy, + OS_POLICY_LINUX); + goto end; + } + StreamTcpSessionPktFree(p); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the setting up a OS policy. Te OS policy values are defined in + * the config string "dummy_conf_string1" + * + * \retval On success it returns 1 and on failure 0 + */ + +static int StreamTcpTest16 (void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + struct in_addr addr; + IPV4Hdr ipv4h; + char os_policy_name[10] = "windows"; + char *ip_addr; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&addr, 0, sizeof(addr)); + memset(&ipv4h, 0, sizeof(ipv4h)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + strlcpy(os_policy_name, "linux\0", sizeof(os_policy_name)); + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + addr.s_addr = inet_addr("192.168.0.1"); + 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->dst.family = AF_INET; + p->dst.address.address_un_data32[0] = addr.s_addr; + p->ip4h = &ipv4h; + + StreamTcpCreateTestPacket(payload, 0x41, 3, sizeof(payload)); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, sizeof(payload)); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(15); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(14); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + addr.s_addr = inet_addr("192.168.1.1"); + p->tcph->th_seq = htonl(25); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + p->dst.address.address_un_data32[0] = addr.s_addr; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(24); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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 != 13 && + ((TcpSession *)(p->flow->protoctx))->server.next_seq != 23) { + printf("failed in next_seq match client.next_seq %"PRIu32"" + " server.next_seq %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->client.next_seq, + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->client.os_policy != + OS_POLICY_LINUX && ((TcpSession *) + (p->flow->protoctx))->server.os_policy != OS_POLICY_WINDOWS) + { + printf("failed in setting up OS policy, client.os_policy: %"PRIu8"" + " should be %"PRIu8" and server.os_policy: %"PRIu8"" + " should be %"PRIu8"\n", ((TcpSession *) + (p->flow->protoctx))->client.os_policy, OS_POLICY_LINUX, + ((TcpSession *)(p->flow->protoctx))->server.os_policy, + OS_POLICY_WINDOWS); + goto end; + } + StreamTcpSessionPktFree(p); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the setting up a OS policy. Te OS policy values are defined in + * the config string "dummy_conf_string1". To check the setting of + * Default os policy + * + * \retval On success it returns 1 and on failure 0 + */ + +static int StreamTcpTest17 (void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + uint8_t payload[4]; + struct in_addr addr; + IPV4Hdr ipv4h; + char os_policy_name[10] = "windows"; + char *ip_addr; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&addr, 0, sizeof(addr)); + memset(&ipv4h, 0, sizeof(ipv4h)); + FLOW_INITIALIZE(&f); + p->flow = &f; + int ret = 0; + + StreamTcpInitConfig(TRUE); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + strlcpy(os_policy_name, "linux\0", sizeof(os_policy_name)); + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + addr.s_addr = inet_addr("192.168.0.1"); + 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->dst.family = AF_INET; + p->dst.address.address_un_data32[0] = addr.s_addr; + p->ip4h = &ipv4h; + + StreamTcpCreateTestPacket(payload, 0x41, 3, sizeof(payload)); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, sizeof(payload)); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(15); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(14); + p->tcph->th_ack = htonl(23); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x43, 3, sizeof(payload)); /*CCC*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + addr.s_addr = inet_addr("10.1.1.1"); + p->tcph->th_seq = htonl(25); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + p->dst.address.address_un_data32[0] = addr.s_addr; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + p->tcph->th_seq = htonl(24); + p->tcph->th_ack = htonl(13); + p->tcph->th_flags = TH_ACK|TH_PUSH; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x44, 3, sizeof(payload)); /*DDD*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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 != 13 && + ((TcpSession *)(p->flow->protoctx))->server.next_seq != 23) { + printf("failed in next_seq match client.next_seq %"PRIu32"" + " server.next_seq %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->client.next_seq, + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->client.os_policy != + OS_POLICY_LINUX && ((TcpSession *) + (p->flow->protoctx))->server.os_policy != OS_POLICY_DEFAULT) + { + printf("failed in setting up OS policy, client.os_policy: %"PRIu8"" + " should be %"PRIu8" and server.os_policy: %"PRIu8"" + " should be %"PRIu8"\n", ((TcpSession *) + (p->flow->protoctx))->client.os_policy, OS_POLICY_LINUX, + ((TcpSession *)(p->flow->protoctx))->server.os_policy, + OS_POLICY_DEFAULT); + goto end; + } + StreamTcpSessionPktFree(p); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** \test Test the various OS policies based on different IP addresses from + confuguration defined in 'dummy_conf_string1' */ +static int StreamTcpTest18 (void) +{ + + struct in_addr addr; + char os_policy_name[10] = "windows"; + char *ip_addr; + TcpStream stream; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + IPV4Hdr ipv4h; + int ret = 0; + + memset(&addr, 0, sizeof(addr)); + memset(&stream, 0, sizeof(stream)); + memset(p, 0, SIZE_OF_PACKET); + memset(&ipv4h, 0, sizeof(ipv4h)); + + StreamTcpInitConfig(TRUE); + SCHInfoCleanResources(); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + p->dst.family = AF_INET; + p->ip4h = &ipv4h; + addr.s_addr = inet_addr("192.168.1.1"); + p->dst.address.address_un_data32[0] = addr.s_addr; + StreamTcpSetOSPolicy(&stream, p); + + if (stream.os_policy != OS_POLICY_WINDOWS) + goto end; + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCFree(p); + return ret; +} +/** \test Test the various OS policies based on different IP addresses from + confuguration defined in 'dummy_conf_string1' */ +static int StreamTcpTest19 (void) +{ + + struct in_addr addr; + char os_policy_name[10] = "windows"; + char *ip_addr; + TcpStream stream; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + IPV4Hdr ipv4h; + int ret = 0; + + memset(&addr, 0, sizeof(addr)); + memset(&stream, 0, sizeof(stream)); + memset(p, 0, SIZE_OF_PACKET); + memset(&ipv4h, 0, sizeof(ipv4h)); + + StreamTcpInitConfig(TRUE); + SCHInfoCleanResources(); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + p->dst.family = AF_INET; + p->ip4h = &ipv4h; + addr.s_addr = inet_addr("192.168.0.30"); + p->dst.address.address_un_data32[0] = addr.s_addr; + StreamTcpSetOSPolicy(&stream, p); + + if (stream.os_policy != OS_POLICY_WINDOWS) { + printf("expected os_policy: %"PRIu8" but received %"PRIu8": ", + OS_POLICY_WINDOWS, stream.os_policy); + goto end; + } + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCFree(p); + return ret; +} +/** \test Test the various OS policies based on different IP addresses from + confuguration defined in 'dummy_conf_string1' */ +static int StreamTcpTest20 (void) +{ + + struct in_addr addr; + char os_policy_name[10] = "linux"; + char *ip_addr; + TcpStream stream; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + IPV4Hdr ipv4h; + int ret = 0; + + memset(&addr, 0, sizeof(addr)); + memset(&stream, 0, sizeof(stream)); + memset(p, 0, SIZE_OF_PACKET); + memset(&ipv4h, 0, sizeof(ipv4h)); + + StreamTcpInitConfig(TRUE); + SCHInfoCleanResources(); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + p->dst.family = AF_INET; + p->ip4h = &ipv4h; + addr.s_addr = inet_addr("192.168.0.1"); + p->dst.address.address_un_data32[0] = addr.s_addr; + StreamTcpSetOSPolicy(&stream, p); + + if (stream.os_policy != OS_POLICY_LINUX) { + printf("expected os_policy: %"PRIu8" but received %"PRIu8"\n", + OS_POLICY_LINUX, stream.os_policy); + goto end; + } + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCFree(p); + return ret; +} +/** \test Test the various OS policies based on different IP addresses from + confuguration defined in 'dummy_conf_string1' */ +static int StreamTcpTest21 (void) +{ + + struct in_addr addr; + char os_policy_name[10] = "linux"; + char *ip_addr; + TcpStream stream; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + IPV4Hdr ipv4h; + int ret = 0; + + memset(&addr, 0, sizeof(addr)); + memset(&stream, 0, sizeof(stream)); + memset(p, 0, SIZE_OF_PACKET); + memset(&ipv4h, 0, sizeof(ipv4h)); + + StreamTcpInitConfig(TRUE); + SCHInfoCleanResources(); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + p->dst.family = AF_INET; + p->ip4h = &ipv4h; + addr.s_addr = inet_addr("192.168.1.30"); + p->dst.address.address_un_data32[0] = addr.s_addr; + StreamTcpSetOSPolicy(&stream, p); + + if (stream.os_policy != OS_POLICY_LINUX) { + printf("expected os_policy: %"PRIu8" but received %"PRIu8"\n", + OS_POLICY_LINUX, stream.os_policy); + goto end; + } + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCFree(p); + return ret; +} +/** \test Test the various OS policies based on different IP addresses from + confuguration defined in 'dummy_conf_string1' */ +static int StreamTcpTest22 (void) +{ + + struct in_addr addr; + char os_policy_name[10] = "windows"; + char *ip_addr; + TcpStream stream; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + IPV4Hdr ipv4h; + int ret = 0; + + memset(&addr, 0, sizeof(addr)); + memset(&stream, 0, sizeof(stream)); + memset(p, 0, SIZE_OF_PACKET); + memset(&ipv4h, 0, sizeof(ipv4h)); + + StreamTcpInitConfig(TRUE); + SCHInfoCleanResources(); + + /* Load the config string in to parser */ + ConfCreateContextBackup(); + ConfInit(); + ConfYamlLoadString(dummy_conf_string1, strlen(dummy_conf_string1)); + + /* Get the IP address as string and add it to Host info tree for lookups */ + ip_addr = StreamTcpParseOSPolicy(os_policy_name); + SCHInfoAddHostOSInfo(os_policy_name, ip_addr, -1); + + p->dst.family = AF_INET; + p->ip4h = &ipv4h; + addr.s_addr = inet_addr("123.231.2.1"); + p->dst.address.address_un_data32[0] = addr.s_addr; + StreamTcpSetOSPolicy(&stream, p); + + if (stream.os_policy != OS_POLICY_DEFAULT) { + printf("expected os_policy: %"PRIu8" but received %"PRIu8"\n", + OS_POLICY_DEFAULT, stream.os_policy); + goto end; + } + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + ConfDeInit(); + ConfRestoreContextBackup(); + SCFree(p); + return ret; +} + +/** \test Test the stream mem leaks conditions. */ +static int StreamTcpTest23(void) +{ + TcpSession ssn; + Flow f; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + uint8_t packet[1460] = ""; + ThreadVars tv; + int result = 1; + PacketQueue pq; + + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + + memset(&pq,0,sizeof(PacketQueue)); + memset(&ssn, 0, sizeof (TcpSession)); + memset(p, 0, SIZE_OF_PACKET); + memset(&f, 0, sizeof (Flow)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&tv, 0, sizeof (ThreadVars)); + + StreamTcpInitConfig(TRUE); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + FLOW_INITIALIZE(&f); + ssn.client.os_policy = OS_POLICY_BSD; + f.protoctx = &ssn; + p->src.family = AF_INET; + p->dst.family = AF_INET; + p->proto = IPPROTO_TCP; + p->flow = &f; + tcph.th_win = 5480; + tcph.th_flags = TH_PUSH | TH_ACK; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + p->payload = packet; + ssn.client.ra_app_base_seq = ssn.client.ra_raw_base_seq = ssn.client.last_ack = 3184324453UL; + + p->tcph->th_seq = htonl(3184324453UL); + p->tcph->th_ack = htonl(3373419609UL); + p->payload_len = 2; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling: "); + result &= 0; + goto end; + } + + p->tcph->th_seq = htonl(3184324455UL); + p->tcph->th_ack = htonl(3373419621UL); + p->payload_len = 2; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling: "); + result &= 0; + goto end; + } + + p->tcph->th_seq = htonl(3184324453UL); + p->tcph->th_ack = htonl(3373419621UL); + p->payload_len = 6; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling: "); + result &= 0; +// goto end; + } + + if(ssn.client.seg_list_tail != NULL && ssn.client.seg_list_tail->payload_len != 4) { + printf("failed in segment reassmebling: "); + result &= 0; + } + +end: + StreamTcpReturnStreamSegments(&ssn.client); + StreamTcpFreeConfig(TRUE); + if (SC_ATOMIC_GET(st_memuse) == 0) { + result &= 1; + } else { + printf("smemuse.stream_memuse %"PRIu64"\n", SC_ATOMIC_GET(st_memuse)); + } + SCFree(p); + FLOW_DESTROY(&f); + return result; +} + +/** \test Test the stream mem leaks conditions. */ +static int StreamTcpTest24(void) +{ + TcpSession ssn; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + uint8_t packet[1460] = ""; + ThreadVars tv; + int result = 1; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + StreamTcpInitConfig(TRUE); + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + memset(&ssn, 0, sizeof (TcpSession)); + memset(p, 0, SIZE_OF_PACKET); + memset(&f, 0, sizeof (Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&tcph, 0, sizeof (TCPHdr)); + FLOW_INITIALIZE(&f); + ssn.client.os_policy = OS_POLICY_BSD; + f.protoctx = &ssn; + p->src.family = AF_INET; + p->dst.family = AF_INET; + p->proto = IPPROTO_TCP; + p->flow = &f; + tcph.th_win = 5480; + tcph.th_flags = TH_PUSH | TH_ACK; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + p->payload = packet; + ssn.client.ra_app_base_seq = ssn.client.ra_raw_base_seq = ssn.client.last_ack = 3184324453UL; + + p->tcph->th_seq = htonl(3184324455UL); + p->tcph->th_ack = htonl(3373419621UL); + p->payload_len = 4; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + p->tcph->th_seq = htonl(3184324459UL); + p->tcph->th_ack = htonl(3373419633UL); + p->payload_len = 2; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + p->tcph->th_seq = htonl(3184324459UL); + p->tcph->th_ack = htonl(3373419657UL); + p->payload_len = 4; + + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + if(ssn.client.seg_list_tail != NULL && ssn.client.seg_list_tail->payload_len != 2) { + printf("failed in segment reassmebling\n"); + result &= 0; + } + +end: + StreamTcpReturnStreamSegments(&ssn.client); + StreamTcpFreeConfig(TRUE); + if (SC_ATOMIC_GET(st_memuse) == 0) { + result &= 1; + } else { + printf("smemuse.stream_memuse %"PRIu64"\n", SC_ATOMIC_GET(st_memuse)); + } + SCFree(p); + FLOW_DESTROY(&f); + return result; +} + +/** + * \test Test the initialization of tcp streams with congestion flags + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest25(void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + stt.ra_ctx = ra_ctx; + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN | TH_CWR; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + + /* prevent L7 from kicking in */ + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 4096); + StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 4096); + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the initialization of tcp streams with congestion flags + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest26(void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + stt.ra_ctx = ra_ctx; + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN | TH_ECN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the initialization of tcp streams with congestion flags + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest27(void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + stt.ra_ctx = ra_ctx; + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN | TH_CWR | TH_ECN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + 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, 4); /*BBB*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + p->flowflags = FLOW_PKT_TOCLIENT; + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) + goto end; + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** \test Test the memcap incrementing/decrementing and memcap check */ +static int StreamTcpTest28(void) +{ + uint8_t ret = 0; + StreamTcpInitConfig(TRUE); + uint32_t memuse = SC_ATOMIC_GET(st_memuse); + + StreamTcpIncrMemuse(500); + if (SC_ATOMIC_GET(st_memuse) != (memuse+500)) { + printf("failed in incrementing the memory"); + goto end; + } + + StreamTcpDecrMemuse(500); + if (SC_ATOMIC_GET(st_memuse) != memuse) { + printf("failed in decrementing the memory"); + goto end; + } + + if (StreamTcpCheckMemcap(500) != 1) { + printf("failed in validating the memcap"); + goto end; + } + + if (StreamTcpCheckMemcap((memuse + stream_config.memcap)) != 0) { + printf("failed in validating the overflowed memcap"); + goto end; + } + + StreamTcpFreeConfig(TRUE); + + if (SC_ATOMIC_GET(st_memuse) != 0) { + printf("failed in clearing the memory"); + goto end; + } + + ret = 1; + return ret; +end: + StreamTcpFreeConfig(TRUE); + return ret; +} + +#if 0 +/** + * \test Test the resetting of the sesison with bad checksum packet and later + * send the malicious contents on the session. Engine should drop the + * packet with the bad checksum. + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest29(void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpSession ssn; + IPV4Hdr ipv4h; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + struct in_addr addr; + struct in_addr addr1; + TCPCache tcpc; + TCPVars tcpvars; + TcpStream server; + TcpStream client; + + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset (&ipv4h, 0, sizeof(IPV4Hdr)); + memset (&addr, 0, sizeof(addr)); + memset (&addr1, 0, sizeof(addr1)); + memset (&tcpc, 0, sizeof(tcpc)); + memset (&tcpvars, 0, sizeof(tcpvars)); + memset(&ssn, 0, sizeof (TcpSession)); + memset(&server, 0, sizeof (TcpStream)); + memset(&client, 0, sizeof (TcpStream)); + uint8_t packet[1460] = ""; + int result = 1; + + FLOW_INITIALIZE(&f); + StreamTcpInitConfig(TRUE); + + /* prevent L7 from kicking in */ + + ssn.client.os_policy = OS_POLICY_BSD; + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.flow = &f; + tcph.th_win = 5480; + p.tcph = &tcph; + p.payload = packet; + p.ip4h = &ipv4h; + p.tcpc = tcpc; + p.tcpc.level4_comp_csum = -1; + tcpvars.hlen = 20; + p.tcpvars = tcpvars; + ssn.state = TCP_ESTABLISHED; + addr.s_addr = inet_addr("10.1.3.53"); + p.dst.address.address_un_data32[0] = addr.s_addr; + addr1.s_addr = inet_addr("10.1.3.7"); + p.src.address.address_un_data32[0] = addr1.s_addr; + f.protoctx = &ssn; + stt.ra_ctx = ra_ctx; + ssn.server = server; + ssn.client = client; + ssn.client.isn = 10; + ssn.client.window = 5184; + ssn.client.last_ack = 10; + ssn.client.ra_base_seq = 10; + ssn.client.next_win = 5184; + ssn.server.isn = 119197101; + ssn.server.window = 5184; + ssn.server.next_win = 5184; + ssn.server.last_ack = 119197101; + ssn.server.ra_base_seq = 119197101; + + tcph.th_flags = TH_PUSH | TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(11); + p.tcph->th_ack = htonl(119197102); + p.payload_len = 4; + p.ip4h->ip_src = addr1; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_ACK; + p.flowflags = FLOW_PKT_TOCLIENT; + p.tcph->th_seq = htonl(119197102); + p.tcph->th_ack = htonl(15); + p.payload_len = 0; + p.ip4h->ip_src = addr; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_RST | TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(15); + p.tcph->th_ack = htonl(119197102); + p.payload_len = 0; + p.ip4h->ip_src = addr1; + p.tcph->th_sum = 12345; + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + if (ssn.state != TCP_ESTABLISHED) { + printf("the ssn.state should be TCP_ESTABLISHED(%"PRIu8"), not %"PRIu8"" + "\n", TCP_ESTABLISHED, ssn.state); + result &= 0; + goto end; + } + +end: + StreamTcpReturnStreamSegments(&ssn.client); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** + * \test Test the overlapping of the packet with bad checksum packet and later + * send the malicious contents on the session. Engine should drop the + * packet with the bad checksum. + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest30(void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpSession ssn; + IPV4Hdr ipv4h; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + struct in_addr addr; + struct in_addr addr1; + TCPCache tcpc; + TCPVars tcpvars; + TcpStream server; + TcpStream client; + + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset (&ipv4h, 0, sizeof(IPV4Hdr)); + memset (&addr, 0, sizeof(addr)); + memset (&addr1, 0, sizeof(addr1)); + memset (&tcpc, 0, sizeof(tcpc)); + memset (&tcpvars, 0, sizeof(tcpvars)); + memset(&ssn, 0, sizeof (TcpSession)); + memset(&server, 0, sizeof (TcpStream)); + memset(&client, 0, sizeof (TcpStream)); + uint8_t payload[9] = "AAAAAAAAA"; + uint8_t payload1[9] = "GET /EVIL"; + uint8_t expected_content[9] = { 0x47, 0x45, 0x54, 0x20, 0x2f, 0x45, 0x56, + 0x49, 0x4c }; + int result = 1; + + FLOW_INITIALIZE(&f); + StreamTcpInitConfig(TRUE); + + /* prevent L7 from kicking in */ + + ssn.client.os_policy = OS_POLICY_BSD; + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.flow = &f; + tcph.th_win = 5480; + p.tcph = &tcph; + p.payload = payload; + p.ip4h = &ipv4h; + p.tcpc = tcpc; + p.tcpc.level4_comp_csum = -1; + p.tcpvars = tcpvars; + ssn.state = TCP_ESTABLISHED; + addr.s_addr = inet_addr("10.1.3.53"); + p.dst.address.address_un_data32[0] = addr.s_addr; + addr1.s_addr = inet_addr("10.1.3.7"); + p.src.address.address_un_data32[0] = addr1.s_addr; + f.protoctx = &ssn; + stt.ra_ctx = ra_ctx; + ssn.server = server; + ssn.client = client; + ssn.client.isn = 10; + ssn.client.window = 5184; + ssn.client.last_ack = 10; + ssn.client.ra_base_seq = 10; + ssn.client.next_win = 5184; + ssn.server.isn = 1351079940; + ssn.server.window = 5184; + ssn.server.next_win = 1351088132; + ssn.server.last_ack = 1351079940; + ssn.server.ra_base_seq = 1351079940; + + tcph.th_flags = TH_PUSH | TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(11); + p.tcph->th_ack = htonl(1351079940); + p.payload_len = 9; + p.ip4h->ip_src = addr1; + p.tcph->th_sum = 12345; + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_PUSH | TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(11); + p.tcph->th_ack = htonl(1351079940); + p.payload = payload1; + p.payload_len = 9; + p.ip4h->ip_src = addr1; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_ACK; + p.flowflags = FLOW_PKT_TOCLIENT; + p.tcph->th_seq = htonl(1351079940); + p.tcph->th_ack = htonl(20); + p.payload_len = 0; + p.ip4h->ip_src = addr; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + if (StreamTcpCheckStreamContents(expected_content, 9, &ssn.client) != 1) { + printf("the contents are not as expected(GET /EVIL), contents are: "); + PrintRawDataFp(stdout, ssn.client.seg_list->payload, 9); + result &= 0; + goto end; + } + +end: + StreamTcpReturnStreamSegments(&ssn.client); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** + * \test Test the multiple SYN packet handling with bad checksum and timestamp + * value. Engine should drop the bad checksum packet and establish + * TCP session correctly. + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest31(void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpSession ssn; + IPV4Hdr ipv4h; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + struct in_addr addr; + struct in_addr addr1; + TCPCache tcpc; + TCPVars tcpvars; + TcpStream server; + TcpStream client; + TCPOpt tcpopt; + + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset (&ipv4h, 0, sizeof(IPV4Hdr)); + memset (&addr, 0, sizeof(addr)); + memset (&addr1, 0, sizeof(addr1)); + memset (&tcpc, 0, sizeof(tcpc)); + memset (&tcpvars, 0, sizeof(tcpvars)); + memset(&ssn, 0, sizeof (TcpSession)); + memset(&server, 0, sizeof (TcpStream)); + memset(&client, 0, sizeof (TcpStream)); + memset(&tcpopt, 0, sizeof (TCPOpt)); + int result = 1; + + StreamTcpInitConfig(TRUE); + + FLOW_INITIALIZE(&f); + /* prevent L7 from kicking in */ + + ssn.client.os_policy = OS_POLICY_LINUX; + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.flow = &f; + tcph.th_win = 5480; + p.tcph = &tcph; + p.ip4h = &ipv4h; + p.tcpc = tcpc; + p.tcpc.level4_comp_csum = -1; + p.tcpvars = tcpvars; + p.tcpvars.ts = &tcpopt; + addr.s_addr = inet_addr("10.1.3.53"); + p.dst.address.address_un_data32[0] = addr.s_addr; + addr1.s_addr = inet_addr("10.1.3.7"); + p.src.address.address_un_data32[0] = addr1.s_addr; + f.protoctx = &ssn; + stt.ra_ctx = ra_ctx; + ssn.server = server; + ssn.client = client; + ssn.client.isn = 10; + ssn.client.window = 5184; + ssn.client.last_ack = 10; + ssn.client.ra_base_seq = 10; + ssn.client.next_win = 5184; + ssn.server.isn = 1351079940; + ssn.server.window = 5184; + ssn.server.next_win = 1351088132; + ssn.server.last_ack = 1351079940; + ssn.server.ra_base_seq = 1351079940; + + tcph.th_flags = TH_SYN; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(10); + p.payload_len = 0; + p.ip4h->ip_src = addr1; + p.tcpc.ts1 = 100; + p.tcph->th_sum = 12345; + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_SYN; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(10); + p.payload_len = 0; + p.ip4h->ip_src = addr1; + p.tcpc.ts1 = 10; + p.tcpc.level4_comp_csum = -1; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + ssn.flags |= STREAMTCP_FLAG_TIMESTAMP; + tcph.th_flags = TH_SYN | TH_ACK; + p.flowflags = FLOW_PKT_TOCLIENT; + p.tcph->th_seq = htonl(1351079940); + p.tcph->th_ack = htonl(11); + p.payload_len = 0; + p.tcpc.ts1 = 10; + p.ip4h->ip_src = addr; + p.tcpc.level4_comp_csum = -1; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + tcph.th_flags = TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + p.tcph->th_seq = htonl(11); + p.tcph->th_ack = htonl(1351079941); + p.payload_len = 0; + p.tcpc.ts1 = 10; + p.ip4h->ip_src = addr1; + p.tcpc.level4_comp_csum = -1; + p.tcph->th_sum = TCPCalculateChecksum((uint16_t *)&(p.ip4h->ip_src), + (uint16_t *)p.tcph, + (p.payload_len + + p.tcpvars.hlen) ); + + if (StreamTcp(&tv, &p, (void *)&stt, NULL, NULL) != TM_ECODE_OK) { + printf("failed in segment reassmebling\n"); + result &= 0; + goto end; + } + + if (ssn.state != TCP_ESTABLISHED) { + printf("the should have been changed to TCP_ESTABLISHED!!\n "); + result &= 0; + goto end; + } + +end: + StreamTcpReturnStreamSegments(&ssn.client); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** + * \test Test the initialization of tcp streams with ECN & CWR flags + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest32(void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + stt.ra_ctx = ra_ctx; + p.flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN | TH_CWR | TH_ECN; + p.tcph = &tcph; + p.flowflags = FLOW_PKT_TOSERVER; + + StreamTcpInitConfig(TRUE); + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) + goto end; + + p.tcph->th_ack = htonl(1); + p.tcph->th_flags = TH_SYN | TH_ACK | TH_ECN; + p.flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + p.tcph->th_ack = htonl(1); + p.tcph->th_seq = htonl(1); + p.tcph->th_flags = TH_ACK | TH_ECN | TH_CWR; + p.flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + p.tcph->th_ack = htonl(1); + p.tcph->th_seq = htonl(2); + p.tcph->th_flags = TH_PUSH | TH_ACK | TH_ECN | TH_CWR; + p.flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p.payload = payload; + p.payload_len = 3; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + p.flowflags = FLOW_PKT_TOCLIENT; + p.tcph->th_flags = TH_ACK; + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p.flow->protoctx)->state != TCP_ESTABLISHED) { + printf("the TCP state should be TCP_ESTABLISEHD\n"); + goto end; + } + StreamTcpSessionClear(p.flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + return ret; +} + +/** + * \test Test the allocation of TCP session for a given packet when the same + * ports have been used to start the new session after resetting the + * previous session. + * + * \retval On success it returns 1 and on failure 0. + */ + +static int StreamTcpTest33 (void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpReassemblyThreadCtx ra_ctx; + StreamMsgQueue stream_q; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset(&stream_q, 0, sizeof(StreamMsgQueue)); + memset(&ra_ctx, 0, sizeof(TcpReassemblyThreadCtx)); + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + p.flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p.tcph = &tcph; + p.flowflags = FLOW_PKT_TOSERVER; + int ret = 0; + ra_ctx.stream_q = &stream_q; + stt.ra_ctx = &ra_ctx; + + StreamTcpInitConfig(TRUE); + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -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, &pq) == -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, &pq) == -1) + goto end; + + p.tcph->th_ack = htonl(1); + p.tcph->th_seq = htonl(1); + p.tcph->th_flags = TH_RST | TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) + goto end; + + if (((TcpSession *)(p.flow->protoctx))->state != TCP_CLOSED) { + printf("Tcp session should have been closed\n"); + goto end; + } + + p.tcph->th_seq = htonl(1); + p.tcph->th_flags = TH_SYN; + p.flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) + goto end; + + p.tcph->th_seq = htonl(1); + p.tcph->th_ack = htonl(2); + p.tcph->th_flags = TH_SYN | TH_ACK; + p.flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) + goto end; + + p.tcph->th_ack = htonl(2); + p.tcph->th_seq = htonl(2); + p.tcph->th_flags = TH_ACK; + p.flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) + goto end; + + if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) { + printf("Tcp session should have been ESTABLISHED\n"); + goto end; + } + + ret = 1; +end: + StreamTcpSessionClear(p.flow->protoctx); + StreamTcpFreeConfig(TRUE); + return ret; +} + +/** + * \test Test the allocation of TCP session for a given packet when the SYN + * packet is sent with the PUSH flag set. + * + * \retval On success it returns 1 and on failure 0. + */ + +static int StreamTcpTest34 (void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpReassemblyThreadCtx ra_ctx; + StreamMsgQueue stream_q; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset(&stream_q, 0, sizeof(StreamMsgQueue)); + memset(&ra_ctx, 0, sizeof(TcpReassemblyThreadCtx)); + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + p.flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN|TH_PUSH; + p.tcph = &tcph; + p.flowflags = FLOW_PKT_TOSERVER; + int ret = 0; + ra_ctx.stream_q = &stream_q; + stt.ra_ctx = &ra_ctx; + + StreamTcpInitConfig(TRUE); + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -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, &pq) == -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, &pq) == -1) + goto end; + + if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) { + printf("Tcp session should have been establisehd\n"); + goto end; + } + + ret = 1; +end: + StreamTcpSessionClear(p.flow->protoctx); + StreamTcpFreeConfig(TRUE); + return ret; +} + +/** + * \test Test the allocation of TCP session for a given packet when the SYN + * packet is sent with the URG flag set. + * + * \retval On success it returns 1 and on failure 0. + */ + +static int StreamTcpTest35 (void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + TcpReassemblyThreadCtx ra_ctx; + StreamMsgQueue stream_q; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + memset(&stream_q, 0, sizeof(StreamMsgQueue)); + memset(&ra_ctx, 0, sizeof(TcpReassemblyThreadCtx)); + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + p.flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN|TH_URG; + p.tcph = &tcph; + p.flowflags = FLOW_PKT_TOSERVER; + int ret = 0; + ra_ctx.stream_q = &stream_q; + stt.ra_ctx = &ra_ctx; + + StreamTcpInitConfig(TRUE); + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -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, &pq) == -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, &pq) == -1) + goto end; + + if (((TcpSession *)(p.flow->protoctx))->state != TCP_ESTABLISHED) { + printf("Tcp session should have been establisehd\n"); + goto end; + } + + ret = 1; +end: + StreamTcpSessionClear(p.flow->protoctx); + StreamTcpFreeConfig(TRUE); + return ret; +} + +/** + * \test Test the processing of PSH and URG flag in tcp session. + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest36(void) +{ + Packet p; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset (&p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + stt.ra_ctx = ra_ctx; + p.flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p.tcph = &tcph; + p.flowflags = FLOW_PKT_TOSERVER; + + StreamTcpInitConfig(TRUE); + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1) { + printf("failed in processing packet\n"); + 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, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + 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, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p.flow->protoctx)->state != TCP_ESTABLISHED) { + printf("the TCP state should be TCP_ESTABLISEHD\n"); + goto end; + } + + p.tcph->th_ack = htonl(2); + p.tcph->th_seq = htonl(1); + p.tcph->th_flags = TH_PUSH | TH_ACK | TH_URG; + p.flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p.payload = payload; + p.payload_len = 3; + + if (StreamTcpPacket(&tv, &p, &stt, &pq) == -1 || (TcpSession *)p.flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p.flow->protoctx)->client.next_seq != 4) { + printf("the ssn->client.next_seq should be 4, but it is %"PRIu32"\n", + ((TcpSession *)p.flow->protoctx)->client.next_seq); + goto end; + } + + StreamTcpSessionClear(p.flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + return ret; +} +#endif + +/** + * \test Test the processing of out of order FIN packets in tcp session. + * + * \retval On success it returns 1 and on failure 0. + */ +static int StreamTcpTest37(void) +{ + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + int ret = 0; + PacketQueue pq; + memset(&pq,0,sizeof(PacketQueue)); + + memset(p, 0, SIZE_OF_PACKET); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + FLOW_INITIALIZE(&f); + + stt.ra_ctx = ra_ctx; + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet\n"); + 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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) { + printf("failed in processing packet\n"); + 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, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p->flow->protoctx)->state != TCP_ESTABLISHED) { + printf("the TCP state should be TCP_ESTABLISEHD\n"); + goto end; + } + + p->tcph->th_ack = htonl(2); + p->tcph->th_seq = htonl(4); + p->tcph->th_flags = TH_ACK|TH_FIN; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p->flow->protoctx)->state != TCP_CLOSE_WAIT) { + printf("the TCP state should be TCP_CLOSE_WAIT\n"); + goto end; + } + + p->tcph->th_ack = htonl(1); + p->tcph->th_seq = htonl(1); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + p->tcph->th_ack = htonl(4); + p->tcph->th_seq = htonl(2); + p->tcph->th_flags = TH_ACK; + p->payload_len = 0; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1 || (TcpSession *)p->flow->protoctx == NULL) { + printf("failed in processing packet\n"); + goto end; + } + + if (((TcpSession *)p->flow->protoctx)->client.ra_raw_base_seq != 3) { + printf("the ssn->client.next_seq should be 3, but it is %"PRIu32"\n", + ((TcpSession *)p->flow->protoctx)->client.ra_raw_base_seq); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** + * \test Test the validation of the ACK number before setting up the + * stream.last_ack. + * + * \retval On success it returns 1 and on failure 0. + */ + +static int StreamTcpTest38 (void) +{ + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[128]; + TCPHdr tcph; + PacketQueue pq; + + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&pq,0,sizeof(PacketQueue)); + + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + FLOW_INITIALIZE(&f); + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + stt.ra_ctx = ra_ctx; + + StreamTcpInitConfig(TRUE); + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + 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, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + 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, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + p->tcph->th_ack = htonl(29847); + p->tcph->th_seq = htonl(2); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + /* last_ack value should be 1 as the previous sent ACK value is out of + window */ + if (((TcpSession *)(p->flow->protoctx))->server.last_ack != 1) { + printf("the server.last_ack should be 1, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.last_ack); + goto end; + } + + p->tcph->th_ack = htonl(1); + p->tcph->th_seq = htonl(1); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x41, 127, 128); /*AAA*/ + p->payload = payload; + p->payload_len = 127; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->server.next_seq != 128) { + printf("the server.next_seq should be 128, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + p->tcph->th_ack = htonl(256); // in window, but beyond next_seq + p->tcph->th_seq = htonl(5); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + /* last_ack value should be 256, as the previous sent ACK value + is inside window */ + if (((TcpSession *)(p->flow->protoctx))->server.last_ack != 256) { + printf("the server.last_ack should be 1, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.last_ack); + goto end; + } + + p->tcph->th_ack = htonl(128); + p->tcph->th_seq = htonl(8); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + /* last_ack value should be 256 as the previous sent ACK value is inside + window */ + if (((TcpSession *)(p->flow->protoctx))->server.last_ack != 256) { + printf("the server.last_ack should be 256, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.last_ack); + goto end; + } + + ret = 1; + +end: + StreamTcpSessionClear(p->flow->protoctx); + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + if (stt.ra_ctx != NULL) + StreamTcpReassembleFreeThreadCtx(stt.ra_ctx); + return ret; +} + +/** + * \test Test the validation of the ACK number before setting up the + * stream.last_ack and update the next_seq after loosing the . + * + * \retval On success it returns 1 and on failure 0. + */ + +static int StreamTcpTest39 (void) +{ + Flow f; + ThreadVars tv; + StreamTcpThread stt; + uint8_t payload[4]; + TCPHdr tcph; + PacketQueue pq; + + TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(NULL); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + memset(&pq,0,sizeof(PacketQueue)); + + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + FLOW_INITIALIZE(&f); + p->flow = &f; + tcph.th_win = htons(5480); + tcph.th_flags = TH_SYN; + p->tcph = &tcph; + p->flowflags = FLOW_PKT_TOSERVER; + int ret = 0; + stt.ra_ctx = ra_ctx; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + 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, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + 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, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + p->tcph->th_ack = htonl(1); + p->tcph->th_seq = htonl(1); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + if (((TcpSession *)(p->flow->protoctx))->server.next_seq != 4) { + printf("the server.next_seq should be 4, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + p->tcph->th_ack = htonl(4); + p->tcph->th_seq = htonl(2); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + /* last_ack value should be 4 as the previous sent ACK value is inside + window */ + if (((TcpSession *)(p->flow->protoctx))->server.last_ack != 4) { + printf("the server.last_ack should be 4, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.last_ack); + goto end; + } + + p->tcph->th_seq = htonl(4); + p->tcph->th_ack = htonl(5); + p->tcph->th_flags = TH_PUSH | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + StreamTcpCreateTestPacket(payload, 0x41, 3, 4); /*AAA*/ + p->payload = payload; + p->payload_len = 3; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) { + printf("failed in processing packet in StreamTcpPacket\n"); + goto end; + } + + /* next_seq value should be 2987 as the previous sent ACK value is inside + window */ + if (((TcpSession *)(p->flow->protoctx))->server.next_seq != 7) { + printf("the server.next_seq should be 7, but it is %"PRIu32"\n", + ((TcpSession *)(p->flow->protoctx))->server.next_seq); + goto end; + } + + ret = 1; + +end: + StreamTcpSessionClear(p->flow->protoctx); + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + if (stt.ra_ctx != NULL) + StreamTcpReassembleFreeThreadCtx(stt.ra_ctx); + return ret; +} + +static int StreamTcpTest40(void) +{ + uint8_t raw_vlan[] = { + 0x00, 0x20, 0x08, 0x00, 0x45, 0x00, 0x00, 0x34, + 0x3b, 0x36, 0x40, 0x00, 0x40, 0x06, 0xb7, 0xc9, + 0x83, 0x97, 0x20, 0x81, 0x83, 0x97, 0x20, 0x15, + 0x04, 0x8a, 0x17, 0x70, 0x4e, 0x14, 0xdf, 0x55, + 0x4d, 0x3d, 0x5a, 0x61, 0x80, 0x10, 0x6b, 0x50, + 0x3c, 0x4c, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, + 0x00, 0x04, 0xf0, 0xc8, 0x01, 0x99, 0xa3, 0xf3 + }; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + PACKET_INITIALIZE(p); + + SET_PKT_LEN(p, sizeof(raw_vlan)); + memcpy(GET_PKT_DATA(p), raw_vlan, sizeof(raw_vlan)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + FlowInitConfig(FLOW_QUIET); + + DecodeVLAN(&tv, &dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), NULL); + + if(p->vlanh == NULL) { + SCFree(p); + return 0; + } + + if(p->tcph == NULL) { + SCFree(p); + return 0; + } + + Packet *np = StreamTcpPseudoSetup(p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + if (np == NULL) { + printf("the packet received from packet allocation is NULL: "); + return 0; + } + + StreamTcpPseudoPacketSetupHeader(np,p); + + if (((uint8_t *)p->tcph - (uint8_t *)p->ip4h) != ((uint8_t *)np->tcph - (uint8_t *)np->ip4h)) { + return 0; + } + + PACKET_RECYCLE(np); + PACKET_RECYCLE(p); + FlowShutdown(); + + return 1; +} + +static int StreamTcpTest41(void) +{ + /* IPV6/TCP/no eth header */ + uint8_t raw_ip[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x28, 0x06, 0x40, + 0x20, 0x01, 0x06, 0x18, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0x99, 0xcc, 0x70, + 0x20, 0x01, 0x06, 0x18, 0x00, 0x01, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x8c, 0x9b, 0x00, 0x50, 0x6a, 0xe7, 0x07, 0x36, + 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x16, 0x30, + 0x29, 0x9c, 0x00, 0x00, 0x02, 0x04, 0x05, 0x8c, + 0x04, 0x02, 0x08, 0x0a, 0x00, 0xdd, 0x1a, 0x39, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x02 }; + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (unlikely(p == NULL)) + return 0; + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + PACKET_INITIALIZE(p); + + if (PacketCopyData(p, raw_ip, sizeof(raw_ip)) == -1) { + PacketFree(p); + return 1; + } + + FlowInitConfig(FLOW_QUIET); + + DecodeRaw(&tv, &dtv, p, raw_ip, GET_PKT_LEN(p), NULL); + + if (p->ip6h == NULL) { + printf("expected a valid ipv6 header but it was NULL: "); + FlowShutdown(); + SCFree(p); + return 1; + } + + if(p->tcph == NULL) { + SCFree(p); + return 0; + } + + Packet *np = StreamTcpPseudoSetup(p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + if (np == NULL) { + printf("the packet received from packet allocation is NULL: "); + return 0; + } + + StreamTcpPseudoPacketSetupHeader(np,p); + + if (((uint8_t *)p->tcph - (uint8_t *)p->ip6h) != ((uint8_t *)np->tcph - (uint8_t *)np->ip6h)) { + return 0; + } + + PACKET_RECYCLE(np); + PACKET_RECYCLE(p); + SCFree(p); + FlowShutdown(); + + return 1; +} + +/** \test multiple different SYN/ACK, pick first */ +static int StreamTcpTest42 (void) +{ + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + FLOW_INITIALIZE(&f); + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(501); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 500) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 500); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** \test multiple different SYN/ACK, pick second */ +static int StreamTcpTest43 (void) +{ + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + FLOW_INITIALIZE(&f); + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(1001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 1000) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 1000); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** \test multiple different SYN/ACK, pick neither */ +static int StreamTcpTest44 (void) +{ + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + FLOW_INITIALIZE(&f); + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(3001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_SYN_RECV) { + SCLogDebug("state not TCP_SYN_RECV"); + goto end; + } + + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + FLOW_DESTROY(&f); + return ret; +} + +/** \test multiple different SYN/ACK, over the limit */ +static int StreamTcpTest45 (void) +{ + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + stream_config.max_synack_queued = 2; + + FLOW_INITIALIZE(&f); + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + SCMutexLock(&f.m); + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(2000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(3000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(1001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 1000) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 1000); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCMutexUnlock(&f.m); + SCFree(p); + 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", StreamTcp4WHSTest01, 1); + UtRegisterTest("StreamTcp4WHSTest02", StreamTcp4WHSTest02, 1); + UtRegisterTest("StreamTcp4WHSTest03", StreamTcp4WHSTest03, 1); + UtRegisterTest("StreamTcpTest14 -- setup OS policy", StreamTcpTest14, 1); + UtRegisterTest("StreamTcpTest15 -- setup OS policy", StreamTcpTest15, 1); + UtRegisterTest("StreamTcpTest16 -- setup OS policy", StreamTcpTest16, 1); + UtRegisterTest("StreamTcpTest17 -- setup OS policy", StreamTcpTest17, 1); + UtRegisterTest("StreamTcpTest18 -- setup OS policy", StreamTcpTest18, 1); + UtRegisterTest("StreamTcpTest19 -- setup OS policy", StreamTcpTest19, 1); + UtRegisterTest("StreamTcpTest20 -- setup OS policy", StreamTcpTest20, 1); + UtRegisterTest("StreamTcpTest21 -- setup OS policy", StreamTcpTest21, 1); + UtRegisterTest("StreamTcpTest22 -- setup OS policy", StreamTcpTest22, 1); + UtRegisterTest("StreamTcpTest23 -- stream memory leaks", StreamTcpTest23, 1); + UtRegisterTest("StreamTcpTest24 -- stream memory leaks", StreamTcpTest24, 1); + UtRegisterTest("StreamTcpTest25 -- test ecn/cwr sessions", + StreamTcpTest25, 1); + UtRegisterTest("StreamTcpTest26 -- test ecn/cwr sessions", + StreamTcpTest26, 1); + UtRegisterTest("StreamTcpTest27 -- test ecn/cwr sessions", + StreamTcpTest27, 1); + UtRegisterTest("StreamTcpTest28 -- Memcap Test", StreamTcpTest28, 1); + +#if 0 /* VJ 2010/09/01 disabled since they blow up on Fedora and Fedora is + * right about blowing up. The checksum functions are not used properly + * in the tests. */ + UtRegisterTest("StreamTcpTest29 -- Badchecksum Reset Test", StreamTcpTest29, 1); + UtRegisterTest("StreamTcpTest30 -- Badchecksum Overlap Test", StreamTcpTest30, 1); + UtRegisterTest("StreamTcpTest31 -- MultipleSyns Test", StreamTcpTest31, 1); + UtRegisterTest("StreamTcpTest32 -- Bogus CWR Test", StreamTcpTest32, 1); + UtRegisterTest("StreamTcpTest33 -- RST-SYN Again Test", StreamTcpTest33, 1); + UtRegisterTest("StreamTcpTest34 -- SYN-PUSH Test", StreamTcpTest34, 1); + UtRegisterTest("StreamTcpTest35 -- SYN-URG Test", StreamTcpTest35, 1); + UtRegisterTest("StreamTcpTest36 -- PUSH-URG Test", StreamTcpTest36, 1); +#endif + UtRegisterTest("StreamTcpTest37 -- Out of order FIN Test", StreamTcpTest37, 1); + + UtRegisterTest("StreamTcpTest38 -- validate ACK", StreamTcpTest38, 1); + UtRegisterTest("StreamTcpTest39 -- update next_seq", StreamTcpTest39, 1); + + UtRegisterTest("StreamTcpTest40 -- pseudo setup", StreamTcpTest40, 1); + UtRegisterTest("StreamTcpTest41 -- pseudo setup", StreamTcpTest41, 1); + + UtRegisterTest("StreamTcpTest42 -- SYN/ACK queue", StreamTcpTest42, 1); + UtRegisterTest("StreamTcpTest43 -- SYN/ACK queue", StreamTcpTest43, 1); + UtRegisterTest("StreamTcpTest44 -- SYN/ACK queue", StreamTcpTest44, 1); + UtRegisterTest("StreamTcpTest45 -- SYN/ACK queue", StreamTcpTest45, 1); + + /* set up the reassembly tests as well */ + StreamTcpReassembleRegisterTests(); + + StreamTcpSackRegisterTests (); +#endif /* UNITTESTS */ +} + |