]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
stream: SYN queue support
authorVictor Julien <vjulien@oisf.net>
Thu, 9 Feb 2023 16:11:21 +0000 (17:11 +0100)
committerVictor Julien <vjulien@oisf.net>
Thu, 16 Feb 2023 16:18:36 +0000 (17:18 +0100)
Support case where there are multiple SYN retransmits, where
each has a new timestamp.

Before this patch, Suricata would only accept a SYN/ACK that
matches the last timestamp. However, observed behavior is that
the server may choose to only respond to the first. In IPS mode
this could lead to a connection timing out as Suricata drops
the SYN/ACK it considers wrong, and the server continues to
retransmit it.

This patch reuses the SYN/ACK queuing logic to keep a list
of SYN packets and their window, timestamp, wscale and sackok
settings. Then when the SYN/ACK arrives, it is first evaluated
against the normal session state. But if it fails due to a
timestamp mismatch, it will look for queued SYN's and see if
any of them match the timestamp. If one does, the ssn is updated
to use that SYN and the SYN/ACK is accepted.

Bug: #5856.

rules/stream-events.rules
src/decode-events.c
src/decode-events.h
src/stream-tcp-private.h
src/stream-tcp.c

index 7ffeb6b5d30b10ff4a70953240fcf84a1bae89dc..5cc6169d1190abf7a46c6f89d691d04cdf2e7a59 100644 (file)
@@ -10,8 +10,9 @@ 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; classtype:protocol-command-decode; sid:2210005; rev:2;)
 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; classtype:protocol-command-decode; sid:2210006; rev:2;)
 alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake SYNACK with wrong ack"; stream-event:3whs_synack_with_wrong_ack; classtype:protocol-command-decode; sid:2210007; rev:2;)
-# Excessive SYN/ACKs within a session. Limit is set in stream engine, "stream.max-synack-queued".
+# Excessive SYNs or 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; classtype:protocol-command-decode; sid:2210055; rev:2;)
+alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake excessive different SYNs"; stream-event:3whs_syn_flood; classtype:protocol-command-decode; sid:2210063; 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; classtype:protocol-command-decode; sid:2210008; rev:2;)
 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; classtype:protocol-command-decode; sid:2210009; rev:2;)
 alert tcp any any -> any any (msg:"SURICATA STREAM 3way handshake wrong seq wrong ack"; stream-event:3whs_wrong_seq_wrong_ack; classtype:protocol-command-decode; sid:2210010; rev:2;)
@@ -101,5 +102,5 @@ alert tcp any any -> any any (msg:"SURICATA STREAM FIN SYN reuse"; stream-event:
 # Depth setting reached for a stream. Very common in normal traffic, so disable by default.
 #alert tcp any any -> any any (msg:"SURICATA STREAM reassembly depth reached"; stream-event:reassembly_depth_reached; classtype:protocol-command-decode; sid:2210062; rev:1;)
 
-# next sid 2210063
+# next sid 2210064
 
index ee917b0f8d1d77f6ae5fee1424b422b32781023a..0a2626beca999d93f3873f3b1afe1eac52eb0710 100644 (file)
@@ -634,6 +634,10 @@ const struct DecodeEvents_ DEvents[] = {
             "stream.3whs_syn_toclient_on_syn_recv",
             STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV,
     },
+    {
+            "stream.3whs_syn_flood",
+            STREAM_3WHS_SYN_FLOOD,
+    },
     {
             "stream.3whs_wrong_seq_wrong_ack",
             STREAM_3WHS_WRONG_SEQ_WRONG_ACK,
index 2024054fda58e9f93104b9ab616ce9ae13e49577..150ec9f75cca2be5f6d033e5962fbc00924f90fb 100644 (file)
@@ -235,6 +235,7 @@ enum {
     STREAM_3WHS_SYNACK_FLOOD,
     STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV,
     STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV,
+    STREAM_3WHS_SYN_FLOOD,
     STREAM_3WHS_WRONG_SEQ_WRONG_ACK,
     STREAM_3WHS_ACK_DATA_INJECT,
     STREAM_4WHS_SYNACK_WITH_WRONG_ACK,
index 6c2117a578b6e95bb6c04d40a730ad30056bf14b..0a42f306f8314c972df716f518af620dfb5815c1 100644 (file)
@@ -31,7 +31,7 @@
 #define STREAMTCP_QUEUE_FLAG_WS     0x02
 #define STREAMTCP_QUEUE_FLAG_SACK   0x04
 
-/** currently only SYN/ACK */
+/** Tracking SYNs and SYN/ACKs */
 typedef struct TcpStateQueue_ {
     uint8_t flags;
     uint8_t wscale;
index e1f65ec04a1d46417075d112095207ffada8eff5..cfdc4b7ec7b3e107143d0b9f7298177ef8571a86 100644 (file)
@@ -1540,155 +1540,190 @@ static inline bool StateSynSentValidateTimestamp(TcpSession *ssn, Packet *p)
     return true;
 }
 
-/**
- *  \brief  Function to handle the TCP_SYN_SENT state. The function handles
- *          SYN, SYN/ACK, RST packets and correspondingly changes the connection
- *          state.
- *
- *  \param  tv      Thread Variable containing  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)
+static void TcpStateQueueInitFromSsnSyn(const TcpSession *ssn, TcpStateQueue *q)
 {
-    DEBUG_VALIDATE_BUG_ON(ssn == NULL);
+    BUG_ON(ssn->state != TCP_SYN_SENT); // TODO
+    memset(q, 0, sizeof(*q));
 
-    SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ?
-               "toclient":"toserver");
+    /* SYN won't use wscale yet. So window should be limited to 16 bits. */
+    DEBUG_VALIDATE_BUG_ON(ssn->server.window > UINT16_MAX);
+    q->win = (uint16_t)ssn->server.window;
 
-    /* check for bad responses */
-    if (StateSynSentValidateTimestamp(ssn, p) == false) {
-        StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
-        return -1;
+    q->pkt_ts = ssn->client.last_pkt_ts;
+
+    if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_SACK;
+    }
+    if (ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_WS;
+        q->wscale = ssn->server.wscale;
+    }
+    if (ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_TS;
+        q->ts = ssn->client.last_ts;
     }
 
-    /* RST */
-    if (p->tcph->th_flags & TH_RST) {
-        if (!StreamTcpValidateRst(ssn, p))
-            return -1;
+    SCLogDebug("ssn %p: state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, q, q->seq, q->win,
+            BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS), q->ts);
+}
 
-        if (PKT_IS_TOSERVER(p)) {
-            if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn) &&
-                    SEQ_EQ(TCP_GET_WINDOW(p), 0) &&
-                    SEQ_EQ(TCP_GET_ACK(p), (ssn->client.isn + 1)))
-            {
-                SCLogDebug("ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
-                ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
-                StreamTcpCloseSsnWithReset(p, ssn);
-            }
-        } else {
-            ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
-            SCLogDebug("ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
-            StreamTcpCloseSsnWithReset(p, ssn);
-        }
+static void TcpStateQueueInitFromPktSyn(const Packet *p, TcpStateQueue *q)
+{
+#if defined(DEBUG_VALIDATION) || defined(DEBUG)
+    const TcpSession *ssn = p->flow->protoctx;
+    BUG_ON(ssn->state != TCP_SYN_SENT);
+#endif
+    memset(q, 0, sizeof(*q));
 
-    /* FIN */
-    } else if (p->tcph->th_flags & TH_FIN) {
-        /** \todo */
+    q->win = TCP_GET_WINDOW(p);
+    q->pkt_ts = SCTIME_SECS(p->ts);
 
-    /* SYN/ACK */
-    } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
-        if ((ssn->flags & STREAMTCP_FLAG_4WHS) && PKT_IS_TOSERVER(p)) {
-            SCLogDebug("ssn %p: SYN/ACK received on 4WHS session", ssn);
+    if (TCP_GET_SACKOK(p) == 1) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_SACK;
+    }
+    if (TCP_HAS_WSCALE(p)) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_WS;
+        q->wscale = TCP_GET_WSCALE(p);
+    }
+    if (TCP_HAS_TS(p)) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_TS;
+        q->ts = TCP_GET_TSVAL(p);
+    }
 
-            /* Check if the SYN/ACK packet ack's the earlier
-             * received SYN packet. */
-            if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->server.isn + 1))) {
-                StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_ACK);
+#if defined(DEBUG)
+    SCLogDebug("ssn %p: state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, q, q->seq, q->win,
+            BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS), q->ts);
+#endif
+}
 
-                SCLogDebug("ssn %p: 4WHS ACK mismatch, packet ACK %"PRIu32""
-                        " != %" PRIu32 " from stream", ssn,
-                        TCP_GET_ACK(p), ssn->server.isn + 1);
-                return -1;
-            }
+static void TcpStateQueueInitFromPktSynAck(const Packet *p, TcpStateQueue *q)
+{
+#if defined(DEBUG_VALIDATION) || defined(DEBUG)
+    const TcpSession *ssn = p->flow->protoctx;
+    BUG_ON(ssn->state != TCP_SYN_SENT);
+#endif
+    memset(q, 0, sizeof(*q));
 
-            /* Check if the SYN/ACK packet SEQ's the *FIRST* received SYN
-             * packet. */
-            if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
-                StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_SYN);
+    q->win = TCP_GET_WINDOW(p);
+    q->pkt_ts = SCTIME_SECS(p->ts);
 
-                SCLogDebug("ssn %p: 4WHS SEQ mismatch, packet SEQ %"PRIu32""
-                        " != %" PRIu32 " from *first* SYN pkt", ssn,
-                        TCP_GET_SEQ(p), ssn->client.isn);
-                return -1;
-            }
+    if (TCP_GET_SACKOK(p) == 1) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_SACK;
+    }
+    if (TCP_HAS_WSCALE(p)) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_WS;
+        q->wscale = TCP_GET_WSCALE(p);
+    }
+    if (TCP_HAS_TS(p)) {
+        q->flags |= STREAMTCP_QUEUE_FLAG_TS;
+        q->ts = TCP_GET_TSECR(p);
+    }
 
+#if defined(DEBUG)
+    SCLogDebug("ssn %p: state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, q, q->seq, q->win,
+            BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS), q->ts);
+#endif
+}
 
-            /* update state */
-            StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
-            SCLogDebug("ssn %p: =~ 4WHS ssn state is now TCP_SYN_RECV", ssn);
+/** \internal
+ *  \brief Find the Queued SYN that is the same as this SYN/ACK
+ *  \retval q or NULL */
+static const TcpStateQueue *StreamTcp3whsFindSyn(const TcpSession *ssn, TcpStateQueue *s)
+{
+    SCLogDebug("ssn %p: search state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, s, s->seq, s->win,
+            BOOL2STR(s->flags & STREAMTCP_QUEUE_FLAG_TS), s->ts);
+
+    for (const TcpStateQueue *q = ssn->queue; q != NULL; q = q->next) {
+        SCLogDebug("ssn %p: queue state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, q, q->seq,
+                q->win, BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS), q->ts);
+        if ((s->flags & STREAMTCP_QUEUE_FLAG_TS) == (q->flags & STREAMTCP_QUEUE_FLAG_TS) &&
+                s->ts == q->ts) {
+            return q;
+        }
+    }
+    return NULL;
+}
 
-            /* sequence number & window */
-            ssn->client.isn = TCP_GET_SEQ(p);
-            STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
-            ssn->client.next_seq = ssn->client.isn + 1;
+/** \note the SEQ values *must* be the same */
+static int StreamTcp3whsStoreSyn(TcpSession *ssn, Packet *p)
+{
+    TcpStateQueue search;
+    TcpStateQueueInitFromSsnSyn(ssn, &search);
 
-            ssn->server.window = TCP_GET_WINDOW(p);
-            SCLogDebug("ssn %p: 4WHS window %" PRIu32 "", ssn,
-                    ssn->client.window);
+    /* first see if this is already in our list */
+    if (ssn->queue != NULL && StreamTcp3whsFindSyn(ssn, &search) != NULL)
+        return 0;
 
-            /* Set the timestamp values used to validate the timestamp of
-             * received packets. */
-            if ((TCP_HAS_TS(p)) &&
-                    (ssn->server.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP))
-            {
-                ssn->client.last_ts = TCP_GET_TSVAL(p);
-                SCLogDebug("ssn %p: 4WHS ssn->client.last_ts %" PRIu32" "
-                        "ssn->server.last_ts %" PRIu32"", ssn,
-                        ssn->client.last_ts, ssn->server.last_ts);
-                ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
-                ssn->client.last_pkt_ts = SCTIME_SECS(p->ts);
-                if (ssn->client.last_ts == 0)
-                    ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
-            } else {
-                ssn->server.last_ts = 0;
-                ssn->client.last_ts = 0;
-                ssn->server.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
-            }
+    if (ssn->queue_len == stream_config.max_synack_queued) { // TODO
+        SCLogDebug("ssn %p: =~ SYN queue limit reached", ssn);
+        StreamTcpSetEvent(p, STREAM_3WHS_SYN_FLOOD);
+        return -1;
+    }
 
-            ssn->server.last_ack = TCP_GET_ACK(p);
-            ssn->client.last_ack = ssn->client.isn + 1;
+    if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpStateQueue)) == 0) {
+        SCLogDebug("ssn %p: =~ SYN queue failed: stream memcap reached", ssn);
+        return -1;
+    }
 
-            /** check for the presense of the ws ptr to determine if we
-             *  support wscale at all */
-            if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) &&
-                    (TCP_HAS_WSCALE(p)))
-            {
-                ssn->server.wscale = TCP_GET_WSCALE(p);
-            } else {
-                ssn->server.wscale = 0;
-            }
+    TcpStateQueue *q = SCCalloc(1, sizeof(*q));
+    if (unlikely(q == NULL)) {
+        SCLogDebug("ssn %p: =~ SYN queue failed: alloc failed", ssn);
+        return -1;
+    }
+    StreamTcpIncrMemuse((uint64_t)sizeof(TcpStateQueue));
 
-            if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) &&
-                    TCP_GET_SACKOK(p) == 1) {
-                ssn->flags |= STREAMTCP_FLAG_SACKOK;
-                SCLogDebug("ssn %p: SACK permitted for 4WHS session", ssn);
-            }
+    *q = search;
+    /* put in list */
+    q->next = ssn->queue;
+    ssn->queue = q;
+    ssn->queue_len++;
+    return 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);
+static inline void StreamTcp3whsStoreSynApplyToSsn(TcpSession *ssn, const TcpStateQueue *q)
+{
+    if (q->flags & STREAMTCP_QUEUE_FLAG_TS) {
+        ssn->client.last_pkt_ts = q->pkt_ts;
+        ssn->client.last_ts = q->ts;
+        ssn->client.flags |= STREAMTCP_STREAM_FLAG_TIMESTAMP;
+        SCLogDebug("ssn: %p client.last_ts updated to %u", ssn, ssn->client.last_ts);
+    }
+    if (q->flags & STREAMTCP_QUEUE_FLAG_WS) {
+        ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
+        ssn->server.wscale = q->wscale;
+    } else {
+        ssn->flags &= STREAMTCP_FLAG_SERVER_WSCALE;
+        ssn->server.wscale = 0;
+    }
+    ssn->server.window = q->win;
 
-            /* done here */
-            return 0;
-        }
+    if (q->flags & STREAMTCP_QUEUE_FLAG_SACK) {
+        ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
+    } else {
+        ssn->flags &= ~STREAMTCP_FLAG_CLIENT_SACKOK;
+    }
+}
 
-        if (PKT_IS_TOSERVER(p)) {
-            StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_IN_WRONG_DIRECTION);
-            SCLogDebug("ssn %p: SYN/ACK received in the wrong direction", ssn);
-            return -1;
-        }
+/**
+ *  \brief  Function to handle the TCP_SYN_SENT state. The function handles
+ *          SYN, SYN/ACK, RST packets and correspondingly changes the connection
+ *          state.
+ *
+ *  \param  tv      Thread Variable containing  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)
+{
+    DEBUG_VALIDATE_BUG_ON(ssn == NULL);
+
+    SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ? "toclient" : "toserver");
+
+    /* common case: SYN/ACK from server to client */
+    if ((p->tcph->th_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK) && PKT_IS_TOCLIENT(p)) {
+        SCLogDebug("ssn %p: SYN/ACK on SYN_SENT state for packet %" PRIu64, ssn, p->pcap_cnt);
 
         if (!(TCP_HAS_TFO(p) || (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN))) {
             /* Check if the SYN/ACK packet ack's the earlier
@@ -1715,7 +1750,159 @@ static int StreamTcpPacketStateSynSent(
             ssn->flags |= STREAMTCP_FLAG_TCP_FAST_OPEN;
             StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
         }
+
+        const bool ts_mismatch = !StateSynSentValidateTimestamp(ssn, p);
+        if (ts_mismatch) {
+            SCLogDebug("ssn %p: ts_mismatch:%s", ssn, BOOL2STR(ts_mismatch));
+            if (ssn->queue) {
+                TcpStateQueue search;
+                TcpStateQueueInitFromPktSynAck(p, &search);
+
+                const TcpStateQueue *q = StreamTcp3whsFindSyn(ssn, &search);
+                if (q == NULL) {
+                    SCLogDebug("not found: mismatch");
+                    StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
+                    return -1;
+                }
+                SCLogDebug("ssn %p: found queued SYN state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u",
+                        ssn, q, q->seq, q->win, BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS),
+                        q->ts);
+
+                StreamTcp3whsStoreSynApplyToSsn(ssn, q);
+
+            } else {
+                SCLogDebug("not found: no queue");
+                StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
+                return -1;
+            }
+        }
+
+        /* clear ssn->queue on state change: TcpSession can be reused by SYN/ACK */
+        StreamTcp3wsFreeQueue(ssn);
+
         StreamTcp3whsSynAckUpdate(ssn, p, /* no queue override */NULL);
+        return 0;
+
+    } else if ((p->tcph->th_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK) && PKT_IS_TOSERVER(p)) {
+
+        if (!(ssn->flags & STREAMTCP_FLAG_4WHS)) {
+            StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_IN_WRONG_DIRECTION);
+            SCLogDebug("ssn %p: SYN/ACK received in the wrong direction", ssn);
+            return -1;
+        }
+
+        SCLogDebug("ssn %p: SYN/ACK received on 4WHS session", ssn);
+
+        /* Check if the SYN/ACK packet ack's the earlier
+         * received SYN packet. */
+        if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->server.isn + 1))) {
+            StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_ACK);
+
+            SCLogDebug("ssn %p: 4WHS ACK mismatch, packet ACK %" PRIu32 ""
+                       " != %" PRIu32 " from stream",
+                    ssn, TCP_GET_ACK(p), ssn->server.isn + 1);
+            return -1;
+        }
+
+        /* Check if the SYN/ACK packet SEQ's the *FIRST* received SYN
+         * packet. */
+        if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
+            StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_SYN);
+
+            SCLogDebug("ssn %p: 4WHS SEQ mismatch, packet SEQ %" PRIu32 ""
+                       " != %" PRIu32 " from *first* SYN pkt",
+                    ssn, TCP_GET_SEQ(p), ssn->client.isn);
+            return -1;
+        }
+
+        /* update state */
+        StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
+        SCLogDebug("ssn %p: =~ 4WHS ssn state is now TCP_SYN_RECV", ssn);
+
+        /* sequence number & window */
+        ssn->client.isn = TCP_GET_SEQ(p);
+        STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
+        ssn->client.next_seq = ssn->client.isn + 1;
+
+        ssn->server.window = TCP_GET_WINDOW(p);
+        SCLogDebug("ssn %p: 4WHS window %" PRIu32 "", ssn, ssn->client.window);
+
+        /* Set the timestamp values used to validate the timestamp of
+         * received packets. */
+        if ((TCP_HAS_TS(p)) && (ssn->server.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP)) {
+            ssn->client.last_ts = TCP_GET_TSVAL(p);
+            SCLogDebug("ssn %p: 4WHS ssn->client.last_ts %" PRIu32 " "
+                       "ssn->server.last_ts %" PRIu32 "",
+                    ssn, ssn->client.last_ts, ssn->server.last_ts);
+            ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
+            ssn->client.last_pkt_ts = SCTIME_SECS(p->ts);
+            if (ssn->client.last_ts == 0)
+                ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
+        } else {
+            ssn->server.last_ts = 0;
+            ssn->client.last_ts = 0;
+            ssn->server.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
+        }
+
+        ssn->server.last_ack = TCP_GET_ACK(p);
+        ssn->client.last_ack = ssn->client.isn + 1;
+
+        /** check for the presense of the ws ptr to determine if we
+         *  support wscale at all */
+        if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) && (TCP_HAS_WSCALE(p))) {
+            ssn->server.wscale = TCP_GET_WSCALE(p);
+        } else {
+            ssn->server.wscale = 0;
+        }
+
+        if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) && TCP_GET_SACKOK(p) == 1) {
+            ssn->flags |= STREAMTCP_FLAG_SACKOK;
+            SCLogDebug("ssn %p: SACK permitted for 4WHS session", ssn);
+        }
+
+        ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
+        ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
+        SCLogDebug("ssn %p: 4WHS ssn->client.next_win %" PRIu32 "", ssn, ssn->client.next_win);
+        SCLogDebug("ssn %p: 4WHS ssn->server.next_win %" PRIu32 "", ssn, ssn->server.next_win);
+        SCLogDebug("ssn %p: 4WHS ssn->client.isn %" PRIu32 ", "
+                   "ssn->client.next_seq %" PRIu32 ", "
+                   "ssn->client.last_ack %" PRIu32 " "
+                   "(ssn->server.last_ack %" PRIu32 ")",
+                ssn, ssn->client.isn, ssn->client.next_seq, ssn->client.last_ack,
+                ssn->server.last_ack);
+
+        /* done here */
+        return 0;
+    }
+
+    /* check for bad responses */
+    if (StateSynSentValidateTimestamp(ssn, p) == false) {
+        StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
+        return -1;
+    }
+
+    /* RST */
+    if (p->tcph->th_flags & TH_RST) {
+
+        if (!StreamTcpValidateRst(ssn, p))
+            return -1;
+
+        if (PKT_IS_TOSERVER(p)) {
+            if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn) && SEQ_EQ(TCP_GET_WINDOW(p), 0) &&
+                    SEQ_EQ(TCP_GET_ACK(p), (ssn->client.isn + 1))) {
+                SCLogDebug("ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
+                ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
+                StreamTcpCloseSsnWithReset(p, ssn);
+            }
+        } else {
+            ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
+            SCLogDebug("ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
+            StreamTcpCloseSsnWithReset(p, ssn);
+        }
+
+        /* FIN */
+    } else if (p->tcph->th_flags & TH_FIN) {
+        /** \todo */
 
     } else if (p->tcph->th_flags & TH_SYN) {
         SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent", ssn);
@@ -1781,27 +1968,23 @@ static int StreamTcpPacketStateSynSent(
                     ssn->client.isn, ssn->client.next_seq,
                     ssn->client.last_ack);
         } else if (PKT_IS_TOSERVER(p)) {
-            /*
-             * On retransmitted SYN packets, the timestamp value must be updated,
-             * to avoid dropping any SYN+ACK packets that respond to a retransmitted SYN
-             * with an updated timestamp in StateSynSentValidateTimestamp.
-             */
-            if ((ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP) && TCP_HAS_TS(p)) {
-                uint32_t ts_val = TCP_GET_TSVAL(p);
+            /* on a SYN resend we queue up the SYN's until a SYN/ACK moves the state
+             * to SYN_RECV. We update the ssn to the most recent, as it is most likely
+             * to be correct. */
 
-                // Check whether packets have been received in the correct order (only ever update)
-                if (ssn->client.last_ts < ts_val) {
-                    ssn->client.last_ts = ts_val;
-                    ssn->client.last_pkt_ts = SCTIME_SECS(p->ts);
-                }
+            TcpStateQueue syn_pkt, syn_ssn;
+            TcpStateQueueInitFromPktSyn(p, &syn_pkt);
+            TcpStateQueueInitFromSsnSyn(ssn, &syn_ssn);
 
-                SCLogDebug("ssn %p: Retransmitted SYN. Updated timestamp from packet %" PRIu64, ssn,
-                        p->pcap_cnt);
+            if (memcmp(&syn_pkt, &syn_ssn, sizeof(TcpStateQueue)) != 0) {
+                /* store the old session settings */
+                StreamTcp3whsStoreSyn(ssn, p);
+                SCLogDebug("ssn %p: Retransmitted SYN. Updating ssn from packet %" PRIu64
+                           ". Stored previous state",
+                        ssn, p->pcap_cnt);
             }
+            StreamTcp3whsStoreSynApplyToSsn(ssn, &syn_pkt);
         }
-
-        /** \todo check if it's correct or set event */
-
     } else if (p->tcph->th_flags & TH_ACK) {
         /* Handle the asynchronous stream, when we receive a  SYN packet
            and now instead of receiving a SYN/ACK we receive a ACK from the