diff --git a/src/Makefile.am b/src/Makefile.am index 19120566cb..b60cd618c3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/decode-tcp.h b/src/decode-tcp.h index 6bea58f19b..7018716b76 100644 --- a/src/decode-tcp.h +++ b/src/decode-tcp.h @@ -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; \ diff --git a/src/stream-tcp-private.h b/src/stream-tcp-private.h index 5f28500597..a31a117916 100644 --- a/src/stream-tcp-private.h +++ b/src/stream-tcp-private.h @@ -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 diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index 7df62c0265..bbdbab01ea 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -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; diff --git a/src/stream-tcp-sack.c b/src/stream-tcp-sack.c new file mode 100644 index 0000000000..642487ffea --- /dev/null +++ b/src/stream-tcp-sack.c @@ -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 + * + * 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 +} diff --git a/src/stream-tcp-sack.h b/src/stream-tcp-sack.h new file mode 100644 index 0000000000..b17a538270 --- /dev/null +++ b/src/stream-tcp-sack.h @@ -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 + */ + +#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__*/ diff --git a/src/stream-tcp.c b/src/stream-tcp.c index 1358e18ab2..754de54d66 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -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 * \author Gurvinder Singh * + * 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 */ }