/* 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 * \author Gurvinder Singh * * 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)) \ { \ SCLogDebug("ssn %p: last_ack set to %"PRIu32", moved %u forward", (ssn), (ack), (ack) - (stream)->last_ack); \ if ((SEQ_LEQ((stream)->last_ack, (stream)->next_seq) && SEQ_GT((ack),(stream)->next_seq))) { \ SCLogDebug("last_ack just passed next_seq: %u (was %u) > %u", (ack), (stream)->last_ack, (stream)->next_seq); \ } else { \ SCLogDebug("next_seq (%u) <> last_ack now %d", (stream)->next_seq, (int)(stream)->next_seq - (ack)); \ }\ (stream)->last_ack = (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 partially ack'd data */ if (SEQ_LT(TCP_GET_SEQ(p), stream->last_ack) && SEQ_GT((TCP_GET_SEQ(p) + p->payload_len), stream->last_ack)) { StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION); SCReturnInt(1); } /* 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, next_seq %u", TCP_GET_SEQ(p), p->payload_len, (TCP_GET_SEQ(p) + p->payload_len), stream->last_ack, stream->next_seq); 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); /* if next_seq has fallen behind last_ack, we got some catching up to do */ } else if (SEQ_LT(ssn->client.next_seq, ssn->client.last_ack)) { ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len; SCLogDebug("ssn %p: ssn->client.next_seq %"PRIu32 " (next_seq had fallen behind last_ack)", ssn, ssn->client.next_seq); } else { SCLogDebug("ssn %p: no update to ssn->client.next_seq %"PRIu32 " SEQ %u SEQ+ %u last_ack %u", ssn, ssn->client.next_seq, TCP_GET_SEQ(p), TCP_GET_SEQ(p)+p->payload_len, ssn->client.last_ack); } /* 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 next_seq has fallen behind last_ack, we got some catching up to do */ } else if (SEQ_LT(ssn->server.next_seq, ssn->server.last_ack)) { ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len; SCLogDebug("ssn %p: ssn->server.next_seq %"PRIu32 " (next_seq had fallen behind last_ack)", ssn, ssn->server.next_seq); } else { SCLogDebug("ssn %p: no update to ssn->server.next_seq %"PRIu32 " SEQ %u SEQ+ %u last_ack %u", ssn, ssn->server.next_seq, TCP_GET_SEQ(p), TCP_GET_SEQ(p)+p->payload_len, ssn->server.last_ack); } 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; if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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; if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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)) { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0) 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 { if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0) 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 */ }