]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
stream: handle extra different SYN/ACK
authorVictor Julien <victor@inliniac.net>
Sat, 6 Apr 2013 18:54:25 +0000 (20:54 +0200)
committerVictor Julien <victor@inliniac.net>
Fri, 19 Apr 2013 07:01:13 +0000 (09:01 +0200)
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.

rules/stream-events.rules
src/decode-events.h
src/detect-engine-event.h
src/stream-tcp-private.h
src/stream-tcp.c
src/stream-tcp.h
suricata.yaml.in

index f97eb1a7d0dd7ae5963552666a4e815efc9c8e96..856a3a4aeeef7f882baa8584c1577d05af53824c 100644 (file)
@@ -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
 
index 31b2636477d7f6664092a54a94ce5d04696d5e7b..232a9c077080dda6d43efe6fd9664ed74ff1ddcd 100644 (file)
@@ -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,
index 58fe0eb3e9de80c699a28dfc481a07adf42451e2..4517de56ced0425eb70cbc246a041b74814d43f1 100644 (file)
@@ -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, },
index 59666e72a34ff3863aff45d04cfceb1e858a67e3..f26930784e009b3cb9729d7dea907f14ba8f6363 100644 (file)
 
 #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__ */
index 46c86000dd67b1466e43b99cf71fb637ef73cf2e..d324cc123f8387449ed5892cf287a08796c8fdee 100644 (file)
@@ -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();
 
index d346e2adc6c91d7676b63f5eb9dc7865d8dcc960..31b3c467b8f3fc498888394765fab4c003a5a506 100644 (file)
@@ -65,6 +65,7 @@ typedef struct TcpStreamCnf_ {
      */
     uint32_t reassembly_inline_window;
     uint8_t flags;
+    uint8_t max_synack_queued;
 } TcpStreamCnf;
 
 typedef struct StreamTcpThread_ {
index 6199580bd63f7b9354edc8f42285bab58658de2f..c73e7901a578df937cf7804154a1f2fed8501e9b 100644 (file)
@@ -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