]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
stream: improve gap handling with 'incomplete'
authorVictor Julien <victor@inliniac.net>
Sun, 17 May 2020 12:22:59 +0000 (14:22 +0200)
committerVictor Julien <victor@inliniac.net>
Mon, 27 Jul 2020 15:07:21 +0000 (17:07 +0200)
Make sure stream requiring more data because of 'incomplete' records
properly move ahead if there is a GAP in the window of required data.

src/stream-tcp-reassemble.c

index 759be814b6af7041bc7ac7246725b41438804ebf..43bca85547adffa189aba1f1ddb572582b22a12f 100644 (file)
@@ -885,16 +885,41 @@ static StreamingBufferBlock *GetBlock(StreamingBuffer *sb, const uint64_t offset
     return NULL;
 }
 
+static inline uint64_t GetAbsLastAck(const TcpStream *stream)
+{
+    if (STREAM_LASTACK_GT_BASESEQ(stream)) {
+        return STREAM_BASE_OFFSET(stream) +
+            (stream->last_ack - stream->base_seq);
+    } else {
+        return STREAM_BASE_OFFSET(stream);
+    }
+}
+
+static inline bool GapAhead(TcpStream *stream, StreamingBufferBlock *cur_blk)
+{
+    StreamingBufferBlock *nblk = SBB_RB_NEXT(cur_blk);
+    if (nblk && (cur_blk->offset + cur_blk->len < nblk->offset) &&
+            GetAbsLastAck(stream) >= (cur_blk->offset + cur_blk->len)) {
+        return true;
+    }
+    return false;
+}
+
 /** \internal
  *
  *  Get buffer, or first part of the buffer if data gaps exist.
  *
  *  \brief get stream data from offset
- *  \param offset stream offset */
-static void GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data_len, uint64_t offset)
+ *  \param offset stream offset
+ *  \param check_for_gap check if there is a gap ahead. Optional as it is only
+ *                       needed for app-layer incomplete support.
+ *  \retval bool pkt loss ahead */
+static bool GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data_len,
+        uint64_t offset, const bool check_for_gap)
 {
     const uint8_t *mydata;
     uint32_t mydata_len;
+    bool gap_ahead = false;
 
     if (RB_EMPTY(&stream->sb.sbb_tree)) {
         SCLogDebug("getting one blob");
@@ -908,7 +933,7 @@ static void GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data
         if (blk == NULL) {
             *data = NULL;
             *data_len = 0;
-            return;
+            return false;
         }
 
         /* block at expected offset */
@@ -916,6 +941,8 @@ static void GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data
 
             StreamingBufferSBBGetData(&stream->sb, blk, data, data_len);
 
+            gap_ahead = check_for_gap && GapAhead(stream, blk);
+
         /* block past out offset */
         } else if (blk->offset > offset) {
             SCLogDebug("gap, want data at offset %"PRIu64", "
@@ -930,11 +957,15 @@ static void GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data
                     offset, blk->offset, blk->len);
             StreamingBufferSBBGetDataAtOffset(&stream->sb, blk, data, data_len, offset);
             SCLogDebug("data %p, data_len %u", *data, *data_len);
+
+            gap_ahead = check_for_gap && GapAhead(stream, blk);
+
         } else {
             *data = NULL;
             *data_len = 0;
         }
     }
+    return gap_ahead;
 }
 
 /** \internal
@@ -1047,9 +1078,12 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
 
     const uint8_t *mydata;
     uint32_t mydata_len;
+    bool gap_ahead = false;
 
     while (1) {
-        GetAppBuffer(*stream, &mydata, &mydata_len, app_progress);
+        bool check_for_gap_ahead = ((*stream)->data_required > 0);
+        gap_ahead = GetAppBuffer(*stream, &mydata, &mydata_len,
+                app_progress, check_for_gap_ahead);
         DEBUG_VALIDATE_BUG_ON(mydata_len > (uint32_t)INT_MAX);
         if (mydata == NULL && mydata_len > 0 && CheckGap(ssn, *stream, p)) {
             SCLogDebug("sending GAP to app-layer (size: %u)", mydata_len);
@@ -1067,12 +1101,13 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
 
             /* a GAP also consumes 'data required'. TODO perhaps we can use
              * this to skip post GAP data until the start of a next record. */
-            if ((*stream)->data_required > mydata_len) {
-                (*stream)->data_required -= mydata_len;
-            } else {
-                (*stream)->data_required = 0;
+            if ((*stream)->data_required > 0) {
+                if ((*stream)->data_required > mydata_len) {
+                    (*stream)->data_required -= mydata_len;
+                } else {
+                    (*stream)->data_required = 0;
+                }
             }
-
             if (r < 0)
                 return 0;
 
@@ -1099,7 +1134,12 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
 
     if ((p->flags & PKT_PSEUDO_STREAM_END) == 0 || ssn->state < TCP_CLOSED) {
         if (mydata_len < (*stream)->data_required) {
-            SCLogDebug("mydata_len %u data_required %u", mydata_len, (*stream)->data_required);
+            if (gap_ahead) {
+                (*stream)->app_progress_rel += mydata_len;
+                (*stream)->data_required -= mydata_len;
+                // TODO send incomplete data to app-layer with special flag
+                // indicating its all there is for this rec?
+            }
             SCReturnInt(0);
         }
     }