diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index 42eb1c650b..5c32fa5dcb 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -270,6 +270,8 @@ int StreamTcpReassembleInit(char quiet) #ifdef DEBUG extern uint32_t applayererrors; extern uint32_t applayerhttperrors; +static uint32_t dbg_app_layer_gap; +static uint32_t dbg_app_layer_gap_candidate; #endif void StreamTcpReassembleFree(char quiet) @@ -309,6 +311,8 @@ void StreamTcpReassembleFree(char quiet) SCMutexDestroy(&segment_pool_cnt_mutex); SCLogInfo("applayererrors %u", applayererrors); SCLogInfo("applayerhttperrors %u", applayerhttperrors); + SCLogInfo("dbg_app_layer_gap %u", dbg_app_layer_gap); + SCLogInfo("dbg_app_layer_gap_candidate %u", dbg_app_layer_gap_candidate); #endif } @@ -460,9 +464,30 @@ static int ReassembleInsertSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ct TcpSegment *list_seg = stream->seg_list; TcpSegment *next_list_seg = NULL; + PrintList(stream->seg_list); + int ret_value = 0; char return_seg = FALSE; + /* before our ra_app_base_seq we don't insert it in our list, + * or ra_raw_base_seq if in stream gap state */ + if ((!(stream->flags & STREAMTCP_STREAM_FLAG_GAP) && + SEQ_LEQ((TCP_GET_SEQ(p)+p->payload_len),(stream->ra_app_base_seq+1))) || + (stream->flags & STREAMTCP_STREAM_FLAG_GAP && + SEQ_LEQ((TCP_GET_SEQ(p)+p->payload_len),(stream->ra_raw_base_seq+1)))) + { + SCLogDebug("not inserting: SEQ+payload %"PRIu32", last_ack %"PRIu32", " + "ra_app_base_seq %"PRIu32, (TCP_GET_SEQ(p)+p->payload_len), + stream->last_ack, stream->ra_app_base_seq); + return_seg = TRUE; + ret_value = -1; + goto end; + } + + SCLogDebug("SEQ+payload %"PRIu32", last_ack %"PRIu32", " + "ra_app_base_seq %"PRIu32, (TCP_GET_SEQ(p)+p->payload_len), + stream->last_ack, stream->ra_app_base_seq); + if (list_seg == NULL) { SCLogDebug("empty list, inserting seg %p seq %" PRIu32 ", " "len %" PRIu32 "", seg, seg->seq, seg->payload_len); @@ -1261,6 +1286,8 @@ static int HandleSegmentStartsAfterListSegment(ThreadVars *tv, TcpReassemblyThre { handle_beyond = TRUE; } + } else { + fill_gap = TRUE; } SCLogDebug("fill_gap %s, handle_beyond %s", fill_gap?"TRUE":"FALSE", @@ -1269,11 +1296,15 @@ static int HandleSegmentStartsAfterListSegment(ThreadVars *tv, TcpReassemblyThre if (fill_gap == TRUE) { /* if there is a gap after this list_seg we fill it now with a * new seg */ - SCLogDebug("filling gap: list_seg->next->seq %"PRIu32"", - list_seg->next?list_seg->next->seq:0); + if (list_seg->next != NULL) { + SCLogDebug("filling gap: list_seg->next->seq %"PRIu32"", + list_seg->next?list_seg->next->seq:0); - packet_length = list_seg->next->seq - (list_seg->seq + - list_seg->payload_len); + packet_length = list_seg->next->seq - (list_seg->seq + + list_seg->payload_len); + } else { + packet_length = seg->payload_len - overlap; + } if (packet_length > (seg->payload_len - overlap)) { packet_length = seg->payload_len - overlap; } @@ -1300,7 +1331,7 @@ static int HandleSegmentStartsAfterListSegment(ThreadVars *tv, TcpReassemblyThre StreamTcpSegmentDataReplace(new_seg, seg, new_seg->seq, new_seg->payload_len); - /*update the stream last_seg in case of removal of list_seg*/ + /* update the stream last_seg in case of removal of list_seg */ if (stream->seg_list_tail == list_seg) stream->seg_list_tail = new_seg; } @@ -1399,8 +1430,6 @@ int StreamTcpReassembleHandleSegmentHandleData(ThreadVars *tv, TcpReassemblyThre memcpy(seg->payload, p->payload, p->payload_len); seg->payload_len = p->payload_len; seg->seq = TCP_GET_SEQ(p); - seg->next = NULL; - seg->prev = NULL; stream->reassembly_depth += p->payload_len; if (ReassembleInsertSegment(tv, ra_ctx, stream, seg, p) != 0) { @@ -1470,9 +1499,12 @@ static void StreamTcpSetupMsg(TcpSession *ssn, TcpStream *stream, Packet *p, SCReturn; } -/** \brief Check the minimum size limits for reassembly. +/** + * \brief Check the minimum size limits for reassembly. + * * \retval 0 don't reassemble yet - * \retval 1 do reassemble */ + * \retval 1 do reassemble + */ static int StreamTcpReassembleRawCheckLimit(TcpSession *ssn, TcpStream *stream, Packet *p) { @@ -1708,14 +1740,7 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx, seg = next_seg; continue; } -/* - if ((ssn->flags & STREAMTCP_FLAG_APPPROTO_DETECTION_COMPLETED) && - (seg->flags & SEGMENTTCP_FLAG_APPLAYER_PROCESSED)) { - TcpSegment *next_seg = seg->next; - seg = next_seg; - continue; - } -*/ + /* If packets are fully before ra_base_seq, skip them. We do this * because we've reassembled up to the ra_base_seq point already, * so we won't do anything with segments before it anyway. */ @@ -1747,19 +1772,11 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx, /* we've run into a sequence gap */ if (SEQ_GT(seg->seq, next_seq)) { - /* see what the length of the gap is, gap length is seg->seq - - * (ra_base_seq +1) */ -#ifdef DEBUG - uint32_t gap_len = seg->seq - next_seq; - SCLogDebug("expected next_seq %" PRIu32 ", got %" PRIu32 " , " - "stream->last_ack %" PRIu32 ". Seq gap %" PRIu32"", - next_seq, seg->seq, stream->last_ack, gap_len); -#endif - - next_seq = seg->seq; - /* pass on data before the gap */ + /* first, pass on data before the gap */ if (data_len > 0) { + SCLogDebug("pre GAP data"); + STREAM_SET_FLAGS(ssn, stream, p, flags); /* if app layer protocol has not been detected till yet, @@ -1783,33 +1800,57 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx, } } - /* we need to update the ra_base_seq, if app layer proto has - been detected and we are setting new stream message. Otherwise - every smsg will be with flag STREAM_START set, which we - don't want :-) */ - if (ssn->flags & STREAMTCP_FLAG_APPPROTO_DETECTION_COMPLETED) { - stream->ra_app_base_seq = ra_base_seq; - } else { - stream->tmp_ra_app_base_seq = ra_base_seq; - } + /* don't conclude it's a gap straight away. If ra_base_seq is lower + * than last_ack - the window, we consider it a gap. */ + if (SEQ_GT((stream->last_ack - stream->window), ra_base_seq) || + ssn->state > TCP_ESTABLISHED) + { + /* see what the length of the gap is, gap length is seg->seq - + * (ra_base_seq +1) */ +#ifdef DEBUG + uint32_t gap_len = seg->seq - next_seq; + SCLogDebug("expected next_seq %" PRIu32 ", got %" PRIu32 " , " + "stream->last_ack %" PRIu32 ". Seq gap %" PRIu32"", + next_seq, seg->seq, stream->last_ack, gap_len); +#endif - /* We have missed the packet and end host has ack'd it, so - * IDS should advance it's ra_base_seq and should not consider this - * packet any longer, even if it is retransmitted, as end host will - * drop it anyway */ - ra_base_seq = seg->seq - 1; + next_seq = seg->seq; - /* send gap signal */ - STREAM_SET_FLAGS(ssn, stream, p, flags); - AppLayerHandleTCPData(&ra_ctx->dp_ctx, p->flow, ssn, - NULL, 0, flags|STREAM_GAP); - data_len = 0; + /* we need to update the ra_base_seq, if app layer proto has + been detected and we are setting new stream message. Otherwise + every smsg will be with flag STREAM_START set, which we + don't want :-) */ + if (ssn->flags & STREAMTCP_FLAG_APPPROTO_DETECTION_COMPLETED) { + stream->ra_app_base_seq = ra_base_seq; + } else { + stream->tmp_ra_app_base_seq = ra_base_seq; + } - /* set a GAP flag and make sure not bothering this stream anymore */ - stream->flags |= STREAMTCP_STREAM_FLAG_GAP; - /* flag reassembly as started, so the to_client part can start */ - ssn->flags |= STREAMTCP_FLAG_TOSERVER_REASSEMBLY_STARTED; - break; + /* We have missed the packet and end host has ack'd it, so + * IDS should advance it's ra_base_seq and should not consider this + * packet any longer, even if it is retransmitted, as end host will + * drop it anyway */ + ra_base_seq = seg->seq - 1; + + /* send gap signal */ + STREAM_SET_FLAGS(ssn, stream, p, flags); + AppLayerHandleTCPData(&ra_ctx->dp_ctx, p->flow, ssn, + NULL, 0, flags|STREAM_GAP); + data_len = 0; + + /* set a GAP flag and make sure not bothering this stream anymore */ + stream->flags |= STREAMTCP_STREAM_FLAG_GAP; + /* flag reassembly as started, so the to_client part can start */ + ssn->flags |= STREAMTCP_FLAG_TOSERVER_REASSEMBLY_STARTED; + + dbg_app_layer_gap++; + break; + } else { + SCLogDebug("possible GAP, but waiting to see if out of order " + "packets might solve that"); + dbg_app_layer_gap_candidate++; + break; + } } /* if the segment ends beyond ra_base_seq we need to consider it */ @@ -1848,6 +1889,12 @@ static int StreamTcpReassembleAppLayer (TcpReassemblyThreadCtx *ra_ctx, SCLogDebug("payload_offset is %"PRIu16", payload_len is %"PRIu16"" " and stream->last_ack is %"PRIu32"", payload_offset, payload_len, stream->last_ack); + + if (payload_len == 0) { + SCLogDebug("no payload_len, so bail out"); + break; + } + /* copy the data into the smsg */ uint16_t copy_size = sizeof(data) - data_len; if (copy_size > payload_len) { @@ -2093,10 +2140,13 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, SCLogDebug("seg %p", seg); /* if app layer protocol has been detected, then remove all the segments - which has been previously processed and reassembled */ + * which has been previously processed and reassembled + * + * If the stream is in GAP state the app layer flag won't be set */ if ((ssn->flags & STREAMTCP_FLAG_APPPROTO_DETECTION_COMPLETED) && (seg->flags & SEGMENTTCP_FLAG_RAW_PROCESSED) && - (seg->flags & SEGMENTTCP_FLAG_APPLAYER_PROCESSED)) + (seg->flags & SEGMENTTCP_FLAG_APPLAYER_PROCESSED || + stream->flags & STREAMTCP_STREAM_FLAG_GAP)) { SCLogDebug("segment(%p) of length %"PRIu16" has been processed," " so return it to pool", seg, seg->payload_len); @@ -2119,10 +2169,7 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, ra_base_seq or if they are beyond ra_base_seq, but the segment offset from which we need to copy in to smsg is beyond the stream->last_ack. As we are copying until the stream->last_ack only */ - - /** \todo we should probably not even insert them into the seglist */ - if (SEQ_LEQ((seg->seq + seg->payload_len), (ra_base_seq+1)) || - SEQ_LEQ(stream->last_ack, (ra_base_seq + (ra_base_seq - seg->seq)))) + if (SEQ_LT((seg->seq + seg->payload_len), ra_base_seq)) { SCLogDebug("removing pre ra_base_seq %"PRIu32" seg %p seq %"PRIu32"" " len %"PRIu16"", ra_base_seq, seg, seg->seq, @@ -2138,15 +2185,6 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, /* we've run into a sequence gap */ if (SEQ_GT(seg->seq, next_seq)) { - /* see what the length of the gap is, gap length is seg->seq - - * (ra_base_seq +1) */ - uint32_t gap_len = seg->seq - next_seq; - SCLogDebug("expected next_seq %" PRIu32 ", got %" PRIu32 " , " - "stream->last_ack %" PRIu32 ". Seq gap %" PRIu32"", - next_seq, seg->seq, stream->last_ack, gap_len); - - next_seq = seg->seq; - /* pass on pre existing smsg (if any) */ if (smsg != NULL && smsg->data.data_len > 0) { /* if app layer protocol has not been detected till yet, @@ -2159,30 +2197,49 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, smsg = NULL; } - if (smsg == NULL) { - smsg = StreamMsgGetFromPool(); + /* don't conclude it's a gap straight away. If ra_base_seq is lower + * than last_ack - the window, we consider it a gap. */ + if (SEQ_GT((stream->last_ack - stream->window), ra_base_seq) || + ssn->state > TCP_ESTABLISHED) + { + /* see what the length of the gap is, gap length is seg->seq - + * (ra_base_seq +1) */ + uint32_t gap_len = seg->seq - next_seq; + SCLogDebug("expected next_seq %" PRIu32 ", got %" PRIu32 " , " + "stream->last_ack %" PRIu32 ". Seq gap %" PRIu32"", + next_seq, seg->seq, stream->last_ack, gap_len); + + next_seq = seg->seq; + if (smsg == NULL) { - SCLogDebug("stream_msg_pool is empty"); - return -1; + smsg = StreamMsgGetFromPool(); + if (smsg == NULL) { + SCLogDebug("stream_msg_pool is empty"); + return -1; + } } - } - stream->ra_raw_base_seq = ra_base_seq; + stream->ra_raw_base_seq = ra_base_seq; - StreamTcpSetupMsg(ssn, stream, p, smsg); + StreamTcpSetupMsg(ssn, stream, p, smsg); - /* We have missed the packet and end host has ack'd it, so - * IDS should advance it's ra_base_seq and should not consider this - * packet any longer, even if it is retransmitted, as end host will - * drop it anyway */ - ra_base_seq = seg->seq - 1; + /* We have missed the packet and end host has ack'd it, so + * IDS should advance it's ra_base_seq and should not consider this + * packet any longer, even if it is retransmitted, as end host will + * drop it anyway */ + ra_base_seq = seg->seq - 1; - SCLogDebug("setting STREAM_GAP"); - smsg->flags |= STREAM_GAP; - smsg->gap.gap_size = gap_len; + SCLogDebug("setting STREAM_GAP"); + smsg->flags |= STREAM_GAP; + smsg->gap.gap_size = gap_len; - StreamMsgPutInQueue(ra_ctx->stream_q,smsg); - smsg = NULL; - smsg_offset = 0; + StreamMsgPutInQueue(ra_ctx->stream_q,smsg); + smsg = NULL; + smsg_offset = 0; + } else { + SCLogDebug("possible GAP, but waiting to see if out of order " + "packets might solve that"); + break; + } } /* if the segment ends beyond ra_base_seq we need to consider it */ @@ -2190,18 +2247,6 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, SCLogDebug("seg->seq %" PRIu32 ", seg->payload_len %" PRIu32 ", " "ra_base_seq %" PRIu32 "", seg->seq, seg->payload_len, ra_base_seq); - if (smsg == NULL) { - smsg = StreamMsgGetFromPool(); - if (smsg == NULL) { - SCLogDebug("stream_msg_pool is empty"); - return -1; - } - - smsg_offset = 0; - - StreamTcpSetupMsg(ssn, stream, p, smsg); - } - smsg->data.seq = ra_base_seq; /* handle segments partly before ra_base_seq */ if (SEQ_GT(ra_base_seq, seg->seq)) { @@ -2234,6 +2279,26 @@ static int StreamTcpReassembleRaw (TcpReassemblyThreadCtx *ra_ctx, SCLogDebug("payload_offset is %"PRIu16", payload_len is %"PRIu16"" " and stream->last_ack is %"PRIu32"", payload_offset, payload_len, stream->last_ack); + + if (payload_len == 0) { + SCLogDebug("no payload_len, so bail out"); + break; + } + + if (smsg == NULL) { + smsg = StreamMsgGetFromPool(); + if (smsg == NULL) { + SCLogDebug("stream_msg_pool is empty"); + return -1; + } + + smsg_offset = 0; + + StreamTcpSetupMsg(ssn, stream, p, smsg); + } + smsg->data.seq = ra_base_seq; + + /* copy the data into the smsg */ uint16_t copy_size = sizeof (smsg->data.data) - smsg_offset; if (copy_size > payload_len) { @@ -2781,8 +2846,9 @@ void StreamTcpSegmentDataCopy(TcpSegment *dst_seg, TcpSegment *src_seg) * \brief Function to get the segment of required length from the pool. * * \param len Length which tells the required size of needed segment. + * + * \retval seg Segment from the pool or NULL */ - TcpSegment* StreamTcpGetSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, uint16_t len) { uint16_t idx = segment_pool_idx[len]; @@ -2806,13 +2872,17 @@ TcpSegment* StreamTcpGetSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, /* Increment the counter to show that we are not able to serve the segment request due to memcap limit */ SCPerfCounterIncr(ra_ctx->counter_tcp_segment_memcap, tv->sc_perf_pca); - } else { + } + #ifdef DEBUG - SCMutexLock(&segment_pool_cnt_mutex); - segment_pool_cnt++; - SCMutexUnlock(&segment_pool_cnt_mutex); + SCMutexLock(&segment_pool_cnt_mutex); + segment_pool_cnt++; + SCMutexUnlock(&segment_pool_cnt_mutex); #endif - } + + seg->flags = 0; + seg->next = NULL; + seg->prev = NULL; return seg; } @@ -5088,6 +5158,7 @@ static int StreamTcpReassembleTest35(void) { p->payload_len = 142; stream.last_ack = 2257022285UL; stream.ra_raw_base_seq = 2257022172UL; + stream.ra_app_base_seq = 2257022172UL; if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &stream, p, &pq) == -1) { SCFree(p); @@ -5183,16 +5254,17 @@ static int StreamTcpReassembleTest36(void) { /** \test Test the bug 76 condition */ static int StreamTcpReassembleTest37(void) { TcpSession ssn; - Packet *p = SCMalloc(SIZE_OF_PACKET); - if (p == NULL) - return 0; Flow f; TCPHdr tcph; TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(); TcpStream stream; - memset(&stream, 0, sizeof (TcpStream)); - stream.os_policy = OS_POLICY_BSD; uint8_t packet[1460] = ""; + PacketQueue pq; + ThreadVars tv; + + Packet *p = SCMalloc(SIZE_OF_PACKET); + if (p == NULL) + return 0; StreamTcpInitConfig(TRUE); @@ -5202,15 +5274,16 @@ static int StreamTcpReassembleTest37(void) { StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOSERVER, 10); StreamMsgQueueSetMinChunkLen(FLOW_PKT_TOCLIENT, 10); - PacketQueue pq; - memset(&pq,0,sizeof(PacketQueue)); - memset(&ssn, 0, sizeof (TcpSession)); memset(p, 0, SIZE_OF_PACKET); p->pkt = (uint8_t *)(p + 1); + + memset(&stream, 0, sizeof (TcpStream)); + memset(&pq,0,sizeof(PacketQueue)); + memset(&ssn, 0, sizeof (TcpSession)); memset(&f, 0, sizeof (Flow)); memset(&tcph, 0, sizeof (TCPHdr)); - ThreadVars tv; memset(&tv, 0, sizeof (ThreadVars)); + FLOW_INITIALIZE(&f); f.protoctx = &ssn; p->src.family = AF_INET; @@ -5222,14 +5295,17 @@ static int StreamTcpReassembleTest37(void) { p->tcph = &tcph; p->flowflags = FLOW_PKT_TOSERVER; p->payload = packet; + stream.os_policy = OS_POLICY_BSD; p->tcph->th_seq = htonl(3061088537UL); p->tcph->th_ack = htonl(1729548549UL); p->payload_len = 1391; stream.last_ack = 3061091137UL; stream.ra_raw_base_seq = 3061091309UL; + stream.ra_app_base_seq = 3061091309UL; - if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &stream, p, &pq) == -1) { + /* pre base_seq, so should be rejected */ + if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &stream, p, &pq) != -1) { SCFree(p); return 0; } @@ -5239,6 +5315,7 @@ static int StreamTcpReassembleTest37(void) { p->payload_len = 1391; stream.last_ack = 3061091137UL; stream.ra_raw_base_seq = 3061091309UL; + stream.ra_app_base_seq = 3061091309UL; if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &stream, p, &pq) == -1) { SCFree(p); @@ -5250,6 +5327,7 @@ static int StreamTcpReassembleTest37(void) { p->payload_len = 1391; stream.last_ack = 3061091137UL; stream.ra_raw_base_seq = 3061091309UL; + stream.ra_app_base_seq = 3061091309UL; if (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &stream, p, &pq) == -1) { SCFree(p);