mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3668 lines
124 KiB
C
3668 lines
124 KiB
C
/* Copyright (C) 2007-2016 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 Gurvinder Singh <gurvindersinghdahiya@gmail.com>
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
*
|
|
* Reference:
|
|
* Judy Novak, Steve Sturges: Target-Based TCP Stream Reassembly August, 2007
|
|
*
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "suricata.h"
|
|
#include "debug.h"
|
|
#include "detect.h"
|
|
#include "flow.h"
|
|
#include "threads.h"
|
|
#include "conf.h"
|
|
|
|
#include "flow-util.h"
|
|
|
|
#include "threadvars.h"
|
|
#include "tm-threads.h"
|
|
|
|
#include "util-pool.h"
|
|
#include "util-unittest.h"
|
|
#include "util-print.h"
|
|
#include "util-host-os-info.h"
|
|
#include "util-unittest-helper.h"
|
|
#include "util-byte.h"
|
|
#include "util-device.h"
|
|
|
|
#include "stream-tcp.h"
|
|
#include "stream-tcp-private.h"
|
|
#include "stream-tcp-reassemble.h"
|
|
#include "stream-tcp-inline.h"
|
|
#include "stream-tcp-list.h"
|
|
#include "stream-tcp-util.h"
|
|
|
|
#include "stream.h"
|
|
|
|
#include "util-debug.h"
|
|
#include "app-layer-protos.h"
|
|
#include "app-layer.h"
|
|
#include "app-layer-events.h"
|
|
|
|
#include "detect-engine-state.h"
|
|
|
|
#include "util-profiling.h"
|
|
#include "util-validate.h"
|
|
|
|
#ifdef DEBUG
|
|
static SCMutex segment_pool_memuse_mutex;
|
|
static uint64_t segment_pool_memuse = 0;
|
|
static uint64_t segment_pool_memcnt = 0;
|
|
#endif
|
|
|
|
static PoolThread *segment_thread_pool = NULL;
|
|
/* init only, protect initializing and growing pool */
|
|
static SCMutex segment_thread_pool_mutex = SCMUTEX_INITIALIZER;
|
|
|
|
/* Memory use counter */
|
|
SC_ATOMIC_DECLARE(uint64_t, ra_memuse);
|
|
|
|
/* prototypes */
|
|
TcpSegment *StreamTcpGetSegment(ThreadVars *tv, TcpReassemblyThreadCtx *);
|
|
void StreamTcpCreateTestPacket(uint8_t *, uint8_t, uint8_t, uint8_t);
|
|
void StreamTcpReassemblePseudoPacketCreate(TcpStream *, Packet *, PacketQueue *);
|
|
|
|
void StreamTcpReassembleInitMemuse(void)
|
|
{
|
|
SC_ATOMIC_INIT(ra_memuse);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to Increment the memory usage counter for the TCP reassembly
|
|
* segments
|
|
*
|
|
* \param size Size of the TCP segment and its payload length memory allocated
|
|
*/
|
|
void StreamTcpReassembleIncrMemuse(uint64_t size)
|
|
{
|
|
(void) SC_ATOMIC_ADD(ra_memuse, size);
|
|
SCLogDebug("REASSEMBLY %"PRIu64", incr %"PRIu64, StreamTcpReassembleMemuseGlobalCounter(), size);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to Decrease the memory usage counter for the TCP reassembly
|
|
* segments
|
|
*
|
|
* \param size Size of the TCP segment and its payload length memory allocated
|
|
*/
|
|
void StreamTcpReassembleDecrMemuse(uint64_t size)
|
|
{
|
|
#ifdef UNITTESTS
|
|
uint64_t presize = SC_ATOMIC_GET(ra_memuse);
|
|
if (RunmodeIsUnittests()) {
|
|
BUG_ON(presize > UINT_MAX);
|
|
}
|
|
#endif
|
|
|
|
(void) SC_ATOMIC_SUB(ra_memuse, size);
|
|
|
|
#ifdef UNITTESTS
|
|
if (RunmodeIsUnittests()) {
|
|
uint64_t postsize = SC_ATOMIC_GET(ra_memuse);
|
|
BUG_ON(postsize > presize);
|
|
}
|
|
#endif
|
|
SCLogDebug("REASSEMBLY %"PRIu64", decr %"PRIu64, StreamTcpReassembleMemuseGlobalCounter(), size);
|
|
return;
|
|
}
|
|
|
|
uint64_t StreamTcpReassembleMemuseGlobalCounter(void)
|
|
{
|
|
uint64_t smemuse = SC_ATOMIC_GET(ra_memuse);
|
|
return smemuse;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to Check the reassembly memory usage counter against the
|
|
* allowed max memory usgae for TCP segments.
|
|
*
|
|
* \param size Size of the TCP segment and its payload length memory allocated
|
|
* \retval 1 if in bounds
|
|
* \retval 0 if not in bounds
|
|
*/
|
|
int StreamTcpReassembleCheckMemcap(uint64_t size)
|
|
{
|
|
uint64_t memcapcopy = SC_ATOMIC_GET(stream_config.reassembly_memcap);
|
|
if (memcapcopy == 0 ||
|
|
(uint64_t)((uint64_t)size + SC_ATOMIC_GET(ra_memuse)) <= memcapcopy)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Update memcap value
|
|
*
|
|
* \param size new memcap value
|
|
*/
|
|
int StreamTcpReassembleSetMemcap(uint64_t size)
|
|
{
|
|
if (size == 0 || (uint64_t)SC_ATOMIC_GET(ra_memuse) < size) {
|
|
SC_ATOMIC_SET(stream_config.reassembly_memcap, size);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Return memcap value
|
|
*
|
|
* \return memcap memcap value
|
|
*/
|
|
uint64_t StreamTcpReassembleGetMemcap()
|
|
{
|
|
uint64_t memcapcopy = SC_ATOMIC_GET(stream_config.reassembly_memcap);
|
|
return memcapcopy;
|
|
}
|
|
|
|
/* memory functions for the streaming buffer API */
|
|
|
|
/*
|
|
void *(*Malloc)(size_t size);
|
|
*/
|
|
static void *ReassembleMalloc(size_t size)
|
|
{
|
|
if (StreamTcpReassembleCheckMemcap(size) == 0)
|
|
return NULL;
|
|
void *ptr = SCMalloc(size);
|
|
if (ptr == NULL)
|
|
return NULL;
|
|
StreamTcpReassembleIncrMemuse(size);
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
void *(*Calloc)(size_t n, size_t size);
|
|
*/
|
|
static void *ReassembleCalloc(size_t n, size_t size)
|
|
{
|
|
if (StreamTcpReassembleCheckMemcap(n * size) == 0)
|
|
return NULL;
|
|
void *ptr = SCCalloc(n, size);
|
|
if (ptr == NULL)
|
|
return NULL;
|
|
StreamTcpReassembleIncrMemuse(n * size);
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
void *(*Realloc)(void *ptr, size_t orig_size, size_t size);
|
|
*/
|
|
static void *ReassembleRealloc(void *optr, size_t orig_size, size_t size)
|
|
{
|
|
if (size > orig_size) {
|
|
if (StreamTcpReassembleCheckMemcap(size - orig_size) == 0)
|
|
return NULL;
|
|
}
|
|
void *nptr = SCRealloc(optr, size);
|
|
if (nptr == NULL)
|
|
return NULL;
|
|
|
|
if (size > orig_size) {
|
|
StreamTcpReassembleIncrMemuse(size - orig_size);
|
|
} else {
|
|
StreamTcpReassembleDecrMemuse(orig_size - size);
|
|
}
|
|
return nptr;
|
|
}
|
|
|
|
/*
|
|
void (*Free)(void *ptr, size_t size);
|
|
*/
|
|
static void ReassembleFree(void *ptr, size_t size)
|
|
{
|
|
SCFree(ptr);
|
|
StreamTcpReassembleDecrMemuse(size);
|
|
}
|
|
|
|
/** \brief alloc a tcp segment pool entry */
|
|
static void *TcpSegmentPoolAlloc(void)
|
|
{
|
|
if (StreamTcpReassembleCheckMemcap((uint32_t)sizeof(TcpSegment)) == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
TcpSegment *seg = NULL;
|
|
|
|
seg = SCMalloc(sizeof (TcpSegment));
|
|
if (unlikely(seg == NULL))
|
|
return NULL;
|
|
return seg;
|
|
}
|
|
|
|
static int TcpSegmentPoolInit(void *data, void *initdata)
|
|
{
|
|
TcpSegment *seg = (TcpSegment *) data;
|
|
|
|
/* do this before the can bail, so TcpSegmentPoolCleanup
|
|
* won't have uninitialized memory to consider. */
|
|
memset(seg, 0, sizeof (TcpSegment));
|
|
|
|
if (StreamTcpReassembleCheckMemcap((uint32_t)sizeof(TcpSegment)) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
SCMutexLock(&segment_pool_memuse_mutex);
|
|
segment_pool_memuse += sizeof(TcpSegment);
|
|
segment_pool_memcnt++;
|
|
SCLogDebug("segment_pool_memcnt %"PRIu64"", segment_pool_memcnt);
|
|
SCMutexUnlock(&segment_pool_memuse_mutex);
|
|
#endif
|
|
|
|
StreamTcpReassembleIncrMemuse((uint32_t)sizeof(TcpSegment));
|
|
return 1;
|
|
}
|
|
|
|
/** \brief clean up a tcp segment pool entry */
|
|
static void TcpSegmentPoolCleanup(void *ptr)
|
|
{
|
|
if (ptr == NULL)
|
|
return;
|
|
|
|
StreamTcpReassembleDecrMemuse((uint32_t)sizeof(TcpSegment));
|
|
|
|
#ifdef DEBUG
|
|
SCMutexLock(&segment_pool_memuse_mutex);
|
|
segment_pool_memuse -= sizeof(TcpSegment);
|
|
segment_pool_memcnt--;
|
|
SCLogDebug("segment_pool_memcnt %"PRIu64"", segment_pool_memcnt);
|
|
SCMutexUnlock(&segment_pool_memuse_mutex);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* \brief Function to return the segment back to the pool.
|
|
*
|
|
* \param seg Segment which will be returned back to the pool.
|
|
*/
|
|
void StreamTcpSegmentReturntoPool(TcpSegment *seg)
|
|
{
|
|
if (seg == NULL)
|
|
return;
|
|
|
|
PoolThreadReturn(segment_thread_pool, seg);
|
|
}
|
|
|
|
/**
|
|
* \brief return all segments in this stream into the pool(s)
|
|
*
|
|
* \param stream the stream to cleanup
|
|
*/
|
|
void StreamTcpReturnStreamSegments (TcpStream *stream)
|
|
{
|
|
TcpSegment *seg = NULL, *safe = NULL;
|
|
RB_FOREACH_SAFE(seg, TCPSEG, &stream->seg_tree, safe)
|
|
{
|
|
RB_REMOVE(TCPSEG, &stream->seg_tree, seg);
|
|
StreamTcpSegmentReturntoPool(seg);
|
|
}
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
/** \internal
|
|
* \brief check if segments falls before stream 'offset' */
|
|
static inline int SEGMENT_BEFORE_OFFSET(TcpStream *stream, TcpSegment *seg, uint64_t offset)
|
|
{
|
|
if (seg->sbseg.stream_offset + seg->sbseg.segment_len <= offset)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/** \param f locked flow */
|
|
void StreamTcpDisableAppLayer(Flow *f)
|
|
{
|
|
if (f->protoctx == NULL)
|
|
return;
|
|
|
|
TcpSession *ssn = (TcpSession *)f->protoctx;
|
|
StreamTcpSetStreamFlagAppProtoDetectionCompleted(&ssn->client);
|
|
StreamTcpSetStreamFlagAppProtoDetectionCompleted(&ssn->server);
|
|
StreamTcpDisableAppLayerReassembly(ssn);
|
|
}
|
|
|
|
/** \param f locked flow */
|
|
int StreamTcpAppLayerIsDisabled(Flow *f)
|
|
{
|
|
if (f->protoctx == NULL || f->proto != IPPROTO_TCP)
|
|
return 0;
|
|
|
|
TcpSession *ssn = (TcpSession *)f->protoctx;
|
|
return (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
}
|
|
|
|
static int StreamTcpReassemblyConfig(char quiet)
|
|
{
|
|
uint32_t segment_prealloc = 2048;
|
|
ConfNode *seg = ConfGetNode("stream.reassembly.segment-prealloc");
|
|
if (seg) {
|
|
uint32_t prealloc = 0;
|
|
if (ByteExtractStringUint32(&prealloc, 10, strlen(seg->val), seg->val) == -1)
|
|
{
|
|
SCLogError(SC_ERR_INVALID_ARGUMENT, "segment-prealloc of "
|
|
"%s is invalid", seg->val);
|
|
return -1;
|
|
}
|
|
segment_prealloc = prealloc;
|
|
}
|
|
if (!quiet)
|
|
SCLogConfig("stream.reassembly \"segment-prealloc\": %u", segment_prealloc);
|
|
stream_config.prealloc_segments = segment_prealloc;
|
|
|
|
int overlap_diff_data = 0;
|
|
ConfGetBool("stream.reassembly.check-overlap-different-data", &overlap_diff_data);
|
|
if (overlap_diff_data) {
|
|
StreamTcpReassembleConfigEnableOverlapCheck();
|
|
}
|
|
if (StreamTcpInlineMode() == TRUE) {
|
|
StreamTcpReassembleConfigEnableOverlapCheck();
|
|
}
|
|
|
|
stream_config.sbcnf.flags = STREAMING_BUFFER_NOFLAGS;
|
|
stream_config.sbcnf.buf_size = 2048;
|
|
stream_config.sbcnf.Malloc = ReassembleMalloc;
|
|
stream_config.sbcnf.Calloc = ReassembleCalloc;
|
|
stream_config.sbcnf.Realloc = ReassembleRealloc;
|
|
stream_config.sbcnf.Free = ReassembleFree;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int StreamTcpReassembleInit(char quiet)
|
|
{
|
|
/* init the memcap/use tracker */
|
|
StreamTcpReassembleInitMemuse();
|
|
|
|
if (StreamTcpReassemblyConfig(quiet) < 0)
|
|
return -1;
|
|
|
|
#ifdef DEBUG
|
|
SCMutexInit(&segment_pool_memuse_mutex, NULL);
|
|
#endif
|
|
StatsRegisterGlobalCounter("tcp.reassembly_memuse",
|
|
StreamTcpReassembleMemuseGlobalCounter);
|
|
return 0;
|
|
}
|
|
|
|
void StreamTcpReassembleFree(char quiet)
|
|
{
|
|
SCMutexLock(&segment_thread_pool_mutex);
|
|
if (segment_thread_pool != NULL) {
|
|
PoolThreadFree(segment_thread_pool);
|
|
segment_thread_pool = NULL;
|
|
}
|
|
SCMutexUnlock(&segment_thread_pool_mutex);
|
|
SCMutexDestroy(&segment_thread_pool_mutex);
|
|
|
|
#ifdef DEBUG
|
|
if (segment_pool_memuse > 0)
|
|
SCLogInfo("segment_pool_memuse %"PRIu64"", segment_pool_memuse);
|
|
if (segment_pool_memcnt > 0)
|
|
SCLogInfo("segment_pool_memcnt %"PRIu64"", segment_pool_memcnt);
|
|
SCMutexDestroy(&segment_pool_memuse_mutex);
|
|
#endif
|
|
}
|
|
|
|
TcpReassemblyThreadCtx *StreamTcpReassembleInitThreadCtx(ThreadVars *tv)
|
|
{
|
|
SCEnter();
|
|
TcpReassemblyThreadCtx *ra_ctx = SCMalloc(sizeof(TcpReassemblyThreadCtx));
|
|
if (unlikely(ra_ctx == NULL))
|
|
return NULL;
|
|
|
|
memset(ra_ctx, 0x00, sizeof(TcpReassemblyThreadCtx));
|
|
|
|
ra_ctx->app_tctx = AppLayerGetCtxThread(tv);
|
|
|
|
SCMutexLock(&segment_thread_pool_mutex);
|
|
if (segment_thread_pool == NULL) {
|
|
segment_thread_pool = PoolThreadInit(1, /* thread */
|
|
0, /* unlimited */
|
|
stream_config.prealloc_segments,
|
|
sizeof(TcpSegment),
|
|
TcpSegmentPoolAlloc,
|
|
TcpSegmentPoolInit, NULL,
|
|
TcpSegmentPoolCleanup, NULL);
|
|
ra_ctx->segment_thread_pool_id = 0;
|
|
SCLogDebug("pool size %d, thread segment_thread_pool_id %d",
|
|
PoolThreadSize(segment_thread_pool),
|
|
ra_ctx->segment_thread_pool_id);
|
|
} else {
|
|
/* grow segment_thread_pool until we have a element for our thread id */
|
|
ra_ctx->segment_thread_pool_id = PoolThreadGrow(segment_thread_pool,
|
|
0, /* unlimited */
|
|
stream_config.prealloc_segments,
|
|
sizeof(TcpSegment),
|
|
TcpSegmentPoolAlloc,
|
|
TcpSegmentPoolInit, NULL,
|
|
TcpSegmentPoolCleanup, NULL);
|
|
SCLogDebug("pool size %d, thread segment_thread_pool_id %d",
|
|
PoolThreadSize(segment_thread_pool),
|
|
ra_ctx->segment_thread_pool_id);
|
|
}
|
|
SCMutexUnlock(&segment_thread_pool_mutex);
|
|
if (ra_ctx->segment_thread_pool_id < 0 || segment_thread_pool == NULL) {
|
|
SCLogError(SC_ERR_MEM_ALLOC, "failed to setup/expand stream segment pool. Expand stream.reassembly.memcap?");
|
|
StreamTcpReassembleFreeThreadCtx(ra_ctx);
|
|
SCReturnPtr(NULL, "TcpReassemblyThreadCtx");
|
|
}
|
|
|
|
SCReturnPtr(ra_ctx, "TcpReassemblyThreadCtx");
|
|
}
|
|
|
|
void StreamTcpReassembleFreeThreadCtx(TcpReassemblyThreadCtx *ra_ctx)
|
|
{
|
|
SCEnter();
|
|
AppLayerDestroyCtxThread(ra_ctx->app_tctx);
|
|
SCFree(ra_ctx);
|
|
SCReturn;
|
|
}
|
|
|
|
/**
|
|
* \brief check if stream in pkt direction has depth reached
|
|
*
|
|
* \param p packet with *LOCKED* flow
|
|
*
|
|
* \retval 1 stream has depth reached
|
|
* \retval 0 stream does not have depth reached
|
|
*/
|
|
int StreamTcpReassembleDepthReached(Packet *p)
|
|
{
|
|
if (p->flow != NULL && p->flow->protoctx != NULL) {
|
|
TcpSession *ssn = p->flow->protoctx;
|
|
TcpStream *stream;
|
|
if (p->flowflags & FLOW_PKT_TOSERVER) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
return (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) ? 1 : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Function to Check the reassembly depth valuer against the
|
|
* allowed max depth of the stream reassmbly for TCP streams.
|
|
*
|
|
* \param stream stream direction
|
|
* \param seq sequence number where "size" starts
|
|
* \param size size of the segment that is added
|
|
*
|
|
* \retval size Part of the size that fits in the depth, 0 if none
|
|
*/
|
|
static uint32_t StreamTcpReassembleCheckDepth(TcpSession *ssn, TcpStream *stream,
|
|
uint32_t seq, uint32_t size)
|
|
{
|
|
SCEnter();
|
|
|
|
/* if the configured depth value is 0, it means there is no limit on
|
|
reassembly depth. Otherwise carry on my boy ;) */
|
|
if (ssn->reassembly_depth == 0) {
|
|
SCReturnUInt(size);
|
|
}
|
|
|
|
/* if the final flag is set, we're not accepting anymore */
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
|
|
SCReturnUInt(0);
|
|
}
|
|
|
|
uint64_t seg_depth;
|
|
if (SEQ_GT(stream->base_seq, seq)) {
|
|
if (SEQ_LEQ(seq+size, stream->base_seq)) {
|
|
SCLogDebug("segment entirely before base_seq, weird: base %u, seq %u, re %u",
|
|
stream->base_seq, seq, seq+size);
|
|
SCReturnUInt(0);
|
|
}
|
|
|
|
seg_depth = STREAM_BASE_OFFSET(stream) + size - (stream->base_seq - seq);
|
|
} else {
|
|
seg_depth = STREAM_BASE_OFFSET(stream) + ((seq + size) - stream->base_seq);
|
|
}
|
|
|
|
/* if the base_seq has moved passed the depth window we stop
|
|
* checking and just reject the rest of the packets including
|
|
* retransmissions. Saves us the hassle of dealing with sequence
|
|
* wraps as well */
|
|
SCLogDebug("seq + size %u, base %u, seg_depth %"PRIu64" limit %u", (seq + size),
|
|
stream->base_seq, seg_depth,
|
|
ssn->reassembly_depth);
|
|
|
|
if (seg_depth > (uint64_t)ssn->reassembly_depth) {
|
|
SCLogDebug("STREAMTCP_STREAM_FLAG_DEPTH_REACHED");
|
|
stream->flags |= STREAMTCP_STREAM_FLAG_DEPTH_REACHED;
|
|
SCReturnUInt(0);
|
|
}
|
|
SCLogDebug("NOT STREAMTCP_STREAM_FLAG_DEPTH_REACHED");
|
|
SCLogDebug("%"PRIu64" <= %u", seg_depth, ssn->reassembly_depth);
|
|
#if 0
|
|
SCLogDebug("full depth not yet reached: %"PRIu64" <= %"PRIu32,
|
|
(stream->base_seq_offset + stream->base_seq + size),
|
|
(stream->isn + ssn->reassembly_depth));
|
|
#endif
|
|
if (SEQ_GEQ(seq, stream->isn) && SEQ_LT(seq, (stream->isn + ssn->reassembly_depth))) {
|
|
/* packet (partly?) fits the depth window */
|
|
|
|
if (SEQ_LEQ((seq + size),(stream->isn + 1 + ssn->reassembly_depth))) {
|
|
/* complete fit */
|
|
SCReturnUInt(size);
|
|
} else {
|
|
stream->flags |= STREAMTCP_STREAM_FLAG_DEPTH_REACHED;
|
|
/* partial fit, return only what fits */
|
|
uint32_t part = (stream->isn + 1 + ssn->reassembly_depth) - seq;
|
|
DEBUG_VALIDATE_BUG_ON(part > size);
|
|
if (part > size)
|
|
part = size;
|
|
SCReturnUInt(part);
|
|
}
|
|
}
|
|
|
|
SCReturnUInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Insert a packets TCP data into the stream reassembly engine.
|
|
*
|
|
* \retval 0 good segment, as far as we checked.
|
|
* \retval -1 badness, reason to drop in inline mode
|
|
*
|
|
* If the retval is 0 the segment is inserted correctly, or overlap is handled,
|
|
* or it wasn't added because of reassembly depth.
|
|
*
|
|
*/
|
|
int StreamTcpReassembleHandleSegmentHandleData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
|
|
TcpSession *ssn, TcpStream *stream, Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
if (ssn->data_first_seen_dir == 0) {
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
ssn->data_first_seen_dir = STREAM_TOSERVER;
|
|
} else {
|
|
ssn->data_first_seen_dir = STREAM_TOCLIENT;
|
|
}
|
|
}
|
|
|
|
/* If the OS policy is not set then set the OS policy for this stream */
|
|
if (stream->os_policy == 0) {
|
|
StreamTcpSetOSPolicy(stream, p);
|
|
}
|
|
|
|
if ((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) &&
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED)) {
|
|
SCLogDebug("ssn %p: both app and raw reassembly disabled, not reassembling", ssn);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/* If we have reached the defined depth for either of the stream, then stop
|
|
reassembling the TCP session */
|
|
uint32_t size = StreamTcpReassembleCheckDepth(ssn, stream, TCP_GET_SEQ(p), p->payload_len);
|
|
SCLogDebug("ssn %p: check depth returned %"PRIu32, ssn, size);
|
|
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
|
|
/* increment stream depth counter */
|
|
StatsIncr(tv, ra_ctx->counter_tcp_stream_depth);
|
|
}
|
|
if (size == 0) {
|
|
SCLogDebug("ssn %p: depth reached, not reassembling", ssn);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
DEBUG_VALIDATE_BUG_ON(size > p->payload_len);
|
|
if (size > p->payload_len)
|
|
size = p->payload_len;
|
|
|
|
TcpSegment *seg = StreamTcpGetSegment(tv, ra_ctx);
|
|
if (seg == NULL) {
|
|
SCLogDebug("segment_pool is empty");
|
|
StreamTcpSetEvent(p, STREAM_REASSEMBLY_NO_SEGMENT);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
TCP_SEG_LEN(seg) = size;
|
|
seg->seq = TCP_GET_SEQ(p);
|
|
|
|
/* proto detection skipped, but now we do get data. Set event. */
|
|
if (RB_EMPTY(&stream->seg_tree) &&
|
|
stream->flags & STREAMTCP_STREAM_FLAG_APPPROTO_DETECTION_SKIPPED) {
|
|
|
|
AppLayerDecoderEventsSetEventRaw(&p->app_layer_events,
|
|
APPLAYER_PROTO_DETECTION_SKIPPED);
|
|
}
|
|
|
|
if (StreamTcpReassembleInsertSegment(tv, ra_ctx, stream, seg, p, TCP_GET_SEQ(p), p->payload, p->payload_len) != 0) {
|
|
SCLogDebug("StreamTcpReassembleInsertSegment failed");
|
|
SCReturnInt(-1);
|
|
}
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
static uint8_t StreamGetAppLayerFlags(TcpSession *ssn, TcpStream *stream,
|
|
Packet *p, enum StreamUpdateDir dir)
|
|
{
|
|
uint8_t flag = 0;
|
|
|
|
if (!(stream->flags & STREAMTCP_STREAM_FLAG_APPPROTO_DETECTION_COMPLETED)) {
|
|
flag |= STREAM_START;
|
|
}
|
|
|
|
if (ssn->state == TCP_CLOSED) {
|
|
flag |= STREAM_EOF;
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
|
|
flag |= STREAM_MIDSTREAM;
|
|
}
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END) {
|
|
flag |= STREAM_EOF;
|
|
}
|
|
|
|
if (dir == UPDATE_DIR_OPPOSING) {
|
|
if (p->flowflags & FLOW_PKT_TOSERVER) {
|
|
flag |= STREAM_TOCLIENT;
|
|
} else {
|
|
flag |= STREAM_TOSERVER;
|
|
}
|
|
} else {
|
|
if (p->flowflags & FLOW_PKT_TOSERVER) {
|
|
flag |= STREAM_TOSERVER;
|
|
} else {
|
|
flag |= STREAM_TOCLIENT;
|
|
}
|
|
}
|
|
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
|
|
flag |= STREAM_DEPTH;
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
/**
|
|
* \brief Check the minimum size limits for reassembly.
|
|
*
|
|
* \retval 0 don't reassemble yet
|
|
* \retval 1 do reassemble
|
|
*/
|
|
static int StreamTcpReassembleRawCheckLimit(const TcpSession *ssn,
|
|
const TcpStream *stream, const Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
/* if any of these flags is set we always inspect immediately */
|
|
#define STREAMTCP_STREAM_FLAG_FLUSH_FLAGS \
|
|
( STREAMTCP_STREAM_FLAG_DEPTH_REACHED \
|
|
| STREAMTCP_STREAM_FLAG_TRIGGER_RAW \
|
|
| STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED)
|
|
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_FLUSH_FLAGS) {
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) {
|
|
SCLogDebug("reassembling now as STREAMTCP_STREAM_FLAG_DEPTH_REACHED "
|
|
"is set, so not expecting any new data segments");
|
|
}
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_TRIGGER_RAW) {
|
|
SCLogDebug("reassembling now as STREAMTCP_STREAM_FLAG_TRIGGER_RAW is set");
|
|
}
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED) {
|
|
SCLogDebug("reassembling now as STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED is set, "
|
|
"so no new segments will be considered");
|
|
}
|
|
SCReturnInt(1);
|
|
}
|
|
#undef STREAMTCP_STREAM_FLAG_FLUSH_FLAGS
|
|
|
|
/* some states mean we reassemble no matter how much data we have */
|
|
if (ssn->state > TCP_TIME_WAIT)
|
|
SCReturnInt(1);
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
SCReturnInt(1);
|
|
|
|
/* check if we have enough data to do raw reassembly */
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
uint32_t delta = stream->last_ack - stream->base_seq;
|
|
/* get max absolute offset */
|
|
uint64_t max_offset = STREAM_BASE_OFFSET(stream) + delta;
|
|
|
|
int64_t diff = max_offset - STREAM_RAW_PROGRESS(stream);
|
|
if ((int64_t)stream_config.reassembly_toserver_chunk_size <= diff) {
|
|
SCReturnInt(1);
|
|
} else {
|
|
SCLogDebug("toserver min chunk len not yet reached: "
|
|
"last_ack %"PRIu32", ra_raw_base_seq %"PRIu32", %"PRIu32" < "
|
|
"%"PRIu32"", stream->last_ack, stream->base_seq,
|
|
(stream->last_ack - stream->base_seq),
|
|
stream_config.reassembly_toserver_chunk_size);
|
|
SCReturnInt(0);
|
|
}
|
|
}
|
|
} else {
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
uint32_t delta = stream->last_ack - stream->base_seq;
|
|
/* get max absolute offset */
|
|
uint64_t max_offset = STREAM_BASE_OFFSET(stream) + delta;
|
|
|
|
int64_t diff = max_offset - STREAM_RAW_PROGRESS(stream);
|
|
|
|
if ((int64_t)stream_config.reassembly_toclient_chunk_size <= diff) {
|
|
SCReturnInt(1);
|
|
} else {
|
|
SCLogDebug("toclient min chunk len not yet reached: "
|
|
"last_ack %"PRIu32", base_seq %"PRIu32", %"PRIu32" < "
|
|
"%"PRIu32"", stream->last_ack, stream->base_seq,
|
|
(stream->last_ack - stream->base_seq),
|
|
stream_config.reassembly_toclient_chunk_size);
|
|
SCReturnInt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief see what if any work the TCP session still needs
|
|
*/
|
|
int StreamNeedsReassembly(const TcpSession *ssn, uint8_t direction)
|
|
{
|
|
const TcpStream *stream = NULL;
|
|
#ifdef DEBUG
|
|
const char *dirstr = NULL;
|
|
#endif
|
|
if (direction == STREAM_TOSERVER) {
|
|
stream = &ssn->client;
|
|
#ifdef DEBUG
|
|
dirstr = "client";
|
|
#endif
|
|
} else {
|
|
stream = &ssn->server;
|
|
#ifdef DEBUG
|
|
dirstr = "server";
|
|
#endif
|
|
}
|
|
|
|
int use_app = 1;
|
|
int use_raw = 1;
|
|
|
|
if ((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) ||
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_GAP))
|
|
{
|
|
// app is dead
|
|
use_app = 0;
|
|
}
|
|
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_DISABLE_RAW) {
|
|
// raw is dead
|
|
use_raw = 0;
|
|
}
|
|
|
|
uint64_t right_edge = STREAM_BASE_OFFSET(stream) + stream->sb.buf_offset;
|
|
|
|
SCLogDebug("%s: app %"PRIu64" (use: %s), raw %"PRIu64" (use: %s). Stream right edge: %"PRIu64,
|
|
dirstr,
|
|
STREAM_APP_PROGRESS(stream), use_app ? "yes" : "no",
|
|
STREAM_RAW_PROGRESS(stream), use_raw ? "yes" : "no",
|
|
right_edge);
|
|
if (use_raw) {
|
|
if (right_edge > STREAM_RAW_PROGRESS(stream)) {
|
|
SCLogDebug("%s: STREAM_HAS_UNPROCESSED_SEGMENTS_NEED_ONLY_DETECTION", dirstr);
|
|
return STREAM_HAS_UNPROCESSED_SEGMENTS_NEED_ONLY_DETECTION;
|
|
}
|
|
}
|
|
if (use_app) {
|
|
if (right_edge > STREAM_APP_PROGRESS(stream)) {
|
|
SCLogDebug("%s: STREAM_HAS_UNPROCESSED_SEGMENTS_NEED_ONLY_DETECTION", dirstr);
|
|
return STREAM_HAS_UNPROCESSED_SEGMENTS_NEED_ONLY_DETECTION;
|
|
}
|
|
}
|
|
|
|
SCLogDebug("%s: STREAM_HAS_UNPROCESSED_SEGMENTS_NONE", dirstr);
|
|
return STREAM_HAS_UNPROCESSED_SEGMENTS_NONE;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static uint64_t GetStreamSize(TcpStream *stream)
|
|
{
|
|
if (stream) {
|
|
uint64_t size = 0;
|
|
uint32_t cnt = 0;
|
|
|
|
TcpSegment *seg;
|
|
RB_FOREACH(seg, TCPSEG, &stream->seg_tree) {
|
|
cnt++;
|
|
size += (uint64_t)TCP_SEG_LEN(seg);
|
|
}
|
|
|
|
SCLogDebug("size %"PRIu64", cnt %"PRIu32, size, cnt);
|
|
return size;
|
|
}
|
|
return (uint64_t)0;
|
|
}
|
|
|
|
static void GetSessionSize(TcpSession *ssn, Packet *p)
|
|
{
|
|
uint64_t size = 0;
|
|
if (ssn) {
|
|
size = GetStreamSize(&ssn->client);
|
|
size += GetStreamSize(&ssn->server);
|
|
|
|
//if (size > 900000)
|
|
// SCLogInfo("size %"PRIu64", packet %"PRIu64, size, p->pcap_cnt);
|
|
SCLogDebug("size %"PRIu64", packet %"PRIu64, size, p->pcap_cnt);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/** \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)
|
|
{
|
|
const uint8_t *mydata;
|
|
uint32_t mydata_len;
|
|
|
|
if (RB_EMPTY(&stream->sb.sbb_tree)) {
|
|
SCLogDebug("getting one blob");
|
|
|
|
StreamingBufferGetDataAtOffset(&stream->sb, &mydata, &mydata_len, offset);
|
|
|
|
*data = mydata;
|
|
*data_len = mydata_len;
|
|
} else {
|
|
StreamingBufferBlock *blk = stream->sb.head;
|
|
|
|
if (blk->offset > offset) {
|
|
SCLogDebug("gap, want data at offset %"PRIu64", "
|
|
"got data at %"PRIu64". GAP of size %"PRIu64,
|
|
offset, blk->offset, blk->offset - offset);
|
|
*data = NULL;
|
|
*data_len = blk->offset - offset;
|
|
|
|
} else if (offset >= (blk->offset + blk->len)) {
|
|
|
|
*data = NULL;
|
|
StreamingBufferBlock *nblk = SBB_RB_NEXT(blk);
|
|
*data_len = nblk ? nblk->offset - offset : 0;
|
|
if (nblk) {
|
|
SCLogDebug("gap, want data at offset %"PRIu64", "
|
|
"got data at %"PRIu64". GAP of size %"PRIu64,
|
|
offset, nblk->offset, nblk->offset - offset);
|
|
}
|
|
|
|
} else if (offset > blk->offset && offset < (blk->offset + blk->len)) {
|
|
SCLogDebug("get data from offset %"PRIu64". SBB %"PRIu64"/%u",
|
|
offset, blk->offset, blk->len);
|
|
StreamingBufferSBBGetDataAtOffset(&stream->sb, blk, data, data_len, offset);
|
|
SCLogDebug("data %p, data_len %u", *data, *data_len);
|
|
} else {
|
|
StreamingBufferSBBGetData(&stream->sb, blk, data, data_len);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \internal
|
|
* \brief check to see if we should declare a GAP
|
|
* Call this when the app layer didn't get data at the requested
|
|
* offset.
|
|
*/
|
|
static inline bool CheckGap(TcpSession *ssn, TcpStream *stream, Packet *p)
|
|
{
|
|
const uint64_t app_progress = STREAM_APP_PROGRESS(stream);
|
|
uint64_t last_ack_abs = STREAM_BASE_OFFSET(stream);
|
|
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
/* get window of data that is acked */
|
|
const uint32_t delta = stream->last_ack - stream->base_seq;
|
|
DEBUG_VALIDATE_BUG_ON(delta > 10000000ULL && delta > stream->window);
|
|
/* get max absolute offset */
|
|
last_ack_abs += delta;
|
|
|
|
const int ackadded = (ssn->state >= TCP_FIN_WAIT1) ? 1 : 0;
|
|
last_ack_abs -= ackadded;
|
|
|
|
SCLogDebug("last_ack %u abs %"PRIu64, stream->last_ack, last_ack_abs);
|
|
SCLogDebug("next_seq %u", stream->next_seq);
|
|
|
|
/* if last_ack_abs is beyond the app_progress data that we haven't seen
|
|
* has been ack'd. This looks like a GAP. */
|
|
if (last_ack_abs > app_progress) {
|
|
/* however, we can accept ACKs a bit too liberally. If last_ack
|
|
* is beyond next_seq, we only consider it a gap now if we do
|
|
* already have data beyond the gap. */
|
|
if (SEQ_GT(stream->last_ack, stream->next_seq)) {
|
|
if (RB_EMPTY(&stream->sb.sbb_tree)) {
|
|
SCLogDebug("packet %"PRIu64": no GAP. "
|
|
"next_seq %u < last_ack %u, but no data in list",
|
|
p->pcap_cnt, stream->next_seq, stream->last_ack);
|
|
return false;
|
|
} else {
|
|
const uint64_t next_seq_abs = STREAM_BASE_OFFSET(stream) + (stream->next_seq - stream->base_seq);
|
|
const StreamingBufferBlock *blk = stream->sb.head;
|
|
if (blk->offset > next_seq_abs && blk->offset < last_ack_abs) {
|
|
/* ack'd data after the gap */
|
|
SCLogDebug("packet %"PRIu64": GAP. "
|
|
"next_seq %u < last_ack %u, but ACK'd data beyond gap.",
|
|
p->pcap_cnt, stream->next_seq, stream->last_ack);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
SCLogDebug("packet %"PRIu64": GAP! "
|
|
"last_ack_abs %"PRIu64" > app_progress %"PRIu64", "
|
|
"but we have no data.",
|
|
p->pcap_cnt, last_ack_abs, app_progress);
|
|
return true;
|
|
}
|
|
}
|
|
SCLogDebug("packet %"PRIu64": no GAP. "
|
|
"last_ack_abs %"PRIu64" <= app_progress %"PRIu64,
|
|
p->pcap_cnt, last_ack_abs, app_progress);
|
|
return false;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief get stream buffer and update the app-layer
|
|
* \retval 0 success
|
|
*/
|
|
static int ReassembleUpdateAppLayer (ThreadVars *tv,
|
|
TcpReassemblyThreadCtx *ra_ctx,
|
|
TcpSession *ssn, TcpStream *stream,
|
|
Packet *p, enum StreamUpdateDir dir)
|
|
{
|
|
uint64_t app_progress = STREAM_APP_PROGRESS(stream);
|
|
|
|
SCLogDebug("app progress %"PRIu64, app_progress);
|
|
SCLogDebug("last_ack %u, base_seq %u", stream->last_ack, stream->base_seq);
|
|
|
|
const uint8_t *mydata;
|
|
uint32_t mydata_len;
|
|
|
|
while (1) {
|
|
GetAppBuffer(stream, &mydata, &mydata_len, app_progress);
|
|
if (mydata == NULL && mydata_len > 0 && CheckGap(ssn, stream, p)) {
|
|
SCLogDebug("sending GAP to app-layer (size: %u)", mydata_len);
|
|
|
|
int r = AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
|
|
NULL, mydata_len,
|
|
StreamGetAppLayerFlags(ssn, stream, p, dir)|STREAM_GAP);
|
|
AppLayerProfilingStore(ra_ctx->app_tctx, p);
|
|
|
|
StreamTcpSetEvent(p, STREAM_REASSEMBLY_SEQ_GAP);
|
|
StatsIncr(tv, ra_ctx->counter_tcp_reass_gap);
|
|
|
|
stream->app_progress_rel += mydata_len;
|
|
app_progress += mydata_len;
|
|
if (r < 0)
|
|
break;
|
|
|
|
continue;
|
|
} else if (mydata == NULL || mydata_len == 0) {
|
|
/* Possibly a gap, but no new data. */
|
|
return 0;
|
|
}
|
|
SCLogDebug("%"PRIu64" got %p/%u", p->pcap_cnt, mydata, mydata_len);
|
|
break;
|
|
}
|
|
|
|
//PrintRawDataFp(stdout, mydata, mydata_len);
|
|
|
|
SCLogDebug("stream %p data in buffer %p of len %u and offset %"PRIu64,
|
|
stream, &stream->sb, mydata_len, app_progress);
|
|
|
|
/* get window of data that is acked */
|
|
if (StreamTcpInlineMode() == FALSE) {
|
|
if (p->flags & PKT_PSEUDO_STREAM_END) {
|
|
// fall through, we use all available data
|
|
} else {
|
|
uint64_t last_ack_abs = app_progress; /* absolute right edge of ack'd data */
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
/* get window of data that is acked */
|
|
uint32_t delta = stream->last_ack - stream->base_seq;
|
|
DEBUG_VALIDATE_BUG_ON(delta > 10000000ULL && delta > stream->window);
|
|
/* get max absolute offset */
|
|
last_ack_abs += delta;
|
|
}
|
|
|
|
/* see if the buffer contains unack'd data as well */
|
|
if (app_progress + mydata_len > last_ack_abs) {
|
|
uint32_t check = mydata_len;
|
|
mydata_len = last_ack_abs - app_progress;
|
|
BUG_ON(mydata_len > check);
|
|
SCLogDebug("data len adjusted to %u to make sure only ACK'd "
|
|
"data is considered", mydata_len);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update the app-layer */
|
|
int r = AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
|
|
(uint8_t *)mydata, mydata_len,
|
|
StreamGetAppLayerFlags(ssn, stream, p, dir));
|
|
AppLayerProfilingStore(ra_ctx->app_tctx, p);
|
|
|
|
/* see if we can update the progress */
|
|
if (r == 0 && mydata_len > 0 &&
|
|
StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(stream))
|
|
{
|
|
SCLogDebug("app progress %"PRIu64" increasing with data len %u to %"PRIu64,
|
|
app_progress, mydata_len, app_progress + mydata_len);
|
|
|
|
stream->app_progress_rel += mydata_len;
|
|
SCLogDebug("app progress now %"PRIu64, STREAM_APP_PROGRESS(stream));
|
|
} else {
|
|
SCLogDebug("NOT UPDATED app progress still %"PRIu64, app_progress);
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Update the stream reassembly upon receiving a packet.
|
|
*
|
|
* For IDS mode, the stream is in the opposite direction of the packet,
|
|
* as the ACK-packet is ACK'ing the stream.
|
|
*
|
|
* One of the utilities call by this function AppLayerHandleTCPData(),
|
|
* has a feature where it will call this very same function for the
|
|
* stream opposing the stream it is called with. This shouldn't cause
|
|
* any issues, since processing of each stream is independent of the
|
|
* other stream.
|
|
*/
|
|
int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
|
|
TcpSession *ssn, TcpStream *stream,
|
|
Packet *p, enum StreamUpdateDir dir)
|
|
{
|
|
SCEnter();
|
|
|
|
/* this function can be directly called by app layer protocol
|
|
* detection. */
|
|
if ((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) ||
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) {
|
|
SCLogDebug("stream no reassembly flag set or app-layer disabled.");
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
SCLogDebug("stream->seg_tree RB_MIN %p", RB_MIN(TCPSEG, &stream->seg_tree));
|
|
GetSessionSize(ssn, p);
|
|
#endif
|
|
/* if no segments are in the list or all are already processed,
|
|
* and state is beyond established, we send an empty msg */
|
|
if (STREAM_HAS_SEEN_DATA(stream) && STREAM_RIGHT_EDGE(stream) <= STREAM_APP_PROGRESS(stream))
|
|
{
|
|
/* send an empty EOF msg if we have no segments but TCP state
|
|
* is beyond ESTABLISHED */
|
|
if (ssn->state >= TCP_CLOSING || (p->flags & PKT_PSEUDO_STREAM_END)) {
|
|
SCLogDebug("sending empty eof message");
|
|
/* send EOF to app layer */
|
|
AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
|
|
NULL, 0,
|
|
StreamGetAppLayerFlags(ssn, stream, p, dir));
|
|
AppLayerProfilingStore(ra_ctx->app_tctx, p);
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
}
|
|
|
|
/* with all that out of the way, lets update the app-layer */
|
|
return ReassembleUpdateAppLayer(tv, ra_ctx, ssn, stream, p, dir);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief get stream data from offset
|
|
* \param offset stream offset */
|
|
static int GetRawBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data_len,
|
|
StreamingBufferBlock **iter, uint64_t offset, uint64_t *data_offset)
|
|
{
|
|
const uint8_t *mydata;
|
|
uint32_t mydata_len;
|
|
if (RB_EMPTY(&stream->sb.sbb_tree)) {
|
|
SCLogDebug("getting one blob for offset %"PRIu64, offset);
|
|
|
|
uint64_t roffset = offset;
|
|
if (offset)
|
|
StreamingBufferGetDataAtOffset(&stream->sb, &mydata, &mydata_len, offset);
|
|
else {
|
|
StreamingBufferGetData(&stream->sb, &mydata, &mydata_len, &roffset);
|
|
}
|
|
|
|
*data = mydata;
|
|
*data_len = mydata_len;
|
|
*data_offset = roffset;
|
|
} else {
|
|
SCLogDebug("multiblob %s. Want offset %"PRIu64,
|
|
*iter == NULL ? "starting" : "continuing", offset);
|
|
if (*iter == NULL) {
|
|
StreamingBufferBlock key = { .offset = offset, .len = 0 };
|
|
*iter = SBB_RB_FIND_INCLUSIVE(&stream->sb.sbb_tree, &key);
|
|
SCLogDebug("*iter %p", *iter);
|
|
}
|
|
if (*iter == NULL) {
|
|
SCLogDebug("no data");
|
|
*data = NULL;
|
|
*data_len = 0;
|
|
*data_offset = 0;
|
|
return 0;
|
|
}
|
|
SCLogDebug("getting multiple blobs. Iter %p, %"PRIu64"/%u", *iter, (*iter)->offset, (*iter)->len);
|
|
|
|
StreamingBufferSBBGetData(&stream->sb, (*iter), &mydata, &mydata_len);
|
|
SCLogDebug("mydata %p", mydata);
|
|
|
|
if ((*iter)->offset < offset) {
|
|
uint64_t delta = offset - (*iter)->offset;
|
|
if (delta < mydata_len) {
|
|
*data = mydata + delta;
|
|
*data_len = mydata_len - delta;
|
|
*data_offset = offset;
|
|
} else {
|
|
SCLogDebug("no data (yet)");
|
|
*data = NULL;
|
|
*data_len = 0;
|
|
*data_offset = 0;
|
|
}
|
|
|
|
} else {
|
|
*data = mydata;
|
|
*data_len = mydata_len;
|
|
*data_offset = (*iter)->offset;
|
|
}
|
|
|
|
*iter = SBB_RB_NEXT(*iter);
|
|
SCLogDebug("*iter %p", *iter);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** \brief does the stream engine have data to inspect?
|
|
*
|
|
* Returns true if there is data to inspect. In IDS case this is
|
|
* about ACK'd data in the packet's direction.
|
|
*
|
|
* In the IPS case this is about the packet itself.
|
|
*/
|
|
bool StreamReassembleRawHasDataReady(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
if (RB_EMPTY(&stream->seg_tree)) {
|
|
return false;
|
|
}
|
|
|
|
if (stream->flags & (STREAMTCP_STREAM_FLAG_NOREASSEMBLY|
|
|
STREAMTCP_STREAM_FLAG_DISABLE_RAW))
|
|
return false;
|
|
|
|
if (StreamTcpInlineMode() == FALSE) {
|
|
if ((STREAM_RAW_PROGRESS(stream) == STREAM_BASE_OFFSET(stream) + stream->sb.buf_offset)) {
|
|
return false;
|
|
}
|
|
if (StreamTcpReassembleRawCheckLimit(ssn, stream, p) == 1) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (p->payload_len > 0 && (p->flags & PKT_STREAM_ADD)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** \brief update stream engine after detection
|
|
*
|
|
* Tasked with progressing the 'progress' for Raw reassembly.
|
|
* 2 main scenario's:
|
|
* 1. progress is != 0, so we use this
|
|
* 2. progress is 0, meaning the detect engine didn't touch
|
|
* raw at all. In this case we need to look into progressing
|
|
* raw anyway.
|
|
*
|
|
* Additionally, this function is tasked with disabling raw
|
|
* reassembly if the app-layer requested to disable it.
|
|
*/
|
|
void StreamReassembleRawUpdateProgress(TcpSession *ssn, Packet *p, uint64_t progress)
|
|
{
|
|
TcpStream *stream;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
if (progress > STREAM_RAW_PROGRESS(stream)) {
|
|
uint32_t slide = progress - STREAM_RAW_PROGRESS(stream);
|
|
stream->raw_progress_rel += slide;
|
|
stream->flags &= ~STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
|
|
/* if app is active and beyond raw, sync raw to app */
|
|
} else if (progress == 0 &&
|
|
STREAM_APP_PROGRESS(stream) > STREAM_RAW_PROGRESS(stream) &&
|
|
!(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) &&
|
|
!(stream->flags & STREAMTCP_STREAM_FLAG_GAP))
|
|
{
|
|
/* if trigger raw is set we sync the 2 trackers */
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_TRIGGER_RAW)
|
|
{
|
|
uint32_t slide = STREAM_APP_PROGRESS(stream) - STREAM_RAW_PROGRESS(stream);
|
|
stream->raw_progress_rel += slide;
|
|
stream->flags &= ~STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
|
|
/* otherwise mix in the tcp window */
|
|
} else {
|
|
uint64_t tcp_window = stream->window;
|
|
if (tcp_window > 0 && STREAM_APP_PROGRESS(stream) > tcp_window) {
|
|
uint64_t new_raw = STREAM_APP_PROGRESS(stream) - tcp_window;
|
|
if (new_raw > STREAM_RAW_PROGRESS(stream)) {
|
|
uint32_t slide = new_raw - STREAM_RAW_PROGRESS(stream);
|
|
stream->raw_progress_rel += slide;
|
|
}
|
|
}
|
|
}
|
|
/* app is dead */
|
|
} else if (progress == 0) {
|
|
uint64_t tcp_window = stream->window;
|
|
uint64_t stream_right_edge = STREAM_BASE_OFFSET(stream) + stream->sb.buf_offset;
|
|
if (tcp_window < stream_right_edge) {
|
|
uint64_t new_raw = stream_right_edge - tcp_window;
|
|
if (new_raw > STREAM_RAW_PROGRESS(stream)) {
|
|
uint32_t slide = new_raw - STREAM_RAW_PROGRESS(stream);
|
|
stream->raw_progress_rel += slide;
|
|
}
|
|
}
|
|
stream->flags &= ~STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
|
|
} else {
|
|
SCLogDebug("p->pcap_cnt %"PRIu64": progress %"PRIu64" app %"PRIu64" raw %"PRIu64" tcp win %"PRIu32,
|
|
p->pcap_cnt, progress, STREAM_APP_PROGRESS(stream),
|
|
STREAM_RAW_PROGRESS(stream), stream->window);
|
|
}
|
|
|
|
/* if we were told to accept no more raw data, we can mark raw as
|
|
* disabled now. */
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED) {
|
|
stream->flags |= STREAMTCP_STREAM_FLAG_DISABLE_RAW;
|
|
SCLogDebug("ssn %p: STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED set, "
|
|
"now that detect ran also set STREAMTCP_STREAM_FLAG_DISABLE_RAW", ssn);
|
|
}
|
|
|
|
SCLogDebug("stream raw progress now %"PRIu64, STREAM_RAW_PROGRESS(stream));
|
|
}
|
|
|
|
/** \internal
|
|
* \brief get a buffer around the current packet and run the callback on it
|
|
*
|
|
* The inline/IPS scanning method takes the current payload and wraps it in
|
|
* data from other segments.
|
|
*
|
|
* How much data is inspected is controlled by the available data, chunk_size
|
|
* and the payload size of the packet.
|
|
*
|
|
* Large packets: if payload size is close to the chunk_size, where close is
|
|
* defined as more than 67% of the chunk_size, a larger chunk_size will be
|
|
* used: payload_len + 33% of the chunk_size.
|
|
* If the payload size if equal to or bigger than the chunk_size, we use
|
|
* payload len + 33% of the chunk size.
|
|
*/
|
|
static int StreamReassembleRawInline(TcpSession *ssn, const Packet *p,
|
|
StreamReassembleRawFunc Callback, void *cb_data, uint64_t *progress_out)
|
|
{
|
|
SCEnter();
|
|
int r = 0;
|
|
|
|
TcpStream *stream;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
if (p->payload_len == 0 || (p->flags & PKT_STREAM_ADD) == 0 ||
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY))
|
|
{
|
|
*progress_out = STREAM_RAW_PROGRESS(stream);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t chunk_size = PKT_IS_TOSERVER(p) ?
|
|
stream_config.reassembly_toserver_chunk_size :
|
|
stream_config.reassembly_toclient_chunk_size;
|
|
if (chunk_size <= p->payload_len) {
|
|
chunk_size = p->payload_len + (chunk_size / 3);
|
|
SCLogDebug("packet payload len %u, so chunk_size adjusted to %u",
|
|
p->payload_len, chunk_size);
|
|
} else if (((chunk_size / 3 ) * 2) < p->payload_len) {
|
|
chunk_size = p->payload_len + ((chunk_size / 3));
|
|
SCLogDebug("packet payload len %u, so chunk_size adjusted to %u",
|
|
p->payload_len, chunk_size);
|
|
}
|
|
|
|
uint64_t packet_leftedge_abs = STREAM_BASE_OFFSET(stream) + (TCP_GET_SEQ(p) - stream->base_seq);
|
|
uint64_t packet_rightedge_abs = packet_leftedge_abs + p->payload_len;
|
|
SCLogDebug("packet_leftedge_abs %"PRIu64", rightedge %"PRIu64,
|
|
packet_leftedge_abs, packet_rightedge_abs);
|
|
|
|
const uint8_t *mydata = NULL;
|
|
uint32_t mydata_len = 0;
|
|
uint64_t mydata_offset = 0;
|
|
/* simply return progress from the block we inspected. */
|
|
bool return_progress = false;
|
|
|
|
if (RB_EMPTY(&stream->sb.sbb_tree)) {
|
|
/* continues block */
|
|
StreamingBufferGetData(&stream->sb, &mydata, &mydata_len, &mydata_offset);
|
|
return_progress = true;
|
|
|
|
} else {
|
|
SCLogDebug("finding our SBB from offset %"PRIu64, packet_leftedge_abs);
|
|
/* find our block */
|
|
StreamingBufferBlock key = { .offset = packet_leftedge_abs, .len = p->payload_len };
|
|
StreamingBufferBlock *sbb = SBB_RB_FIND_INCLUSIVE(&stream->sb.sbb_tree, &key);
|
|
if (sbb) {
|
|
SCLogDebug("found %p offset %"PRIu64" len %u", sbb, sbb->offset, sbb->len);
|
|
StreamingBufferSBBGetData(&stream->sb, sbb, &mydata, &mydata_len);
|
|
mydata_offset = sbb->offset;
|
|
}
|
|
}
|
|
|
|
/* this can only happen if the segment insert of our current 'p' failed */
|
|
uint64_t mydata_rightedge_abs = mydata_offset + mydata_len;
|
|
if ((mydata == NULL || mydata_len == 0) || /* no data */
|
|
(mydata_offset >= packet_rightedge_abs || /* data all to the right */
|
|
packet_leftedge_abs >= mydata_rightedge_abs) || /* data all to the left */
|
|
(packet_leftedge_abs < mydata_offset || /* data missing at the start */
|
|
packet_rightedge_abs > mydata_rightedge_abs)) /* data missing at the end */
|
|
{
|
|
/* no data, or data is incomplete or wrong: use packet data */
|
|
mydata = p->payload;
|
|
mydata_len = p->payload_len;
|
|
mydata_offset = packet_leftedge_abs;
|
|
//mydata_rightedge_abs = packet_rightedge_abs;
|
|
} else {
|
|
/* adjust buffer to match chunk_size */
|
|
SCLogDebug("chunk_size %u mydata_len %u", chunk_size, mydata_len);
|
|
if (mydata_len > chunk_size) {
|
|
uint32_t excess = mydata_len - chunk_size;
|
|
SCLogDebug("chunk_size %u mydata_len %u excess %u", chunk_size, mydata_len, excess);
|
|
|
|
if (mydata_rightedge_abs == packet_rightedge_abs) {
|
|
mydata += excess;
|
|
mydata_len -= excess;
|
|
mydata_offset += excess;
|
|
SCLogDebug("cutting front of the buffer with %u", excess);
|
|
} else if (mydata_offset == packet_leftedge_abs) {
|
|
mydata_len -= excess;
|
|
SCLogDebug("cutting tail of the buffer with %u", excess);
|
|
} else {
|
|
uint32_t before = (uint32_t)(packet_leftedge_abs - mydata_offset);
|
|
uint32_t after = (uint32_t)(mydata_rightedge_abs - packet_rightedge_abs);
|
|
SCLogDebug("before %u after %u", before, after);
|
|
|
|
if (after >= (chunk_size - p->payload_len) / 2) {
|
|
// more trailing data than we need
|
|
|
|
if (before >= (chunk_size - p->payload_len) / 2) {
|
|
// also more heading data, devide evenly
|
|
before = after = (chunk_size - p->payload_len) / 2;
|
|
} else {
|
|
// heading data is less than requested, give the
|
|
// rest to the trailing data
|
|
after = (chunk_size - p->payload_len) - before;
|
|
}
|
|
} else {
|
|
// less trailing data than requested
|
|
|
|
if (before >= (chunk_size - p->payload_len) / 2) {
|
|
before = (chunk_size - p->payload_len) - after;
|
|
} else {
|
|
// both smaller than their requested size
|
|
}
|
|
}
|
|
|
|
/* adjust the buffer */
|
|
uint32_t skip = (uint32_t)(packet_leftedge_abs - mydata_offset) - before;
|
|
uint32_t cut = (uint32_t)(mydata_rightedge_abs - packet_rightedge_abs) - after;
|
|
DEBUG_VALIDATE_BUG_ON(skip > mydata_len);
|
|
DEBUG_VALIDATE_BUG_ON(cut > mydata_len);
|
|
DEBUG_VALIDATE_BUG_ON(skip + cut > mydata_len);
|
|
mydata += skip;
|
|
mydata_len -= (skip + cut);
|
|
mydata_offset += skip;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* run the callback */
|
|
r = Callback(cb_data, mydata, mydata_len);
|
|
BUG_ON(r < 0);
|
|
|
|
if (return_progress) {
|
|
*progress_out = (mydata_offset + mydata_len);
|
|
} else {
|
|
/* several blocks of data, so we need to be a bit more careful:
|
|
* - if last_ack is beyond last progress, move progress forward to last_ack
|
|
* - if our block matches or starts before last ack, return right edge of
|
|
* our block.
|
|
*/
|
|
uint64_t last_ack_abs = STREAM_BASE_OFFSET(stream);
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
uint32_t delta = stream->last_ack - stream->base_seq;
|
|
DEBUG_VALIDATE_BUG_ON(delta > 10000000ULL && delta > stream->window);
|
|
/* get max absolute offset */
|
|
last_ack_abs += delta;
|
|
}
|
|
SCLogDebug("last_ack_abs %"PRIu64, last_ack_abs);
|
|
|
|
if (STREAM_RAW_PROGRESS(stream) < last_ack_abs) {
|
|
if (mydata_offset > last_ack_abs) {
|
|
/* gap between us and last ack, set progress to last ack */
|
|
*progress_out = last_ack_abs;
|
|
} else {
|
|
*progress_out = (mydata_offset + mydata_len);
|
|
}
|
|
} else {
|
|
*progress_out = STREAM_RAW_PROGRESS(stream);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/** \brief access 'raw' reassembly data.
|
|
*
|
|
* Access data as tracked by 'raw' tracker. Data is made available to
|
|
* callback that is passed to this function.
|
|
*
|
|
* In the case of IDS the callback may be run multiple times if data
|
|
* contains gaps. It will then be run for each block of data that is
|
|
* continuous.
|
|
*
|
|
* The callback should give on of 2 return values:
|
|
* - 0 ok
|
|
* - 1 done
|
|
* The value 1 will break the loop if there is a block list that is
|
|
* inspected.
|
|
*
|
|
* This function will return the 'progress' value that has been
|
|
* consumed until now.
|
|
*
|
|
* \param ssn tcp session
|
|
* \param stream tcp stream
|
|
* \param Callback the function pointer to the callback function
|
|
* \param cb_data callback data
|
|
* \param[in] progress_in progress to work from
|
|
* \param[out] progress_out absolute progress value of the data this
|
|
* call handled.
|
|
* \param eof we're wrapping up so inspect all data we have, incl unACKd
|
|
* \param respect_inspect_depth use Stream::min_inspect_depth if set
|
|
*
|
|
* `respect_inspect_depth` is used to avoid useless inspection of too
|
|
* much data.
|
|
*/
|
|
static int StreamReassembleRawDo(TcpSession *ssn, TcpStream *stream,
|
|
StreamReassembleRawFunc Callback, void *cb_data,
|
|
const uint64_t progress_in,
|
|
uint64_t *progress_out, bool eof,
|
|
bool respect_inspect_depth)
|
|
{
|
|
SCEnter();
|
|
int r = 0;
|
|
|
|
StreamingBufferBlock *iter = NULL;
|
|
uint64_t progress = progress_in;
|
|
uint64_t last_ack_abs = STREAM_BASE_OFFSET(stream); /* absolute right edge of ack'd data */
|
|
|
|
/* if the app layer triggered a flush, and we're supposed to
|
|
* use a minimal inspect depth, we actually take the app progress
|
|
* as that is the right edge of the data. Then we take the window
|
|
* of 'min_inspect_depth' before that. */
|
|
if (respect_inspect_depth &&
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_TRIGGER_RAW)
|
|
&& stream->min_inspect_depth)
|
|
{
|
|
progress = STREAM_APP_PROGRESS(stream);
|
|
if (stream->min_inspect_depth >= progress) {
|
|
progress = 0;
|
|
} else {
|
|
progress -= stream->min_inspect_depth;
|
|
}
|
|
SCLogDebug("applied min inspect depth due to STREAMTCP_STREAM_FLAG_TRIGGER_RAW: progress %"PRIu64, progress);
|
|
|
|
SCLogDebug("stream app %"PRIu64", raw %"PRIu64, STREAM_APP_PROGRESS(stream), STREAM_RAW_PROGRESS(stream));
|
|
}
|
|
|
|
SCLogDebug("progress %"PRIu64", min inspect depth %u %s", progress, stream->min_inspect_depth, stream->flags & STREAMTCP_STREAM_FLAG_TRIGGER_RAW ? "STREAMTCP_STREAM_FLAG_TRIGGER_RAW":"(no trigger)");
|
|
|
|
/* get window of data that is acked */
|
|
if (STREAM_LASTACK_GT_BASESEQ(stream)) {
|
|
SCLogDebug("last_ack %u, base_seq %u", stream->last_ack, stream->base_seq);
|
|
uint32_t delta = stream->last_ack - stream->base_seq;
|
|
DEBUG_VALIDATE_BUG_ON(delta > 10000000ULL && delta > stream->window);
|
|
/* get max absolute offset */
|
|
last_ack_abs += delta;
|
|
SCLogDebug("last_ack_abs %"PRIu64, last_ack_abs);
|
|
}
|
|
|
|
/* loop through available buffers. On no packet loss we'll have a single
|
|
* iteration. On missing data we'll walk the blocks */
|
|
while (1) {
|
|
const uint8_t *mydata;
|
|
uint32_t mydata_len;
|
|
uint64_t mydata_offset = 0;
|
|
|
|
GetRawBuffer(stream, &mydata, &mydata_len, &iter, progress, &mydata_offset);
|
|
if (mydata_len == 0) {
|
|
SCLogDebug("no data");
|
|
break;
|
|
}
|
|
//PrintRawDataFp(stdout, mydata, mydata_len);
|
|
|
|
SCLogDebug("raw progress %"PRIu64, progress);
|
|
SCLogDebug("stream %p data in buffer %p of len %u and offset %"PRIu64,
|
|
stream, &stream->sb, mydata_len, progress);
|
|
|
|
if (eof) {
|
|
// inspect all remaining data, ack'd or not
|
|
} else {
|
|
if (last_ack_abs < progress) {
|
|
SCLogDebug("nothing to do");
|
|
goto end;
|
|
}
|
|
|
|
SCLogDebug("last_ack_abs %"PRIu64", raw_progress %"PRIu64, last_ack_abs, progress);
|
|
SCLogDebug("raw_progress + mydata_len %"PRIu64", last_ack_abs %"PRIu64, progress + mydata_len, last_ack_abs);
|
|
|
|
/* see if the buffer contains unack'd data as well */
|
|
if (progress + mydata_len > last_ack_abs) {
|
|
uint32_t check = mydata_len;
|
|
mydata_len = last_ack_abs - progress;
|
|
BUG_ON(check < mydata_len);
|
|
SCLogDebug("data len adjusted to %u to make sure only ACK'd "
|
|
"data is considered", mydata_len);
|
|
}
|
|
|
|
}
|
|
if (mydata_len == 0)
|
|
break;
|
|
|
|
SCLogDebug("data %p len %u", mydata, mydata_len);
|
|
|
|
/* we have data. */
|
|
r = Callback(cb_data, mydata, mydata_len);
|
|
BUG_ON(r < 0);
|
|
|
|
if (mydata_offset == progress) {
|
|
SCLogDebug("progress %"PRIu64" increasing with data len %u to %"PRIu64,
|
|
progress, mydata_len, progress_in + mydata_len);
|
|
|
|
progress += mydata_len;
|
|
SCLogDebug("raw progress now %"PRIu64, progress);
|
|
|
|
/* data is beyond the progress we'd like, and before last ack. Gap. */
|
|
} else if (mydata_offset > progress && mydata_offset < last_ack_abs) {
|
|
SCLogDebug("GAP: data is missing from %"PRIu64" (%u bytes), setting to first data we have: %"PRIu64, progress, (uint32_t)(mydata_offset - progress), mydata_offset);
|
|
SCLogDebug("last_ack_abs %"PRIu64, last_ack_abs);
|
|
progress = mydata_offset;
|
|
SCLogDebug("raw progress now %"PRIu64, progress);
|
|
|
|
} else {
|
|
SCLogDebug("not increasing progress, data gap => mydata_offset "
|
|
"%"PRIu64" != progress %"PRIu64, mydata_offset, progress);
|
|
}
|
|
|
|
if (iter == NULL || r == 1)
|
|
break;
|
|
}
|
|
end:
|
|
*progress_out = progress;
|
|
return r;
|
|
}
|
|
|
|
int StreamReassembleRaw(TcpSession *ssn, const Packet *p,
|
|
StreamReassembleRawFunc Callback, void *cb_data,
|
|
uint64_t *progress_out, bool respect_inspect_depth)
|
|
{
|
|
/* handle inline seperately as the logic is very different */
|
|
if (StreamTcpInlineMode() == TRUE) {
|
|
return StreamReassembleRawInline(ssn, p, Callback, cb_data, progress_out);
|
|
}
|
|
|
|
TcpStream *stream;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
if ((stream->flags & (STREAMTCP_STREAM_FLAG_NOREASSEMBLY|STREAMTCP_STREAM_FLAG_DISABLE_RAW)) ||
|
|
StreamTcpReassembleRawCheckLimit(ssn, stream, p) == 0)
|
|
{
|
|
*progress_out = STREAM_RAW_PROGRESS(stream);
|
|
return 0;
|
|
}
|
|
|
|
return StreamReassembleRawDo(ssn, stream, Callback, cb_data,
|
|
STREAM_RAW_PROGRESS(stream), progress_out,
|
|
(p->flags & PKT_PSEUDO_STREAM_END), respect_inspect_depth);
|
|
}
|
|
|
|
int StreamReassembleLog(TcpSession *ssn, TcpStream *stream,
|
|
StreamReassembleRawFunc Callback, void *cb_data,
|
|
uint64_t progress_in,
|
|
uint64_t *progress_out, bool eof)
|
|
{
|
|
if (stream->flags & (STREAMTCP_STREAM_FLAG_NOREASSEMBLY))
|
|
return 0;
|
|
|
|
return StreamReassembleRawDo(ssn, stream, Callback, cb_data,
|
|
progress_in, progress_out, eof, false);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief update app layer based on received ACK
|
|
*
|
|
* \retval r 0 on success, -1 on error
|
|
*/
|
|
static int StreamTcpReassembleHandleSegmentUpdateACK (ThreadVars *tv,
|
|
TcpReassemblyThreadCtx *ra_ctx, TcpSession *ssn, TcpStream *stream, Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
if (StreamTcpReassembleAppLayer(tv, ra_ctx, ssn, stream, p, UPDATE_DIR_OPPOSING) < 0)
|
|
SCReturnInt(-1);
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
int StreamTcpReassembleHandleSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
|
|
TcpSession *ssn, TcpStream *stream,
|
|
Packet *p, PacketQueue *pq)
|
|
{
|
|
SCEnter();
|
|
|
|
DEBUG_VALIDATE_BUG_ON(p->tcph == NULL);
|
|
|
|
SCLogDebug("ssn %p, stream %p, p %p, p->payload_len %"PRIu16"",
|
|
ssn, stream, p, p->payload_len);
|
|
|
|
/* we need to update the opposing stream in
|
|
* StreamTcpReassembleHandleSegmentUpdateACK */
|
|
TcpStream *opposing_stream = NULL;
|
|
if (stream == &ssn->client) {
|
|
opposing_stream = &ssn->server;
|
|
} else {
|
|
opposing_stream = &ssn->client;
|
|
}
|
|
|
|
/* default IDS: update opposing side (triggered by ACK) */
|
|
enum StreamUpdateDir dir = UPDATE_DIR_OPPOSING;
|
|
/* inline and stream end and flow timeout packets trigger same dir handling */
|
|
if (StreamTcpInlineMode()) {
|
|
dir = UPDATE_DIR_PACKET;
|
|
} else if (p->flags & PKT_PSEUDO_STREAM_END) {
|
|
dir = UPDATE_DIR_PACKET;
|
|
} else if (p->tcph->th_flags & TH_RST) { // accepted rst
|
|
dir = UPDATE_DIR_PACKET;
|
|
} else if ((p->tcph->th_flags & TH_FIN) && ssn->state > TCP_TIME_WAIT) {
|
|
dir = UPDATE_DIR_PACKET;
|
|
} else if (ssn->state == TCP_CLOSED) {
|
|
dir = UPDATE_DIR_BOTH;
|
|
}
|
|
|
|
/* handle ack received */
|
|
if ((dir == UPDATE_DIR_OPPOSING || dir == UPDATE_DIR_BOTH) &&
|
|
StreamTcpReassembleHandleSegmentUpdateACK(tv, ra_ctx, ssn, opposing_stream, p) != 0)
|
|
{
|
|
SCLogDebug("StreamTcpReassembleHandleSegmentUpdateACK error");
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
/* if this segment contains data, insert it */
|
|
if (p->payload_len > 0 && !(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) {
|
|
SCLogDebug("calling StreamTcpReassembleHandleSegmentHandleData");
|
|
|
|
if (StreamTcpReassembleHandleSegmentHandleData(tv, ra_ctx, ssn, stream, p) != 0) {
|
|
SCLogDebug("StreamTcpReassembleHandleSegmentHandleData error");
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
SCLogDebug("packet %"PRIu64" set PKT_STREAM_ADD", p->pcap_cnt);
|
|
p->flags |= PKT_STREAM_ADD;
|
|
} else {
|
|
SCLogDebug("ssn %p / stream %p: not calling StreamTcpReassembleHandleSegmentHandleData:"
|
|
" p->payload_len %u, STREAMTCP_STREAM_FLAG_NOREASSEMBLY %s",
|
|
ssn, stream, p->payload_len,
|
|
(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) ? "true" : "false");
|
|
|
|
}
|
|
|
|
/* if the STREAMTCP_STREAM_FLAG_DEPTH_REACHED is set, but not the
|
|
* STREAMTCP_STREAM_FLAG_NOREASSEMBLY flag, it means the DEPTH flag
|
|
* was *just* set. In this case we trigger the AppLayer Truncate
|
|
* logic, to inform the applayer no more data in this direction is
|
|
* to be expected. */
|
|
if ((stream->flags &
|
|
(STREAMTCP_STREAM_FLAG_DEPTH_REACHED|STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) ==
|
|
STREAMTCP_STREAM_FLAG_DEPTH_REACHED)
|
|
{
|
|
SCLogDebug("STREAMTCP_STREAM_FLAG_DEPTH_REACHED, truncate applayer");
|
|
if (dir != UPDATE_DIR_PACKET) {
|
|
SCLogDebug("override: direction now UPDATE_DIR_PACKET so we "
|
|
"can trigger Truncate");
|
|
dir = UPDATE_DIR_PACKET;
|
|
}
|
|
}
|
|
|
|
/* in stream inline mode even if we have no data we call the reassembly
|
|
* functions to handle EOF */
|
|
if (dir == UPDATE_DIR_PACKET || dir == UPDATE_DIR_BOTH) {
|
|
SCLogDebug("inline (%s) or PKT_PSEUDO_STREAM_END (%s)",
|
|
StreamTcpInlineMode()?"true":"false",
|
|
(p->flags & PKT_PSEUDO_STREAM_END) ?"true":"false");
|
|
if (StreamTcpReassembleAppLayer(tv, ra_ctx, ssn, stream, p, dir) < 0) {
|
|
SCReturnInt(-1);
|
|
}
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief get a segment from the pool
|
|
*
|
|
* \retval seg Segment from the pool or NULL
|
|
*/
|
|
TcpSegment *StreamTcpGetSegment(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx)
|
|
{
|
|
TcpSegment *seg = (TcpSegment *) PoolThreadGetById(segment_thread_pool, ra_ctx->segment_thread_pool_id);
|
|
SCLogDebug("seg we return is %p", seg);
|
|
if (seg == NULL) {
|
|
/* Increment the counter to show that we are not able to serve the
|
|
segment request due to memcap limit */
|
|
StatsIncr(tv, ra_ctx->counter_tcp_segment_memcap);
|
|
} else {
|
|
memset(&seg->sbseg, 0, sizeof(seg->sbseg));
|
|
}
|
|
|
|
return seg;
|
|
}
|
|
|
|
/**
|
|
* \brief Trigger RAW stream reassembly
|
|
*
|
|
* Used by AppLayerTriggerRawStreamReassembly to trigger RAW stream
|
|
* reassembly from the applayer, for example upon completion of a
|
|
* HTTP request.
|
|
*
|
|
* It sets a flag in the stream so that the next Raw call will return
|
|
* the data.
|
|
*
|
|
* \param ssn TcpSession
|
|
*/
|
|
void StreamTcpReassembleTriggerRawReassembly(TcpSession *ssn, int direction)
|
|
{
|
|
#ifdef DEBUG
|
|
BUG_ON(ssn == NULL);
|
|
#endif
|
|
|
|
if (ssn != NULL) {
|
|
if (direction == STREAM_TOSERVER) {
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
} else {
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
}
|
|
|
|
SCLogDebug("flagged ssn %p for immediate raw reassembly", ssn);
|
|
}
|
|
}
|
|
|
|
void StreamTcpReassemblySetMinInspectDepth(TcpSession *ssn, int direction, uint32_t depth)
|
|
{
|
|
#ifdef DEBUG
|
|
BUG_ON(ssn == NULL);
|
|
#endif
|
|
|
|
if (ssn != NULL) {
|
|
if (direction == STREAM_TOSERVER) {
|
|
ssn->client.min_inspect_depth = depth;
|
|
SCLogDebug("ssn %p: set client.min_inspect_depth to %u", ssn, depth);
|
|
} else {
|
|
ssn->server.min_inspect_depth = depth;
|
|
SCLogDebug("ssn %p: set server.min_inspect_depth to %u", ssn, depth);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
/** unit tests and it's support functions below */
|
|
|
|
#define SET_ISN(stream, setseq) \
|
|
(stream)->isn = (setseq); \
|
|
(stream)->base_seq = (setseq) + 1
|
|
|
|
/** \brief The Function to create the packet with given payload, which is used
|
|
* to test the reassembly of the engine.
|
|
*
|
|
* \param payload The variable used to store the payload contents of the
|
|
* current packet.
|
|
* \param value The value which current payload will have for this packet
|
|
* \param payload_len The length of the filed payload for current packet.
|
|
* \param len Length of the payload array
|
|
*/
|
|
|
|
void StreamTcpCreateTestPacket(uint8_t *payload, uint8_t value,
|
|
uint8_t payload_len, uint8_t len)
|
|
{
|
|
uint8_t i;
|
|
for (i = 0; i < payload_len; i++)
|
|
payload[i] = value;
|
|
for (; i < len; i++)
|
|
payload = NULL;
|
|
}
|
|
|
|
/** \brief The Function Checks the reassembled stream contents against predefined
|
|
* stream contents according to OS policy used.
|
|
*
|
|
* \param stream_policy Predefined value of stream for different OS policies
|
|
* \param stream Reassembled stream returned from the reassembly functions
|
|
*/
|
|
|
|
int StreamTcpCheckStreamContents(uint8_t *stream_policy, uint16_t sp_size, TcpStream *stream)
|
|
{
|
|
if (StreamingBufferCompareRawData(&stream->sb, stream_policy,(uint32_t)sp_size) == 0)
|
|
{
|
|
//PrintRawDataFp(stdout, stream_policy, sp_size);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int VALIDATE(TcpStream *stream, uint8_t *data, uint32_t data_len)
|
|
{
|
|
if (StreamingBufferCompareRawData(&stream->sb,
|
|
data, data_len) == 0)
|
|
{
|
|
SCReturnInt(0);
|
|
}
|
|
SCLogInfo("OK");
|
|
PrintRawDataFp(stdout, data, data_len);
|
|
return 1;
|
|
}
|
|
|
|
#define MISSED_START(isn) \
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL; \
|
|
TcpSession ssn; \
|
|
ThreadVars tv; \
|
|
memset(&tv, 0, sizeof(tv)); \
|
|
\
|
|
StreamTcpUTInit(&ra_ctx); \
|
|
\
|
|
StreamTcpUTSetupSession(&ssn); \
|
|
StreamTcpUTSetupStream(&ssn.server, (isn)); \
|
|
StreamTcpUTSetupStream(&ssn.client, (isn)); \
|
|
\
|
|
TcpStream *stream = &ssn.client;
|
|
|
|
#define MISSED_END \
|
|
StreamTcpUTClearSession(&ssn); \
|
|
StreamTcpUTDeinit(ra_ctx); \
|
|
PASS
|
|
|
|
#define MISSED_STEP(seq, seg, seglen, buf, buflen) \
|
|
StreamTcpUTAddPayload(&tv, ra_ctx, &ssn, stream, (seq), (uint8_t *)(seg), (seglen)); \
|
|
FAIL_IF(!(VALIDATE(stream, (uint8_t *)(buf), (buflen))));
|
|
|
|
/**
|
|
* \test Test the handling of packets missed by both IDS and the end host.
|
|
* The packet is missed in the starting of the stream.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest25 (void)
|
|
{
|
|
MISSED_START(6);
|
|
MISSED_STEP(10, "BB", 2, "\0\0\0BB", 5);
|
|
MISSED_STEP(12, "CC", 2, "\0\0\0BBCC", 7);
|
|
MISSED_STEP(7, "AAA", 3, "AAABBCC", 7);
|
|
MISSED_END;
|
|
}
|
|
|
|
/**
|
|
* \test Test the handling of packets missed by both IDS and the end host.
|
|
* The packet is missed in the middle of the stream.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest26 (void)
|
|
{
|
|
MISSED_START(9);
|
|
MISSED_STEP(10, "AAA", 3, "AAA", 3);
|
|
MISSED_STEP(15, "CC", 2, "AAA\0\0CC", 7);
|
|
MISSED_STEP(13, "BB", 2, "AAABBCC", 7);
|
|
MISSED_END;
|
|
}
|
|
|
|
/**
|
|
* \test Test the handling of packets missed by both IDS and the end host.
|
|
* The packet is missed in the end of the stream.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest27 (void)
|
|
{
|
|
MISSED_START(9);
|
|
MISSED_STEP(10, "AAA", 3, "AAA", 3);
|
|
MISSED_STEP(13, "BB", 2, "AAABB", 5);
|
|
MISSED_STEP(15, "CC", 2, "AAABBCC", 7);
|
|
MISSED_END;
|
|
}
|
|
|
|
/**
|
|
* \test Test the handling of packets missed by IDS, but the end host has
|
|
* received it and send the acknowledgment of it. The packet is missed
|
|
* in the starting of the stream.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest28 (void)
|
|
{
|
|
MISSED_START(6);
|
|
MISSED_STEP(10, "AAA", 3, "\0\0\0AAA", 6);
|
|
MISSED_STEP(13, "BB", 2, "\0\0\0AAABB", 8);
|
|
ssn.state = TCP_TIME_WAIT;
|
|
MISSED_STEP(15, "CC", 2, "\0\0\0AAABBCC", 10);
|
|
MISSED_END;
|
|
}
|
|
|
|
/**
|
|
* \test Test the handling of packets missed by IDS, but the end host has
|
|
* received it and send the acknowledgment of it. The packet is missed
|
|
* in the middle of the stream.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest29 (void)
|
|
{
|
|
MISSED_START(9);
|
|
MISSED_STEP(10, "AAA", 3, "AAA", 3);
|
|
ssn.state = TCP_TIME_WAIT;
|
|
MISSED_STEP(15, "CC", 2, "AAA\0\0CC", 7);
|
|
MISSED_END;
|
|
}
|
|
|
|
static int StreamTcpReassembleTest33(void)
|
|
{
|
|
TcpSession ssn;
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF(unlikely(p == NULL));
|
|
Flow f;
|
|
TCPHdr tcph;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ssn.client.os_policy = OS_POLICY_BSD;
|
|
uint8_t packet[1460] = "";
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
|
|
PacketQueue pq;
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset(&f, 0, sizeof (Flow));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = &ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
p->src.family = AF_INET;
|
|
p->dst.family = AF_INET;
|
|
p->proto = IPPROTO_TCP;
|
|
p->flow = &f;
|
|
tcph.th_win = 5480;
|
|
tcph.th_flags = TH_PUSH | TH_ACK;
|
|
p->tcph = &tcph;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = packet;
|
|
|
|
p->tcph->th_seq = htonl(10);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 10;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(20);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 10;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(40);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 10;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(5);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 30;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
SCFree(p);
|
|
PASS;
|
|
}
|
|
|
|
static int StreamTcpReassembleTest34(void)
|
|
{
|
|
TcpSession ssn;
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF(unlikely(p == NULL));
|
|
Flow f;
|
|
TCPHdr tcph;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ssn.client.os_policy = OS_POLICY_BSD;
|
|
uint8_t packet[1460] = "";
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
PacketQueue pq;
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset(&f, 0, sizeof (Flow));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = &ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
p->src.family = AF_INET;
|
|
p->dst.family = AF_INET;
|
|
p->proto = IPPROTO_TCP;
|
|
p->flow = &f;
|
|
tcph.th_win = 5480;
|
|
tcph.th_flags = TH_PUSH | TH_ACK;
|
|
p->tcph = &tcph;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = packet;
|
|
SET_ISN(&ssn.client, 857961230);
|
|
|
|
p->tcph->th_seq = htonl(857961230);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 304;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(857961534);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 1460;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(857963582);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 1460;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(857960946);
|
|
p->tcph->th_ack = htonl(31);
|
|
p->payload_len = 1460;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
SCFree(p);
|
|
PASS;
|
|
}
|
|
|
|
/** \test Test the bug 76 condition */
|
|
static int StreamTcpReassembleTest37(void)
|
|
{
|
|
TcpSession ssn;
|
|
Flow f;
|
|
TCPHdr tcph;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
uint8_t packet[1460] = "";
|
|
PacketQueue pq;
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF(unlikely(p == NULL));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset(&f, 0, sizeof (Flow));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = &ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
p->src.family = AF_INET;
|
|
p->dst.family = AF_INET;
|
|
p->proto = IPPROTO_TCP;
|
|
p->flow = &f;
|
|
tcph.th_win = 5480;
|
|
tcph.th_flags = TH_PUSH | TH_ACK;
|
|
p->tcph = &tcph;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = packet;
|
|
ssn.client.os_policy = OS_POLICY_BSD;
|
|
|
|
p->tcph->th_seq = htonl(3061088537UL);
|
|
p->tcph->th_ack = htonl(1729548549UL);
|
|
p->payload_len = 1391;
|
|
ssn.client.last_ack = 3061091137UL;
|
|
SET_ISN(&ssn.client, 3061091309UL);
|
|
|
|
/* pre base_seq, so should be rejected */
|
|
FAIL_IF (StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) != -1);
|
|
|
|
p->tcph->th_seq = htonl(3061089928UL);
|
|
p->tcph->th_ack = htonl(1729548549UL);
|
|
p->payload_len = 1391;
|
|
ssn.client.last_ack = 3061091137UL;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
p->tcph->th_seq = htonl(3061091319UL);
|
|
p->tcph->th_ack = htonl(1729548549UL);
|
|
p->payload_len = 1391;
|
|
ssn.client.last_ack = 3061091137UL;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx,&ssn, &ssn.client, p, &pq) == -1);
|
|
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
SCFree(p);
|
|
PASS;
|
|
}
|
|
|
|
/**
|
|
* \test Test to make sure that we don't return the segments until the app
|
|
* layer proto has been detected and after that remove the processed
|
|
* segments.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest39 (void)
|
|
{
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF(unlikely(p == NULL));
|
|
Flow f;
|
|
ThreadVars tv;
|
|
StreamTcpThread stt;
|
|
TCPHdr tcph;
|
|
PacketQueue pq;
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset (&f, 0, sizeof(Flow));
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
memset(&stt, 0, sizeof (stt));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.flags = FLOW_IPV4;
|
|
f.proto = IPPROTO_TCP;
|
|
p->flow = &f;
|
|
p->tcph = &tcph;
|
|
|
|
StreamTcpUTInit(&stt.ra_ctx);
|
|
|
|
/* handshake */
|
|
tcph.th_win = htons(5480);
|
|
tcph.th_flags = TH_SYN;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
|
|
TcpSession *ssn = (TcpSession *)f.protoctx;
|
|
FAIL_IF_NULL(ssn);
|
|
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(!RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != 0);
|
|
|
|
/* handshake */
|
|
p->tcph->th_ack = htonl(1);
|
|
p->tcph->th_flags = TH_SYN | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(!RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != 0);
|
|
|
|
/* handshake */
|
|
p->tcph->th_ack = htonl(1);
|
|
p->tcph->th_seq = htonl(1);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(!RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != 0);
|
|
|
|
/* partial request */
|
|
uint8_t request1[] = { 0x47, 0x45, };
|
|
p->tcph->th_ack = htonl(1);
|
|
p->tcph->th_seq = htonl(1);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = sizeof(request1);
|
|
p->payload = request1;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != STREAM_TOSERVER);
|
|
|
|
/* response ack against partial request */
|
|
p->tcph->th_ack = htonl(3);
|
|
p->tcph->th_seq = htonl(1);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != STREAM_TOSERVER);
|
|
|
|
/* complete partial request */
|
|
uint8_t request2[] = {
|
|
0x54, 0x20, 0x2f, 0x69, 0x6e, 0x64,
|
|
0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x20,
|
|
0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x30,
|
|
0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20,
|
|
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73,
|
|
0x74, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d,
|
|
0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x41,
|
|
0x70, 0x61, 0x63, 0x68, 0x65, 0x42, 0x65, 0x6e,
|
|
0x63, 0x68, 0x2f, 0x32, 0x2e, 0x33, 0x0d, 0x0a,
|
|
0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x3a, 0x20,
|
|
0x2a, 0x2f, 0x2a, 0x0d, 0x0a, 0x0d, 0x0a };
|
|
p->tcph->th_ack = htonl(1);
|
|
p->tcph->th_seq = htonl(3);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = sizeof(request2);
|
|
p->payload = request2;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_UNKNOWN);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(!RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(ssn->data_first_seen_dir != STREAM_TOSERVER);
|
|
|
|
/* response - request ack */
|
|
uint8_t response[] = {
|
|
0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31,
|
|
0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d,
|
|
0x0a, 0x44, 0x61, 0x74, 0x65, 0x3a, 0x20, 0x46,
|
|
0x72, 0x69, 0x2c, 0x20, 0x32, 0x33, 0x20, 0x53,
|
|
0x65, 0x70, 0x20, 0x32, 0x30, 0x31, 0x31, 0x20,
|
|
0x30, 0x36, 0x3a, 0x32, 0x39, 0x3a, 0x33, 0x39,
|
|
0x20, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x53, 0x65,
|
|
0x72, 0x76, 0x65, 0x72, 0x3a, 0x20, 0x41, 0x70,
|
|
0x61, 0x63, 0x68, 0x65, 0x2f, 0x32, 0x2e, 0x32,
|
|
0x2e, 0x31, 0x35, 0x20, 0x28, 0x55, 0x6e, 0x69,
|
|
0x78, 0x29, 0x20, 0x44, 0x41, 0x56, 0x2f, 0x32,
|
|
0x0d, 0x0a, 0x4c, 0x61, 0x73, 0x74, 0x2d, 0x4d,
|
|
0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x3a,
|
|
0x20, 0x54, 0x68, 0x75, 0x2c, 0x20, 0x30, 0x34,
|
|
0x20, 0x4e, 0x6f, 0x76, 0x20, 0x32, 0x30, 0x31,
|
|
0x30, 0x20, 0x31, 0x35, 0x3a, 0x30, 0x34, 0x3a,
|
|
0x34, 0x36, 0x20, 0x47, 0x4d, 0x54, 0x0d, 0x0a,
|
|
0x45, 0x54, 0x61, 0x67, 0x3a, 0x20, 0x22, 0x61,
|
|
0x62, 0x38, 0x39, 0x36, 0x35, 0x2d, 0x32, 0x63,
|
|
0x2d, 0x34, 0x39, 0x34, 0x33, 0x62, 0x37, 0x61,
|
|
0x37, 0x66, 0x37, 0x66, 0x38, 0x30, 0x22, 0x0d,
|
|
0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d,
|
|
0x52, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x3a, 0x20,
|
|
0x62, 0x79, 0x74, 0x65, 0x73, 0x0d, 0x0a, 0x43,
|
|
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c,
|
|
0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x34,
|
|
0x34, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
|
|
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x63,
|
|
0x6c, 0x6f, 0x73, 0x65, 0x0d, 0x0a, 0x43, 0x6f,
|
|
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79,
|
|
0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74,
|
|
0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x0d, 0x0a, 0x58,
|
|
0x2d, 0x50, 0x61, 0x64, 0x3a, 0x20, 0x61, 0x76,
|
|
0x6f, 0x69, 0x64, 0x20, 0x62, 0x72, 0x6f, 0x77,
|
|
0x73, 0x65, 0x72, 0x20, 0x62, 0x75, 0x67, 0x0d,
|
|
0x0a, 0x0d, 0x0a, 0x3c, 0x68, 0x74, 0x6d, 0x6c,
|
|
0x3e, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x3c,
|
|
0x68, 0x31, 0x3e, 0x49, 0x74, 0x20, 0x77, 0x6f,
|
|
0x72, 0x6b, 0x73, 0x21, 0x3c, 0x2f, 0x68, 0x31,
|
|
0x3e, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e,
|
|
0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e };
|
|
p->tcph->th_ack = htonl(88);
|
|
p->tcph->th_seq = htonl(1);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = sizeof(response);
|
|
p->payload = response;
|
|
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_UNKNOWN);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
|
|
/* response ack from request */
|
|
p->tcph->th_ack = htonl(328);
|
|
p->tcph->th_seq = htonl(88);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response - acking */
|
|
p->tcph->th_ack = htonl(88);
|
|
p->tcph->th_seq = htonl(328);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response ack from request */
|
|
p->tcph->th_ack = htonl(328);
|
|
p->tcph->th_seq = htonl(88);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response - acking the request again*/
|
|
p->tcph->th_ack = htonl(88);
|
|
p->tcph->th_seq = htonl(328);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/*** New Request ***/
|
|
|
|
/* partial request */
|
|
p->tcph->th_ack = htonl(328);
|
|
p->tcph->th_seq = htonl(88);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = sizeof(request1);
|
|
p->payload = request1;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response ack against partial request */
|
|
p->tcph->th_ack = htonl(90);
|
|
p->tcph->th_seq = htonl(328);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* complete request */
|
|
p->tcph->th_ack = htonl(328);
|
|
p->tcph->th_seq = htonl(90);
|
|
p->tcph->th_flags = TH_PUSH | TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = sizeof(request2);
|
|
p->payload = request2;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)))));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response ack against second partial request */
|
|
p->tcph->th_ack = htonl(175);
|
|
p->tcph->th_seq = htonl(328);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
FAIL_IF(ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED);
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PP_DONE(&f, STREAM_TOSERVER));
|
|
FAIL_IF(!FLOW_IS_PM_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(FLOW_IS_PP_DONE(&f, STREAM_TOCLIENT));
|
|
FAIL_IF(ssn->data_first_seen_dir != APP_LAYER_DATA_ALREADY_SENT_TO_APP_LAYER);
|
|
FAIL_IF(RB_EMPTY(&ssn->client.seg_tree));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree))));
|
|
FAIL_IF(!TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->client.seg_tree)))));
|
|
FAIL_IF(RB_EMPTY(&ssn->server.seg_tree));
|
|
FAIL_IF(TCPSEG_RB_NEXT(RB_MIN(TCPSEG, &ssn->server.seg_tree)));
|
|
|
|
/* response acking a request */
|
|
p->tcph->th_ack = htonl(175);
|
|
p->tcph->th_seq = htonl(328);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->server));
|
|
FAIL_IF(!StreamTcpIsSetStreamFlagAppProtoDetectionCompleted(&ssn->client));
|
|
FAIL_IF(f.alproto != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_ts != ALPROTO_HTTP);
|
|
FAIL_IF(f.alproto_tc != ALPROTO_HTTP);
|
|
|
|
StreamTcpPruneSession(&f, STREAM_TOSERVER);
|
|
StreamTcpPruneSession(&f, STREAM_TOCLIENT);
|
|
|
|
/* request acking a response */
|
|
p->tcph->th_ack = htonl(328);
|
|
p->tcph->th_seq = htonl(175);
|
|
p->tcph->th_flags = TH_ACK;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload_len = 0;
|
|
p->payload = NULL;
|
|
FAIL_IF(StreamTcpPacket(&tv, p, &stt, &pq) == -1);
|
|
|
|
StreamTcpSessionClear(ssn);
|
|
StreamTcpUTDeinit(stt.ra_ctx);
|
|
SCFree(p);
|
|
PASS;
|
|
}
|
|
|
|
/**
|
|
* \test Test to make sure that we sent all the segments from the initial
|
|
* segments to app layer until we have detected the app layer proto.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest40 (void)
|
|
{
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF_NULL(p);
|
|
Flow *f = NULL;
|
|
TCPHdr tcph;
|
|
TcpSession ssn;
|
|
PacketQueue pq;
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
|
|
TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(&tv);
|
|
FAIL_IF_NULL(ra_ctx);
|
|
|
|
uint8_t httpbuf1[] = "P";
|
|
uint32_t httplen1 = sizeof(httpbuf1) - 1; /* minus the \0 */
|
|
uint8_t httpbuf3[] = "O";
|
|
uint32_t httplen3 = sizeof(httpbuf3) - 1; /* minus the \0 */
|
|
uint8_t httpbuf4[] = "S";
|
|
uint32_t httplen4 = sizeof(httpbuf4) - 1; /* minus the \0 */
|
|
uint8_t httpbuf5[] = "T \r\n";
|
|
uint32_t httplen5 = sizeof(httpbuf5) - 1; /* minus the \0 */
|
|
|
|
uint8_t httpbuf2[] = "HTTP/1.0 200 OK\r\nServer: VictorServer/1.0\r\n\r\n";
|
|
uint32_t httplen2 = sizeof(httpbuf2) - 1; /* minus the \0 */
|
|
|
|
SET_ISN(&ssn.server, 9);
|
|
ssn.server.last_ack = 10;
|
|
SET_ISN(&ssn.client, 9);
|
|
ssn.client.isn = 9;
|
|
|
|
f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 200, 220);
|
|
FAIL_IF_NULL(f);
|
|
f->protoctx = &ssn;
|
|
f->proto = IPPROTO_TCP;
|
|
p->flow = f;
|
|
|
|
tcph.th_win = htons(5480);
|
|
tcph.th_seq = htonl(10);
|
|
tcph.th_ack = htonl(10);
|
|
tcph.th_flags = TH_ACK|TH_PUSH;
|
|
p->tcph = &tcph;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = httpbuf1;
|
|
p->payload_len = httplen1;
|
|
ssn.state = TCP_ESTABLISHED;
|
|
TcpStream *s = &ssn.client;
|
|
SCLogDebug("1 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload = httpbuf2;
|
|
p->payload_len = httplen2;
|
|
tcph.th_seq = htonl(10);
|
|
tcph.th_ack = htonl(11);
|
|
s = &ssn.server;
|
|
ssn.server.last_ack = 11;
|
|
SCLogDebug("2 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = httpbuf3;
|
|
p->payload_len = httplen3;
|
|
tcph.th_seq = htonl(11);
|
|
tcph.th_ack = htonl(55);
|
|
s = &ssn.client;
|
|
ssn.client.last_ack = 55;
|
|
SCLogDebug("3 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload = httpbuf2;
|
|
p->payload_len = httplen2;
|
|
tcph.th_seq = htonl(55);
|
|
tcph.th_ack = htonl(12);
|
|
s = &ssn.server;
|
|
ssn.server.last_ack = 12;
|
|
SCLogDebug("4 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
/* check is have the segment in the list and flagged or not */
|
|
TcpSegment *seg = RB_MIN(TCPSEG, &ssn.client.seg_tree);
|
|
FAIL_IF_NULL(seg);
|
|
FAIL_IF(SEGMENT_BEFORE_OFFSET(&ssn.client, seg, STREAM_APP_PROGRESS(&ssn.client)));
|
|
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = httpbuf4;
|
|
p->payload_len = httplen4;
|
|
tcph.th_seq = htonl(12);
|
|
tcph.th_ack = htonl(100);
|
|
s = &ssn.client;
|
|
ssn.client.last_ack = 100;
|
|
SCLogDebug("5 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload = httpbuf2;
|
|
p->payload_len = httplen2;
|
|
tcph.th_seq = htonl(100);
|
|
tcph.th_ack = htonl(13);
|
|
s = &ssn.server;
|
|
ssn.server.last_ack = 13;
|
|
SCLogDebug("6 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = httpbuf5;
|
|
p->payload_len = httplen5;
|
|
tcph.th_seq = htonl(13);
|
|
tcph.th_ack = htonl(145);
|
|
s = &ssn.client;
|
|
ssn.client.last_ack = 145;
|
|
SCLogDebug("7 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload = httpbuf2;
|
|
p->payload_len = httplen2;
|
|
tcph.th_seq = htonl(145);
|
|
tcph.th_ack = htonl(16);
|
|
s = &ssn.server;
|
|
ssn.server.last_ack = 16;
|
|
SCLogDebug("8 -- start");
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
FAIL_IF(f->alproto != ALPROTO_HTTP);
|
|
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpReassembleFreeThreadCtx(ra_ctx);
|
|
StreamTcpFreeConfig(TRUE);
|
|
SCFree(p);
|
|
UTHFreeFlow(f);
|
|
PASS;
|
|
}
|
|
|
|
/** \test Test the memcap incrementing/decrementing and memcap check */
|
|
static int StreamTcpReassembleTest44(void)
|
|
{
|
|
StreamTcpInitConfig(TRUE);
|
|
uint32_t memuse = SC_ATOMIC_GET(ra_memuse);
|
|
StreamTcpReassembleIncrMemuse(500);
|
|
FAIL_IF(SC_ATOMIC_GET(ra_memuse) != (memuse+500));
|
|
StreamTcpReassembleDecrMemuse(500);
|
|
FAIL_IF(SC_ATOMIC_GET(ra_memuse) != memuse);
|
|
FAIL_IF(StreamTcpReassembleCheckMemcap(500) != 1);
|
|
FAIL_IF(StreamTcpReassembleCheckMemcap((1 + memuse + SC_ATOMIC_GET(stream_config.reassembly_memcap))) != 0);
|
|
StreamTcpFreeConfig(TRUE);
|
|
FAIL_IF(SC_ATOMIC_GET(ra_memuse) != 0);
|
|
PASS;
|
|
}
|
|
|
|
/**
|
|
* \test Test to make sure that reassembly_depth is enforced.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest45 (void)
|
|
{
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
TcpSession ssn;
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof(tv));
|
|
uint8_t payload[100] = {0};
|
|
uint16_t payload_size = 100;
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
stream_config.reassembly_depth = 100;
|
|
|
|
StreamTcpUTSetupSession(&ssn);
|
|
ssn.reassembly_depth = 100;
|
|
StreamTcpUTSetupStream(&ssn.server, 100);
|
|
StreamTcpUTSetupStream(&ssn.client, 100);
|
|
|
|
int r = StreamTcpUTAddPayload(&tv, ra_ctx, &ssn, &ssn.client, 101, payload, payload_size);
|
|
FAIL_IF(r != 0);
|
|
FAIL_IF(ssn.client.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED);
|
|
|
|
r = StreamTcpUTAddPayload(&tv, ra_ctx, &ssn, &ssn.client, 201, payload, payload_size);
|
|
FAIL_IF(r != 0);
|
|
FAIL_IF(!(ssn.client.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED));
|
|
|
|
StreamTcpUTClearStream(&ssn.server);
|
|
StreamTcpUTClearStream(&ssn.client);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
PASS;
|
|
}
|
|
|
|
/**
|
|
* \test Test the unlimited config value of reassembly depth.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest46 (void)
|
|
{
|
|
int result = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
TcpSession ssn;
|
|
ThreadVars tv;
|
|
memset(&tv, 0, sizeof(tv));
|
|
uint8_t payload[100] = {0};
|
|
uint16_t payload_size = 100;
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
stream_config.reassembly_depth = 0;
|
|
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.server, 100);
|
|
StreamTcpUTSetupStream(&ssn.client, 100);
|
|
|
|
int r = StreamTcpUTAddPayload(&tv, ra_ctx, &ssn, &ssn.client, 101, payload, payload_size);
|
|
if (r != 0)
|
|
goto end;
|
|
if (ssn.client.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) {
|
|
printf("STREAMTCP_STREAM_FLAG_NOREASSEMBLY set: ");
|
|
goto end;
|
|
}
|
|
|
|
r = StreamTcpUTAddPayload(&tv, ra_ctx, &ssn, &ssn.client, 201, payload, payload_size);
|
|
if (r != 0)
|
|
goto end;
|
|
if (ssn.client.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) {
|
|
printf("STREAMTCP_STREAM_FLAG_NOREASSEMBLY set: ");
|
|
goto end;
|
|
}
|
|
|
|
result = 1;
|
|
end:
|
|
StreamTcpUTClearStream(&ssn.server);
|
|
StreamTcpUTClearStream(&ssn.client);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test Test to make sure we detect the sequence wrap around and continue
|
|
* stream reassembly properly.
|
|
*
|
|
* \retval On success it returns 1 and on failure 0.
|
|
*/
|
|
|
|
static int StreamTcpReassembleTest47 (void)
|
|
{
|
|
Packet *p = PacketGetFromAlloc();
|
|
FAIL_IF(unlikely(p == NULL));
|
|
Flow *f = NULL;
|
|
TCPHdr tcph;
|
|
TcpSession ssn;
|
|
ThreadVars tv;
|
|
PacketQueue pq;
|
|
memset(&pq,0,sizeof(PacketQueue));
|
|
memset(&tcph, 0, sizeof (TCPHdr));
|
|
memset(&tv, 0, sizeof (ThreadVars));
|
|
StreamTcpInitConfig(TRUE);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
TcpReassemblyThreadCtx *ra_ctx = StreamTcpReassembleInitThreadCtx(&tv);
|
|
|
|
uint8_t httpbuf1[] = "GET /EVILSUFF HTTP/1.1\r\n\r\n";
|
|
uint32_t httplen1 = sizeof(httpbuf1) - 1; /* minus the \0 */
|
|
|
|
SET_ISN(&ssn.server, 572799781UL);
|
|
ssn.server.last_ack = 572799782UL;
|
|
|
|
SET_ISN(&ssn.client, 4294967289UL);
|
|
ssn.client.last_ack = 21;
|
|
|
|
f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 200, 220);
|
|
FAIL_IF(f == NULL);
|
|
f->protoctx = &ssn;
|
|
f->proto = IPPROTO_TCP;
|
|
p->flow = f;
|
|
|
|
tcph.th_win = htons(5480);
|
|
ssn.state = TCP_ESTABLISHED;
|
|
TcpStream *s = NULL;
|
|
uint8_t cnt = 0;
|
|
|
|
for (cnt=0; cnt < httplen1; cnt++) {
|
|
tcph.th_seq = htonl(ssn.client.isn + 1 + cnt);
|
|
tcph.th_ack = htonl(572799782UL);
|
|
tcph.th_flags = TH_ACK|TH_PUSH;
|
|
p->tcph = &tcph;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
p->payload = &httpbuf1[cnt];
|
|
p->payload_len = 1;
|
|
s = &ssn.client;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
|
|
p->flowflags = FLOW_PKT_TOCLIENT;
|
|
p->payload = NULL;
|
|
p->payload_len = 0;
|
|
tcph.th_seq = htonl(572799782UL);
|
|
tcph.th_ack = htonl(ssn.client.isn + 1 + cnt);
|
|
tcph.th_flags = TH_ACK;
|
|
p->tcph = &tcph;
|
|
s = &ssn.server;
|
|
|
|
FAIL_IF(StreamTcpReassembleHandleSegment(&tv, ra_ctx, &ssn, s, p, &pq) == -1);
|
|
}
|
|
|
|
FAIL_IF(f->alproto != ALPROTO_HTTP);
|
|
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpReassembleFreeThreadCtx(ra_ctx);
|
|
StreamTcpFreeConfig(TRUE);
|
|
SCFree(p);
|
|
UTHFreeFlow(f);
|
|
PASS;
|
|
}
|
|
|
|
/** \test 3 in order segments in inline reassembly */
|
|
static int StreamTcpReassembleInlineTest01(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 17;
|
|
ret = 1;
|
|
end:
|
|
FLOWLOCK_UNLOCK(&f);
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test 3 in order segments, then reassemble, add one more and reassemble again.
|
|
* test the sliding window reassembly.
|
|
*/
|
|
static int StreamTcpReassembleInlineTest02(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 17;
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 17, 'D', 5) == -1) {
|
|
printf("failed to add segment 4: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 22;
|
|
ret = 1;
|
|
end:
|
|
FLOWLOCK_UNLOCK(&f);
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test 3 in order segments, then reassemble, add one more and reassemble again.
|
|
* test the sliding window reassembly with a small window size so that we
|
|
* cutting off at the start (left edge)
|
|
*/
|
|
static int StreamTcpReassembleInlineTest03(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
stream_config.reassembly_toserver_chunk_size = 15;
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
p->flowflags |= FLOW_PKT_TOSERVER;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 17;
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 17, 'D', 5) == -1) {
|
|
printf("failed to add segment 4: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 22;
|
|
|
|
p->tcph->th_seq = htonl(17);
|
|
ret = 1;
|
|
end:
|
|
FLOWLOCK_UNLOCK(&f);
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test 3 in order segments, then reassemble, add one more and reassemble again.
|
|
* test the sliding window reassembly with a small window size so that we
|
|
* cutting off at the start (left edge) with small packet overlap.
|
|
*/
|
|
static int StreamTcpReassembleInlineTest04(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
stream_config.reassembly_toserver_chunk_size = 16;
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
p->flowflags |= FLOW_PKT_TOSERVER;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 17;
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 17, 'D', 5) == -1) {
|
|
printf("failed to add segment 4: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 22;
|
|
|
|
p->tcph->th_seq = htonl(17);
|
|
ret = 1;
|
|
end:
|
|
FLOWLOCK_UNLOCK(&f);
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test 3 in order segments, then reassemble, add one more and reassemble again.
|
|
* test the sliding window reassembly with a small window size so that we
|
|
* cutting off at the start (left edge). Test if the first segment is
|
|
* removed from the list.
|
|
*/
|
|
static int StreamTcpReassembleInlineTest08(void)
|
|
{
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
TcpSession ssn;
|
|
Flow f;
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
stream_config.reassembly_toserver_chunk_size = 15;
|
|
ssn.client.flags |= STREAMTCP_STREAM_FLAG_GAP;
|
|
f.protoctx = &ssn;
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
FAIL_IF(p == NULL);
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
p->flowflags |= FLOW_PKT_TOSERVER;
|
|
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1);
|
|
ssn.client.next_seq = 17;
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 17, 'D', 5) == -1);
|
|
ssn.client.next_seq = 22;
|
|
p->tcph->th_seq = htonl(17);
|
|
StreamTcpPruneSession(&f, STREAM_TOSERVER);
|
|
|
|
TcpSegment *seg = RB_MIN(TCPSEG, &ssn.client.seg_tree);
|
|
FAIL_IF_NULL(seg);
|
|
FAIL_IF_NOT(seg->seq == 2);
|
|
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
PASS;
|
|
}
|
|
|
|
/** \test 3 in order segments, then reassemble, add one more and reassemble again.
|
|
* test the sliding window reassembly with a small window size so that we
|
|
* cutting off at the start (left edge). Test if the first segment is
|
|
* removed from the list.
|
|
*/
|
|
static int StreamTcpReassembleInlineTest09(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
stream_config.reassembly_toserver_chunk_size = 20;
|
|
ssn.client.flags |= STREAMTCP_STREAM_FLAG_GAP;
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(17);
|
|
p->flow = &f;
|
|
p->flowflags |= FLOW_PKT_TOSERVER;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 17, 'D', 5) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 12;
|
|
ssn.client.last_ack = 10;
|
|
|
|
/* close the GAP and see if we properly reassemble and update base_seq */
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1) {
|
|
printf("failed to add segment 4: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 22;
|
|
|
|
p->tcph->th_seq = htonl(12);
|
|
|
|
TcpSegment *seg = RB_MIN(TCPSEG, &ssn.client.seg_tree);
|
|
FAIL_IF_NULL(seg);
|
|
FAIL_IF_NOT(seg->seq == 2);
|
|
|
|
ret = 1;
|
|
end:
|
|
FLOWLOCK_UNLOCK(&f);
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test App Layer reassembly.
|
|
*/
|
|
static int StreamTcpReassembleInlineTest10(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow *f = NULL;
|
|
Packet *p = NULL;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTInitInline();
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.server, 1);
|
|
ssn.server.last_ack = 2;
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
ssn.client.last_ack = 2;
|
|
ssn.data_first_seen_dir = STREAM_TOSERVER;
|
|
|
|
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (f == NULL)
|
|
goto end;
|
|
f->protoctx = &ssn;
|
|
f->proto = IPPROTO_TCP;
|
|
|
|
uint8_t stream_payload1[] = "GE";
|
|
uint8_t stream_payload2[] = "T /";
|
|
uint8_t stream_payload3[] = "HTTP/1.0\r\n\r\n";
|
|
|
|
p = UTHBuildPacketReal(stream_payload3, 12, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
if (p == NULL) {
|
|
printf("couldn't get a packet: ");
|
|
goto end;
|
|
}
|
|
p->tcph->th_seq = htonl(7);
|
|
p->flow = f;
|
|
p->flowflags = FLOW_PKT_TOSERVER;
|
|
|
|
FLOWLOCK_WRLOCK(f);
|
|
if (StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.client, 2, stream_payload1, 2) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 4;
|
|
|
|
int r = StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.client, p, UPDATE_DIR_PACKET);
|
|
if (r < 0) {
|
|
printf("StreamTcpReassembleAppLayer failed: ");
|
|
goto end;
|
|
}
|
|
|
|
/* ssn.server.ra_app_base_seq should be isn here. */
|
|
if (ssn.client.base_seq != 2 || ssn.client.base_seq != ssn.client.isn+1) {
|
|
printf("expected ra_app_base_seq 1, got %u: ", ssn.client.base_seq);
|
|
goto end;
|
|
}
|
|
|
|
if (StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.client, 4, stream_payload2, 3) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
if (StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.client, 7, stream_payload3, 12) == -1) {
|
|
printf("failed to add segment 3: ");
|
|
goto end;
|
|
}
|
|
ssn.client.next_seq = 19;
|
|
|
|
r = StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.client, p, UPDATE_DIR_PACKET);
|
|
if (r < 0) {
|
|
printf("StreamTcpReassembleAppLayer failed: ");
|
|
goto end;
|
|
}
|
|
|
|
FAIL_IF_NOT(STREAM_APP_PROGRESS(&ssn.client) == 17);
|
|
|
|
ret = 1;
|
|
end:
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
FLOWLOCK_UNLOCK(f);
|
|
UTHFreeFlow(f);
|
|
return ret;
|
|
}
|
|
|
|
/** \test test insert with overlap
|
|
*/
|
|
static int StreamTcpReassembleInsertTest01(void)
|
|
{
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
Flow f;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
ssn.client.os_policy = OS_POLICY_LAST;
|
|
FLOW_INITIALIZE(&f);
|
|
|
|
uint8_t payload[] = { 'C', 'C', 'C', 'C', 'C' };
|
|
Packet *p = UTHBuildPacketReal(payload, 5, IPPROTO_TCP, "1.1.1.1", "2.2.2.2", 1024, 80);
|
|
FAIL_IF(p == NULL);
|
|
p->tcph->th_seq = htonl(12);
|
|
p->flow = &f;
|
|
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 5) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 7, 'B', 5) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 14, 'D', 2) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 16, 'D', 6) == -1);
|
|
FAIL_IF(StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 12, 'C', 5) == -1);
|
|
ssn.client.next_seq = 21;
|
|
|
|
FLOW_DESTROY(&f);
|
|
UTHFreePacket(p);
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
PASS;
|
|
}
|
|
|
|
/** \test test insert with overlaps
|
|
*/
|
|
static int StreamTcpReassembleInsertTest02(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
|
|
int i;
|
|
for (i = 2; i < 10; i++) {
|
|
int len;
|
|
len = i % 2;
|
|
if (len == 0)
|
|
len = 1;
|
|
int seq;
|
|
seq = i * 10;
|
|
if (seq < 2)
|
|
seq = 2;
|
|
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, seq, 'A', len) == -1) {
|
|
printf("failed to add segment 1: ");
|
|
goto end;
|
|
}
|
|
}
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'B', 1024) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
|
|
ret = 1;
|
|
end:
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/** \test test insert with overlaps
|
|
*/
|
|
static int StreamTcpReassembleInsertTest03(void)
|
|
{
|
|
int ret = 0;
|
|
TcpReassemblyThreadCtx *ra_ctx = NULL;
|
|
ThreadVars tv;
|
|
TcpSession ssn;
|
|
|
|
memset(&tv, 0x00, sizeof(tv));
|
|
|
|
StreamTcpUTInit(&ra_ctx);
|
|
StreamTcpUTSetupSession(&ssn);
|
|
StreamTcpUTSetupStream(&ssn.client, 1);
|
|
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, 2, 'A', 1024) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
|
|
int i;
|
|
for (i = 2; i < 10; i++) {
|
|
int len;
|
|
len = i % 2;
|
|
if (len == 0)
|
|
len = 1;
|
|
int seq;
|
|
seq = i * 10;
|
|
if (seq < 2)
|
|
seq = 2;
|
|
|
|
if (StreamTcpUTAddSegmentWithByte(&tv, ra_ctx, &ssn.client, seq, 'B', len) == -1) {
|
|
printf("failed to add segment 2: ");
|
|
goto end;
|
|
}
|
|
}
|
|
ret = 1;
|
|
end:
|
|
StreamTcpUTClearSession(&ssn);
|
|
StreamTcpUTDeinit(ra_ctx);
|
|
return ret;
|
|
}
|
|
|
|
#include "tests/stream-tcp-reassemble.c"
|
|
#endif /* UNITTESTS */
|
|
|
|
/** \brief The Function Register the Unit tests to test the reassembly engine
|
|
* for various OS policies.
|
|
*/
|
|
|
|
void StreamTcpReassembleRegisterTests(void)
|
|
{
|
|
#ifdef UNITTESTS
|
|
UtRegisterTest("StreamTcpReassembleTest25 -- Gap at Start Reassembly Test",
|
|
StreamTcpReassembleTest25);
|
|
UtRegisterTest("StreamTcpReassembleTest26 -- Gap at middle Reassembly Test",
|
|
StreamTcpReassembleTest26);
|
|
UtRegisterTest("StreamTcpReassembleTest27 -- Gap at after Reassembly Test",
|
|
StreamTcpReassembleTest27);
|
|
UtRegisterTest("StreamTcpReassembleTest28 -- Gap at Start IDS missed packet Reassembly Test",
|
|
StreamTcpReassembleTest28);
|
|
UtRegisterTest("StreamTcpReassembleTest29 -- Gap at Middle IDS missed packet Reassembly Test",
|
|
StreamTcpReassembleTest29);
|
|
UtRegisterTest("StreamTcpReassembleTest33 -- Bug test",
|
|
StreamTcpReassembleTest33);
|
|
UtRegisterTest("StreamTcpReassembleTest34 -- Bug test",
|
|
StreamTcpReassembleTest34);
|
|
UtRegisterTest("StreamTcpReassembleTest37 -- Bug76 test",
|
|
StreamTcpReassembleTest37);
|
|
UtRegisterTest("StreamTcpReassembleTest39 -- app proto test",
|
|
StreamTcpReassembleTest39);
|
|
UtRegisterTest("StreamTcpReassembleTest40 -- app proto test",
|
|
StreamTcpReassembleTest40);
|
|
UtRegisterTest("StreamTcpReassembleTest44 -- Memcap Test",
|
|
StreamTcpReassembleTest44);
|
|
UtRegisterTest("StreamTcpReassembleTest45 -- Depth Test",
|
|
StreamTcpReassembleTest45);
|
|
UtRegisterTest("StreamTcpReassembleTest46 -- Depth Test",
|
|
StreamTcpReassembleTest46);
|
|
UtRegisterTest("StreamTcpReassembleTest47 -- TCP Sequence Wraparound Test",
|
|
StreamTcpReassembleTest47);
|
|
|
|
UtRegisterTest("StreamTcpReassembleInlineTest01 -- inline RAW ra",
|
|
StreamTcpReassembleInlineTest01);
|
|
UtRegisterTest("StreamTcpReassembleInlineTest02 -- inline RAW ra 2",
|
|
StreamTcpReassembleInlineTest02);
|
|
UtRegisterTest("StreamTcpReassembleInlineTest03 -- inline RAW ra 3",
|
|
StreamTcpReassembleInlineTest03);
|
|
UtRegisterTest("StreamTcpReassembleInlineTest04 -- inline RAW ra 4",
|
|
StreamTcpReassembleInlineTest04);
|
|
UtRegisterTest("StreamTcpReassembleInlineTest08 -- inline RAW ra 8 cleanup",
|
|
StreamTcpReassembleInlineTest08);
|
|
UtRegisterTest("StreamTcpReassembleInlineTest09 -- inline RAW ra 9 GAP cleanup",
|
|
StreamTcpReassembleInlineTest09);
|
|
|
|
UtRegisterTest("StreamTcpReassembleInlineTest10 -- inline APP ra 10",
|
|
StreamTcpReassembleInlineTest10);
|
|
|
|
UtRegisterTest("StreamTcpReassembleInsertTest01 -- insert with overlap",
|
|
StreamTcpReassembleInsertTest01);
|
|
UtRegisterTest("StreamTcpReassembleInsertTest02 -- insert with overlap",
|
|
StreamTcpReassembleInsertTest02);
|
|
UtRegisterTest("StreamTcpReassembleInsertTest03 -- insert with overlap",
|
|
StreamTcpReassembleInsertTest03);
|
|
|
|
StreamTcpInlineRegisterTests();
|
|
StreamTcpUtilRegisterTests();
|
|
StreamTcpListRegisterTests();
|
|
StreamTcpReassembleRawRegisterTests();
|
|
#endif /* UNITTESTS */
|
|
}
|