summaryrefslogtreecommitdiffstats
path: root/src/stream-tcp-list.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stream-tcp-list.c')
-rw-r--r--src/stream-tcp-list.c992
1 files changed, 992 insertions, 0 deletions
diff --git a/src/stream-tcp-list.c b/src/stream-tcp-list.c
new file mode 100644
index 0000000..1648254
--- /dev/null
+++ b/src/stream-tcp-list.c
@@ -0,0 +1,992 @@
+/* Copyright (C) 2007-2023 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
+ *
+ * Segment list functions for insertions, overlap handling, removal and
+ * more.
+ */
+
+#include "suricata-common.h"
+#include "rust.h"
+#include "stream-tcp-private.h"
+#include "stream-tcp.h"
+#include "stream-tcp-reassemble.h"
+#include "stream-tcp-inline.h"
+#include "stream-tcp-list.h"
+#include "util-streaming-buffer.h"
+#include "util-print.h"
+#include "util-validate.h"
+#include "app-layer-frames.h"
+
+static void StreamTcpRemoveSegmentFromStream(TcpStream *stream, TcpSegment *seg);
+
+static int check_overlap_different_data = 0;
+
+void StreamTcpReassembleConfigEnableOverlapCheck(void)
+{
+ check_overlap_different_data = 1;
+}
+
+/*
+ * Inserts and overlap handling
+ */
+
+RB_GENERATE(TCPSEG, TcpSegment, rb, TcpSegmentCompare);
+
+int TcpSegmentCompare(struct TcpSegment *a, struct TcpSegment *b)
+{
+ if (SEQ_GT(a->seq, b->seq))
+ return 1;
+ else if (SEQ_LT(a->seq, b->seq))
+ return -1;
+ else {
+ if (a->payload_len == b->payload_len)
+ return 0;
+ else if (a->payload_len > b->payload_len)
+ return 1;
+ else
+ return -1;
+ }
+}
+
+/** \internal
+ * \brief insert segment data into the streaming buffer
+ * \param seg segment to store stream offset in
+ * \param data segment data after overlap handling (if any)
+ * \param data_len data length
+ *
+ * \return SC_OK on success
+ * \return SC_ENOMEM on error (memory allocation error)
+ */
+static inline int InsertSegmentDataCustom(TcpStream *stream, TcpSegment *seg, uint8_t *data, uint16_t data_len)
+{
+ uint64_t stream_offset;
+ uint32_t data_offset;
+
+ if (likely(SEQ_GEQ(seg->seq, stream->base_seq))) {
+ stream_offset = STREAM_BASE_OFFSET(stream) + (seg->seq - stream->base_seq);
+ data_offset = 0;
+ } else {
+ /* segment is partly before base_seq */
+ data_offset = stream->base_seq - seg->seq;
+ stream_offset = STREAM_BASE_OFFSET(stream);
+ }
+
+ SCLogDebug("stream %p buffer %p, stream_offset %"PRIu64", "
+ "data_offset %"PRIu16", SEQ %u BASE %u, data_len %u",
+ stream, &stream->sb, stream_offset,
+ data_offset, seg->seq, stream->base_seq, data_len);
+ DEBUG_VALIDATE_BUG_ON(data_offset > data_len);
+ if (data_len <= data_offset) {
+ SCReturnInt(SC_OK);
+ }
+
+ int ret = StreamingBufferInsertAt(&stream->sb, &stream_config.sbcnf, &seg->sbseg,
+ data + data_offset, data_len - data_offset, stream_offset);
+ if (ret != SC_OK) {
+ SCReturnInt(ret);
+ }
+#ifdef DEBUG
+ {
+ const uint8_t *mydata;
+ uint32_t mydata_len;
+ uint64_t mydata_offset;
+ StreamingBufferGetData(&stream->sb, &mydata, &mydata_len, &mydata_offset);
+
+ SCLogDebug("stream %p seg %p data in buffer %p of len %u and offset %"PRIu64,
+ stream, seg, &stream->sb, mydata_len, mydata_offset);
+ //PrintRawDataFp(stdout, mydata, mydata_len);
+ }
+#endif
+ SCReturnInt(SC_OK);
+}
+
+/** \internal
+ * \brief check if this segments overlaps with an in-tree seg.
+ * \retval true
+ * \retval false
+ */
+static inline bool CheckOverlap(struct TCPSEG *tree, TcpSegment *seg)
+{
+ const uint32_t re = SEG_SEQ_RIGHT_EDGE(seg);
+ SCLogDebug("start. SEQ %u payload_len %u. Right edge: %u. Seg %p",
+ seg->seq, seg->payload_len, re, seg);
+
+ /* check forward */
+ TcpSegment *next = TCPSEG_RB_NEXT(seg);
+ if (next) {
+ // next has same seq, so data must overlap
+ if (SEQ_EQ(next->seq, seg->seq))
+ return true;
+ // our right edge is beyond next seq, overlap
+ if (SEQ_GT(re, next->seq))
+ return true;
+ }
+ /* check backwards */
+ TcpSegment *prev = TCPSEG_RB_PREV(seg);
+ if (prev) {
+ // prev has same seq, so data must overlap
+ if (SEQ_EQ(prev->seq, seg->seq))
+ return true;
+ // prev's right edge is beyond our seq, overlap
+ const uint32_t prev_re = SEG_SEQ_RIGHT_EDGE(prev);
+ if (SEQ_GT(prev_re, seg->seq))
+ return true;
+ }
+
+ SCLogDebug("no overlap");
+ return false;
+}
+
+/** \internal
+ * \brief insert the segment into the proper place in the tree
+ * don't worry about the data or overlaps
+ *
+ * \retval 2 not inserted, data overlap
+ * \retval 1 inserted with overlap detected
+ * \retval 0 inserted, no overlap
+ * \retval -EINVAL seg out of seq range
+ */
+static int DoInsertSegment (TcpStream *stream, TcpSegment *seg, TcpSegment **dup_seg, Packet *p)
+{
+ /* in lossy traffic, we can get here with the wrong sequence numbers */
+ if (SEQ_LEQ(SEG_SEQ_RIGHT_EDGE(seg), stream->base_seq)) {
+ return -EINVAL;
+ }
+
+ /* fast track */
+ if (RB_EMPTY(&stream->seg_tree)) {
+ SCLogDebug("empty tree, inserting seg %p seq %" PRIu32 ", "
+ "len %" PRIu32 "", seg, seg->seq, TCP_SEG_LEN(seg));
+ TCPSEG_RB_INSERT(&stream->seg_tree, seg);
+ stream->segs_right_edge = SEG_SEQ_RIGHT_EDGE(seg);
+ return 0;
+ }
+
+ /* insert and then check if there was any overlap with other segments */
+ TcpSegment *res = TCPSEG_RB_INSERT(&stream->seg_tree, seg);
+ if (res) {
+ SCLogDebug("seg has a duplicate in the tree seq %u/%u",
+ res->seq, res->payload_len);
+ /* exact duplicate SEQ + payload_len */
+ *dup_seg = res;
+ return 2; // duplicate has overlap by definition.
+ } else {
+ if (SEQ_GT(SEG_SEQ_RIGHT_EDGE(seg), stream->segs_right_edge))
+ stream->segs_right_edge = SEG_SEQ_RIGHT_EDGE(seg);
+
+ /* insert succeeded, now check if we overlap with someone */
+ if (CheckOverlap(&stream->seg_tree, seg) == true) {
+ SCLogDebug("seg %u has overlap in the tree", seg->seq);
+ return 1;
+ }
+ }
+ SCLogDebug("seg %u: no overlap", seg->seq);
+ return 0;
+}
+
+/** \internal
+ * \brief handle overlap per list segment
+ *
+ * For a list segment handle the overlap according to the policy.
+ *
+ * The 'buf' parameter points to the memory that will be inserted into
+ * the stream after the overlap checks are complete. As it will
+ * unconditionally overwrite whats in the stream now, the overlap
+ * policies are applied to this buffer. It starts with the 'new' data,
+ * so when the policy states 'old' data has to be used, 'buf' is
+ * updated to contain the 'old' data here.
+ *
+ * \param buf stack allocated buffer sized p->payload_len that will be
+ * inserted into the stream buffer
+ *
+ * \retval 1 if data was different
+ * \retval 0 data was the same or we didn't check for differences
+ */
+static int DoHandleDataOverlap(TcpStream *stream, const TcpSegment *list,
+ const TcpSegment *seg, uint8_t *buf, Packet *p)
+{
+ SCLogDebug("handle overlap for segment %p seq %u len %u re %u, "
+ "list segment %p seq %u len %u re %u", seg, seg->seq,
+ p->payload_len, SEG_SEQ_RIGHT_EDGE(seg),
+ list, list->seq, TCP_SEG_LEN(list), SEG_SEQ_RIGHT_EDGE(list));
+
+ int data_is_different = 0;
+ int use_new_data = 0;
+
+ if (StreamTcpInlineMode()) {
+ SCLogDebug("inline mode");
+ if (StreamTcpInlineSegmentCompare(stream, p, list) != 0) {
+ SCLogDebug("already accepted data not the same as packet data, rewrite packet");
+ StreamTcpInlineSegmentReplacePacket(stream, p, list);
+ data_is_different = 1;
+
+ /* in inline mode we check for different data unconditionally,
+ * but setting events still depends on config */
+ if (check_overlap_different_data) {
+ StreamTcpSetEvent(p, STREAM_REASSEMBLY_OVERLAP_DIFFERENT_DATA);
+ }
+ }
+
+ /* IDS mode */
+ } else {
+ if (check_overlap_different_data) {
+ if (StreamTcpInlineSegmentCompare(stream, p, list) != 0) {
+ SCLogDebug("data is different from what is in the list");
+ data_is_different = 1;
+ }
+ } else {
+ /* if we're not checking, assume it's different */
+ data_is_different = 1;
+ }
+
+ /* apply overlap policies */
+
+ if (stream->os_policy == OS_POLICY_LAST) {
+ /* buf will start with LAST data (from the segment),
+ * so if policy is LAST we're now done here. */
+ return (check_overlap_different_data && data_is_different);
+ }
+
+ /* start at the same seq */
+ if (SEQ_EQ(seg->seq, list->seq)) {
+ SCLogDebug("seg starts at list segment");
+
+ if (SEQ_LT(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg ends before list end, end overlapped by list");
+ } else {
+ if (SEQ_GT(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg ends beyond list end, list overlapped and more");
+ switch (stream->os_policy) {
+ case OS_POLICY_LINUX:
+ if (data_is_different) {
+ use_new_data = 1;
+ }
+ break;
+ }
+ } else {
+ SCLogDebug("full overlap");
+ }
+
+ switch (stream->os_policy) {
+ case OS_POLICY_OLD_LINUX:
+ case OS_POLICY_SOLARIS:
+ case OS_POLICY_HPUX11:
+ if (data_is_different) {
+ use_new_data = 1;
+ }
+ break;
+ }
+ }
+
+ /* new seg starts before list segment */
+ } else if (SEQ_LT(seg->seq, list->seq)) {
+ SCLogDebug("seg starts before list segment");
+
+ if (SEQ_LT(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg ends before list end, end overlapped by list");
+ } else {
+ if (SEQ_GT(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg starts before and fully overlaps list and beyond");
+ } else {
+ SCLogDebug("seg starts before and fully overlaps list");
+ }
+
+ switch (stream->os_policy) {
+ case OS_POLICY_SOLARIS:
+ case OS_POLICY_HPUX11:
+ if (data_is_different) {
+ use_new_data = 1;
+ }
+ break;
+ }
+ }
+
+ switch (stream->os_policy) {
+ case OS_POLICY_BSD:
+ case OS_POLICY_HPUX10:
+ case OS_POLICY_IRIX:
+ case OS_POLICY_WINDOWS:
+ case OS_POLICY_WINDOWS2K3:
+ case OS_POLICY_OLD_LINUX:
+ case OS_POLICY_LINUX:
+ case OS_POLICY_MACOS:
+ if (data_is_different) {
+ use_new_data = 1;
+ }
+ break;
+ }
+
+ /* new seg starts after list segment */
+ } else { //if (SEQ_GT(seg->seq, list->seq)) {
+ SCLogDebug("seg starts after list segment");
+
+ if (SEQ_EQ(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg after and is fully overlapped by list");
+ } else if (SEQ_GT(SEG_SEQ_RIGHT_EDGE(seg), SEG_SEQ_RIGHT_EDGE(list))) {
+ SCLogDebug("seg starts after list and ends after list");
+
+ switch (stream->os_policy) {
+ case OS_POLICY_SOLARIS:
+ case OS_POLICY_HPUX11:
+ if (data_is_different) {
+ use_new_data = 1;
+ }
+ break;
+ }
+ } else {
+ SCLogDebug("seg starts after list and ends before list end");
+
+ }
+ }
+ }
+
+ SCLogDebug("data_is_different %s, use_new_data %s",
+ data_is_different ? "yes" : "no",
+ use_new_data ? "yes" : "no");
+
+ /* if the data is different and we don't want to use the new (seg)
+ * data, we have to update buf with the list data */
+ if (data_is_different && !use_new_data) {
+ /* we need to copy list into seg */
+ uint32_t list_offset = 0;
+ uint32_t seg_offset = 0;
+ uint32_t list_len;
+ uint16_t seg_len = p->payload_len;
+ uint32_t list_seq = list->seq;
+
+ const uint8_t *list_data;
+ StreamingBufferSegmentGetData(&stream->sb, &list->sbseg, &list_data, &list_len);
+ DEBUG_VALIDATE_BUG_ON(list_len > USHRT_MAX);
+ if (list_data == NULL || list_len == 0 || list_len > USHRT_MAX)
+ return 0;
+
+ /* if list seg is partially before base_seq, list_len (from stream) and
+ * TCP_SEG_LEN(list) will not be the same */
+ if (SEQ_GEQ(list->seq, stream->base_seq)) {
+ ;
+ } else {
+ list_seq = stream->base_seq;
+ list_len = SEG_SEQ_RIGHT_EDGE(list) - stream->base_seq;
+ }
+
+ if (SEQ_LT(seg->seq, list_seq)) {
+ seg_offset = list_seq - seg->seq;
+ seg_len -= seg_offset;
+ } else if (SEQ_GT(seg->seq, list_seq)) {
+ list_offset = seg->seq - list_seq;
+ list_len -= list_offset;
+ }
+
+ if (SEQ_LT(seg->seq + seg_offset + seg_len, list_seq + list_offset + list_len)) {
+ list_len -= (list_seq + list_offset + list_len) - (seg->seq + seg_offset + seg_len);
+ }
+ SCLogDebug("here goes nothing: list %u %u, seg %u %u", list_offset, list_len, seg_offset, seg_len);
+
+ //PrintRawDataFp(stdout, list_data + list_offset, list_len);
+ //PrintRawDataFp(stdout, buf + seg_offset, seg_len);
+
+ memcpy(buf + seg_offset, list_data + list_offset, list_len);
+ //PrintRawDataFp(stdout, buf, p->payload_len);
+ }
+ return (check_overlap_different_data && data_is_different);
+}
+
+/** \internal
+ * \brief walk segment tree backwards to see if there are overlaps
+ *
+ * Walk back from the current segment which is already in the tree.
+ * We walk until we can't possibly overlap anymore.
+ */
+static int DoHandleDataCheckBackwards(TcpStream *stream,
+ TcpSegment *seg, uint8_t *buf, Packet *p)
+{
+ int retval = 0;
+
+ SCLogDebug("check tree backwards: insert data for segment %p seq %u len %u re %u",
+ seg, seg->seq, TCP_SEG_LEN(seg), SEG_SEQ_RIGHT_EDGE(seg));
+
+ /* check backwards */
+ TcpSegment *tree_seg = NULL, *s = seg;
+ RB_FOREACH_REVERSE_FROM(tree_seg, TCPSEG, s) {
+ if (tree_seg == seg)
+ continue;
+
+ int overlap = 0;
+ if (SEQ_LEQ(SEG_SEQ_RIGHT_EDGE(tree_seg), stream->base_seq)) {
+ // segment entirely before base_seq
+ ;
+ } else if (SEQ_LEQ(tree_seg->seq + tree_seg->payload_len, seg->seq)) {
+ SCLogDebug("list segment too far to the left, no more overlap will be found");
+ break;
+ } else if (SEQ_GT(SEG_SEQ_RIGHT_EDGE(tree_seg), seg->seq)) {
+ overlap = 1;
+ }
+
+ SCLogDebug("(back) tree seg %u len %u re %u overlap? %s",
+ tree_seg->seq, TCP_SEG_LEN(tree_seg),
+ SEG_SEQ_RIGHT_EDGE(tree_seg), overlap ? "yes" : "no");
+
+ if (overlap) {
+ retval |= DoHandleDataOverlap(stream, tree_seg, seg, buf, p);
+ }
+ }
+ return retval;
+}
+
+/** \internal
+ * \brief walk segment tree in forward direction to see if there are overlaps
+ *
+ * Walk forward from the current segment which is already in the tree.
+ * We walk until the next segs start with a SEQ beyond our right edge.
+ *
+ * \retval 1 data was different
+ * \retval 0 data was the same
+ */
+static int DoHandleDataCheckForward(TcpStream *stream,
+ TcpSegment *seg, uint8_t *buf, Packet *p)
+{
+ int retval = 0;
+
+ uint32_t seg_re = SEG_SEQ_RIGHT_EDGE(seg);
+
+ SCLogDebug("check list forward: insert data for segment %p seq %u len %u re %u",
+ seg, seg->seq, TCP_SEG_LEN(seg), seg_re);
+
+ TcpSegment *tree_seg = NULL, *s = seg;
+ RB_FOREACH_FROM(tree_seg, TCPSEG, s) {
+ if (tree_seg == seg)
+ continue;
+
+ int overlap = 0;
+ if (SEQ_GT(seg_re, tree_seg->seq))
+ overlap = 1;
+ else if (SEQ_LEQ(seg_re, tree_seg->seq)) {
+ SCLogDebug("tree segment %u too far ahead, "
+ "no more overlaps can happen", tree_seg->seq);
+ break;
+ }
+
+ SCLogDebug("(fwd) in-tree seg %u len %u re %u overlap? %s",
+ tree_seg->seq, TCP_SEG_LEN(tree_seg),
+ SEG_SEQ_RIGHT_EDGE(tree_seg), overlap ? "yes" : "no");
+
+ if (overlap) {
+ retval |= DoHandleDataOverlap(stream, tree_seg, seg, buf, p);
+ }
+ }
+ return retval;
+}
+
+/**
+ * \param tree_seg in-tree duplicate of `seg`
+ * \retval res 0 ok, -1 insertion error due to memcap
+ */
+static int DoHandleData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
+ TcpStream *stream, TcpSegment *seg, TcpSegment *tree_seg, Packet *p)
+{
+ int result = 0;
+ TcpSegment *handle = seg;
+
+ SCLogDebug("insert data for segment %p seq %u len %u re %u",
+ seg, seg->seq, TCP_SEG_LEN(seg), SEG_SEQ_RIGHT_EDGE(seg));
+
+ /* create temporary buffer to contain the data we will insert. Overlap
+ * handling may update it. By using this we don't have to track whether
+ * parts of the data are already inserted or not. */
+ uint8_t buf[p->payload_len];
+ memcpy(buf, p->payload, p->payload_len);
+
+ /* if tree_seg is set, we have an exact duplicate that we need to check */
+ if (tree_seg) {
+ DoHandleDataOverlap(stream, tree_seg, seg, buf, p);
+ handle = tree_seg;
+ }
+
+ const bool is_head = !(TCPSEG_RB_PREV(handle));
+ const bool is_tail = !(TCPSEG_RB_NEXT(handle));
+
+ /* new list head */
+ if (is_head && !is_tail) {
+ result = DoHandleDataCheckForward(stream, handle, buf, p);
+
+ /* new list tail */
+ } else if (!is_head && is_tail) {
+ result = DoHandleDataCheckBackwards(stream, handle, buf, p);
+
+ /* middle of the list */
+ } else if (!is_head && !is_tail) {
+ result = DoHandleDataCheckBackwards(stream, handle, buf, p);
+ result |= DoHandleDataCheckForward(stream, handle, buf, p);
+ }
+
+ /* we had an overlap with different data */
+ if (result) {
+ StreamTcpSetEvent(p, STREAM_REASSEMBLY_OVERLAP_DIFFERENT_DATA);
+ StatsIncr(tv, ra_ctx->counter_tcp_reass_overlap_diff_data);
+ }
+
+ /* insert the temp buffer now that we've (possibly) updated
+ * it to account for the overlap policies */
+ int res = InsertSegmentDataCustom(stream, handle, buf, p->payload_len);
+ if (res != SC_OK) {
+ if (res == SC_ENOMEM) {
+ StatsIncr(tv, ra_ctx->counter_tcp_segment_memcap);
+ StreamTcpSetEvent(p, STREAM_REASSEMBLY_INSERT_MEMCAP);
+ } else if (res == SC_ELIMIT) {
+ StreamTcpSetEvent(p, STREAM_REASSEMBLY_INSERT_LIMIT);
+ } else if (res == SC_EINVAL) {
+ StreamTcpSetEvent(p, STREAM_REASSEMBLY_INSERT_INVALID);
+ } else {
+ DEBUG_VALIDATE_BUG_ON(1);
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/** \internal
+ * \brief Add the header data to the segment
+ * \param rp packet to take the headers from. Might differ from `pp` in tunnels.
+ * \param pp packet to take the payload size from.
+ */
+static void StreamTcpSegmentAddPacketDataDo(TcpSegment *seg, const Packet *rp, const Packet *pp)
+{
+ if (GET_PKT_DATA(rp) != NULL && GET_PKT_LEN(rp) > pp->payload_len) {
+ seg->pcap_hdr_storage->ts.tv_sec = SCTIME_SECS(rp->ts);
+ seg->pcap_hdr_storage->ts.tv_usec = SCTIME_USECS(rp->ts);
+ seg->pcap_hdr_storage->pktlen = GET_PKT_LEN(rp) - pp->payload_len;
+ /*
+ * pkt_hdr members are initially allocated 64 bytes of memory. Thus,
+ * need to check that this is sufficient and allocate more memory if
+ * not.
+ */
+ if (seg->pcap_hdr_storage->pktlen > seg->pcap_hdr_storage->alloclen) {
+ uint8_t *tmp_pkt_hdr = StreamTcpReassembleRealloc(seg->pcap_hdr_storage->pkt_hdr,
+ seg->pcap_hdr_storage->alloclen, seg->pcap_hdr_storage->pktlen);
+ if (tmp_pkt_hdr == NULL) {
+ SCLogDebug("Failed to realloc");
+ seg->pcap_hdr_storage->ts.tv_sec = 0;
+ seg->pcap_hdr_storage->ts.tv_usec = 0;
+ seg->pcap_hdr_storage->pktlen = 0;
+ return;
+ } else {
+ seg->pcap_hdr_storage->pkt_hdr = tmp_pkt_hdr;
+ seg->pcap_hdr_storage->alloclen = GET_PKT_LEN(rp) - pp->payload_len;
+ }
+ }
+ memcpy(seg->pcap_hdr_storage->pkt_hdr, GET_PKT_DATA(rp),
+ (size_t)GET_PKT_LEN(rp) - pp->payload_len);
+ } else {
+ seg->pcap_hdr_storage->ts.tv_sec = 0;
+ seg->pcap_hdr_storage->ts.tv_usec = 0;
+ seg->pcap_hdr_storage->pktlen = 0;
+ }
+}
+
+/**
+ * \brief Adds the following information to the TcpSegment from the current
+ * packet being processed: time values, packet length, and the
+ * header data of the packet. This information is added to the TcpSegment so
+ * that it can be used in pcap capturing (log-pcap-stream) to dump the tcp
+ * session at the beginning of the pcap capture.
+ * \param seg TcpSegment where information is being stored.
+ * \param p Packet being processed.
+ * \param tv Thread-specific variables.
+ * \param ra_ctx TcpReassembly thread-specific variables
+ */
+static void StreamTcpSegmentAddPacketData(
+ TcpSegment *seg, Packet *p, ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx)
+{
+ if (seg->pcap_hdr_storage == NULL || seg->pcap_hdr_storage->pkt_hdr == NULL) {
+ return;
+ }
+
+ if (IS_TUNNEL_PKT(p) && !IS_TUNNEL_ROOT_PKT(p)) {
+ Packet *rp = p->root;
+ StreamTcpSegmentAddPacketDataDo(seg, rp, p);
+ } else {
+ StreamTcpSegmentAddPacketDataDo(seg, p, p);
+ }
+}
+
+/**
+ * \return 0 ok
+ * \return -1 segment not inserted due to memcap issue
+ *
+ * \param seg segment, this function takes total ownership
+ *
+ * In case of error, this function returns the segment to the pool
+ */
+int StreamTcpReassembleInsertSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
+ TcpStream *stream, TcpSegment *seg, Packet *p,
+ uint32_t pkt_seq, uint8_t *pkt_data, uint16_t pkt_datalen)
+{
+ SCEnter();
+
+ TcpSegment *dup_seg = NULL;
+
+ /* insert segment into list. Note: doesn't handle the data */
+ int r = DoInsertSegment (stream, seg, &dup_seg, p);
+
+ if (IsTcpSessionDumpingEnabled()) {
+ StreamTcpSegmentAddPacketData(seg, p, tv, ra_ctx);
+ }
+
+ if (likely(r == 0)) {
+ /* no overlap, straight data insert */
+ int res = InsertSegmentDataCustom(stream, seg, pkt_data, pkt_datalen);
+ if (res != SC_OK) {
+ StatsIncr(tv, ra_ctx->counter_tcp_reass_data_normal_fail);
+ StreamTcpRemoveSegmentFromStream(stream, seg);
+ StreamTcpSegmentReturntoPool(seg);
+ if (res == SC_ENOMEM) {
+ StatsIncr(tv, ra_ctx->counter_tcp_segment_memcap);
+ SCReturnInt(-SC_ENOMEM);
+ }
+ SCReturnInt(-1);
+ }
+
+ } else if (r == 1 || r == 2) {
+ SCLogDebug("overlap (%s%s)", r == 1 ? "normal" : "", r == 2 ? "duplicate" : "");
+
+ if (r == 2) {
+ SCLogDebug("dup_seg %p", dup_seg);
+ }
+
+ /* XXX should we exclude 'retransmissions' here? */
+ StatsIncr(tv, ra_ctx->counter_tcp_reass_overlap);
+
+ /* now let's consider the data in the overlap case */
+ int res = DoHandleData(tv, ra_ctx, stream, seg, dup_seg, p);
+ if (res < 0) {
+ StatsIncr(tv, ra_ctx->counter_tcp_reass_data_overlap_fail);
+
+ if (r == 1) // r == 2 mean seg wasn't added to stream
+ StreamTcpRemoveSegmentFromStream(stream, seg);
+
+ StreamTcpSegmentReturntoPool(seg);
+ SCReturnInt(-1);
+ }
+ if (r == 2) {
+ SCLogDebug("duplicate segment %u/%u, discard it",
+ seg->seq, seg->payload_len);
+
+ StreamTcpSegmentReturntoPool(seg);
+#ifdef DEBUG
+ if (SCLogDebugEnabled()) {
+ TcpSegment *s = NULL, *safe = NULL;
+ RB_FOREACH_SAFE(s, TCPSEG, &stream->seg_tree, safe)
+ {
+ SCLogDebug("tree: seg %p, SEQ %"PRIu32", LEN %"PRIu16", SUM %"PRIu32"%s%s%s",
+ s, s->seq, TCP_SEG_LEN(s),
+ (uint32_t)(s->seq + TCP_SEG_LEN(s)),
+ s->seq == seg->seq ? " DUPLICATE" : "",
+ TCPSEG_RB_PREV(s) == NULL ? " HEAD" : "",
+ TCPSEG_RB_NEXT(s) == NULL ? " TAIL" : "");
+ }
+ }
+#endif
+ }
+ } else {
+ // EINVAL
+ StreamTcpSegmentReturntoPool(seg);
+ }
+
+ SCReturnInt(0);
+}
+
+
+/*
+ * Pruning & removal
+ */
+
+
+static inline bool SegmentInUse(const TcpStream *stream, const TcpSegment *seg)
+{
+ /* if proto detect isn't done, we're not returning */
+ if (!(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) {
+ if (!(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(stream))) {
+ SCReturnInt(true);
+ }
+ }
+
+ SCReturnInt(false);
+}
+
+
+/** \internal
+ * \brief check if we can remove a segment from our segment list
+ *
+ * \retval true
+ * \retval false
+ */
+static inline bool StreamTcpReturnSegmentCheck(const TcpStream *stream, const TcpSegment *seg)
+{
+ if (SegmentInUse(stream, seg)) {
+ SCReturnInt(false);
+ }
+
+ if (!(StreamingBufferSegmentIsBeforeWindow(&stream->sb, &seg->sbseg))) {
+ SCReturnInt(false);
+ }
+
+ SCReturnInt(true);
+}
+
+static inline uint64_t GetLeftEdgeForApp(Flow *f, TcpSession *ssn, TcpStream *stream)
+{
+ const FramesContainer *frames_container = AppLayerFramesGetContainer(f);
+ if (frames_container == NULL)
+ return STREAM_APP_PROGRESS(stream);
+
+ const Frames *frames =
+ stream == &ssn->client ? &frames_container->toserver : &frames_container->toclient;
+ // const uint64_t x = FramesLeftEdge(stream, frames);
+ // BUG_ON(x != (frames->left_edge_rel + STREAM_BASE_OFFSET(stream)));
+ // return x;
+ const uint64_t o = (uint64_t)frames->left_edge_rel + STREAM_BASE_OFFSET(stream);
+ SCLogDebug(
+ "%s: frames left edge: %" PRIu64, &ssn->client == stream ? "toserver" : "toclient", o);
+ return o;
+}
+
+static inline uint64_t GetLeftEdge(Flow *f, TcpSession *ssn, TcpStream *stream)
+{
+ uint64_t left_edge = 0;
+ const bool use_app = !(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
+ const bool use_raw = !(stream->flags & STREAMTCP_STREAM_FLAG_DISABLE_RAW);
+ const bool use_log = stream_config.streaming_log_api;
+ SCLogDebug("use_app %d use_raw %d use_log %d tcp win %u", use_app, use_raw, use_log,
+ stream->window);
+
+ if (use_raw) {
+ uint64_t raw_progress = STREAM_RAW_PROGRESS(stream);
+
+ if (StreamTcpInlineMode() == TRUE) {
+ uint32_t chunk_size = (stream == &ssn->client) ?
+ stream_config.reassembly_toserver_chunk_size :
+ stream_config.reassembly_toclient_chunk_size;
+ if (raw_progress < (uint64_t)chunk_size) {
+ raw_progress = 0;
+ } else {
+ raw_progress -= (uint64_t)chunk_size;
+ }
+ }
+
+ /* apply min inspect depth: if it is set we need to keep data
+ * before the raw progress. */
+ if (use_app && stream->min_inspect_depth && ssn->state < TCP_CLOSED) {
+ if (raw_progress < stream->min_inspect_depth)
+ raw_progress = 0;
+ else
+ raw_progress -= stream->min_inspect_depth;
+
+ SCLogDebug("stream->min_inspect_depth %u, raw_progress %"PRIu64,
+ stream->min_inspect_depth, raw_progress);
+ }
+
+ if (use_app) {
+ const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream);
+ left_edge = MIN(app_le, raw_progress);
+ SCLogDebug("left_edge %" PRIu64 ", using both app:%" PRIu64 ", raw:%" PRIu64, left_edge,
+ app_le, raw_progress);
+ } else {
+ left_edge = raw_progress;
+ SCLogDebug("left_edge %"PRIu64", using only raw:%"PRIu64,
+ left_edge, raw_progress);
+ }
+ } else if (use_app) {
+ const uint64_t app_le = GetLeftEdgeForApp(f, ssn, stream);
+ left_edge = app_le;
+ SCLogDebug("left_edge %" PRIu64 ", using only app:%" PRIu64, left_edge, app_le);
+ } else {
+ left_edge = StreamingBufferGetConsecutiveDataRightEdge(&stream->sb);
+ SCLogDebug("no app & raw: left_edge %"PRIu64" (full stream)", left_edge);
+ }
+
+ if (use_log) {
+ if (use_app || use_raw) {
+ left_edge = MIN(left_edge, STREAM_LOG_PROGRESS(stream));
+ } else {
+ left_edge = STREAM_LOG_PROGRESS(stream);
+ }
+ }
+
+ uint64_t last_ack_abs = STREAM_BASE_OFFSET(stream);
+ if (STREAM_LASTACK_GT_BASESEQ(stream)) {
+ last_ack_abs += (stream->last_ack - stream->base_seq);
+ }
+ /* in IDS mode we shouldn't see the base_seq pass last_ack */
+ DEBUG_VALIDATE_BUG_ON(last_ack_abs < left_edge && StreamTcpInlineMode() == FALSE && !f->ffr &&
+ ssn->state < TCP_CLOSED);
+ left_edge = MIN(left_edge, last_ack_abs);
+
+ /* if we're told to look for overlaps with different data we should
+ * consider data that is ack'd as well. Injected packets may have
+ * been ack'd or injected packet may be too late. */
+ if (StreamTcpInlineMode() == FALSE && check_overlap_different_data) {
+ const uint32_t window = stream->window ? stream->window : 4096;
+ if (window < left_edge)
+ left_edge -= window;
+ else
+ left_edge = 0;
+
+ SCLogDebug("stream:%p left_edge %"PRIu64, stream, left_edge);
+ }
+
+ if (left_edge > 0) {
+ /* we know left edge based on the progress values now,
+ * lets adjust it to make sure in-use segments still have
+ * data */
+ TcpSegment *seg = NULL;
+ RB_FOREACH(seg, TCPSEG, &stream->seg_tree) {
+ if (TCP_SEG_OFFSET(seg) > left_edge) {
+ SCLogDebug("seg beyond left_edge, we're done");
+ break;
+ }
+
+ if (SegmentInUse(stream, seg)) {
+ left_edge = TCP_SEG_OFFSET(seg);
+ SCLogDebug("in-use seg before left_edge, adjust to %"PRIu64" and bail", left_edge);
+ break;
+ }
+ }
+ }
+
+ return left_edge;
+}
+
+static void StreamTcpRemoveSegmentFromStream(TcpStream *stream, TcpSegment *seg)
+{
+ RB_REMOVE(TCPSEG, &stream->seg_tree, seg);
+}
+
+/** \brief Remove idle TcpSegments from TcpSession
+ *
+ * Checks app progress and raw progress and progresses them
+ * if needed, slides the streaming buffer, then gets rid of
+ * excess segments.
+ *
+ * \param f flow
+ * \param flags direction flags
+ */
+void StreamTcpPruneSession(Flow *f, uint8_t flags)
+{
+ SCEnter();
+
+ if (f == NULL || f->protoctx == NULL) {
+ SCReturn;
+ }
+
+ TcpSession *ssn = f->protoctx;
+ TcpStream *stream = NULL;
+
+ if (flags & STREAM_TOSERVER) {
+ stream = &ssn->client;
+ } else if (flags & STREAM_TOCLIENT) {
+ stream = &ssn->server;
+ } else {
+ SCReturn;
+ }
+
+ if (stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) {
+ return;
+ }
+
+ if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
+ stream->flags |= STREAMTCP_STREAM_FLAG_NOREASSEMBLY;
+ SCLogDebug("ssn %p / stream %p: reassembly depth reached, "
+ "STREAMTCP_STREAM_FLAG_NOREASSEMBLY set", ssn, stream);
+ StreamTcpReturnStreamSegments(stream);
+ StreamingBufferClear(&stream->sb, &stream_config.sbcnf);
+ return;
+
+ } else if ((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) &&
+ (stream->flags & STREAMTCP_STREAM_FLAG_DISABLE_RAW)) {
+ SCLogDebug("ssn %p / stream %p: both app and raw are done, "
+ "STREAMTCP_STREAM_FLAG_NOREASSEMBLY set", ssn, stream);
+ stream->flags |= STREAMTCP_STREAM_FLAG_NOREASSEMBLY;
+ StreamTcpReturnStreamSegments(stream);
+ StreamingBufferClear(&stream->sb, &stream_config.sbcnf);
+ return;
+ }
+
+ const uint64_t left_edge = GetLeftEdge(f, ssn, stream);
+ SCLogDebug("buffer left_edge %" PRIu64, left_edge);
+ if (left_edge && left_edge > STREAM_BASE_OFFSET(stream)) {
+ uint32_t slide = left_edge - STREAM_BASE_OFFSET(stream);
+ SCLogDebug("buffer sliding %u to offset %"PRIu64, slide, left_edge);
+
+ if (!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED)) {
+ AppLayerFramesSlide(f, slide, flags & (STREAM_TOSERVER | STREAM_TOCLIENT));
+ }
+ StreamingBufferSlideToOffset(&stream->sb, &stream_config.sbcnf, left_edge);
+ stream->base_seq += slide;
+
+ if (slide <= stream->app_progress_rel) {
+ stream->app_progress_rel -= slide;
+ } else {
+ stream->app_progress_rel = 0;
+ }
+ if (slide <= stream->raw_progress_rel) {
+ stream->raw_progress_rel -= slide;
+ } else {
+ stream->raw_progress_rel = 0;
+ }
+ if (slide <= stream->log_progress_rel) {
+ stream->log_progress_rel -= slide;
+ } else {
+ stream->log_progress_rel = 0;
+ }
+
+ SCLogDebug("stream base_seq %u at stream offset %"PRIu64,
+ stream->base_seq, STREAM_BASE_OFFSET(stream));
+ }
+
+ /* loop through the segments and remove all not in use */
+ TcpSegment *seg = NULL, *safe = NULL;
+ RB_FOREACH_SAFE(seg, TCPSEG, &stream->seg_tree, safe)
+ {
+ SCLogDebug("seg %p, SEQ %"PRIu32", LEN %"PRIu16", SUM %"PRIu32,
+ seg, seg->seq, TCP_SEG_LEN(seg),
+ (uint32_t)(seg->seq + TCP_SEG_LEN(seg)));
+
+ if (StreamTcpReturnSegmentCheck(stream, seg) == 0) {
+ SCLogDebug("not removing segment");
+ break;
+ }
+
+ StreamTcpRemoveSegmentFromStream(stream, seg);
+ StreamTcpSegmentReturntoPool(seg);
+ SCLogDebug("removed segment");
+ continue;
+ }
+
+ SCReturn;
+}
+
+
+/*
+ * unittests
+ */
+
+#ifdef UNITTESTS
+#include "tests/stream-tcp-list.c"
+#endif