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.
pull/344/merge
Victor Julien 13 years ago
parent 00a691fc1b
commit 4c6463f378

@ -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

@ -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,

@ -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, },

@ -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__ */

@ -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();

@ -65,6 +65,7 @@ typedef struct TcpStreamCnf_ {
*/
uint32_t reassembly_inline_window;
uint8_t flags;
uint8_t max_synack_queued;
} TcpStreamCnf;
typedef struct StreamTcpThread_ {

@ -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

Loading…
Cancel
Save