return true;
}
+static void TcpStateQueueInitFromSsnSyn(const TcpSession *ssn, TcpStateQueue *q)
+{
+ BUG_ON(ssn->state != TCP_SYN_SENT); // TODO
+ memset(q, 0, sizeof(*q));
+
+ /* 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;
+
+ 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;
+ }
+
+ 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);
+}
+
+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));
+
+ q->win = TCP_GET_WINDOW(p);
+ q->pkt_ts = p->ts.tv_sec;
+
+ 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);
+ }
+
+#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
+}
+
+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));
+
+ q->win = TCP_GET_WINDOW(p);
+ q->pkt_ts = p->ts.tv_sec;
+
+ 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
+}
+
+/** \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;
+}
+
+/** \note the SEQ values *must* be the same */
+static int StreamTcp3whsStoreSyn(TcpSession *ssn, Packet *p)
+{
+ TcpStateQueue search;
+ TcpStateQueueInitFromSsnSyn(ssn, &search);
+
+ /* first see if this is already in our list */
+ if (ssn->queue != NULL && StreamTcp3whsFindSyn(ssn, &search) != NULL)
+ return 0;
+
+ 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;
+ }
+
+ if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpStateQueue)) == 0) {
+ SCLogDebug("ssn %p: =~ SYN queue failed: stream memcap reached", ssn);
+ return -1;
+ }
+
+ 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));
+
+ *q = search;
+ /* put in list */
+ q->next = ssn->queue;
+ ssn->queue = q;
+ ssn->queue_len++;
+ return 0;
+}
+
+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;
+
+ if (q->flags & STREAMTCP_QUEUE_FLAG_SACK) {
+ ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
+ } else {
+ ssn->flags &= ~STREAMTCP_FLAG_CLIENT_SACKOK;
+ }
+}
+
/**
* \brief Function to handle the TCP_SYN_SENT state. The function handles
* SYN, SYN/ACK, RST packets and correspondingly changes the connection
if (ssn == NULL)
return -1;
- SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ?
- "toclient":"toserver");
-
- /* 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 */
+ SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ? "toclient" : "toserver");
/* SYN/ACK */
- } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
+ 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);
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;
+ }
+
+ /* 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);
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 = p->ts.tv_sec;
- }
+ 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 istead of receving a SYN/ACK we receive a ACK from the