From: Victor Julien Date: Sat, 6 Apr 2013 18:54:25 +0000 (+0200) Subject: stream: handle extra different SYN/ACK X-Git-Tag: suricata-2.0beta1~173 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4c6463f3784f533a07679589dab713096137a439;p=thirdparty%2Fsuricata.git stream: handle extra different SYN/ACK Until now, when processing the TCP 3 way handshake (3whs), retransmissions of SYN/ACKs are silently accepted, unless they are different somehow. If the SEQ or ACK values are different they are considered wrong and events are set. The stream events rules will match on this. In some cases, this is wrong. If the client missed the SYN/ACK, the server may send a different one with a different SEQ. This commit deals with this. As it is impossible to predict which one the client will accept, each is added to a list. Then on receiving the final ACK from the 3whs, the list is checked and the state is updated according to the queued SYN/ACK. --- diff --git a/rules/stream-events.rules b/rules/stream-events.rules index f97eb1a7d0..856a3a4aee 100644 --- a/rules/stream-events.rules +++ b/rules/stream-events.rules @@ -10,6 +10,8 @@ alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYNACK resend alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYNACK resend with different seq"; stream-event:3whs_synack_resend_with_diff_seq; sid:2210005; rev:1;) alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYNACK to server on SYN recv"; stream-event:3whs_synack_toserver_on_syn_recv; sid:2210006; rev:1;) alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYNACK with wrong ack"; stream-event:3whs_synack_with_wrong_ack; sid:2210007; rev:1;) +# Excessive SYN/ACKs within a session. Limit is set in stream engine, "stream.max-synack-queued". +alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake excessive different SYN/ACKs"; stream-event:3whs_synack_flood; sid:2210055; rev:1;) alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYN resend different seq on SYN recv"; stream-event:3whs_syn_resend_diff_seq_on_syn_recv; sid:2210008; rev:1;) alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYN to client on SYN recv"; stream-event:3whs_syn_toclient_on_syn_recv; sid:2210009; rev:1;) alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake wrong seq wrong ack"; stream-event:3whs_wrong_seq_wrong_ack; sid:2210010; rev:1;) @@ -77,5 +79,5 @@ alert tcp any any -> any any (msg:"SURICATA STREAM Packet is retransmission"; st # rule to alert if a stream has excessive retransmissions alert tcp any any -> any any (msg:"SURICATA STREAM excessive retransmissions"; flowbits:isnotset,tcp.retransmission.alerted; flowint:tcp.retransmission.count,>=,10; flowbits:set,tcp.retransmission.alerted; classtype:protocol-command-decode; sid:2210054; rev:1;) -# next sid 2210055 +# next sid 2210056 diff --git a/src/decode-events.h b/src/decode-events.h index 31b2636477..232a9c0770 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -144,6 +144,7 @@ enum { STREAM_3WHS_SYNACK_RESEND_WITH_DIFF_SEQ, STREAM_3WHS_SYNACK_TOSERVER_ON_SYN_RECV, STREAM_3WHS_SYNACK_WITH_WRONG_ACK, + STREAM_3WHS_SYNACK_FLOOD, STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV, STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV, STREAM_3WHS_WRONG_SEQ_WRONG_ACK, diff --git a/src/detect-engine-event.h b/src/detect-engine-event.h index 58fe0eb3e9..4517de56ce 100644 --- a/src/detect-engine-event.h +++ b/src/detect-engine-event.h @@ -133,6 +133,7 @@ struct DetectEngineEvents_ { { "stream.3whs_synack_resend_with_diff_seq", STREAM_3WHS_SYNACK_RESEND_WITH_DIFF_SEQ, }, { "stream.3whs_synack_toserver_on_syn_recv", STREAM_3WHS_SYNACK_TOSERVER_ON_SYN_RECV, }, { "stream.3whs_synack_with_wrong_ack", STREAM_3WHS_SYNACK_WITH_WRONG_ACK, }, + { "stream.3whs_synack_flood", STREAM_3WHS_SYNACK_FLOOD, }, { "stream.3whs_syn_resend_diff_seq_on_syn_recv", STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV, }, { "stream.3whs_syn_toclient_on_syn_recv", STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV, }, { "stream.3whs_wrong_seq_wrong_ack", STREAM_3WHS_WRONG_SEQ_WRONG_ACK, }, diff --git a/src/stream-tcp-private.h b/src/stream-tcp-private.h index 59666e72a3..f26930784e 100644 --- a/src/stream-tcp-private.h +++ b/src/stream-tcp-private.h @@ -26,6 +26,22 @@ #include "decode.h" +#define STREAMTCP_QUEUE_FLAG_TS 0x01 +#define STREAMTCP_QUEUE_FLAG_WS 0x02 +#define STREAMTCP_QUEUE_FLAG_SACK 0x04 + +/** currently only SYN/ACK */ +typedef struct TcpStateQueue_ { + uint8_t flags; + uint8_t wscale; + uint16_t win; + uint32_t seq; + uint32_t ack; + uint32_t ts; + uint32_t pkt_ts; + struct TcpStateQueue_ *next; +} TcpStateQueue; + typedef struct StreamTcpSackRecord_ { uint32_t le; /**< left edge, host order */ uint32_t re; /**< right edge, host order */ @@ -100,7 +116,7 @@ enum /** Server supports wscale (even though it can be 0) */ #define STREAMTCP_FLAG_SERVER_WSCALE 0x0010 -/** vacancy at 0x0008 */ +/** vacancy at 0x0020 */ /** Flag to indicate that the session is handling asynchronous stream.*/ #define STREAMTCP_FLAG_ASYNC 0x0040 @@ -186,6 +202,7 @@ enum typedef struct TcpSession_ { uint8_t state; + uint8_t queue_len; /**< length of queue list below */ uint16_t flags; TcpStream server; TcpStream client; @@ -193,6 +210,8 @@ typedef struct TcpSession_ { struct StreamMsg_ *toserver_smsg_tail; /**< list of stream msgs (for detection inspection) */ struct StreamMsg_ *toclient_smsg_head; /**< list of stream msgs (for detection inspection) */ struct StreamMsg_ *toclient_smsg_tail; /**< list of stream msgs (for detection inspection) */ + + TcpStateQueue *queue; /**< list of SYN/ACK candidates */ } TcpSession; #endif /* __STREAM_TCP_PRIVATE_H__ */ diff --git a/src/stream-tcp.c b/src/stream-tcp.c index 46c86000dd..d324cc123f 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -77,6 +77,7 @@ #define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP (64 * 1024 * 1024) /* 64mb */ #define STREAMTCP_DEFAULT_TOSERVER_CHUNK_SIZE 2560 #define STREAMTCP_DEFAULT_TOCLIENT_CHUNK_SIZE 2560 +#define STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED 5 #define STREAMTCP_NEW_TIMEOUT 60 #define STREAMTCP_EST_TIMEOUT 3600 @@ -273,6 +274,7 @@ int StreamTcpSessionPoolInit(void *data, void* initdata) void StreamTcpSessionPoolCleanup(void *s) { StreamMsg *smsg = NULL; + TcpStateQueue *q, *q_next; if (s == NULL) return; @@ -307,6 +309,16 @@ void StreamTcpSessionPoolCleanup(void *s) } ssn->toclient_smsg_head = NULL; + q = ssn->queue; + while (q != NULL) { + q_next = q->next; + SCFree(q); + q = q_next; + StreamTcpDecrMemuse((uint64_t)sizeof(TcpStateQueue)); + } + ssn->queue = NULL; + ssn->queue_len = 0; + StreamTcpDecrMemuse((uint64_t)sizeof(TcpSession)); } @@ -416,6 +428,19 @@ void StreamTcpInitConfig(char quiet) SCLogInfo("stream.\"inline\": %s", stream_inline ? "enabled" : "disabled"); } + if ((ConfGetInt("stream.max-synack-queued", &value)) == 1) { + if (value >= 0 || value <= 255) { + stream_config.max_synack_queued = (uint8_t)value; + } else { + stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED; + } + } else { + stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED; + } + if (!quiet) { + SCLogInfo("stream \"max-synack-queued\": %"PRIu8, stream_config.max_synack_queued); + } + char *temp_stream_reassembly_memcap_str; if (ConfGet("stream.reassembly.memcap", &temp_stream_reassembly_memcap_str) == 1) { if (ParseSizeStringU64(temp_stream_reassembly_memcap_str, @@ -906,34 +931,154 @@ static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p, return 0; } +/** \internal + * \brief Setup TcpStateQueue based on SYN/ACK packet + */ +static inline void StreamTcp3whsSynAckToStateQueue(Packet *p, TcpStateQueue *q) { + q->flags = 0; + q->wscale = 0; + q->ts = 0; + q->win = TCP_GET_WINDOW(p); + q->seq = TCP_GET_SEQ(p); + q->ack = TCP_GET_ACK(p); + q->pkt_ts = p->ts.tv_sec; + + if (TCP_GET_SACKOK(p) == 1) + q->flags |= STREAMTCP_QUEUE_FLAG_SACK; + + if (p->tcpvars.ws != NULL) { + q->flags |= STREAMTCP_QUEUE_FLAG_WS; + q->wscale = TCP_GET_WSCALE(p); + } + if (p->tcpvars.ts != NULL) { + q->flags |= STREAMTCP_QUEUE_FLAG_TS; + q->ts = TCP_GET_TSVAL(p); + } +} + +/** \internal + * \brief Find the Queued SYN/ACK that is the same as this SYN/ACK + * \retval q or NULL */ +TcpStateQueue *StreamTcp3whsFindSynAckBySynAck(TcpSession *ssn, Packet *p) { + TcpStateQueue *q = ssn->queue; + TcpStateQueue search; + + StreamTcp3whsSynAckToStateQueue(p, &search); + + while (q != NULL) { + if (search.flags == q->flags && + search.wscale == q->wscale && + search.win == q->win && + search.seq == q->seq && + search.ack == q->ack && + search.ts == q->ts) { + return q; + } + + q = q->next; + } + + return q; +} + +int StreamTcp3whsQueueSynAck(TcpSession *ssn, Packet *p) { + /* first see if this is already in our list */ + if (StreamTcp3whsFindSynAckBySynAck(ssn, p) != NULL) + return 0; + + if (ssn->queue_len == stream_config.max_synack_queued) { + SCLogDebug("ssn %p: =~ SYN/ACK queue limit reached", ssn); + StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_FLOOD); + return -1; + } + + if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpStateQueue)) == 0) { + SCLogDebug("ssn %p: =~ SYN/ACK queue failed: stream memcap reached", ssn); + return -1; + } + + TcpStateQueue *q = SCMalloc(sizeof(*q)); + if (q == NULL) { + SCLogDebug("ssn %p: =~ SYN/ACK queue failed: alloc failed", ssn); + return -1; + } + memset(q, 0x00, sizeof(*q)); + StreamTcpIncrMemuse((uint64_t)sizeof(TcpStateQueue)); + + StreamTcp3whsSynAckToStateQueue(p, q); + + /* put in list */ + q->next = ssn->queue; + ssn->queue = q; + ssn->queue_len++; + return 0; +} + +/** \internal + * \brief Find the Queued SYN/ACK that goes with this ACK + * \retval q or NULL */ +TcpStateQueue *StreamTcp3whsFindSynAckByAck(TcpSession *ssn, Packet *p) { + uint32_t ack = TCP_GET_SEQ(p); + uint32_t seq = TCP_GET_ACK(p) - 1; + TcpStateQueue *q = ssn->queue; + + while (q != NULL) { + if (seq == q->seq && + ack == q->ack) { + return q; + } + + q = q->next; + } + + return NULL; +} + /** \internal * \brief Update SSN after receiving a valid SYN/ACK + * + * Normally we update the SSN from the SYN/ACK packet. But in case + * of queued SYN/ACKs, we can use one of those. + * + * \param ssn TCP session + * \param p Packet + * \param q queued state if used, NULL otherwise + * + * To make sure all SYN/ACK based state updates are in one place, + * this function can updated based on Packet or TcpStateQueue, where + * the latter takes precedence. */ -static void StreamTcp3whsSynAckUpdate(TcpSession *ssn, Packet *p) { +static void StreamTcp3whsSynAckUpdate(TcpSession *ssn, Packet *p, TcpStateQueue *q) { + TcpStateQueue update; + if (likely(q == NULL)) { + StreamTcp3whsSynAckToStateQueue(p, &update); + q = &update; + } + if (ssn->state != TCP_SYN_RECV) { /* update state */ StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV); SCLogDebug("ssn %p: =~ ssn state is now TCP_SYN_RECV", ssn); } /* sequence number & window */ - ssn->server.isn = TCP_GET_SEQ(p); + ssn->server.isn = q->seq; STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn); ssn->server.next_seq = ssn->server.isn + 1; - ssn->client.window = TCP_GET_WINDOW(p); + ssn->client.window = q->win; SCLogDebug("ssn %p: window %" PRIu32 "", ssn, ssn->server.window); /* Set the timestamp values used to validate the timestamp of * received packets.*/ - if ((p->tcpvars.ts != NULL) && + if ((q->flags & STREAMTCP_QUEUE_FLAG_TS) && (ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP)) { - ssn->server.last_ts = TCP_GET_TSVAL(p); + ssn->server.last_ts = q->ts; SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" " "ssn->client.last_ts %" PRIu32"", ssn, ssn->server.last_ts, ssn->client.last_ts); ssn->flags |= STREAMTCP_FLAG_TIMESTAMP; - ssn->server.last_pkt_ts = p->ts.tv_sec; + ssn->server.last_pkt_ts = q->pkt_ts; if (ssn->server.last_ts == 0) ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; } else { @@ -942,23 +1087,25 @@ static void StreamTcp3whsSynAckUpdate(TcpSession *ssn, Packet *p) { ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP; } - ssn->client.last_ack = TCP_GET_ACK(p); + ssn->client.last_ack = q->ack; ssn->server.last_ack = ssn->server.isn + 1; /** check for the presense of the ws ptr to determine if we * support wscale at all */ if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) && - (p->tcpvars.ws != NULL)) + (q->flags & STREAMTCP_QUEUE_FLAG_WS)) { - ssn->client.wscale = TCP_GET_WSCALE(p); + ssn->client.wscale = q->wscale; } else { ssn->client.wscale = 0; } if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) && - TCP_GET_SACKOK(p) == 1) { + (q->flags & STREAMTCP_QUEUE_FLAG_SACK)) { ssn->flags |= STREAMTCP_FLAG_SACKOK; SCLogDebug("ssn %p: SACK permitted for session", ssn); + } else { + ssn->flags &= ~STREAMTCP_FLAG_SACKOK; } ssn->server.next_win = ssn->server.last_ack + ssn->server.window; @@ -1142,7 +1289,7 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p, return -1; } - StreamTcp3whsSynAckUpdate(ssn, p); + StreamTcp3whsSynAckUpdate(ssn, p, /* no queue override */NULL); } else if (p->tcph->th_flags & TH_SYN) { SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent", ssn); @@ -1377,15 +1524,15 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p, } /* Check if the SYN/ACK packet SEQ the earlier - * received SYN/ACK packet. */ + * received SYN/ACK packet, server resend with different ISN. */ if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.isn))) { SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != " "%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p), ssn->client.isn); - StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_RESEND_WITH_DIFF_SEQ); - - return -1; + if (StreamTcp3whsQueueSynAck(ssn, p) == -1) + return -1; + SCLogDebug("ssn %p: queued different SYN/ACK", ssn); } } else if (p->tcph->th_flags & TH_SYN) { @@ -1406,6 +1553,18 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p, } } else if (p->tcph->th_flags & TH_ACK) { + if (ssn->queue_len) { + SCLogDebug("ssn %p: checking ACK against queued SYN/ACKs", ssn); + TcpStateQueue *q = StreamTcp3whsFindSynAckByAck(ssn, p); + if (q != NULL) { + SCLogDebug("ssn %p: here we update state against queued SYN/ACK", ssn); + StreamTcp3whsSynAckUpdate(ssn, p, /* using queue to update state */q); + } else { + SCLogDebug("ssn %p: none found, now checking ACK against original SYN/ACK (state)", ssn); + } + } + + /* If the timestamp option is enabled for both the streams, then * validate the received packet timestamp value against the * stream->last_ts. If the timestamp is valid then process the @@ -9191,6 +9350,380 @@ static int StreamTcpTest41(void) { return 1; } +/** \test multiple different SYN/ACK, pick first */ +static int StreamTcpTest42 (void) { + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + p->pkt = (uint8_t *)(p + 1); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(501); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 500) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 500); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCFree(p); + return ret; +} + +/** \test multiple different SYN/ACK, pick second */ +static int StreamTcpTest43 (void) { + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + p->pkt = (uint8_t *)(p + 1); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(1001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 1000) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 1000); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCFree(p); + return ret; +} + +/** \test multiple different SYN/ACK, pick neither */ +static int StreamTcpTest44 (void) { + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + p->pkt = (uint8_t *)(p + 1); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(3001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_SYN_RECV) { + SCLogDebug("state not TCP_SYN_RECV"); + goto end; + } + + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCFree(p); + return ret; +} + +/** \test multiple different SYN/ACK, over the limit */ +static int StreamTcpTest45 (void) { + int ret = 0; + Flow f; + ThreadVars tv; + StreamTcpThread stt; + TCPHdr tcph; + PacketQueue pq; + Packet *p = SCMalloc(SIZE_OF_PACKET); + TcpSession *ssn; + + if (unlikely(p == NULL)) + return 0; + memset(p, 0, SIZE_OF_PACKET); + p->pkt = (uint8_t *)(p + 1); + + memset(&pq,0,sizeof(PacketQueue)); + memset (&f, 0, sizeof(Flow)); + memset(&tv, 0, sizeof (ThreadVars)); + memset(&stt, 0, sizeof (StreamTcpThread)); + memset(&tcph, 0, sizeof (TCPHdr)); + + StreamTcpInitConfig(TRUE); + stream_config.max_synack_queued = 2; + + p->tcph = &tcph; + tcph.th_win = htons(5480); + p->flow = &f; + + /* SYN pkt */ + tcph.th_flags = TH_SYN; + tcph.th_seq = htonl(100); + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(500); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(1000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(2000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + /* SYN/ACK */ + p->tcph->th_seq = htonl(3000); + p->tcph->th_ack = htonl(101); + p->tcph->th_flags = TH_SYN | TH_ACK; + p->flowflags = FLOW_PKT_TOCLIENT; + + if (StreamTcpPacket(&tv, p, &stt, &pq) != -1) + goto end; + + /* ACK */ + p->tcph->th_ack = htonl(1001); + p->tcph->th_seq = htonl(101); + p->tcph->th_flags = TH_ACK; + p->flowflags = FLOW_PKT_TOSERVER; + + if (StreamTcpPacket(&tv, p, &stt, &pq) == -1) + goto end; + + ssn = p->flow->protoctx; + + if (ssn->state != TCP_ESTABLISHED) { + printf("state not TCP_ESTABLISHED: "); + goto end; + } + + if (ssn->server.isn != 1000) { + SCLogDebug("ssn->server.isn %"PRIu32" != %"PRIu32"", + ssn->server.isn, 1000); + goto end; + } + if (ssn->client.isn != 100) { + SCLogDebug("ssn->client.isn %"PRIu32" != %"PRIu32"", + ssn->client.isn, 100); + goto end; + } + + StreamTcpSessionClear(p->flow->protoctx); + + ret = 1; +end: + StreamTcpFreeConfig(TRUE); + SCFree(p); + return ret; +} + #endif /* UNITTESTS */ void StreamTcpRegisterTests (void) { @@ -9263,6 +9796,11 @@ void StreamTcpRegisterTests (void) { UtRegisterTest("StreamTcpTest40 -- pseudo setup", StreamTcpTest40, 1); UtRegisterTest("StreamTcpTest41 -- pseudo setup", StreamTcpTest41, 1); + UtRegisterTest("StreamTcpTest42 -- SYN/ACK queue", StreamTcpTest42, 1); + UtRegisterTest("StreamTcpTest43 -- SYN/ACK queue", StreamTcpTest43, 1); + UtRegisterTest("StreamTcpTest44 -- SYN/ACK queue", StreamTcpTest44, 1); + UtRegisterTest("StreamTcpTest45 -- SYN/ACK queue", StreamTcpTest45, 1); + /* set up the reassembly tests as well */ StreamTcpReassembleRegisterTests(); diff --git a/src/stream-tcp.h b/src/stream-tcp.h index d346e2adc6..31b3c467b8 100644 --- a/src/stream-tcp.h +++ b/src/stream-tcp.h @@ -65,6 +65,7 @@ typedef struct TcpStreamCnf_ { */ uint32_t reassembly_inline_window; uint8_t flags; + uint8_t max_synack_queued; } TcpStreamCnf; typedef struct StreamTcpThread_ { diff --git a/suricata.yaml.in b/suricata.yaml.in index 6199580bd6..c73e7901a5 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -576,13 +576,14 @@ flow-timeouts: # # Warning: locally generated trafic can be # # generated without checksum due to hardware offload # # of checksum. You can control the handling of checksum -# # on a per-interface basis via the 'checksum-checks' -# # option +# # on a per-interface basis via the 'checksum-checks' +# # option # max-sessions: 262144 # 256k concurrent sessions # prealloc-sessions: 32768 # 32k sessions prealloc'd # midstream: false # don't allow midstream session pickups # async-oneside: false # don't enable async stream handling # inline: no # stream inline mode +# max-synack-queued: 5 # Max different SYN/ACKs to queue # # reassembly: # memcap: 64mb # Can be specified in kb, mb, gb. Just a number