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