Implement SACK in the stream engine.

remotes/origin/master-1.1.x
Victor Julien 15 years ago
parent 6fc075d4ae
commit d0374ced38

@ -225,6 +225,7 @@ stream.c stream.h \
stream-tcp.c stream-tcp.h stream-tcp-private.h \
stream-tcp-reassemble.c stream-tcp-reassemble.h \
stream-tcp-inline.c stream-tcp-inline.h \
stream-tcp-sack.c stream-tcp-sack.h \
stream-tcp-util.c stream-tcp-util.h \
respond-reject.c respond-reject.h \
respond-reject-libnet11.h respond-reject-libnet11.c \

@ -91,17 +91,18 @@
/** macro for getting the wscale from the packet. */
#define TCP_GET_WSCALE(p) ((p)->tcpvars.ws ? (((*(uint8_t *)(p)->tcpvars.ws->data) <= TCP_WSCALE_MAX) ? (*(uint8_t *)((p)->tcpvars.ws->data)) : 0) : 0)
#define TCP_GET_SACKOK(p) ((p)->tcpvars.sackok ? 1 : 0)
#define TCP_GET_SACK_PTR(p) (p)->tcpvars.sack ? (p)->tcpvars.sack->data : NULL
#define TCP_GET_SACK_CNT(p) ((p)->tcpvars.sack ? (((p)->tcpvars.sack->len - 2) / 8) : 0)
#define TCP_GET_OFFSET(p) TCP_GET_RAW_OFFSET(p->tcph)
#define TCP_GET_HLEN(p) TCP_GET_OFFSET(p) << 2
#define TCP_GET_SRC_PORT(p) TCP_GET_RAW_SRC_PORT(p->tcph)
#define TCP_GET_DST_PORT(p) TCP_GET_RAW_DST_PORT(p->tcph)
#define TCP_GET_SEQ(p) TCP_GET_RAW_SEQ(p->tcph)
#define TCP_GET_ACK(p) TCP_GET_RAW_ACK(p->tcph)
#define TCP_GET_WINDOW(p) TCP_GET_RAW_WINDOW(p->tcph)
#define TCP_GET_URG_POINTER(p) TCP_GET_RAW_URG_POINTER(p->tcph)
#define TCP_GET_OFFSET(p) TCP_GET_RAW_OFFSET((p)->tcph)
#define TCP_GET_HLEN(p) (TCP_GET_OFFSET((p)) << 2)
#define TCP_GET_SRC_PORT(p) TCP_GET_RAW_SRC_PORT((p)->tcph)
#define TCP_GET_DST_PORT(p) TCP_GET_RAW_DST_PORT((p)->tcph)
#define TCP_GET_SEQ(p) TCP_GET_RAW_SEQ((p)->tcph)
#define TCP_GET_ACK(p) TCP_GET_RAW_ACK((p)->tcph)
#define TCP_GET_WINDOW(p) TCP_GET_RAW_WINDOW((p)->tcph)
#define TCP_GET_URG_POINTER(p) TCP_GET_RAW_URG_POINTER((p)->tcph)
#define TCP_ISSET_FLAG_FIN(p) ((p)->tcph->th_flags & TH_FIN)
#define TCP_ISSET_FLAG_SYN(p) ((p)->tcph->th_flags & TH_SYN)
@ -118,17 +119,22 @@ typedef struct TCPOpt_ {
uint8_t *data;
} TCPOpt;
typedef struct TCPOptSackRecord_ {
uint32_t le; /**< left edge, network order */
uint32_t re; /**< right edge, network order */
} TCPOptSackRecord;
typedef struct TCPHdr_
{
uint16_t th_sport; /* source port */
uint16_t th_dport; /* destination port */
uint32_t th_seq; /* sequence number */
uint32_t th_ack; /* acknowledgement number */
uint8_t th_offx2; /* offset and reserved */
uint8_t th_flags; /* pkt flags */
uint16_t th_win; /* pkt window */
uint16_t th_sum; /* checksum */
uint16_t th_urp; /* urgent pointer */
uint16_t th_sport; /**< source port */
uint16_t th_dport; /**< destination port */
uint32_t th_seq; /**< sequence number */
uint32_t th_ack; /**< acknowledgement number */
uint8_t th_offx2; /**< offset and reserved */
uint8_t th_flags; /**< pkt flags */
uint16_t th_win; /**< pkt window */
uint16_t th_sum; /**< checksum */
uint16_t th_urp; /**< urgent pointer */
} TCPHdr;
typedef struct TCPVars_
@ -140,10 +146,10 @@ typedef struct TCPVars_
TCPOpt tcp_opts[TCP_OPTMAX];
/* ptrs to commonly used and needed opts */
TCPOpt *sackok;
TCPOpt *ts;
TCPOpt *sack;
TCPOpt *sackok;
TCPOpt *ws;
TCPOpt *ts;
TCPOpt *mss;
} TCPVars;
@ -158,11 +164,10 @@ typedef struct TCPCache_ {
#define CLEAR_TCP_PACKET(p) { \
(p)->tcph = NULL; \
(p)->tcpvars.tcp_opt_len = 0; \
(p)->tcpvars.tcp_opt_cnt = 0; \
(p)->tcpvars.sackok = NULL; \
(p)->tcpvars.sack = NULL; \
(p)->tcpvars.ts = NULL; \
(p)->tcpvars.sack = NULL; \
(p)->tcpvars.sackok = NULL; \
(p)->tcpvars.ws = NULL; \
(p)->tcpvars.mss = NULL; \
(p)->tcpc.comp_csum = -1; \

@ -25,6 +25,13 @@
#define __STREAM_TCP_PRIVATE_H__
#include "decode.h"
typedef struct StreamTcpSackRecord_ {
uint32_t le; /**< left edge, host order */
uint32_t re; /**< right edge, host order */
struct StreamTcpSackRecord_ *next;
} StreamTcpSackRecord;
typedef struct TcpSegment_ {
uint8_t *payload;
uint16_t payload_len; /**< actual size of the payload */
@ -59,6 +66,9 @@ typedef struct TcpStream_ {
uint8_t os_policy; /**< target based OS policy used for reassembly and handling packets*/
uint16_t flags; /**< Flag specific to the stream e.g. Timestamp */
TcpSegment *seg_list_tail; /**< Last segment in the reassembled stream seg list*/
StreamTcpSackRecord *sack_head;
StreamTcpSackRecord *sack_tail;
} TcpStream;
/* from /usr/include/netinet/tcp.h */
@ -107,7 +117,10 @@ enum
#define STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT 0x0200
/** Flag to indicate that this stream direction has reassembled chunks */
#define STREAMTCP_FLAG_TOSERVER_REASSEMBLY_STARTED 0x0400
/** Flag to indicate the client (SYN pkt) permits SACK */
#define STREAMTCP_FLAG_CLIENT_SACKOK 0x0800
/** Flag to indicate both sides of the session permit SACK (SYN + SYN/ACK) */
#define STREAMTCP_FLAG_SACKOK 0x1000
/*
* Per STREAM flags

@ -2709,6 +2709,7 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx,
{
SCLogDebug("segment(%p) of length %"PRIu16" has been processed,"
" so return it to pool", seg, seg->payload_len);
next_seq = seg->seq + seg->payload_len;
TcpSegment *next_seg = seg->next;
seg = next_seg;
continue;
@ -2783,6 +2784,7 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx,
data_len = 0;
/* set a GAP flag and make sure not bothering this stream anymore */
SCLogDebug("STREAMTCP_STREAM_FLAG_GAP set");
stream->flags |= STREAMTCP_STREAM_FLAG_GAP;
/* flag reassembly as started, so the to_client part can start */
ssn->flags |= STREAMTCP_FLAG_TOSERVER_REASSEMBLY_STARTED;

@ -0,0 +1,851 @@
/* Copyright (C) 2007-2011 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Victor Julien <victor@inliniac.net>
*
* Stream engine TCP SACK handling.
*/
#include "suricata-common.h"
#include "stream-tcp-private.h"
#include "stream-tcp-sack.h"
#include "util-unittest.h"
#ifdef DEBUG
void StreamTcpSackPrintList(TcpStream *stream) {
StreamTcpSackRecord *rec = stream->sack_head;
for (; rec != NULL; rec = rec->next) {
SCLogDebug("record %8u - %8u", rec->le, rec->re);
}
}
#endif /* DEBUG */
/**
* \brief insert a SACK range
*
* \param le left edge in host order
* \param re right edge in host order
*
* \retval 0 all is good
* \retval -1 error
*/
static int StreamTcpSackInsertRange(TcpStream *stream, uint32_t le, uint32_t re) {
SCLogDebug("le %u, re %u", le, re);
#ifdef DEBUG
StreamTcpSackPrintList(stream);
#endif
if (stream->sack_head != NULL) {
StreamTcpSackRecord *rec;
for (rec = stream->sack_head; rec != NULL; rec = rec->next) {
SCLogDebug("rec %p, le %u, re %u", rec, rec->le, rec->re);
if (SEQ_LT(le, rec->le)) {
SCLogDebug("SEQ_LT(le, rec->le)");
if (SEQ_LT(re, rec->le)) {
SCLogDebug("SEQ_LT(re, rec->le)");
// entirely before, prepend
StreamTcpSackRecord *stsr = SCMalloc(sizeof(StreamTcpSackRecord));
if (stsr == NULL) {
SCReturnInt(-1);
}
stsr->le = le;
stsr->re = re;
stsr->next = stream->sack_head;
stream->sack_head = stsr;
goto end;
} else if (SEQ_EQ(re, rec->le)) {
SCLogDebug("SEQ_EQ(re, rec->le)");
// starts before, ends on rec->le, expand
rec->le = le;
} else if (SEQ_GT(re, rec->le)) {
SCLogDebug("SEQ_GT(re, rec->le)");
// starts before, ends beyond rec->le
if (SEQ_LEQ(re, rec->re)) {
SCLogDebug("SEQ_LEQ(re, rec->re)");
// ends before rec->re, expand
rec->le = le;
} else { // implied if (re > rec->re)
SCLogDebug("implied if (re > rec->re), le set to %u", rec->re);
le = rec->re;
continue;
}
}
} else if (SEQ_EQ(le, rec->le)) {
SCLogDebug("SEQ_EQ(le, rec->le)");
if (SEQ_LEQ(re, rec->re)) {
SCLogDebug("SEQ_LEQ(re, rec->re)");
// new record fully overlapped
SCReturnInt(0);
} else { // implied re > rec->re
SCLogDebug("implied re > rec->re");
if (rec->next != NULL) {
if (SEQ_LEQ(re, rec->next->le)) {
rec->re = re;
goto end;
} else {
rec->re = rec->next->le;
le = rec->next->le;
SCLogDebug("le is now %u", le);
continue;
}
} else {
rec->re = re;
goto end;
}
}
} else { // implied (le > rec->le)
SCLogDebug("implied (le > rec->le)");
if (SEQ_LT(le, rec->re)) {
SCLogDebug("SEQ_LT(le, rec->re))");
// new record fully overlapped
if (SEQ_GT(re, rec->re)) {
SCLogDebug("SEQ_GT(re, rec->re)");
if (rec->next != NULL) {
if (SEQ_LEQ(re, rec->next->le)) {
rec->re = re;
goto end;
} else {
rec->re = rec->next->le;
le = rec->next->le;
continue;
}
} else {
rec->re = re;
goto end;
}
le = rec->re;
//int r = StreamTcpSackInsertRange(stream, rec->re+1, re);
//SCReturnInt(r);
continue;
}
SCLogDebug("new range fully overlapped");
SCReturnInt(0);
} else if (SEQ_EQ(le, rec->re)) {
SCLogDebug("here");
// new record fully overlapped
//int r = StreamTcpSackInsertRange(stream, rec->re+1, re);
//SCReturnInt(r);
le = rec->re;
continue;
} else { /* implied le > rec->re */
SCLogDebug("implied le > rec->re");
if (rec->next == NULL) {
SCLogDebug("rec->next == NULL");
StreamTcpSackRecord *stsr = SCMalloc(sizeof(StreamTcpSackRecord));
if (stsr == NULL) {
SCReturnInt(-1);
}
stsr->le = le;
stsr->re = re;
stsr->next = NULL;
stream->sack_tail->next = stsr;
stream->sack_tail = stsr;
goto end;
} else {
SCLogDebug("implied rec->next != NULL");
if (SEQ_LT(le, rec->next->le) && SEQ_LT(re, rec->next->le)) {
SCLogDebug("SEQ_LT(le, rec->next->le) && SEQ_LT(re, rec->next->le)");
StreamTcpSackRecord *stsr = SCMalloc(sizeof(StreamTcpSackRecord));
if (stsr == NULL) {
SCReturnInt(-1);
}
stsr->le = le;
stsr->re = re;
stsr->next = rec->next;
rec->next = stsr;
} else if (SEQ_LT(le, rec->next->le) && SEQ_GEQ(re, rec->next->le)) {
SCLogDebug("SEQ_LT(le, rec->next->le) && SEQ_GEQ(re, rec->next->le)");
StreamTcpSackRecord *stsr = SCMalloc(sizeof(StreamTcpSackRecord));
if (stsr == NULL) {
SCReturnInt(-1);
}
stsr->le = le;
stsr->re = rec->next->le;
stsr->next = rec->next;
rec->next = stsr;
le = rec->next->le;
}
}
}
}
}
} else {
SCLogDebug("implied empty list");
StreamTcpSackRecord *stsr = SCMalloc(sizeof(StreamTcpSackRecord));
if (stsr == NULL) {
SCReturnInt(-1);
}
stsr->le = le;
stsr->re = re;
stsr->next = NULL;
stream->sack_head = stsr;
stream->sack_tail = stsr;
}
end:
SCReturnInt(0);
}
/**
* \brief Update stream with SACK records from a TCP packet.
*
* \param stream The stream to update.
* \param p packet to get the SACK records from
*
* \retval -1 error
* \retval 0 ok
*/
int StreamTcpSackUpdatePacket(TcpStream *stream, Packet *p) {
int records = TCP_GET_SACK_CNT(p);
int record = 0;
TCPOptSackRecord *sack_rec = (TCPOptSackRecord *)(TCP_GET_SACK_PTR(p));
for (record = 0; record < records; record++) {
SCLogDebug("%p last_ack %u, left edge %u, right edge %u", sack_rec,
stream->last_ack, ntohl(sack_rec->le), ntohl(sack_rec->re));
if (SEQ_LEQ(ntohl(sack_rec->re), stream->last_ack)) {
SCLogDebug("record before last_ack");
goto next;
}
/** \todo need a metric to a check for a right edge limit */
/*
if (SEQ_GT(ntohl(sack_rec->re), stream->next_seq)) {
SCLogDebug("record beyond next_seq %u", stream->next_seq);
goto next;
}
*/
if (SEQ_GEQ(ntohl(sack_rec->le), ntohl(sack_rec->re))) {
SCLogDebug("invalid record: le >= re");
goto next;
}
if (StreamTcpSackInsertRange(stream, ntohl(sack_rec->le),
ntohl(sack_rec->re)) == -1)
{
SCReturnInt(-1);
}
next:
sack_rec++;
}
#ifdef DEBUG
StreamTcpSackPrintList(stream);
#endif
SCReturnInt(0);
}
void StreamTcpSackPruneList(TcpStream *stream) {
SCEnter();
StreamTcpSackRecord *rec = stream->sack_head;
StreamTcpSackRecord *prev = NULL;
while (rec != NULL) {
if (SEQ_LT(rec->re, stream->last_ack)) {
SCLogDebug("removing le %u re %u", rec->le, rec->re);
// fully before last_ack, remove
if (prev != NULL) {
if (rec == stream->sack_tail) {
stream->sack_tail = prev;
prev->next = NULL;
SCFree(rec);
break;
} else {
prev->next = rec->next;
SCFree(rec);
rec = prev->next;
continue;
}
} else {
if (rec->next != NULL) {
stream->sack_head = rec->next;
SCFree(rec);
rec = stream->sack_head;
continue;
} else {
stream->sack_head = NULL;
stream->sack_tail = NULL;
SCFree(rec);
break;
}
}
} else if (SEQ_LT(rec->le, stream->last_ack)) {
SCLogDebug("adjusting record to le %u re %u", rec->le, rec->re);
/* last ack inside this record, update */
rec->le = stream->last_ack;
break;
} else {
SCLogDebug("record beyond last_ack, nothing to do. Bailing out.");
break;
}
}
#ifdef DEBUG
StreamTcpSackPrintList(stream);
#endif
SCReturn;
}
/**
* \brief Free SACK list from a stream
*
* \param stream Stream to cleanup
*/
void StreamTcpSackFreeList(TcpStream *stream) {
SCEnter();
StreamTcpSackRecord *rec = stream->sack_head;
StreamTcpSackRecord *next = NULL;
while (rec != NULL) {
next = rec->next;
SCFree(next);
rec = next;
}
stream->sack_head = NULL;
stream->sack_tail = NULL;
SCReturn;
}
#ifdef UNITTESTS
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest01 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 1, 10);
StreamTcpSackInsertRange(&stream, 10, 20);
StreamTcpSackInsertRange(&stream, 10, 20);
StreamTcpSackInsertRange(&stream, 1, 20);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 1 || stream.sack_head->re != 20) {
printf("list in weird state, head le %u, re %u: ",
stream.sack_head->le, stream.sack_head->re);
goto end;
}
if (StreamTcpSackedSize(&stream) != 19) {
printf("size should be 19, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest02 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 10, 20);
StreamTcpSackInsertRange(&stream, 1, 20);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 1 || stream.sack_head->re != 20) {
printf("list in weird state, head le %u, re %u: ",
stream.sack_head->le, stream.sack_head->re);
goto end;
}
if (StreamTcpSackedSize(&stream) != 19) {
printf("size should be 19, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest03 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 10, 20);
StreamTcpSackInsertRange(&stream, 5, 15);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
StreamTcpSackInsertRange(&stream, 15, 25);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 5) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 20) {
printf("size should be 20, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest04 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 20);
StreamTcpSackInsertRange(&stream, 30, 50);
StreamTcpSackInsertRange(&stream, 10, 25);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 45) {
printf("size should be 45, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest05 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 20);
StreamTcpSackInsertRange(&stream, 30, 50);
StreamTcpSackInsertRange(&stream, 10, 35);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 50) {
printf("size should be 50, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the insertion of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest06 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 9);
StreamTcpSackInsertRange(&stream, 11, 19);
StreamTcpSackInsertRange(&stream, 21, 29);
StreamTcpSackInsertRange(&stream, 31, 39);
StreamTcpSackInsertRange(&stream, 0, 40);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest07 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 9);
StreamTcpSackInsertRange(&stream, 11, 19);
StreamTcpSackInsertRange(&stream, 21, 29);
StreamTcpSackInsertRange(&stream, 31, 39);
StreamTcpSackInsertRange(&stream, 0, 40);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 10;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 30) {
printf("size should be 30, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest08 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 9);
StreamTcpSackInsertRange(&stream, 11, 19);
StreamTcpSackInsertRange(&stream, 21, 29);
StreamTcpSackInsertRange(&stream, 31, 39);
StreamTcpSackInsertRange(&stream, 0, 40);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 41;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 0) {
printf("size should be 0, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest09 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 0, 9);
StreamTcpSackInsertRange(&stream, 11, 19);
StreamTcpSackInsertRange(&stream, 21, 29);
StreamTcpSackInsertRange(&stream, 31, 39);
StreamTcpSackInsertRange(&stream, 0, 40);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 0) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 39;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 1) {
printf("size should be 1, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest10 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 100, 119);
StreamTcpSackInsertRange(&stream, 111, 119);
StreamTcpSackInsertRange(&stream, 121, 129);
StreamTcpSackInsertRange(&stream, 131, 139);
StreamTcpSackInsertRange(&stream, 100, 140);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 100) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 99;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest11 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 100, 119);
StreamTcpSackInsertRange(&stream, 111, 119);
StreamTcpSackInsertRange(&stream, 121, 129);
StreamTcpSackInsertRange(&stream, 131, 139);
StreamTcpSackInsertRange(&stream, 101, 140);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 100) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 99;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 40) {
printf("size should be 40, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
/**
* \test Test the pruning of SACK ranges.
*
* \retval On success it returns 1 and on failure 0.
*/
static int StreamTcpSackTest12 (void) {
TcpStream stream;
int retval = 0;
memset(&stream, 0, sizeof(stream));
StreamTcpSackInsertRange(&stream, 800, 1000);
StreamTcpSackInsertRange(&stream, 700, 900);
StreamTcpSackInsertRange(&stream, 600, 800);
StreamTcpSackInsertRange(&stream, 500, 700);
StreamTcpSackInsertRange(&stream, 100, 600);
#ifdef DEBUG
StreamTcpSackPrintList(&stream);
#endif /* DEBUG */
if (stream.sack_head->le != 100) {
goto end;
}
if (StreamTcpSackedSize(&stream) != 900) {
printf("size should be 900, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
StreamTcpSackInsertRange(&stream, 0, 1000);
if (StreamTcpSackedSize(&stream) != 1000) {
printf("size should be 1000, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
stream.last_ack = 500;
StreamTcpSackPruneList(&stream);
if (StreamTcpSackedSize(&stream) != 500) {
printf("size should be 500, is %u: ", StreamTcpSackedSize(&stream));
goto end;
}
retval = 1;
end:
SCReturnInt(retval);
}
#endif /* UNITTESTS */
void StreamTcpSackRegisterTests (void) {
#ifdef UNITTESTS
UtRegisterTest("StreamTcpSackTest01 -- Insertion",
StreamTcpSackTest01, 1);
UtRegisterTest("StreamTcpSackTest02 -- Insertion",
StreamTcpSackTest02, 1);
UtRegisterTest("StreamTcpSackTest03 -- Insertion",
StreamTcpSackTest03, 1);
UtRegisterTest("StreamTcpSackTest04 -- Insertion",
StreamTcpSackTest04, 1);
UtRegisterTest("StreamTcpSackTest05 -- Insertion",
StreamTcpSackTest05, 1);
UtRegisterTest("StreamTcpSackTest06 -- Insertion",
StreamTcpSackTest06, 1);
UtRegisterTest("StreamTcpSackTest07 -- Pruning",
StreamTcpSackTest07, 1);
UtRegisterTest("StreamTcpSackTest08 -- Pruning",
StreamTcpSackTest08, 1);
UtRegisterTest("StreamTcpSackTest09 -- Pruning",
StreamTcpSackTest09, 1);
UtRegisterTest("StreamTcpSackTest10 -- Pruning",
StreamTcpSackTest10, 1);
UtRegisterTest("StreamTcpSackTest11 -- Insertion && Pruning",
StreamTcpSackTest11, 1);
UtRegisterTest("StreamTcpSackTest12 -- Insertion && Pruning",
StreamTcpSackTest12, 1);
#endif
}

@ -0,0 +1,62 @@
/* Copyright (C) 2007-2011 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Victor Julien <victor@inliniac.net>
*/
#ifndef __STREAM_TCP_SACK_H__
#define __STREAM_TCP_SACK_H__
#include "suricata-common.h"
#include "util-optimize.h"
/**
* \brief Get the size of the SACKed ranges
*
* \param stream Stream to get the size for.
*
* \retval size the size
*
* Optimized for case where SACK is not in use in the
* stream, as it *should* only be used in case of packet
* loss.
*/
static inline uint32_t StreamTcpSackedSize(TcpStream *stream) {
if (likely(stream->sack_head == NULL)) {
SCReturnUInt(0U);
} else {
uint32_t size = 0;
StreamTcpSackRecord *rec = NULL;
for (rec = stream->sack_head; rec != NULL; rec = rec->next) {
size += (rec->re - rec->le);
}
SCReturnUInt(size);
}
}
int StreamTcpSackUpdatePacket(TcpStream *, Packet *);
void StreamTcpSackPruneList(TcpStream *);
void StreamTcpSackFreeList(TcpStream *);
void StreamTcpSackRegisterTests (void);
#endif /* __STREAM_TCP_SACK_H__*/

@ -1,4 +1,4 @@
/* Copyright (C) 2007-2010 Open Information Security Foundation
/* Copyright (C) 2007-2011 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
@ -21,6 +21,8 @@
* \author Victor Julien <victor@inliniac.net>
* \author Gurvinder Singh <gurvindersinghdahiya@gmail.com>
*
* TCP stream tracking and reassembly engine.
*
* \todo - 4WHS: what if after the 2nd SYN we turn out to be normal 3WHS anyway?
*/
@ -50,6 +52,7 @@
#include "stream-tcp-reassemble.h"
#include "stream-tcp.h"
#include "stream-tcp-inline.h"
#include "stream-tcp-sack.h"
#include "stream-tcp-util.h"
#include "stream.h"
@ -171,7 +174,8 @@ int StreamTcpCheckMemcap(uint32_t size) {
SCReturnInt(ret);
}
/** \brief Function to return the stream back to the pool. It returns the
/**
* \brief Function to return the stream back to the pool. It returns the
* segments in the stream to the segment pool.
*
* This function is called when the flow is destroyed, so it should free
@ -194,6 +198,9 @@ void StreamTcpSessionClear(void *ssnptr)
//AppLayerParserCleanupState(ssn);
StreamTcpSackFreeList(&ssn->client);
StreamTcpSackFreeList(&ssn->server);
/* if we have (a) smsg(s), return to the pool */
smsg = ssn->toserver_smsg_head;
while(smsg != NULL) {
@ -230,7 +237,8 @@ void StreamTcpSessionClear(void *ssnptr)
SCReturn;
}
/** \brief Function to return the stream segments back to the pool.
/**
* \brief Function to return the stream segments back to the pool.
*
* We don't clear out the app layer storage here as that is under protection
* of the "use_cnt" reference counter in the flow. This function is called
@ -598,6 +606,7 @@ void StreamTcpSetEvent(Packet *p, uint8_t e) {
if (SEQ_GT((ack), (stream)->last_ack)) { \
(stream)->last_ack = (ack); \
SCLogDebug("ssn %p: last_ack set to %"PRIu32, (ssn), (stream)->last_ack); \
StreamTcpSackPruneList((stream)); \
} \
}
@ -609,8 +618,9 @@ void StreamTcpSetEvent(Packet *p, uint8_t e) {
* \param win window value to test and set
*/
#define StreamTcpUpdateNextWin(ssn, stream, win) { \
if (SEQ_GT((win), (stream)->next_win)) { \
(stream)->next_win = (win); \
uint32_t sacked_size__ = StreamTcpSackedSize((stream)); \
if (SEQ_GT(((win) + sacked_size__), (stream)->next_win)) { \
(stream)->next_win = ((win) + sacked_size__); \
SCLogDebug("ssn %p: next_win set to %"PRIu32, (ssn), (stream)->next_win); \
} \
}
@ -681,6 +691,11 @@ static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
ssn->server.wscale = TCP_GET_WSCALE(p);
}
if (TCP_GET_SACKOK(p) == 1) {
ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
SCLogDebug("ssn %p: SACK permited on SYN packet", ssn);
}
SCLogDebug("ssn %p: ssn->client.isn %" PRIu32 ", "
"ssn->client.next_seq %" PRIu32 ", ssn->client.last_ack "
"%"PRIu32"", ssn, ssn->client.isn, ssn->client.next_seq,
@ -761,6 +776,12 @@ static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
ssn->client.last_ts = 0;
}
if (TCP_GET_SACKOK(p) == 1) {
ssn->flags |= STREAMTCP_FLAG_SACKOK;
SCLogDebug("ssn %p: SYN/ACK with SACK permitted, assuming "
"SACK permitted for both sides", ssn);
}
break;
/* Handle SYN/ACK and 3WHS shake missed together as it is almost
* similar. */
@ -842,6 +863,8 @@ static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq);
ssn->flags |= STREAMTCP_FLAG_SACKOK;
SCLogDebug("ssn %p: assuming SACK permitted for both sides", ssn);
break;
case TH_RST:
case TH_RST|TH_ACK:
@ -938,6 +961,15 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
if (p->tcpvars.ws != NULL) {
ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
ssn->server.wscale = TCP_GET_WSCALE(p);
} else {
ssn->flags &= ~STREAMTCP_FLAG_SERVER_WSCALE;
ssn->server.wscale = 0;
}
if (TCP_GET_SACKOK(p) == 1) {
ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
} else {
ssn->flags &= ~STREAMTCP_FLAG_CLIENT_SACKOK;
}
SCLogDebug("ssn %p: 4WHS ssn->server.isn %" PRIu32 ", "
@ -1031,6 +1063,12 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
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,
@ -1110,6 +1148,12 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
ssn->client.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 session", ssn);
}
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 "", ssn,
@ -1182,8 +1226,8 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
ssn->client.wscale = TCP_WSCALE_MAX;
ssn->server.wscale = TCP_WSCALE_MAX;
/*Set the timestamp values used to validate the timestamp of received
packets.*/
/* Set the timestamp values used to validate the timestamp of
* received packets.*/
if (p->tcpvars.ts != NULL &&
(ssn->client.flags & STREAMTCP_FLAG_TIMESTAMP))
{
@ -1196,6 +1240,9 @@ static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
ssn->client.flags &= ~STREAMTCP_FLAG_ZERO_TIMESTAMP;
}
if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) {
ssn->flags |= STREAMTCP_FLAG_SACKOK;
}
break;
case TH_RST:
case TH_RST|TH_ACK:
@ -1414,6 +1461,7 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
* other than assume that it's set to the max value: 14 */
ssn->server.wscale = TCP_WSCALE_MAX;
ssn->client.wscale = TCP_WSCALE_MAX;
ssn->flags |= STREAMTCP_FLAG_SACKOK;
}
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
@ -1448,6 +1496,7 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
* other than assume that it's set to the max value: 14 */
ssn->server.wscale = TCP_WSCALE_MAX;
ssn->client.wscale = TCP_WSCALE_MAX;
ssn->flags |= STREAMTCP_FLAG_SACKOK;
}
SCLogDebug("ssn %p: synrecv => Asynchronous stream, packet SEQ"
@ -1648,7 +1697,7 @@ static int HandleEstablishedPacketToServer(ThreadVars *tv, TcpSession *ssn, Pack
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM) ||
ssn->flags & STREAMTCP_FLAG_ASYNC)
{
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win"
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win "
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->client.next_win);
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
@ -1667,6 +1716,8 @@ static int HandleEstablishedPacketToServer(ThreadVars *tv, TcpSession *ssn, Pack
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
ssn->server.next_seq = TCP_GET_ACK(p);
StreamTcpSackUpdatePacket(&ssn->server, p);
/* update next_win */
StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window));
@ -1679,8 +1730,9 @@ static int HandleEstablishedPacketToServer(ThreadVars *tv, TcpSession *ssn, Pack
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
ssn->client.last_ack, ssn->client.next_win,
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win);
(TCP_GET_SEQ(p) + p->payload_len) - ssn->client.next_win);
SCLogDebug("ssn %p: window %u sacked %u", ssn, ssn->client.window,
StreamTcpSackedSize(&ssn->client));
StreamTcpSetEvent(p, STREAM_EST_PACKET_OUT_OF_WINDOW);
return -1;
}
@ -1721,8 +1773,8 @@ static int HandleEstablishedPacketToClient(ThreadVars *tv, TcpSession *ssn, Pack
ssn->server.window = TCP_GET_WINDOW(p);
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
ssn->flags &= ~STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
SCLogDebug("ssn %p: adjusted midstream ssn->server.next_win to"
" %" PRIu32 "", ssn, ssn->server.next_win);
SCLogDebug("ssn %p: adjusted midstream ssn->server.next_win to "
"%" PRIu32 "", ssn, ssn->server.next_win);
}
if (!(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len), ssn->server.last_ack))) {
@ -1756,8 +1808,8 @@ static int HandleEstablishedPacketToClient(ThreadVars *tv, TcpSession *ssn, Pack
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->server.next_win) ||
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM) ||
(ssn->flags & STREAMTCP_FLAG_ASYNC)) {
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win"
" %" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win "
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
SCLogDebug("ssn %p: ssn->client.window %"PRIu32"", ssn,
ssn->client.window);
@ -1773,6 +1825,8 @@ static int HandleEstablishedPacketToClient(ThreadVars *tv, TcpSession *ssn, Pack
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
ssn->client.next_seq = TCP_GET_ACK(p);
StreamTcpSackUpdatePacket(&ssn->client, p);
StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window));
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq);
@ -3405,12 +3459,12 @@ int StreamTcpValidateChecksum(Packet *p)
p->tcpc.comp_csum = TCPCalculateChecksum((uint16_t *)&(p->ip4h->ip_src),
(uint16_t *)p->tcph,
(p->payload_len +
p->tcpvars.hlen) );
TCP_GET_HLEN(p)));
} else if (PKT_IS_IPV6(p)) {
p->tcpc.comp_csum = TCPV6CalculateChecksum((uint16_t *)&(p->ip6h->ip6_src),
(uint16_t *)p->tcph,
(p->payload_len +
p->tcpvars.hlen) );
TCP_GET_HLEN(p)));
}
}
@ -8536,6 +8590,7 @@ void StreamTcpRegisterTests (void) {
/* set up the reassembly tests as well */
StreamTcpReassembleRegisterTests();
StreamTcpSackRegisterTests ();
#endif /* UNITTESTS */
}

Loading…
Cancel
Save