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.
6347 lines
236 KiB
C
6347 lines
236 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 Victor Julien <victor@inliniac.net>
|
|
* \author Gurvinder Singh <gurvindersinghdahiya@gmail.com>
|
|
*
|
|
* TCP stream tracking and reassembly engine.
|
|
*
|
|
* \todo - 4WHS: what if after the 2nd SYN we turn out to be normal 3WHS anyway?
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "suricata.h"
|
|
|
|
#include "decode.h"
|
|
#include "debug.h"
|
|
#include "detect.h"
|
|
|
|
#include "flow.h"
|
|
#include "flow-util.h"
|
|
|
|
#include "conf.h"
|
|
#include "conf-yaml-loader.h"
|
|
|
|
#include "threads.h"
|
|
#include "threadvars.h"
|
|
#include "tm-threads.h"
|
|
|
|
#include "util-pool.h"
|
|
#include "util-pool-thread.h"
|
|
#include "util-checksum.h"
|
|
#include "util-unittest.h"
|
|
#include "util-print.h"
|
|
#include "util-debug.h"
|
|
#include "util-device.h"
|
|
|
|
#include "stream-tcp-private.h"
|
|
#include "stream-tcp-reassemble.h"
|
|
#include "stream-tcp.h"
|
|
#include "stream-tcp-inline.h"
|
|
#include "stream-tcp-sack.h"
|
|
#include "stream-tcp-util.h"
|
|
#include "stream.h"
|
|
|
|
#include "pkt-var.h"
|
|
#include "host.h"
|
|
|
|
#include "app-layer.h"
|
|
#include "app-layer-parser.h"
|
|
#include "app-layer-protos.h"
|
|
#include "app-layer-htp-mem.h"
|
|
|
|
#include "util-host-os-info.h"
|
|
#include "util-privs.h"
|
|
#include "util-profiling.h"
|
|
#include "util-misc.h"
|
|
#include "util-validate.h"
|
|
#include "util-runmodes.h"
|
|
#include "util-random.h"
|
|
|
|
#include "source-pcap-file.h"
|
|
|
|
//#define DEBUG
|
|
|
|
#define STREAMTCP_DEFAULT_PREALLOC 2048
|
|
#define STREAMTCP_DEFAULT_MEMCAP (32 * 1024 * 1024) /* 32mb */
|
|
#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP (64 * 1024 * 1024) /* 64mb */
|
|
#define STREAMTCP_DEFAULT_TOSERVER_CHUNK_SIZE 2560
|
|
#define STREAMTCP_DEFAULT_TOCLIENT_CHUNK_SIZE 2560
|
|
#define STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED 5
|
|
|
|
#define STREAMTCP_NEW_TIMEOUT 60
|
|
#define STREAMTCP_EST_TIMEOUT 3600
|
|
#define STREAMTCP_CLOSED_TIMEOUT 120
|
|
|
|
#define STREAMTCP_EMERG_NEW_TIMEOUT 10
|
|
#define STREAMTCP_EMERG_EST_TIMEOUT 300
|
|
#define STREAMTCP_EMERG_CLOSED_TIMEOUT 20
|
|
|
|
static int StreamTcpHandleFin(ThreadVars *tv, StreamTcpThread *, TcpSession *, Packet *, PacketQueueNoLock *);
|
|
void StreamTcpReturnStreamSegments (TcpStream *);
|
|
void StreamTcpInitConfig(char);
|
|
int StreamTcpGetFlowState(void *);
|
|
void StreamTcpSetOSPolicy(TcpStream*, Packet*);
|
|
|
|
static int StreamTcpValidateTimestamp(TcpSession * , Packet *);
|
|
static int StreamTcpHandleTimestamp(TcpSession * , Packet *);
|
|
static int StreamTcpValidateRst(TcpSession * , Packet *);
|
|
static inline int StreamTcpValidateAck(TcpSession *ssn, TcpStream *, Packet *);
|
|
static int StreamTcpStateDispatch(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq,
|
|
uint8_t state);
|
|
|
|
extern int g_detect_disabled;
|
|
|
|
static PoolThread *ssn_pool = NULL;
|
|
static SCMutex ssn_pool_mutex = SCMUTEX_INITIALIZER; /**< init only, protect initializing and growing pool */
|
|
#ifdef DEBUG
|
|
static uint64_t ssn_pool_cnt = 0; /** counts ssns, protected by ssn_pool_mutex */
|
|
#endif
|
|
|
|
TcpStreamCnf stream_config;
|
|
uint64_t StreamTcpReassembleMemuseGlobalCounter(void);
|
|
SC_ATOMIC_DECLARE(uint64_t, st_memuse);
|
|
|
|
void StreamTcpInitMemuse(void)
|
|
{
|
|
SC_ATOMIC_INIT(st_memuse);
|
|
}
|
|
|
|
void StreamTcpIncrMemuse(uint64_t size)
|
|
{
|
|
(void) SC_ATOMIC_ADD(st_memuse, size);
|
|
SCLogDebug("STREAM %"PRIu64", incr %"PRIu64, StreamTcpMemuseCounter(), size);
|
|
return;
|
|
}
|
|
|
|
void StreamTcpDecrMemuse(uint64_t size)
|
|
{
|
|
#ifdef DEBUG_VALIDATION
|
|
uint64_t presize = SC_ATOMIC_GET(st_memuse);
|
|
if (RunmodeIsUnittests()) {
|
|
BUG_ON(presize > UINT_MAX);
|
|
}
|
|
#endif
|
|
|
|
(void) SC_ATOMIC_SUB(st_memuse, size);
|
|
|
|
#ifdef DEBUG_VALIDATION
|
|
if (RunmodeIsUnittests()) {
|
|
uint64_t postsize = SC_ATOMIC_GET(st_memuse);
|
|
BUG_ON(postsize > presize);
|
|
}
|
|
#endif
|
|
SCLogDebug("STREAM %"PRIu64", decr %"PRIu64, StreamTcpMemuseCounter(), size);
|
|
return;
|
|
}
|
|
|
|
uint64_t StreamTcpMemuseCounter(void)
|
|
{
|
|
uint64_t memusecopy = SC_ATOMIC_GET(st_memuse);
|
|
return memusecopy;
|
|
}
|
|
|
|
/**
|
|
* \brief Check if alloc'ing "size" would mean we're over memcap
|
|
*
|
|
* \retval 1 if in bounds
|
|
* \retval 0 if not in bounds
|
|
*/
|
|
int StreamTcpCheckMemcap(uint64_t size)
|
|
{
|
|
uint64_t memcapcopy = SC_ATOMIC_GET(stream_config.memcap);
|
|
if (memcapcopy == 0 || size + SC_ATOMIC_GET(st_memuse) <= memcapcopy)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Update memcap value
|
|
*
|
|
* \param size new memcap value
|
|
*/
|
|
int StreamTcpSetMemcap(uint64_t size)
|
|
{
|
|
if (size == 0 || (uint64_t)SC_ATOMIC_GET(st_memuse) < size) {
|
|
SC_ATOMIC_SET(stream_config.memcap, size);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Return memcap value
|
|
*
|
|
* \param memcap memcap value
|
|
*/
|
|
uint64_t StreamTcpGetMemcap(void)
|
|
{
|
|
uint64_t memcapcopy = SC_ATOMIC_GET(stream_config.memcap);
|
|
return memcapcopy;
|
|
}
|
|
|
|
void StreamTcpStreamCleanup(TcpStream *stream)
|
|
{
|
|
if (stream != NULL) {
|
|
StreamTcpSackFreeList(stream);
|
|
StreamTcpReturnStreamSegments(stream);
|
|
StreamingBufferClear(&stream->sb);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Session cleanup function. Does not free the ssn.
|
|
* \param ssn tcp session
|
|
*/
|
|
void StreamTcpSessionCleanup(TcpSession *ssn)
|
|
{
|
|
SCEnter();
|
|
TcpStateQueue *q, *q_next;
|
|
|
|
if (ssn == NULL)
|
|
return;
|
|
|
|
StreamTcpStreamCleanup(&ssn->client);
|
|
StreamTcpStreamCleanup(&ssn->server);
|
|
|
|
q = ssn->queue;
|
|
while (q != NULL) {
|
|
q_next = q->next;
|
|
SCFree(q);
|
|
q = q_next;
|
|
StreamTcpDecrMemuse((uint64_t)sizeof(TcpStateQueue));
|
|
}
|
|
ssn->queue = NULL;
|
|
ssn->queue_len = 0;
|
|
|
|
SCReturn;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to return the stream back to the pool. It returns the
|
|
* segments in the stream to the segment pool.
|
|
*
|
|
* This function is called when the flow is destroyed, so it should free
|
|
* *everything* related to the tcp session. So including the app layer
|
|
* data. We are guaranteed to only get here when the flow's use_cnt is 0.
|
|
*
|
|
* \param ssn Void ptr to the ssn.
|
|
*/
|
|
void StreamTcpSessionClear(void *ssnptr)
|
|
{
|
|
SCEnter();
|
|
TcpSession *ssn = (TcpSession *)ssnptr;
|
|
if (ssn == NULL)
|
|
return;
|
|
|
|
StreamTcpSessionCleanup(ssn);
|
|
|
|
/* HACK: don't loose track of thread id */
|
|
PoolThreadReserved a = ssn->res;
|
|
memset(ssn, 0, sizeof(TcpSession));
|
|
ssn->res = a;
|
|
|
|
PoolThreadReturn(ssn_pool, ssn);
|
|
#ifdef DEBUG
|
|
SCMutexLock(&ssn_pool_mutex);
|
|
ssn_pool_cnt--;
|
|
SCMutexUnlock(&ssn_pool_mutex);
|
|
#endif
|
|
|
|
SCReturn;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to return the stream segments back to the pool.
|
|
*
|
|
* We don't clear out the app layer storage here as that is under protection
|
|
* of the "use_cnt" reference counter in the flow. This function is called
|
|
* when the use_cnt is always at least 1 (this pkt has incremented the flow
|
|
* use_cnt itself), so we don't bother.
|
|
*
|
|
* \param p Packet used to identify the stream.
|
|
*/
|
|
void StreamTcpSessionPktFree (Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
|
|
if (ssn == NULL)
|
|
SCReturn;
|
|
|
|
StreamTcpReturnStreamSegments(&ssn->client);
|
|
StreamTcpReturnStreamSegments(&ssn->server);
|
|
|
|
SCReturn;
|
|
}
|
|
|
|
/** \brief Stream alloc function for the Pool
|
|
* \retval ptr void ptr to TcpSession structure with all vars set to 0/NULL
|
|
*/
|
|
static void *StreamTcpSessionPoolAlloc(void)
|
|
{
|
|
void *ptr = NULL;
|
|
|
|
if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpSession)) == 0)
|
|
return NULL;
|
|
|
|
ptr = SCMalloc(sizeof(TcpSession));
|
|
if (unlikely(ptr == NULL))
|
|
return NULL;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static int StreamTcpSessionPoolInit(void *data, void* initdata)
|
|
{
|
|
memset(data, 0, sizeof(TcpSession));
|
|
StreamTcpIncrMemuse((uint64_t)sizeof(TcpSession));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** \brief Pool cleanup function
|
|
* \param s Void ptr to TcpSession memory */
|
|
static void StreamTcpSessionPoolCleanup(void *s)
|
|
{
|
|
if (s != NULL) {
|
|
StreamTcpSessionCleanup(s);
|
|
/** \todo not very clean, as the memory is not freed here */
|
|
StreamTcpDecrMemuse((uint64_t)sizeof(TcpSession));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief See if stream engine is dropping invalid packet in inline mode
|
|
*
|
|
* \retval 0 no
|
|
* \retval 1 yes
|
|
*/
|
|
int StreamTcpInlineDropInvalid(void)
|
|
{
|
|
return ((stream_config.flags & STREAMTCP_INIT_FLAG_INLINE)
|
|
&& (stream_config.flags & STREAMTCP_INIT_FLAG_DROP_INVALID));
|
|
}
|
|
|
|
/* hack: stream random range code expects random values in range of 0-RAND_MAX,
|
|
* but we can get both <0 and >RAND_MAX values from RandomGet
|
|
*/
|
|
static int RandomGetWrap(void)
|
|
{
|
|
unsigned long r;
|
|
|
|
do {
|
|
r = RandomGet();
|
|
} while(r >= ULONG_MAX - (ULONG_MAX % RAND_MAX));
|
|
|
|
return r % RAND_MAX;
|
|
}
|
|
|
|
/** \brief To initialize the stream global configuration data
|
|
*
|
|
* \param quiet It tells the mode of operation, if it is TRUE nothing will
|
|
* be get printed.
|
|
*/
|
|
|
|
void StreamTcpInitConfig(char quiet)
|
|
{
|
|
intmax_t value = 0;
|
|
uint16_t rdrange = 10;
|
|
|
|
SCLogDebug("Initializing Stream");
|
|
|
|
memset(&stream_config, 0, sizeof(stream_config));
|
|
|
|
SC_ATOMIC_INIT(stream_config.memcap);
|
|
SC_ATOMIC_INIT(stream_config.reassembly_memcap);
|
|
|
|
if ((ConfGetInt("stream.max-sessions", &value)) == 1) {
|
|
SCLogWarning(SC_WARN_OPTION_OBSOLETE, "max-sessions is obsolete. "
|
|
"Number of concurrent sessions is now only limited by Flow and "
|
|
"TCP stream engine memcaps.");
|
|
}
|
|
|
|
if ((ConfGetInt("stream.prealloc-sessions", &value)) == 1) {
|
|
stream_config.prealloc_sessions = (uint32_t)value;
|
|
} else {
|
|
if (RunmodeIsUnittests()) {
|
|
stream_config.prealloc_sessions = 128;
|
|
} else {
|
|
stream_config.prealloc_sessions = STREAMTCP_DEFAULT_PREALLOC;
|
|
if (ConfGetNode("stream.prealloc-sessions") != NULL) {
|
|
WarnInvalidConfEntry("stream.prealloc_sessions",
|
|
"%"PRIu32,
|
|
stream_config.prealloc_sessions);
|
|
}
|
|
}
|
|
}
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"prealloc-sessions\": %"PRIu32" (per thread)",
|
|
stream_config.prealloc_sessions);
|
|
}
|
|
|
|
const char *temp_stream_memcap_str;
|
|
if (ConfGetValue("stream.memcap", &temp_stream_memcap_str) == 1) {
|
|
uint64_t stream_memcap_copy;
|
|
if (ParseSizeStringU64(temp_stream_memcap_str, &stream_memcap_copy) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing stream.memcap "
|
|
"from conf file - %s. Killing engine",
|
|
temp_stream_memcap_str);
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
SC_ATOMIC_SET(stream_config.memcap, stream_memcap_copy);
|
|
}
|
|
} else {
|
|
SC_ATOMIC_SET(stream_config.memcap, STREAMTCP_DEFAULT_MEMCAP);
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"memcap\": %"PRIu64, SC_ATOMIC_GET(stream_config.memcap));
|
|
}
|
|
|
|
int imidstream;
|
|
ConfGetBool("stream.midstream", &imidstream);
|
|
stream_config.midstream = imidstream != 0;
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"midstream\" session pickups: %s", stream_config.midstream ? "enabled" : "disabled");
|
|
}
|
|
|
|
ConfGetBool("stream.async-oneside", &stream_config.async_oneside);
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"async-oneside\": %s", stream_config.async_oneside ? "enabled" : "disabled");
|
|
}
|
|
|
|
int csum = 0;
|
|
|
|
if ((ConfGetBool("stream.checksum-validation", &csum)) == 1) {
|
|
if (csum == 1) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION;
|
|
}
|
|
/* Default is that we validate the checksum of all the packets */
|
|
} else {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION;
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"checksum-validation\": %s",
|
|
stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION ?
|
|
"enabled" : "disabled");
|
|
}
|
|
|
|
const char *temp_stream_inline_str;
|
|
if (ConfGetValue("stream.inline", &temp_stream_inline_str) == 1) {
|
|
int inl = 0;
|
|
|
|
/* checking for "auto" and falling back to boolean to provide
|
|
* backward compatibility */
|
|
if (strcmp(temp_stream_inline_str, "auto") == 0) {
|
|
if (EngineModeIsIPS()) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_INLINE;
|
|
}
|
|
} else if (ConfGetBool("stream.inline", &inl) == 1) {
|
|
if (inl) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_INLINE;
|
|
}
|
|
}
|
|
} else {
|
|
/* default to 'auto' */
|
|
if (EngineModeIsIPS()) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_INLINE;
|
|
}
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream.\"inline\": %s",
|
|
stream_config.flags & STREAMTCP_INIT_FLAG_INLINE
|
|
? "enabled" : "disabled");
|
|
}
|
|
|
|
int bypass = 0;
|
|
if ((ConfGetBool("stream.bypass", &bypass)) == 1) {
|
|
if (bypass == 1) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_BYPASS;
|
|
}
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"bypass\": %s",
|
|
(stream_config.flags & STREAMTCP_INIT_FLAG_BYPASS)
|
|
? "enabled" : "disabled");
|
|
}
|
|
|
|
int drop_invalid = 0;
|
|
if ((ConfGetBool("stream.drop-invalid", &drop_invalid)) == 1) {
|
|
if (drop_invalid == 1) {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_DROP_INVALID;
|
|
}
|
|
} else {
|
|
stream_config.flags |= STREAMTCP_INIT_FLAG_DROP_INVALID;
|
|
}
|
|
|
|
if ((ConfGetInt("stream.max-synack-queued", &value)) == 1) {
|
|
if (value >= 0 && value <= 255) {
|
|
stream_config.max_synack_queued = (uint8_t)value;
|
|
} else {
|
|
stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED;
|
|
}
|
|
} else {
|
|
stream_config.max_synack_queued = (uint8_t)STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED;
|
|
}
|
|
if (!quiet) {
|
|
SCLogConfig("stream \"max-synack-queued\": %"PRIu8, stream_config.max_synack_queued);
|
|
}
|
|
|
|
const char *temp_stream_reassembly_memcap_str;
|
|
if (ConfGetValue("stream.reassembly.memcap", &temp_stream_reassembly_memcap_str) == 1) {
|
|
uint64_t stream_reassembly_memcap_copy;
|
|
if (ParseSizeStringU64(temp_stream_reassembly_memcap_str,
|
|
&stream_reassembly_memcap_copy) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing "
|
|
"stream.reassembly.memcap "
|
|
"from conf file - %s. Killing engine",
|
|
temp_stream_reassembly_memcap_str);
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
SC_ATOMIC_SET(stream_config.reassembly_memcap, stream_reassembly_memcap_copy);
|
|
}
|
|
} else {
|
|
SC_ATOMIC_SET(stream_config.reassembly_memcap , STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP);
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream.reassembly \"memcap\": %"PRIu64"",
|
|
SC_ATOMIC_GET(stream_config.reassembly_memcap));
|
|
}
|
|
|
|
const char *temp_stream_reassembly_depth_str;
|
|
if (ConfGetValue("stream.reassembly.depth", &temp_stream_reassembly_depth_str) == 1) {
|
|
if (ParseSizeStringU32(temp_stream_reassembly_depth_str,
|
|
&stream_config.reassembly_depth) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing "
|
|
"stream.reassembly.depth "
|
|
"from conf file - %s. Killing engine",
|
|
temp_stream_reassembly_depth_str);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
stream_config.reassembly_depth = 0;
|
|
}
|
|
|
|
if (!quiet) {
|
|
SCLogConfig("stream.reassembly \"depth\": %"PRIu32"", stream_config.reassembly_depth);
|
|
}
|
|
|
|
int randomize = 0;
|
|
if ((ConfGetBool("stream.reassembly.randomize-chunk-size", &randomize)) == 0) {
|
|
/* randomize by default if value not set
|
|
* In ut mode we disable, to get predictible test results */
|
|
if (!(RunmodeIsUnittests()))
|
|
randomize = 1;
|
|
}
|
|
|
|
if (randomize) {
|
|
const char *temp_rdrange;
|
|
if (ConfGetValue("stream.reassembly.randomize-chunk-range",
|
|
&temp_rdrange) == 1) {
|
|
if (ParseSizeStringU16(temp_rdrange, &rdrange) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing "
|
|
"stream.reassembly.randomize-chunk-range "
|
|
"from conf file - %s. Killing engine",
|
|
temp_rdrange);
|
|
exit(EXIT_FAILURE);
|
|
} else if (rdrange >= 100) {
|
|
FatalError(SC_ERR_FATAL,
|
|
"stream.reassembly.randomize-chunk-range "
|
|
"must be lower than 100");
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *temp_stream_reassembly_toserver_chunk_size_str;
|
|
if (ConfGetValue("stream.reassembly.toserver-chunk-size",
|
|
&temp_stream_reassembly_toserver_chunk_size_str) == 1) {
|
|
if (ParseSizeStringU16(temp_stream_reassembly_toserver_chunk_size_str,
|
|
&stream_config.reassembly_toserver_chunk_size) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing "
|
|
"stream.reassembly.toserver-chunk-size "
|
|
"from conf file - %s. Killing engine",
|
|
temp_stream_reassembly_toserver_chunk_size_str);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
stream_config.reassembly_toserver_chunk_size =
|
|
STREAMTCP_DEFAULT_TOSERVER_CHUNK_SIZE;
|
|
}
|
|
|
|
if (randomize) {
|
|
long int r = RandomGetWrap();
|
|
stream_config.reassembly_toserver_chunk_size +=
|
|
(int) (stream_config.reassembly_toserver_chunk_size *
|
|
(r * 1.0 / RAND_MAX - 0.5) * rdrange / 100);
|
|
}
|
|
const char *temp_stream_reassembly_toclient_chunk_size_str;
|
|
if (ConfGetValue("stream.reassembly.toclient-chunk-size",
|
|
&temp_stream_reassembly_toclient_chunk_size_str) == 1) {
|
|
if (ParseSizeStringU16(temp_stream_reassembly_toclient_chunk_size_str,
|
|
&stream_config.reassembly_toclient_chunk_size) < 0) {
|
|
SCLogError(SC_ERR_SIZE_PARSE, "Error parsing "
|
|
"stream.reassembly.toclient-chunk-size "
|
|
"from conf file - %s. Killing engine",
|
|
temp_stream_reassembly_toclient_chunk_size_str);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
stream_config.reassembly_toclient_chunk_size =
|
|
STREAMTCP_DEFAULT_TOCLIENT_CHUNK_SIZE;
|
|
}
|
|
|
|
if (randomize) {
|
|
long int r = RandomGetWrap();
|
|
stream_config.reassembly_toclient_chunk_size +=
|
|
(int) (stream_config.reassembly_toclient_chunk_size *
|
|
(r * 1.0 / RAND_MAX - 0.5) * rdrange / 100);
|
|
}
|
|
if (!quiet) {
|
|
SCLogConfig("stream.reassembly \"toserver-chunk-size\": %"PRIu16,
|
|
stream_config.reassembly_toserver_chunk_size);
|
|
SCLogConfig("stream.reassembly \"toclient-chunk-size\": %"PRIu16,
|
|
stream_config.reassembly_toclient_chunk_size);
|
|
}
|
|
|
|
int enable_raw = 1;
|
|
if (ConfGetBool("stream.reassembly.raw", &enable_raw) == 1) {
|
|
if (!enable_raw) {
|
|
stream_config.stream_init_flags = STREAMTCP_STREAM_FLAG_DISABLE_RAW;
|
|
}
|
|
} else {
|
|
enable_raw = 1;
|
|
}
|
|
if (!quiet)
|
|
SCLogConfig("stream.reassembly.raw: %s", enable_raw ? "enabled" : "disabled");
|
|
|
|
/* init the memcap/use tracking */
|
|
StreamTcpInitMemuse();
|
|
StatsRegisterGlobalCounter("tcp.memuse", StreamTcpMemuseCounter);
|
|
|
|
StreamTcpReassembleInit(quiet);
|
|
|
|
/* set the default free function and flow state function
|
|
* values. */
|
|
FlowSetProtoFreeFunc(IPPROTO_TCP, StreamTcpSessionClear);
|
|
|
|
#ifdef UNITTESTS
|
|
if (RunmodeIsUnittests()) {
|
|
SCMutexLock(&ssn_pool_mutex);
|
|
if (ssn_pool == NULL) {
|
|
ssn_pool = PoolThreadInit(1, /* thread */
|
|
0, /* unlimited */
|
|
stream_config.prealloc_sessions,
|
|
sizeof(TcpSession),
|
|
StreamTcpSessionPoolAlloc,
|
|
StreamTcpSessionPoolInit, NULL,
|
|
StreamTcpSessionPoolCleanup, NULL);
|
|
}
|
|
SCMutexUnlock(&ssn_pool_mutex);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void StreamTcpFreeConfig(char quiet)
|
|
{
|
|
StreamTcpReassembleFree(quiet);
|
|
|
|
SCMutexLock(&ssn_pool_mutex);
|
|
if (ssn_pool != NULL) {
|
|
PoolThreadFree(ssn_pool);
|
|
ssn_pool = NULL;
|
|
}
|
|
SCMutexUnlock(&ssn_pool_mutex);
|
|
SCMutexDestroy(&ssn_pool_mutex);
|
|
|
|
SCLogDebug("ssn_pool_cnt %"PRIu64"", ssn_pool_cnt);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief The function is used to to fetch a TCP session from the
|
|
* ssn_pool, when a TCP SYN is received.
|
|
*
|
|
* \param p packet starting the new TCP session.
|
|
* \param id thread pool id
|
|
*
|
|
* \retval ssn new TCP session.
|
|
*/
|
|
static TcpSession *StreamTcpNewSession (Packet *p, int id)
|
|
{
|
|
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
|
|
|
|
if (ssn == NULL) {
|
|
p->flow->protoctx = PoolThreadGetById(ssn_pool, id);
|
|
#ifdef DEBUG
|
|
SCMutexLock(&ssn_pool_mutex);
|
|
if (p->flow->protoctx != NULL)
|
|
ssn_pool_cnt++;
|
|
SCMutexUnlock(&ssn_pool_mutex);
|
|
#endif
|
|
|
|
ssn = (TcpSession *)p->flow->protoctx;
|
|
if (ssn == NULL) {
|
|
SCLogDebug("ssn_pool is empty");
|
|
return NULL;
|
|
}
|
|
|
|
ssn->state = TCP_NONE;
|
|
ssn->reassembly_depth = stream_config.reassembly_depth;
|
|
ssn->tcp_packet_flags = p->tcph ? p->tcph->th_flags : 0;
|
|
ssn->server.flags = stream_config.stream_init_flags;
|
|
ssn->client.flags = stream_config.stream_init_flags;
|
|
|
|
StreamingBuffer x = STREAMING_BUFFER_INITIALIZER(&stream_config.sbcnf);
|
|
ssn->client.sb = x;
|
|
ssn->server.sb = x;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
ssn->client.tcp_flags = p->tcph ? p->tcph->th_flags : 0;
|
|
ssn->server.tcp_flags = 0;
|
|
} else if (PKT_IS_TOCLIENT(p)) {
|
|
ssn->server.tcp_flags = p->tcph ? p->tcph->th_flags : 0;
|
|
ssn->client.tcp_flags = 0;
|
|
}
|
|
}
|
|
|
|
return ssn;
|
|
}
|
|
|
|
static void StreamTcpPacketSetState(Packet *p, TcpSession *ssn,
|
|
uint8_t state)
|
|
{
|
|
if (state == ssn->state || PKT_IS_PSEUDOPKT(p))
|
|
return;
|
|
|
|
ssn->pstate = ssn->state;
|
|
ssn->state = state;
|
|
|
|
/* update the flow state */
|
|
switch(ssn->state) {
|
|
case TCP_ESTABLISHED:
|
|
case TCP_FIN_WAIT1:
|
|
case TCP_FIN_WAIT2:
|
|
case TCP_CLOSING:
|
|
case TCP_CLOSE_WAIT:
|
|
FlowUpdateState(p->flow, FLOW_STATE_ESTABLISHED);
|
|
break;
|
|
case TCP_LAST_ACK:
|
|
case TCP_TIME_WAIT:
|
|
case TCP_CLOSED:
|
|
FlowUpdateState(p->flow, FLOW_STATE_CLOSED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Function to set the OS policy for the given stream based on the
|
|
* destination of the received packet.
|
|
*
|
|
* \param stream TcpStream of which os_policy needs to set
|
|
* \param p Packet which is used to set the os policy
|
|
*/
|
|
void StreamTcpSetOSPolicy(TcpStream *stream, Packet *p)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (PKT_IS_IPV4(p)) {
|
|
/* Get the OS policy based on destination IP address, as destination
|
|
OS will decide how to react on the anomalies of newly received
|
|
packets */
|
|
ret = SCHInfoGetIPv4HostOSFlavour((uint8_t *)GET_IPV4_DST_ADDR_PTR(p));
|
|
if (ret > 0)
|
|
stream->os_policy = ret;
|
|
else
|
|
stream->os_policy = OS_POLICY_DEFAULT;
|
|
|
|
} else if (PKT_IS_IPV6(p)) {
|
|
/* Get the OS policy based on destination IP address, as destination
|
|
OS will decide how to react on the anomalies of newly received
|
|
packets */
|
|
ret = SCHInfoGetIPv6HostOSFlavour((uint8_t *)GET_IPV6_DST_ADDR(p));
|
|
if (ret > 0)
|
|
stream->os_policy = ret;
|
|
else
|
|
stream->os_policy = OS_POLICY_DEFAULT;
|
|
}
|
|
|
|
if (stream->os_policy == OS_POLICY_BSD_RIGHT)
|
|
stream->os_policy = OS_POLICY_BSD;
|
|
else if (stream->os_policy == OS_POLICY_OLD_SOLARIS)
|
|
stream->os_policy = OS_POLICY_SOLARIS;
|
|
|
|
SCLogDebug("Policy is %"PRIu8"", stream->os_policy);
|
|
|
|
}
|
|
|
|
/**
|
|
* \brief macro to update last_ack only if the new value is higher
|
|
*
|
|
* \param ssn session
|
|
* \param stream stream to update
|
|
* \param ack ACK value to test and set
|
|
*/
|
|
#define StreamTcpUpdateLastAck(ssn, stream, ack) { \
|
|
if (SEQ_GT((ack), (stream)->last_ack)) \
|
|
{ \
|
|
SCLogDebug("ssn %p: last_ack set to %"PRIu32", moved %u forward", (ssn), (ack), (ack) - (stream)->last_ack); \
|
|
if ((SEQ_LEQ((stream)->last_ack, (stream)->next_seq) && SEQ_GT((ack),(stream)->next_seq))) { \
|
|
SCLogDebug("last_ack just passed next_seq: %u (was %u) > %u", (ack), (stream)->last_ack, (stream)->next_seq); \
|
|
} else { \
|
|
SCLogDebug("next_seq (%u) <> last_ack now %d", (stream)->next_seq, (int)(stream)->next_seq - (ack)); \
|
|
}\
|
|
(stream)->last_ack = (ack); \
|
|
StreamTcpSackPruneList((stream)); \
|
|
} else { \
|
|
SCLogDebug("ssn %p: no update: ack %u, last_ack %"PRIu32", next_seq %u (state %u)", \
|
|
(ssn), (ack), (stream)->last_ack, (stream)->next_seq, (ssn)->state); \
|
|
}\
|
|
}
|
|
|
|
#define StreamTcpAsyncLastAckUpdate(ssn, stream) { \
|
|
if ((ssn)->flags & STREAMTCP_FLAG_ASYNC) { \
|
|
if (SEQ_GT((stream)->next_seq, (stream)->last_ack)) { \
|
|
uint32_t ack_diff = (stream)->next_seq - (stream)->last_ack; \
|
|
(stream)->last_ack += ack_diff; \
|
|
SCLogDebug("ssn %p: ASYNC last_ack set to %"PRIu32", moved %u forward", \
|
|
(ssn), (stream)->next_seq, ack_diff); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#define StreamTcpUpdateNextSeq(ssn, stream, seq) { \
|
|
(stream)->next_seq = seq; \
|
|
SCLogDebug("ssn %p: next_seq %" PRIu32, (ssn), (stream)->next_seq); \
|
|
StreamTcpAsyncLastAckUpdate((ssn), (stream)); \
|
|
}
|
|
|
|
/**
|
|
* \brief macro to update next_win only if the new value is higher
|
|
*
|
|
* \param ssn session
|
|
* \param stream stream to update
|
|
* \param win window value to test and set
|
|
*/
|
|
#define StreamTcpUpdateNextWin(ssn, stream, win) { \
|
|
uint32_t sacked_size__ = StreamTcpSackedSize((stream)); \
|
|
if (SEQ_GT(((win) + sacked_size__), (stream)->next_win)) { \
|
|
(stream)->next_win = ((win) + sacked_size__); \
|
|
SCLogDebug("ssn %p: next_win set to %"PRIu32, (ssn), (stream)->next_win); \
|
|
} \
|
|
}
|
|
|
|
static inline void StreamTcpCloseSsnWithReset(Packet *p, TcpSession *ssn)
|
|
{
|
|
ssn->flags |= STREAMTCP_FLAG_CLOSED_BY_RST;
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
|
|
SCLogDebug("ssn %p: (state: %s) Reset received and state changed to "
|
|
"TCP_CLOSED", ssn, StreamTcpStateAsString(ssn->state));
|
|
}
|
|
|
|
static int StreamTcpPacketIsRetransmission(TcpStream *stream, Packet *p)
|
|
{
|
|
if (p->payload_len == 0)
|
|
SCReturnInt(0);
|
|
|
|
/* retransmission of already partially ack'd data */
|
|
if (SEQ_LT(TCP_GET_SEQ(p), stream->last_ack) && SEQ_GT((TCP_GET_SEQ(p) + p->payload_len), stream->last_ack))
|
|
{
|
|
StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION);
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
/* retransmission of already ack'd data */
|
|
if (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), stream->last_ack)) {
|
|
StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION);
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
/* retransmission of in flight data */
|
|
if (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), stream->next_seq)) {
|
|
StreamTcpSetEvent(p, STREAM_PKT_RETRANSMISSION);
|
|
SCReturnInt(2);
|
|
}
|
|
|
|
SCLogDebug("seq %u payload_len %u => %u, last_ack %u, next_seq %u", TCP_GET_SEQ(p),
|
|
p->payload_len, (TCP_GET_SEQ(p) + p->payload_len), stream->last_ack, stream->next_seq);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Function to handle the TCP_CLOSED or NONE state. The function handles
|
|
* packets while the session state is None which means a newly
|
|
* initialized structure, or a fully closed session.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*
|
|
* \retval 0 ok
|
|
* \retval -1 error
|
|
*/
|
|
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn,
|
|
PacketQueueNoLock *pq)
|
|
{
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
StreamTcpSetEvent(p, STREAM_RST_BUT_NO_SESSION);
|
|
SCLogDebug("RST packet received, no session setup");
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
StreamTcpSetEvent(p, STREAM_FIN_BUT_NO_SESSION);
|
|
SCLogDebug("FIN packet received, no session setup");
|
|
return -1;
|
|
|
|
/* SYN/ACK */
|
|
} else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
|
|
if (!stream_config.midstream && stream_config.async_oneside == FALSE)
|
|
return 0;
|
|
|
|
if (ssn == NULL) {
|
|
ssn = StreamTcpNewSession(p, stt->ssn_pool_id);
|
|
if (ssn == NULL) {
|
|
StatsIncr(tv, stt->counter_tcp_ssn_memcap);
|
|
return -1;
|
|
}
|
|
StatsIncr(tv, stt->counter_tcp_sessions);
|
|
StatsIncr(tv, stt->counter_tcp_midstream_pickups);
|
|
}
|
|
|
|
/* reverse packet and flow */
|
|
SCLogDebug("reversing flow and packet");
|
|
PacketSwap(p);
|
|
FlowSwap(p->flow);
|
|
|
|
/* set the state */
|
|
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
|
|
SCLogDebug("ssn %p: =~ midstream picked ssn state is now "
|
|
"TCP_SYN_RECV", ssn);
|
|
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM;
|
|
/* Flag used to change the direct in the later stage in the session */
|
|
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM_SYNACK;
|
|
if (stream_config.async_oneside) {
|
|
SCLogDebug("ssn %p: =~ ASYNC", ssn);
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
}
|
|
|
|
/* sequence number & window */
|
|
ssn->server.isn = TCP_GET_SEQ(p);
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn);
|
|
ssn->server.next_seq = ssn->server.isn + 1;
|
|
ssn->server.window = TCP_GET_WINDOW(p);
|
|
SCLogDebug("ssn %p: server window %u", ssn, ssn->server.window);
|
|
|
|
ssn->client.isn = TCP_GET_ACK(p) - 1;
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
|
|
ssn->client.next_seq = ssn->client.isn + 1;
|
|
|
|
ssn->client.last_ack = TCP_GET_ACK(p);
|
|
ssn->server.last_ack = TCP_GET_SEQ(p);
|
|
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
|
|
/** If the client has a wscale option the server had it too,
|
|
* so set the wscale for the server to max. Otherwise none
|
|
* will have the wscale opt just like it should. */
|
|
if (TCP_HAS_WSCALE(p)) {
|
|
ssn->client.wscale = TCP_GET_WSCALE(p);
|
|
ssn->server.wscale = TCP_WSCALE_MAX;
|
|
SCLogDebug("ssn %p: wscale enabled. client %u server %u",
|
|
ssn, ssn->client.wscale, ssn->server.wscale);
|
|
}
|
|
|
|
SCLogDebug("ssn %p: ssn->client.isn %"PRIu32", ssn->client.next_seq"
|
|
" %"PRIu32", ssn->client.last_ack %"PRIu32"", ssn,
|
|
ssn->client.isn, ssn->client.next_seq,
|
|
ssn->client.last_ack);
|
|
SCLogDebug("ssn %p: ssn->server.isn %"PRIu32", ssn->server.next_seq"
|
|
" %"PRIu32", ssn->server.last_ack %"PRIu32"", ssn,
|
|
ssn->server.isn, ssn->server.next_seq,
|
|
ssn->server.last_ack);
|
|
|
|
/* Set the timestamp value for both streams, if packet has timestamp
|
|
* option enabled.*/
|
|
if (TCP_HAS_TS(p)) {
|
|
ssn->server.last_ts = TCP_GET_TSVAL(p);
|
|
ssn->client.last_ts = TCP_GET_TSECR(p);
|
|
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
|
|
"ssn->client.last_ts %" PRIu32"", ssn,
|
|
ssn->server.last_ts, ssn->client.last_ts);
|
|
|
|
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
|
|
|
|
ssn->server.last_pkt_ts = p->ts.tv_sec;
|
|
if (ssn->server.last_ts == 0)
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
if (ssn->client.last_ts == 0)
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
|
|
} else {
|
|
ssn->server.last_ts = 0;
|
|
ssn->client.last_ts = 0;
|
|
}
|
|
|
|
if (TCP_GET_SACKOK(p) == 1) {
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
SCLogDebug("ssn %p: SYN/ACK with SACK permitted, assuming "
|
|
"SACK permitted for both sides", ssn);
|
|
}
|
|
return 0;
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
if (ssn == NULL) {
|
|
ssn = StreamTcpNewSession(p, stt->ssn_pool_id);
|
|
if (ssn == NULL) {
|
|
StatsIncr(tv, stt->counter_tcp_ssn_memcap);
|
|
return -1;
|
|
}
|
|
|
|
StatsIncr(tv, stt->counter_tcp_sessions);
|
|
}
|
|
|
|
/* set the state */
|
|
StreamTcpPacketSetState(p, ssn, TCP_SYN_SENT);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_SYN_SENT", ssn);
|
|
|
|
if (stream_config.async_oneside) {
|
|
SCLogDebug("ssn %p: =~ ASYNC", ssn);
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
}
|
|
|
|
/* set the sequence numbers and window */
|
|
ssn->client.isn = TCP_GET_SEQ(p);
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
|
|
ssn->client.next_seq = ssn->client.isn + 1;
|
|
|
|
/* Set the stream timestamp value, if packet has timestamp option
|
|
* enabled. */
|
|
if (TCP_HAS_TS(p)) {
|
|
ssn->client.last_ts = TCP_GET_TSVAL(p);
|
|
SCLogDebug("ssn %p: %02x", ssn, ssn->client.last_ts);
|
|
|
|
if (ssn->client.last_ts == 0)
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
|
|
ssn->client.last_pkt_ts = p->ts.tv_sec;
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_TIMESTAMP;
|
|
}
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p);
|
|
if (TCP_HAS_WSCALE(p)) {
|
|
ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
|
|
ssn->server.wscale = TCP_GET_WSCALE(p);
|
|
}
|
|
|
|
if (TCP_GET_SACKOK(p) == 1) {
|
|
ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
|
|
SCLogDebug("ssn %p: SACK permitted on SYN packet", ssn);
|
|
}
|
|
|
|
if (TCP_HAS_TFO(p)) {
|
|
ssn->flags |= STREAMTCP_FLAG_TCP_FAST_OPEN;
|
|
if (p->payload_len) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
SCLogDebug("ssn: %p (TFO) [len: %d] isn %u base_seq %u next_seq %u payload len %u",
|
|
ssn, p->tcpvars.tfo.len, ssn->client.isn, ssn->client.base_seq, ssn->client.next_seq, p->payload_len);
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq);
|
|
}
|
|
}
|
|
|
|
SCLogDebug("ssn %p: ssn->client.isn %" PRIu32 ", "
|
|
"ssn->client.next_seq %" PRIu32 ", ssn->client.last_ack "
|
|
"%"PRIu32"", ssn, ssn->client.isn, ssn->client.next_seq,
|
|
ssn->client.last_ack);
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (!stream_config.midstream)
|
|
return 0;
|
|
|
|
if (ssn == NULL) {
|
|
ssn = StreamTcpNewSession(p, stt->ssn_pool_id);
|
|
if (ssn == NULL) {
|
|
StatsIncr(tv, stt->counter_tcp_ssn_memcap);
|
|
return -1;
|
|
}
|
|
StatsIncr(tv, stt->counter_tcp_sessions);
|
|
StatsIncr(tv, stt->counter_tcp_midstream_pickups);
|
|
}
|
|
/* set the state */
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ midstream picked ssn state is now "
|
|
"TCP_ESTABLISHED", ssn);
|
|
|
|
ssn->flags = STREAMTCP_FLAG_MIDSTREAM;
|
|
ssn->flags |= STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
|
|
if (stream_config.async_oneside) {
|
|
SCLogDebug("ssn %p: =~ ASYNC", ssn);
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
}
|
|
|
|
/** window scaling for midstream pickups, we can't do much other
|
|
* than assume that it's set to the max value: 14 */
|
|
ssn->client.wscale = TCP_WSCALE_MAX;
|
|
ssn->server.wscale = TCP_WSCALE_MAX;
|
|
|
|
/* set the sequence numbers and window */
|
|
ssn->client.isn = TCP_GET_SEQ(p) - 1;
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
|
|
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
ssn->client.last_ack = TCP_GET_SEQ(p);
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
SCLogDebug("ssn %p: ssn->client.isn %u, ssn->client.next_seq %u",
|
|
ssn, ssn->client.isn, ssn->client.next_seq);
|
|
|
|
ssn->server.isn = TCP_GET_ACK(p) - 1;
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn);
|
|
ssn->server.next_seq = ssn->server.isn + 1;
|
|
ssn->server.last_ack = TCP_GET_ACK(p);
|
|
ssn->server.next_win = ssn->server.last_ack;
|
|
|
|
SCLogDebug("ssn %p: ssn->client.next_win %"PRIu32", "
|
|
"ssn->server.next_win %"PRIu32"", ssn,
|
|
ssn->client.next_win, ssn->server.next_win);
|
|
SCLogDebug("ssn %p: ssn->client.last_ack %"PRIu32", "
|
|
"ssn->server.last_ack %"PRIu32"", ssn,
|
|
ssn->client.last_ack, ssn->server.last_ack);
|
|
|
|
/* Set the timestamp value for both streams, if packet has timestamp
|
|
* option enabled.*/
|
|
if (TCP_HAS_TS(p)) {
|
|
ssn->client.last_ts = TCP_GET_TSVAL(p);
|
|
ssn->server.last_ts = TCP_GET_TSECR(p);
|
|
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
|
|
"ssn->client.last_ts %" PRIu32"", ssn,
|
|
ssn->server.last_ts, ssn->client.last_ts);
|
|
|
|
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
|
|
|
|
ssn->client.last_pkt_ts = p->ts.tv_sec;
|
|
if (ssn->server.last_ts == 0)
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
if (ssn->client.last_ts == 0)
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
|
|
} else {
|
|
ssn->server.last_ts = 0;
|
|
ssn->client.last_ts = 0;
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq);
|
|
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
SCLogDebug("ssn %p: assuming SACK permitted for both sides", ssn);
|
|
|
|
} else {
|
|
SCLogDebug("default case");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Setup TcpStateQueue based on SYN/ACK packet
|
|
*/
|
|
static inline void StreamTcp3whsSynAckToStateQueue(Packet *p, TcpStateQueue *q)
|
|
{
|
|
q->flags = 0;
|
|
q->wscale = 0;
|
|
q->ts = 0;
|
|
q->win = TCP_GET_WINDOW(p);
|
|
q->seq = TCP_GET_SEQ(p);
|
|
q->ack = TCP_GET_ACK(p);
|
|
q->pkt_ts = p->ts.tv_sec;
|
|
|
|
if (TCP_GET_SACKOK(p) == 1)
|
|
q->flags |= STREAMTCP_QUEUE_FLAG_SACK;
|
|
|
|
if (TCP_HAS_WSCALE(p)) {
|
|
q->flags |= STREAMTCP_QUEUE_FLAG_WS;
|
|
q->wscale = TCP_GET_WSCALE(p);
|
|
}
|
|
if (TCP_HAS_TS(p)) {
|
|
q->flags |= STREAMTCP_QUEUE_FLAG_TS;
|
|
q->ts = TCP_GET_TSVAL(p);
|
|
}
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Find the Queued SYN/ACK that is the same as this SYN/ACK
|
|
* \retval q or NULL */
|
|
static TcpStateQueue *StreamTcp3whsFindSynAckBySynAck(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStateQueue *q = ssn->queue;
|
|
TcpStateQueue search;
|
|
|
|
StreamTcp3whsSynAckToStateQueue(p, &search);
|
|
|
|
while (q != NULL) {
|
|
if (search.flags == q->flags &&
|
|
search.wscale == q->wscale &&
|
|
search.win == q->win &&
|
|
search.seq == q->seq &&
|
|
search.ack == q->ack &&
|
|
search.ts == q->ts) {
|
|
return q;
|
|
}
|
|
|
|
q = q->next;
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
static int StreamTcp3whsQueueSynAck(TcpSession *ssn, Packet *p)
|
|
{
|
|
/* first see if this is already in our list */
|
|
if (StreamTcp3whsFindSynAckBySynAck(ssn, p) != NULL)
|
|
return 0;
|
|
|
|
if (ssn->queue_len == stream_config.max_synack_queued) {
|
|
SCLogDebug("ssn %p: =~ SYN/ACK queue limit reached", ssn);
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_FLOOD);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpCheckMemcap((uint32_t)sizeof(TcpStateQueue)) == 0) {
|
|
SCLogDebug("ssn %p: =~ SYN/ACK queue failed: stream memcap reached", ssn);
|
|
return -1;
|
|
}
|
|
|
|
TcpStateQueue *q = SCMalloc(sizeof(*q));
|
|
if (unlikely(q == NULL)) {
|
|
SCLogDebug("ssn %p: =~ SYN/ACK queue failed: alloc failed", ssn);
|
|
return -1;
|
|
}
|
|
memset(q, 0x00, sizeof(*q));
|
|
StreamTcpIncrMemuse((uint64_t)sizeof(TcpStateQueue));
|
|
|
|
StreamTcp3whsSynAckToStateQueue(p, q);
|
|
|
|
/* put in list */
|
|
q->next = ssn->queue;
|
|
ssn->queue = q;
|
|
ssn->queue_len++;
|
|
return 0;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Find the Queued SYN/ACK that goes with this ACK
|
|
* \retval q or NULL */
|
|
static TcpStateQueue *StreamTcp3whsFindSynAckByAck(TcpSession *ssn, Packet *p)
|
|
{
|
|
uint32_t ack = TCP_GET_SEQ(p);
|
|
uint32_t seq = TCP_GET_ACK(p) - 1;
|
|
TcpStateQueue *q = ssn->queue;
|
|
|
|
while (q != NULL) {
|
|
if (seq == q->seq &&
|
|
ack == q->ack) {
|
|
return q;
|
|
}
|
|
|
|
q = q->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Update SSN after receiving a valid SYN/ACK
|
|
*
|
|
* Normally we update the SSN from the SYN/ACK packet. But in case
|
|
* of queued SYN/ACKs, we can use one of those.
|
|
*
|
|
* \param ssn TCP session
|
|
* \param p Packet
|
|
* \param q queued state if used, NULL otherwise
|
|
*
|
|
* To make sure all SYN/ACK based state updates are in one place,
|
|
* this function can updated based on Packet or TcpStateQueue, where
|
|
* the latter takes precedence.
|
|
*/
|
|
static void StreamTcp3whsSynAckUpdate(TcpSession *ssn, Packet *p, TcpStateQueue *q)
|
|
{
|
|
TcpStateQueue update;
|
|
if (likely(q == NULL)) {
|
|
StreamTcp3whsSynAckToStateQueue(p, &update);
|
|
q = &update;
|
|
}
|
|
|
|
if (ssn->state != TCP_SYN_RECV) {
|
|
/* update state */
|
|
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_SYN_RECV", ssn);
|
|
}
|
|
/* sequence number & window */
|
|
ssn->server.isn = q->seq;
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn);
|
|
ssn->server.next_seq = ssn->server.isn + 1;
|
|
|
|
ssn->client.window = q->win;
|
|
SCLogDebug("ssn %p: window %" PRIu32 "", ssn, ssn->server.window);
|
|
|
|
/* Set the timestamp values used to validate the timestamp of
|
|
* received packets.*/
|
|
if ((q->flags & STREAMTCP_QUEUE_FLAG_TS) &&
|
|
(ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP))
|
|
{
|
|
ssn->server.last_ts = q->ts;
|
|
SCLogDebug("ssn %p: ssn->server.last_ts %" PRIu32" "
|
|
"ssn->client.last_ts %" PRIu32"", ssn,
|
|
ssn->server.last_ts, ssn->client.last_ts);
|
|
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
|
|
ssn->server.last_pkt_ts = q->pkt_ts;
|
|
if (ssn->server.last_ts == 0)
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
} else {
|
|
ssn->client.last_ts = 0;
|
|
ssn->server.last_ts = 0;
|
|
ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
}
|
|
|
|
ssn->client.last_ack = q->ack;
|
|
ssn->server.last_ack = ssn->server.isn + 1;
|
|
|
|
/** check for the presense of the ws ptr to determine if we
|
|
* support wscale at all */
|
|
if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) &&
|
|
(q->flags & STREAMTCP_QUEUE_FLAG_WS))
|
|
{
|
|
ssn->client.wscale = q->wscale;
|
|
} else {
|
|
ssn->client.wscale = 0;
|
|
}
|
|
|
|
if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) &&
|
|
(q->flags & STREAMTCP_QUEUE_FLAG_SACK)) {
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
SCLogDebug("ssn %p: SACK permitted for session", ssn);
|
|
} else {
|
|
ssn->flags &= ~STREAMTCP_FLAG_SACKOK;
|
|
}
|
|
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 "", ssn,
|
|
ssn->server.next_win);
|
|
SCLogDebug("ssn %p: ssn->client.next_win %" PRIu32 "", ssn,
|
|
ssn->client.next_win);
|
|
SCLogDebug("ssn %p: ssn->server.isn %" PRIu32 ", "
|
|
"ssn->server.next_seq %" PRIu32 ", "
|
|
"ssn->server.last_ack %" PRIu32 " "
|
|
"(ssn->client.last_ack %" PRIu32 ")", ssn,
|
|
ssn->server.isn, ssn->server.next_seq,
|
|
ssn->server.last_ack, ssn->client.last_ack);
|
|
|
|
/* unset the 4WHS flag as we received this SYN/ACK as part of a
|
|
* (so far) valid 3WHS */
|
|
if (ssn->flags & STREAMTCP_FLAG_4WHS)
|
|
SCLogDebug("ssn %p: STREAMTCP_FLAG_4WHS unset, normal SYN/ACK"
|
|
" so considering 3WHS", ssn);
|
|
|
|
ssn->flags &=~ STREAMTCP_FLAG_4WHS;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief detect timestamp anomalies when processing responses to the
|
|
* SYN packet.
|
|
* \retval true packet is ok
|
|
* \retval false packet is bad
|
|
*/
|
|
static inline bool StateSynSentValidateTimestamp(TcpSession *ssn, Packet *p)
|
|
{
|
|
/* we only care about evil server here, so skip TS packets */
|
|
if (PKT_IS_TOSERVER(p) || !(TCP_HAS_TS(p))) {
|
|
return true;
|
|
}
|
|
|
|
TcpStream *receiver_stream = &ssn->client;
|
|
uint32_t ts_echo = TCP_GET_TSECR(p);
|
|
if ((receiver_stream->flags & STREAMTCP_STREAM_FLAG_TIMESTAMP) != 0) {
|
|
if (receiver_stream->last_ts != 0 && ts_echo != 0 &&
|
|
ts_echo != receiver_stream->last_ts)
|
|
{
|
|
SCLogDebug("ssn %p: BAD TSECR echo %u recv %u", ssn,
|
|
ts_echo, receiver_stream->last_ts);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (receiver_stream->last_ts == 0 && ts_echo != 0) {
|
|
SCLogDebug("ssn %p: BAD TSECR echo %u recv %u", ssn,
|
|
ts_echo, receiver_stream->last_ts);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_SYN_SENT state. The function handles
|
|
* SYN, SYN/ACK, RST packets and correspondingly changes the connection
|
|
* state.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn,
|
|
PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
SCLogDebug("ssn %p: pkt received: %s", ssn, PKT_IS_TOCLIENT(p) ?
|
|
"toclient":"toserver");
|
|
|
|
/* check for bad responses */
|
|
if (StateSynSentValidateTimestamp(ssn, p) == false)
|
|
return -1;
|
|
|
|
/* RST */
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn) &&
|
|
SEQ_EQ(TCP_GET_WINDOW(p), 0) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), (ssn->client.isn + 1)))
|
|
{
|
|
SCLogDebug("ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
}
|
|
} else {
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV;
|
|
SCLogDebug("ssn->client.flags |= STREAMTCP_STREAM_FLAG_RST_RECV");
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
}
|
|
|
|
/* FIN */
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
/** \todo */
|
|
|
|
/* SYN/ACK */
|
|
} else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
|
|
if ((ssn->flags & STREAMTCP_FLAG_4WHS) && PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: SYN/ACK received on 4WHS session", ssn);
|
|
|
|
/* Check if the SYN/ACK packet ack's the earlier
|
|
* received SYN packet. */
|
|
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->server.isn + 1))) {
|
|
StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_ACK);
|
|
|
|
SCLogDebug("ssn %p: 4WHS ACK mismatch, packet ACK %"PRIu32""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_ACK(p), ssn->server.isn + 1);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the SYN/ACK packet SEQ's the *FIRST* received SYN
|
|
* packet. */
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
|
|
StreamTcpSetEvent(p, STREAM_4WHS_SYNACK_WITH_WRONG_SYN);
|
|
|
|
SCLogDebug("ssn %p: 4WHS SEQ mismatch, packet SEQ %"PRIu32""
|
|
" != %" PRIu32 " from *first* SYN pkt", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.isn);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* update state */
|
|
StreamTcpPacketSetState(p, ssn, TCP_SYN_RECV);
|
|
SCLogDebug("ssn %p: =~ 4WHS ssn state is now TCP_SYN_RECV", ssn);
|
|
|
|
/* sequence number & window */
|
|
ssn->client.isn = TCP_GET_SEQ(p);
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->client, ssn->client.isn);
|
|
ssn->client.next_seq = ssn->client.isn + 1;
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p);
|
|
SCLogDebug("ssn %p: 4WHS window %" PRIu32 "", ssn,
|
|
ssn->client.window);
|
|
|
|
/* Set the timestamp values used to validate the timestamp of
|
|
* received packets. */
|
|
if ((TCP_HAS_TS(p)) &&
|
|
(ssn->server.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP))
|
|
{
|
|
ssn->client.last_ts = TCP_GET_TSVAL(p);
|
|
SCLogDebug("ssn %p: 4WHS ssn->client.last_ts %" PRIu32" "
|
|
"ssn->server.last_ts %" PRIu32"", ssn,
|
|
ssn->client.last_ts, ssn->server.last_ts);
|
|
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
|
|
ssn->client.last_pkt_ts = p->ts.tv_sec;
|
|
if (ssn->client.last_ts == 0)
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
} else {
|
|
ssn->server.last_ts = 0;
|
|
ssn->client.last_ts = 0;
|
|
ssn->server.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
}
|
|
|
|
ssn->server.last_ack = TCP_GET_ACK(p);
|
|
ssn->client.last_ack = ssn->client.isn + 1;
|
|
|
|
/** check for the presense of the ws ptr to determine if we
|
|
* support wscale at all */
|
|
if ((ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) &&
|
|
(TCP_HAS_WSCALE(p)))
|
|
{
|
|
ssn->server.wscale = TCP_GET_WSCALE(p);
|
|
} else {
|
|
ssn->server.wscale = 0;
|
|
}
|
|
|
|
if ((ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) &&
|
|
TCP_GET_SACKOK(p) == 1) {
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
SCLogDebug("ssn %p: SACK permitted for 4WHS session", ssn);
|
|
}
|
|
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
SCLogDebug("ssn %p: 4WHS ssn->client.next_win %" PRIu32 "", ssn,
|
|
ssn->client.next_win);
|
|
SCLogDebug("ssn %p: 4WHS ssn->server.next_win %" PRIu32 "", ssn,
|
|
ssn->server.next_win);
|
|
SCLogDebug("ssn %p: 4WHS ssn->client.isn %" PRIu32 ", "
|
|
"ssn->client.next_seq %" PRIu32 ", "
|
|
"ssn->client.last_ack %" PRIu32 " "
|
|
"(ssn->server.last_ack %" PRIu32 ")", ssn,
|
|
ssn->client.isn, ssn->client.next_seq,
|
|
ssn->client.last_ack, ssn->server.last_ack);
|
|
|
|
/* done here */
|
|
return 0;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_IN_WRONG_DIRECTION);
|
|
SCLogDebug("ssn %p: SYN/ACK received in the wrong direction", ssn);
|
|
return -1;
|
|
}
|
|
|
|
if (!(TCP_HAS_TFO(p) || (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN))) {
|
|
/* Check if the SYN/ACK packet ack's the earlier
|
|
* received SYN packet. */
|
|
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.isn + 1))) {
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_WITH_WRONG_ACK);
|
|
SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.isn + 1);
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.next_seq))) {
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_WITH_WRONG_ACK);
|
|
SCLogDebug("ssn %p: (TFO) ACK mismatch, packet ACK %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.next_seq);
|
|
return -1;
|
|
}
|
|
SCLogDebug("ssn %p: (TFO) ACK match, packet ACK %" PRIu32 " == "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.next_seq);
|
|
|
|
ssn->flags |= STREAMTCP_FLAG_TCP_FAST_OPEN;
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
}
|
|
StreamTcp3whsSynAckUpdate(ssn, p, /* no queue override */NULL);
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent", ssn);
|
|
if (ssn->flags & STREAMTCP_FLAG_4WHS) {
|
|
SCLogDebug("ssn %p: SYN packet on state SYN_SENT... resent of "
|
|
"4WHS SYN", ssn);
|
|
}
|
|
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
/** a SYN only packet in the opposite direction could be:
|
|
* http://www.breakingpointsystems.com/community/blog/tcp-
|
|
* portals-the-three-way-handshake-is-a-lie
|
|
*
|
|
* \todo improve resetting the session */
|
|
|
|
/* indicate that we're dealing with 4WHS here */
|
|
ssn->flags |= STREAMTCP_FLAG_4WHS;
|
|
SCLogDebug("ssn %p: STREAMTCP_FLAG_4WHS flag set", ssn);
|
|
|
|
/* set the sequence numbers and window for server
|
|
* We leave the ssn->client.isn in place as we will
|
|
* check the SYN/ACK pkt with that.
|
|
*/
|
|
ssn->server.isn = TCP_GET_SEQ(p);
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn);
|
|
ssn->server.next_seq = ssn->server.isn + 1;
|
|
|
|
/* Set the stream timestamp value, if packet has timestamp
|
|
* option enabled. */
|
|
if (TCP_HAS_TS(p)) {
|
|
ssn->server.last_ts = TCP_GET_TSVAL(p);
|
|
SCLogDebug("ssn %p: %02x", ssn, ssn->server.last_ts);
|
|
|
|
if (ssn->server.last_ts == 0)
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
ssn->server.last_pkt_ts = p->ts.tv_sec;
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_TIMESTAMP;
|
|
}
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p);
|
|
if (TCP_HAS_WSCALE(p)) {
|
|
ssn->flags |= STREAMTCP_FLAG_SERVER_WSCALE;
|
|
ssn->server.wscale = TCP_GET_WSCALE(p);
|
|
} else {
|
|
ssn->flags &= ~STREAMTCP_FLAG_SERVER_WSCALE;
|
|
ssn->server.wscale = 0;
|
|
}
|
|
|
|
if (TCP_GET_SACKOK(p) == 1) {
|
|
ssn->flags |= STREAMTCP_FLAG_CLIENT_SACKOK;
|
|
} else {
|
|
ssn->flags &= ~STREAMTCP_FLAG_CLIENT_SACKOK;
|
|
}
|
|
|
|
SCLogDebug("ssn %p: 4WHS ssn->server.isn %" PRIu32 ", "
|
|
"ssn->server.next_seq %" PRIu32 ", "
|
|
"ssn->server.last_ack %"PRIu32"", ssn,
|
|
ssn->server.isn, ssn->server.next_seq,
|
|
ssn->server.last_ack);
|
|
SCLogDebug("ssn %p: 4WHS ssn->client.isn %" PRIu32 ", "
|
|
"ssn->client.next_seq %" PRIu32 ", "
|
|
"ssn->client.last_ack %"PRIu32"", ssn,
|
|
ssn->client.isn, ssn->client.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
/** \todo check if it's correct or set event */
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
/* Handle the asynchronous stream, when we receive a SYN packet
|
|
and now istead of receving a SYN/ACK we receive a ACK from the
|
|
same host, which sent the SYN, this suggests the ASNYC streams.*/
|
|
if (stream_config.async_oneside == FALSE)
|
|
return 0;
|
|
|
|
/* we are in AYNC (one side) mode now. */
|
|
|
|
/* one side async means we won't see a SYN/ACK, so we can
|
|
* only check the SYN. */
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq))) {
|
|
StreamTcpSetEvent(p, STREAM_3WHS_ASYNC_WRONG_SEQ);
|
|
|
|
SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != "
|
|
"%" PRIu32 " from stream",ssn, TCP_GET_SEQ(p),
|
|
ssn->client.next_seq);
|
|
return -1;
|
|
}
|
|
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p);
|
|
ssn->client.last_ack = TCP_GET_SEQ(p);
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
|
|
/* Set the server side parameters */
|
|
ssn->server.isn = TCP_GET_ACK(p) - 1;
|
|
STREAMTCP_SET_RA_BASE_SEQ(&ssn->server, ssn->server.isn);
|
|
ssn->server.next_seq = ssn->server.isn + 1;
|
|
ssn->server.last_ack = ssn->server.next_seq;
|
|
ssn->server.next_win = ssn->server.last_ack;
|
|
|
|
SCLogDebug("ssn %p: synsent => Asynchronous stream, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
|
|
"ssn->client.next_seq %" PRIu32 ""
|
|
,ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
|
|
+ p->payload_len, ssn->client.next_seq);
|
|
|
|
/* if SYN had wscale, assume it to be supported. Otherwise
|
|
* we know it not to be supported. */
|
|
if (ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) {
|
|
ssn->client.wscale = TCP_WSCALE_MAX;
|
|
}
|
|
|
|
/* Set the timestamp values used to validate the timestamp of
|
|
* received packets.*/
|
|
if (TCP_HAS_TS(p) &&
|
|
(ssn->client.flags & STREAMTCP_STREAM_FLAG_TIMESTAMP))
|
|
{
|
|
ssn->flags |= STREAMTCP_FLAG_TIMESTAMP;
|
|
ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_TIMESTAMP;
|
|
ssn->client.last_pkt_ts = p->ts.tv_sec;
|
|
} else {
|
|
ssn->client.last_ts = 0;
|
|
ssn->client.flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) {
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_SYN_RECV state. The function handles
|
|
* SYN, SYN/ACK, ACK, FIN, RST packets and correspondingly changes
|
|
* the connection state.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*
|
|
* \retval 0 ok
|
|
* \retval -1 error
|
|
*/
|
|
|
|
static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn,
|
|
PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
uint8_t reset = TRUE;
|
|
/* After receiveing the RST in SYN_RECV state and if detection
|
|
evasion flags has been set, then the following operating
|
|
systems will not closed the connection. As they consider the
|
|
packet as stray packet and not belonging to the current
|
|
session, for more information check
|
|
http://www.packetstan.com/2010/06/recently-ive-been-on-campaign-to-make.html */
|
|
if (ssn->flags & STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT) {
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((ssn->server.os_policy == OS_POLICY_LINUX) ||
|
|
(ssn->server.os_policy == OS_POLICY_OLD_LINUX) ||
|
|
(ssn->server.os_policy == OS_POLICY_SOLARIS))
|
|
{
|
|
reset = FALSE;
|
|
SCLogDebug("Detection evasion has been attempted, so"
|
|
" not resetting the connection !!");
|
|
}
|
|
} else {
|
|
if ((ssn->client.os_policy == OS_POLICY_LINUX) ||
|
|
(ssn->client.os_policy == OS_POLICY_OLD_LINUX) ||
|
|
(ssn->client.os_policy == OS_POLICY_SOLARIS))
|
|
{
|
|
reset = FALSE;
|
|
SCLogDebug("Detection evasion has been attempted, so"
|
|
" not resetting the connection !!");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reset == TRUE) {
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
/* FIN is handled in the same way as in TCP_ESTABLISHED case */;
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if ((StreamTcpHandleFin(tv, stt, ssn, p, pq)) == -1)
|
|
return -1;
|
|
|
|
/* SYN/ACK */
|
|
} else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
|
|
SCLogDebug("ssn %p: SYN/ACK packet on state SYN_RECV. resent", ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: SYN/ACK-pkt to server in SYN_RECV state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_TOSERVER_ON_SYN_RECV);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the SYN/ACK packets ACK matches the earlier
|
|
* received SYN/ACK packet. */
|
|
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack))) {
|
|
SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.isn + 1);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYNACK_RESEND_WITH_DIFFERENT_ACK);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the SYN/ACK packet SEQ the earlier
|
|
* received SYN/ACK packet, server resend with different ISN. */
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.isn))) {
|
|
SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p),
|
|
ssn->client.isn);
|
|
|
|
if (StreamTcp3whsQueueSynAck(ssn, p) == -1)
|
|
return -1;
|
|
SCLogDebug("ssn %p: queued different SYN/ACK", ssn);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn %p: SYN packet on state SYN_RECV... resent", ssn);
|
|
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
SCLogDebug("ssn %p: SYN-pkt to client in SYN_RECV state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV);
|
|
return -1;
|
|
}
|
|
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
|
|
SCLogDebug("ssn %p: SYN with different SEQ on SYN_RECV state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV);
|
|
return -1;
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->queue_len) {
|
|
SCLogDebug("ssn %p: checking ACK against queued SYN/ACKs", ssn);
|
|
TcpStateQueue *q = StreamTcp3whsFindSynAckByAck(ssn, p);
|
|
if (q != NULL) {
|
|
SCLogDebug("ssn %p: here we update state against queued SYN/ACK", ssn);
|
|
StreamTcp3whsSynAckUpdate(ssn, p, /* using queue to update state */q);
|
|
} else {
|
|
SCLogDebug("ssn %p: none found, now checking ACK against original SYN/ACK (state)", ssn);
|
|
}
|
|
}
|
|
|
|
|
|
/* If the timestamp option is enabled for both the streams, then
|
|
* validate the received packet timestamp value against the
|
|
* stream->last_ts. If the timestamp is valid then process the
|
|
* packet normally otherwise the drop the packet (RFC 1323)*/
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!(StreamTcpValidateTimestamp(ssn, p))) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((ssn->flags & STREAMTCP_FLAG_4WHS) && PKT_IS_TOCLIENT(p)) {
|
|
SCLogDebug("ssn %p: ACK received on 4WHS session",ssn);
|
|
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq))) {
|
|
SCLogDebug("ssn %p: 4WHS wrong seq nr on packet", ssn);
|
|
StreamTcpSetEvent(p, STREAM_4WHS_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: 4WHS invalid ack nr on packet", ssn);
|
|
StreamTcpSetEvent(p, STREAM_4WHS_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
SCLogDebug("4WHS normal pkt");
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: ssn->client.next_win %" PRIu32 ", "
|
|
"ssn->client.last_ack %"PRIu32"", ssn,
|
|
ssn->client.next_win, ssn->client.last_ack);
|
|
return 0;
|
|
}
|
|
|
|
bool ack_indicates_missed_3whs_ack_packet = false;
|
|
/* Check if the ACK received is in right direction. But when we have
|
|
* picked up a mid stream session after missing the initial SYN pkt,
|
|
* in this case the ACK packet can arrive from either client (normal
|
|
* case) or from server itself (asynchronous streams). Therefore
|
|
* the check has been avoided in this case */
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
/* special case, handle 4WHS, so SYN/ACK in the opposite
|
|
* direction */
|
|
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK) {
|
|
SCLogDebug("ssn %p: ACK received on midstream SYN/ACK "
|
|
"pickup session",ssn);
|
|
/* fall through */
|
|
} else if (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN) {
|
|
SCLogDebug("ssn %p: ACK received on TFO session",ssn);
|
|
/* fall through */
|
|
|
|
} else {
|
|
/* if we missed traffic between the S/SA and the current
|
|
* 'wrong direction' ACK, we could end up here. In IPS
|
|
* reject it. But in IDS mode we continue.
|
|
*
|
|
* IPS rejects as it should see all packets, so pktloss
|
|
* should lead to retransmissions. As this can also be
|
|
* pattern for MOTS/MITM injection attacks, we need to be
|
|
* careful.
|
|
*/
|
|
if (StreamTcpInlineMode()) {
|
|
if (p->payload_len > 0 &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack) &&
|
|
SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
|
|
/* packet loss is possible but unlikely here */
|
|
SCLogDebug("ssn %p: possible data injection", ssn);
|
|
StreamTcpSetEvent(p, STREAM_3WHS_ACK_DATA_INJECT);
|
|
return -1;
|
|
}
|
|
|
|
SCLogDebug("ssn %p: ACK received in the wrong direction",
|
|
ssn);
|
|
StreamTcpSetEvent(p, STREAM_3WHS_ACK_IN_WRONG_DIR);
|
|
return -1;
|
|
}
|
|
ack_indicates_missed_3whs_ack_packet = true;
|
|
}
|
|
}
|
|
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ""
|
|
", ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
|
|
TCP_GET_ACK(p));
|
|
|
|
/* Check both seq and ack number before accepting the packet and
|
|
changing to ESTABLISHED state */
|
|
if ((SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) {
|
|
SCLogDebug("normal pkt");
|
|
|
|
/* process the packet normal, No Async streams :) */
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
ssn->server.next_win = ssn->server.last_ack +
|
|
ssn->server.window;
|
|
if (!(ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK)) {
|
|
/* window scaling for midstream pickups, we can't do much
|
|
* other than assume that it's set to the max value: 14 */
|
|
ssn->server.wscale = TCP_WSCALE_MAX;
|
|
ssn->client.wscale = TCP_WSCALE_MAX;
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
}
|
|
}
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
/* If asynchronous stream handling is allowed then set the session,
|
|
if packet's seq number is equal the expected seq no.*/
|
|
} else if (stream_config.async_oneside == TRUE &&
|
|
(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)))
|
|
{
|
|
/*set the ASYNC flag used to indicate the session as async stream
|
|
and helps in relaxing the windows checks.*/
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
ssn->server.next_seq += p->payload_len;
|
|
ssn->server.last_ack = TCP_GET_SEQ(p);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
ssn->client.last_ack = TCP_GET_ACK(p);
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
|
|
ssn->server.window = TCP_GET_WINDOW(p);
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
/* window scaling for midstream pickups, we can't do much
|
|
* other than assume that it's set to the max value: 14 */
|
|
ssn->server.wscale = TCP_WSCALE_MAX;
|
|
ssn->client.wscale = TCP_WSCALE_MAX;
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
}
|
|
|
|
SCLogDebug("ssn %p: synrecv => Asynchronous stream, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
|
|
"ssn->server.next_seq %" PRIu32
|
|
, ssn, TCP_GET_SEQ(p), p->payload_len, TCP_GET_SEQ(p)
|
|
+ p->payload_len, ssn->server.next_seq);
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
/* Upon receiving the packet with correct seq number and wrong
|
|
ACK number, it causes the other end to send RST. But some target
|
|
system (Linux & solaris) does not RST the connection, so it is
|
|
likely to avoid the detection */
|
|
} else if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)){
|
|
ssn->flags |= STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT;
|
|
SCLogDebug("ssn %p: wrong ack nr on packet, possible evasion!!",
|
|
ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_RIGHT_SEQ_WRONG_ACK_EVASION);
|
|
return -1;
|
|
|
|
/* SYN/ACK followed by more TOCLIENT suggesting packet loss */
|
|
} else if (PKT_IS_TOCLIENT(p) && !StreamTcpInlineMode() &&
|
|
SEQ_GT(TCP_GET_SEQ(p), ssn->client.next_seq) &&
|
|
SEQ_GT(TCP_GET_ACK(p), ssn->client.last_ack)) {
|
|
SCLogDebug("ssn %p: ACK for missing data", ssn);
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
SCLogDebug("ssn %p: ACK for missing data: ssn->server.next_seq %u", ssn,
|
|
ssn->server.next_seq);
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
|
|
ssn->client.next_win = ssn->client.last_ack + ssn->client.window;
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p);
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq);
|
|
|
|
/* if we get a packet with a proper ack, but a seq that is beyond
|
|
* next_seq but in-window, we probably missed some packets */
|
|
} else if (SEQ_GT(TCP_GET_SEQ(p), ssn->client.next_seq) &&
|
|
SEQ_LEQ(TCP_GET_SEQ(p), ssn->client.next_win) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) {
|
|
SCLogDebug("ssn %p: ACK for missing data", ssn);
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
SCLogDebug("ssn %p: ACK for missing data: ssn->client.next_seq %u", ssn, ssn->client.next_seq);
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
|
|
ssn->client.window = TCP_GET_WINDOW(p);
|
|
ssn->server.next_win = ssn->server.last_ack +
|
|
ssn->server.window;
|
|
/* window scaling for midstream pickups, we can't do much
|
|
* other than assume that it's set to the max value: 14 */
|
|
ssn->server.wscale = TCP_WSCALE_MAX;
|
|
ssn->client.wscale = TCP_WSCALE_MAX;
|
|
ssn->flags |= STREAMTCP_FLAG_SACKOK;
|
|
}
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
/* toclient packet: after having missed the 3whs's final ACK */
|
|
} else if ((ack_indicates_missed_3whs_ack_packet ||
|
|
(ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN)) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack) &&
|
|
SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
|
|
if (ack_indicates_missed_3whs_ack_packet) {
|
|
SCLogDebug("ssn %p: packet fits perfectly after a missed 3whs-ACK", ssn);
|
|
} else {
|
|
SCLogDebug("ssn %p: (TFO) expected packet fits perfectly after SYN/ACK", ssn);
|
|
}
|
|
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (TCP_GET_SEQ(p) + p->payload_len));
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED);
|
|
SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: wrong seq nr on packet", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_3WHS_WRONG_SEQ_WRONG_ACK);
|
|
return -1;
|
|
}
|
|
|
|
SCLogDebug("ssn %p: ssn->server.next_win %" PRIu32 ", "
|
|
"ssn->server.last_ack %"PRIu32"", ssn,
|
|
ssn->server.next_win, ssn->server.last_ack);
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_ESTABLISHED state packets, which are
|
|
* sent by the client to server. The function handles
|
|
* ACK packets and call StreamTcpReassembleHandleSegment() to handle
|
|
* the reassembly.
|
|
*
|
|
* Timestamp has already been checked at this point.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity etc.
|
|
* \param ssn Pointer to the current TCP session
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
static int HandleEstablishedPacketToServer(ThreadVars *tv, TcpSession *ssn, Packet *p,
|
|
StreamTcpThread *stt, PacketQueueNoLock *pq)
|
|
{
|
|
SCLogDebug("ssn %p: =+ pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ","
|
|
"ACK %" PRIu32 ", WIN %"PRIu16"", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p), TCP_GET_WINDOW(p));
|
|
|
|
if (StreamTcpValidateAck(ssn, &(ssn->server), p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_EST_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
/* check for Keep Alive */
|
|
if ((p->payload_len == 0 || p->payload_len == 1) &&
|
|
(TCP_GET_SEQ(p) == (ssn->client.next_seq - 1))) {
|
|
SCLogDebug("ssn %p: pkt is keep alive", ssn);
|
|
|
|
/* normal pkt */
|
|
} else if (!(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len), ssn->client.last_ack))) {
|
|
if (ssn->flags & STREAMTCP_FLAG_ASYNC) {
|
|
SCLogDebug("ssn %p: server => Asynchrouns stream, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
|
|
" ssn->client.last_ack %" PRIu32 ", ssn->client.next_win"
|
|
"%" PRIu32"(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->client.last_ack, ssn->client.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win);
|
|
|
|
/* update the last_ack to current seq number as the session is
|
|
* async and other stream is not updating it anymore :( */
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_SEQ(p));
|
|
|
|
} else if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p)) &&
|
|
(stream_config.async_oneside == TRUE) &&
|
|
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM)) {
|
|
SCLogDebug("ssn %p: server => Asynchronous stream, packet SEQ."
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
|
|
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
|
|
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->client.last_ack, ssn->client.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win);
|
|
|
|
/* it seems we missed SYN and SYN/ACK packets of this session.
|
|
* Update the last_ack to current seq number as the session
|
|
* is async and other stream is not updating it anymore :( */
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_SEQ(p));
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
|
|
} else if (SEQ_EQ(ssn->client.last_ack, (ssn->client.isn + 1)) &&
|
|
(stream_config.async_oneside == TRUE) &&
|
|
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM)) {
|
|
SCLogDebug("ssn %p: server => Asynchronous stream, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
|
|
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
|
|
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->client.last_ack, ssn->client.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win);
|
|
|
|
/* it seems we missed SYN and SYN/ACK packets of this session.
|
|
* Update the last_ack to current seq number as the session
|
|
* is async and other stream is not updating it anymore :(*/
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_SEQ(p));
|
|
ssn->flags |= STREAMTCP_FLAG_ASYNC;
|
|
|
|
/* if last ack is beyond next_seq, we have accepted ack's for missing data.
|
|
* In this case we do accept the data before last_ack if it is (partly)
|
|
* beyond next seq */
|
|
} else if (SEQ_GT(ssn->client.last_ack, ssn->client.next_seq) &&
|
|
SEQ_GT((TCP_GET_SEQ(p)+p->payload_len),ssn->client.next_seq))
|
|
{
|
|
SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16
|
|
" before last_ack %"PRIu32", after next_seq %"PRIu32":"
|
|
" acked data that we haven't seen before",
|
|
ssn, TCP_GET_SEQ(p), p->payload_len, ssn->client.last_ack, ssn->client.next_seq);
|
|
if (SEQ_EQ(TCP_GET_SEQ(p),ssn->client.next_seq)) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: server => SEQ before last_ack, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "), "
|
|
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
|
|
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->client.last_ack, ssn->client.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->client.next_win);
|
|
|
|
SCLogDebug("ssn %p: rejecting because pkt before last_ack", ssn);
|
|
StreamTcpSetEvent(p, STREAM_EST_PKT_BEFORE_LAST_ACK);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int zerowindowprobe = 0;
|
|
/* zero window probe */
|
|
if (p->payload_len == 1 && TCP_GET_SEQ(p) == ssn->client.next_seq && ssn->client.window == 0) {
|
|
SCLogDebug("ssn %p: zero window probe", ssn);
|
|
zerowindowprobe = 1;
|
|
|
|
/* expected packet */
|
|
} else if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
|
|
/* not completely as expected, but valid */
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p),ssn->client.next_seq) &&
|
|
SEQ_GT((TCP_GET_SEQ(p)+p->payload_len), ssn->client.next_seq))
|
|
{
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (TCP_GET_SEQ(p) + p->payload_len));
|
|
SCLogDebug("ssn %p: ssn->client.next_seq %"PRIu32
|
|
" (started before next_seq, ended after)",
|
|
ssn, ssn->client.next_seq);
|
|
|
|
/* if next_seq has fallen behind last_ack, we got some catching up to do */
|
|
} else if (SEQ_LT(ssn->client.next_seq, ssn->client.last_ack)) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (TCP_GET_SEQ(p) + p->payload_len));
|
|
SCLogDebug("ssn %p: ssn->client.next_seq %"PRIu32
|
|
" (next_seq had fallen behind last_ack)",
|
|
ssn, ssn->client.next_seq);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: no update to ssn->client.next_seq %"PRIu32
|
|
" SEQ %u SEQ+ %u last_ack %u",
|
|
ssn, ssn->client.next_seq,
|
|
TCP_GET_SEQ(p), TCP_GET_SEQ(p)+p->payload_len, ssn->client.last_ack);
|
|
}
|
|
|
|
/* in window check */
|
|
if (zerowindowprobe) {
|
|
SCLogDebug("ssn %p: zero window probe, skipping oow check", ssn);
|
|
} else if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->client.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->client.next_win);
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
SCLogDebug("ssn %p: ssn->server.window %"PRIu32"", ssn,
|
|
ssn->server.window);
|
|
|
|
/* Check if the ACK value is sane and inside the window limit */
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
SCLogDebug("ack %u last_ack %u next_seq %u", TCP_GET_ACK(p), ssn->server.last_ack, ssn->server.next_seq);
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->server, p);
|
|
|
|
/* update next_win */
|
|
StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window));
|
|
|
|
/* handle data (if any) */
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: toserver => SEQ out of window, packet SEQ "
|
|
"%" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
|
|
"ssn->client.last_ack %" PRIu32 ", ssn->client.next_win "
|
|
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->client.last_ack, ssn->client.next_win,
|
|
(TCP_GET_SEQ(p) + p->payload_len) - ssn->client.next_win);
|
|
SCLogDebug("ssn %p: window %u sacked %u", ssn, ssn->client.window,
|
|
StreamTcpSackedSize(&ssn->client));
|
|
StreamTcpSetEvent(p, STREAM_EST_PACKET_OUT_OF_WINDOW);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_ESTABLISHED state packets, which are
|
|
* sent by the server to client. The function handles
|
|
* ACK packets and call StreamTcpReassembleHandleSegment() to handle
|
|
* the reassembly
|
|
*
|
|
* Timestamp has already been checked at this point.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity etc.
|
|
* \param ssn Pointer to the current TCP session
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
static int HandleEstablishedPacketToClient(ThreadVars *tv, TcpSession *ssn, Packet *p,
|
|
StreamTcpThread *stt, PacketQueueNoLock *pq)
|
|
{
|
|
SCLogDebug("ssn %p: =+ pkt (%" PRIu32 ") is to client: SEQ %" PRIu32 ","
|
|
" ACK %" PRIu32 ", WIN %"PRIu16"", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p), TCP_GET_WINDOW(p));
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_EST_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
/* To get the server window value from the servers packet, when connection
|
|
is picked up as midstream */
|
|
if ((ssn->flags & STREAMTCP_FLAG_MIDSTREAM) &&
|
|
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED))
|
|
{
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
ssn->server.next_win = ssn->server.last_ack + ssn->server.window;
|
|
ssn->flags &= ~STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED;
|
|
SCLogDebug("ssn %p: adjusted midstream ssn->server.next_win to "
|
|
"%" PRIu32 "", ssn, ssn->server.next_win);
|
|
}
|
|
|
|
/* check for Keep Alive */
|
|
if ((p->payload_len == 0 || p->payload_len == 1) &&
|
|
(TCP_GET_SEQ(p) == (ssn->server.next_seq - 1))) {
|
|
SCLogDebug("ssn %p: pkt is keep alive", ssn);
|
|
|
|
/* normal pkt */
|
|
} else if (!(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len), ssn->server.last_ack))) {
|
|
if (ssn->flags & STREAMTCP_FLAG_ASYNC) {
|
|
|
|
SCLogDebug("ssn %p: client => Asynchrouns stream, packet SEQ"
|
|
" %" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
|
|
" ssn->client.last_ack %" PRIu32 ", ssn->client.next_win"
|
|
" %"PRIu32"(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->server.last_ack, ssn->server.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->server.next_win);
|
|
|
|
ssn->server.last_ack = TCP_GET_SEQ(p);
|
|
|
|
/* if last ack is beyond next_seq, we have accepted ack's for missing data.
|
|
* In this case we do accept the data before last_ack if it is (partly)
|
|
* beyond next seq */
|
|
} else if (SEQ_GT(ssn->server.last_ack, ssn->server.next_seq) &&
|
|
SEQ_GT((TCP_GET_SEQ(p)+p->payload_len),ssn->server.next_seq))
|
|
{
|
|
SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16
|
|
" before last_ack %"PRIu32", after next_seq %"PRIu32":"
|
|
" acked data that we haven't seen before",
|
|
ssn, TCP_GET_SEQ(p), p->payload_len, ssn->server.last_ack, ssn->server.next_seq);
|
|
if (SEQ_EQ(TCP_GET_SEQ(p),ssn->server.next_seq)) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: PKT SEQ %"PRIu32" payload_len %"PRIu16
|
|
" before last_ack %"PRIu32". next_seq %"PRIu32,
|
|
ssn, TCP_GET_SEQ(p), p->payload_len, ssn->server.last_ack, ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_EST_PKT_BEFORE_LAST_ACK);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int zerowindowprobe = 0;
|
|
/* zero window probe */
|
|
if (p->payload_len == 1 && TCP_GET_SEQ(p) == ssn->server.next_seq && ssn->server.window == 0) {
|
|
SCLogDebug("ssn %p: zero window probe", ssn);
|
|
zerowindowprobe = 1;
|
|
|
|
/* expected packet */
|
|
} else if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
|
|
/* not completely as expected, but valid */
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p),ssn->server.next_seq) &&
|
|
SEQ_GT((TCP_GET_SEQ(p)+p->payload_len), ssn->server.next_seq))
|
|
{
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (TCP_GET_SEQ(p) + p->payload_len));
|
|
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32
|
|
" (started before next_seq, ended after)",
|
|
ssn, ssn->server.next_seq);
|
|
|
|
/* if next_seq has fallen behind last_ack, we got some catching up to do */
|
|
} else if (SEQ_LT(ssn->server.next_seq, ssn->server.last_ack)) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (TCP_GET_SEQ(p) + p->payload_len));
|
|
SCLogDebug("ssn %p: ssn->server.next_seq %"PRIu32
|
|
" (next_seq had fallen behind last_ack)",
|
|
ssn, ssn->server.next_seq);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: no update to ssn->server.next_seq %"PRIu32
|
|
" SEQ %u SEQ+ %u last_ack %u",
|
|
ssn, ssn->server.next_seq,
|
|
TCP_GET_SEQ(p), TCP_GET_SEQ(p)+p->payload_len, ssn->server.last_ack);
|
|
}
|
|
|
|
if (zerowindowprobe) {
|
|
SCLogDebug("ssn %p: zero window probe, skipping oow check", ssn);
|
|
} else if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->server.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
SCLogDebug("ssn %p: ssn->client.window %"PRIu32"", ssn,
|
|
ssn->client.window);
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->client, p);
|
|
|
|
StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq);
|
|
} else {
|
|
SCLogDebug("ssn %p: client => SEQ out of window, packet SEQ"
|
|
"%" PRIu32 ", payload size %" PRIu32 " (%" PRIu32 "),"
|
|
" ssn->server.last_ack %" PRIu32 ", ssn->server.next_win "
|
|
"%" PRIu32 "(%"PRIu32")", ssn, TCP_GET_SEQ(p),
|
|
p->payload_len, TCP_GET_SEQ(p) + p->payload_len,
|
|
ssn->server.last_ack, ssn->server.next_win,
|
|
TCP_GET_SEQ(p) + p->payload_len - ssn->server.next_win);
|
|
StreamTcpSetEvent(p, STREAM_EST_PACKET_OUT_OF_WINDOW);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
*
|
|
* \brief Find the highest sequence number needed to consider all segments as ACK'd
|
|
*
|
|
* Used to treat all segments as ACK'd upon receiving a valid RST.
|
|
*
|
|
* \param stream stream to inspect the segments from
|
|
* \param seq sequence number to check against
|
|
*
|
|
* \retval ack highest ack we need to set
|
|
*/
|
|
static inline uint32_t StreamTcpResetGetMaxAck(TcpStream *stream, uint32_t seq)
|
|
{
|
|
uint32_t ack = seq;
|
|
|
|
if (STREAM_HAS_SEEN_DATA(stream)) {
|
|
const uint32_t tail_seq = STREAM_SEQ_RIGHT_EDGE(stream);
|
|
if (SEQ_GT(tail_seq, ack)) {
|
|
ack = tail_seq;
|
|
}
|
|
}
|
|
|
|
SCReturnUInt(ack);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_ESTABLISHED state. The function handles
|
|
* ACK, FIN, RST packets and correspondingly changes the connection
|
|
* state. The function handles the data inside packets and call
|
|
* StreamTcpReassembleHandleSegment(tv, ) to handle the reassembling.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity etc.
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateEstablished(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
|
|
ssn->server.next_seq);
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
|
|
/* don't return packets to pools here just yet, the pseudo
|
|
* packet will take care, otherwise the normal session
|
|
* cleanup. */
|
|
} else {
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len + 1;
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
|
|
ssn->server.next_seq);
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
|
|
/* don't return packets to pools here just yet, the pseudo
|
|
* packet will take care, otherwise the normal session
|
|
* cleanup. */
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
SCLogDebug("ssn (%p: FIN received SEQ"
|
|
" %" PRIu32 ", last ACK %" PRIu32 ", next win %"PRIu32","
|
|
" win %" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack, ssn->server.next_win,
|
|
ssn->server.window);
|
|
|
|
if ((StreamTcpHandleFin(tv, stt, ssn, p, pq)) == -1)
|
|
return -1;
|
|
|
|
/* SYN/ACK */
|
|
} else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
|
|
SCLogDebug("ssn %p: SYN/ACK packet on state ESTABLISHED... resent",
|
|
ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: SYN/ACK-pkt to server in ESTABLISHED state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_EST_SYNACK_TOSERVER);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the SYN/ACK packets ACK matches the earlier
|
|
* received SYN/ACK packet. */
|
|
if (!(SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack))) {
|
|
SCLogDebug("ssn %p: ACK mismatch, packet ACK %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.isn + 1);
|
|
|
|
StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND_WITH_DIFFERENT_ACK);
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the SYN/ACK packet SEQ the earlier
|
|
* received SYN packet. */
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->server.isn))) {
|
|
SCLogDebug("ssn %p: SEQ mismatch, packet SEQ %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_ACK(p),
|
|
ssn->client.isn + 1);
|
|
|
|
StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND_WITH_DIFF_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_3WHS_CONFIRMED) {
|
|
/* a resend of a SYN while we are established already -- fishy */
|
|
StreamTcpSetEvent(p, STREAM_EST_SYNACK_RESEND);
|
|
return -1;
|
|
}
|
|
|
|
SCLogDebug("ssn %p: SYN/ACK packet on state ESTABLISHED... resent. "
|
|
"Likely due server not receiving final ACK in 3whs", ssn);
|
|
return 0;
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn %p: SYN packet on state ESTABLISHED... resent", ssn);
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
SCLogDebug("ssn %p: SYN-pkt to client in EST state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_EST_SYN_TOCLIENT);
|
|
return -1;
|
|
}
|
|
|
|
if (!(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.isn))) {
|
|
SCLogDebug("ssn %p: SYN with different SEQ on SYN_RECV state", ssn);
|
|
|
|
StreamTcpSetEvent(p, STREAM_EST_SYN_RESEND_DIFF_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
/* a resend of a SYN while we are established already -- fishy */
|
|
StreamTcpSetEvent(p, STREAM_EST_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
/* Urgent pointer size can be more than the payload size, as it tells
|
|
* the future coming data from the sender will be handled urgently
|
|
* until data of size equal to urgent offset has been processed
|
|
* (RFC 2147) */
|
|
|
|
/* If the timestamp option is enabled for both the streams, then
|
|
* validate the received packet timestamp value against the
|
|
* stream->last_ts. If the timestamp is valid then process the
|
|
* packet normally otherwise the drop the packet (RFC 1323) */
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
/* Process the received packet to server */
|
|
HandleEstablishedPacketToServer(tv, ssn, p, stt, pq);
|
|
|
|
SCLogDebug("ssn %p: next SEQ %" PRIu32 ", last ACK %" PRIu32 ","
|
|
" next win %" PRIu32 ", win %" PRIu32 "", ssn,
|
|
ssn->client.next_seq, ssn->server.last_ack
|
|
,ssn->client.next_win, ssn->client.window);
|
|
|
|
} else { /* implied to client */
|
|
if (!(ssn->flags & STREAMTCP_FLAG_3WHS_CONFIRMED)) {
|
|
ssn->flags |= STREAMTCP_FLAG_3WHS_CONFIRMED;
|
|
SCLogDebug("3whs is now confirmed by server");
|
|
}
|
|
|
|
/* Process the received packet to client */
|
|
HandleEstablishedPacketToClient(tv, ssn, p, stt, pq);
|
|
|
|
SCLogDebug("ssn %p: next SEQ %" PRIu32 ", last ACK %" PRIu32 ","
|
|
" next win %" PRIu32 ", win %" PRIu32 "", ssn,
|
|
ssn->server.next_seq, ssn->client.last_ack,
|
|
ssn->server.next_win, ssn->server.window);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the FIN packets for states TCP_SYN_RECV and
|
|
* TCP_ESTABLISHED and changes to another TCP state as required.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*
|
|
* \retval 0 success
|
|
* \retval -1 something wrong with the packet
|
|
*/
|
|
|
|
static int StreamTcpHandleFin(ThreadVars *tv, StreamTcpThread *stt,
|
|
TcpSession *ssn, Packet *p, PacketQueueNoLock *pq)
|
|
{
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ %" PRIu32 ","
|
|
" ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
|
|
TCP_GET_ACK(p));
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 " != "
|
|
"%" PRIu32 " from stream", ssn, TCP_GET_SEQ(p),
|
|
ssn->client.next_seq);
|
|
|
|
StreamTcpSetEvent(p, STREAM_FIN_OUT_OF_WINDOW);
|
|
return -1;
|
|
}
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSE_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSE_WAIT", ssn);
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq))
|
|
ssn->client.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
|
|
SCLogDebug("ssn %p: ssn->client.next_seq %" PRIu32 "", ssn,
|
|
ssn->client.next_seq);
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client packet
|
|
and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK %" PRIu32 "",
|
|
ssn, ssn->client.next_seq, ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ %" PRIu32 ", "
|
|
"ACK %" PRIu32 "", ssn, p->payload_len, TCP_GET_SEQ(p),
|
|
TCP_GET_ACK(p));
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 " != "
|
|
"%" PRIu32 " from stream (last_ack %u win %u = %u)", ssn, TCP_GET_SEQ(p),
|
|
ssn->server.next_seq, ssn->server.last_ack, ssn->server.window, (ssn->server.last_ack + ssn->server.window));
|
|
|
|
StreamTcpSetEvent(p, STREAM_FIN_OUT_OF_WINDOW);
|
|
return -1;
|
|
}
|
|
|
|
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT1);
|
|
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT1", ssn);
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq))
|
|
ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len;
|
|
|
|
SCLogDebug("ssn %p: ssn->server.next_seq %" PRIu32 "", ssn,
|
|
ssn->server.next_seq);
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client packet
|
|
and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK %" PRIu32 "",
|
|
ssn, ssn->server.next_seq, ssn->client.last_ack);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_FIN_WAIT1 state. The function handles
|
|
* ACK, FIN, RST packets and correspondingly changes the connection
|
|
* state.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*
|
|
* \retval 0 success
|
|
* \retval -1 something wrong with the packet
|
|
*/
|
|
|
|
static int StreamTcpPacketStateFinWait1(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if ((p->tcph->th_flags & (TH_FIN|TH_ACK)) == (TH_FIN|TH_ACK)) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
} else if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p)) &&
|
|
SEQ_EQ(ssn->client.last_ack, TCP_GET_ACK(p))) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window))) {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSING);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSING", ssn);
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSING);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSING", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on FinWait1", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->client.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->client.next_win);
|
|
|
|
if (TCP_GET_SEQ(p) == ssn->client.next_seq) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2);
|
|
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
|
|
StreamTcpSetEvent(p, STREAM_FIN1_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->server, p);
|
|
|
|
/* update next_win */
|
|
StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
|
|
} else { /* implied to client */
|
|
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->server.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
|
|
|
|
if (TCP_GET_SEQ(p) == ssn->server.next_seq) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_FIN_WAIT2);
|
|
SCLogDebug("ssn %p: state changed to TCP_FIN_WAIT2", ssn);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN1_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->client, p);
|
|
|
|
/* update next_win */
|
|
StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn (%p): default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_FIN_WAIT2 state. The function handles
|
|
* ACK, RST, FIN packets and correspondingly changes the connection
|
|
* state.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateFinWait2(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq - 1) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->server.last_ack)) {
|
|
SCLogDebug("ssn %p: retransmission", ssn);
|
|
retransmission = 1;
|
|
} else if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ "
|
|
"%" PRIu32 " != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(
|
|
ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq - 1) &&
|
|
SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack)) {
|
|
SCLogDebug("ssn %p: retransmission", ssn);
|
|
retransmission = 1;
|
|
} else if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ "
|
|
"%" PRIu32 " != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_FIN_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on FinWait2", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->client.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->client.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->client.next_win);
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->server, p);
|
|
|
|
/* update next_win */
|
|
StreamTcpUpdateNextWin(ssn, &ssn->server, (ssn->server.last_ack + ssn->server.window));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LEQ(TCP_GET_SEQ(p) + p->payload_len, ssn->server.next_win) ||
|
|
(ssn->flags & (STREAMTCP_FLAG_MIDSTREAM|STREAMTCP_FLAG_ASYNC)))
|
|
{
|
|
SCLogDebug("ssn %p: seq %"PRIu32" in window, ssn->server.next_win "
|
|
"%" PRIu32 "", ssn, TCP_GET_SEQ(p), ssn->server.next_win);
|
|
} else {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_FIN2_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p))) {
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
}
|
|
|
|
StreamTcpSackUpdatePacket(&ssn->client, p);
|
|
|
|
/* update next_win */
|
|
StreamTcpUpdateNextWin(ssn, &ssn->client, (ssn->client.last_ack + ssn->client.window));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_CLOSING state. Upon arrival of ACK
|
|
* the connection goes to TCP_TIME_WAIT state. The state has been
|
|
* reached as both end application has been closed.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateClosing(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on Closing", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (TCP_GET_SEQ(p) != ssn->client.next_seq) {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSING_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSING_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else { /* implied to client */
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (TCP_GET_SEQ(p) != ssn->server.next_seq) {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSING_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSING_INVALID_ACK);
|
|
return -1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT);
|
|
SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("StreamTcpPacketStateClosing (%p): =+ next SEQ "
|
|
"%" PRIu32 ", last ACK %" PRIu32 "", ssn,
|
|
ssn->server.next_seq, ssn->client.last_ack);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_CLOSE_WAIT state. Upon arrival of FIN
|
|
* packet from server the connection goes to TCP_LAST_ACK state.
|
|
* The state is possible only for server host.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateCloseWait(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
SCEnter();
|
|
|
|
if (ssn == NULL) {
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
} else {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
}
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_FIN_OUT_OF_WINDOW);
|
|
SCReturnInt(-1);
|
|
}
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
/* don't update to LAST_ACK here as we want a toclient FIN for that */
|
|
|
|
if (!retransmission)
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) ||
|
|
SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_FIN_OUT_OF_WINDOW);
|
|
SCReturnInt(-1);
|
|
}
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_LAST_ACK);
|
|
SCLogDebug("ssn %p: state changed to TCP_LAST_ACK", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on CloseWait", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
SCReturnInt(-1);
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (p->payload_len > 0 && (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), ssn->client.last_ack))) {
|
|
SCLogDebug("ssn %p: -> retransmission", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_PKT_BEFORE_LAST_ACK);
|
|
SCReturnInt(-1);
|
|
|
|
} else if (SEQ_GT(TCP_GET_SEQ(p), (ssn->client.last_ack + ssn->client.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_ACK_OUT_OF_WINDOW);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p),ssn->client.next_seq))
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->client, (ssn->client.next_seq + p->payload_len));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (p->payload_len > 0 && (SEQ_LEQ((TCP_GET_SEQ(p) + p->payload_len), ssn->server.last_ack))) {
|
|
SCLogDebug("ssn %p: -> retransmission", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_PKT_BEFORE_LAST_ACK);
|
|
SCReturnInt(-1);
|
|
|
|
} else if (SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window)))
|
|
{
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_ACK_OUT_OF_WINDOW);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_CLOSEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
if (SEQ_EQ(TCP_GET_SEQ(p),ssn->server.next_seq))
|
|
StreamTcpUpdateNextSeq(ssn, &ssn->server, (ssn->server.next_seq + p->payload_len));
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_LAST_ACK state. Upon arrival of ACK
|
|
* the connection goes to TCP_CLOSED state and stream memory is
|
|
* returned back to pool. The state is possible only for server host.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateLastAck(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
/** \todo */
|
|
SCLogDebug("ssn (%p): FIN pkt on LastAck", ssn);
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on LastAck", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_LASTACK_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
if (SEQ_LT(TCP_GET_SEQ(p), ssn->client.next_seq)) {
|
|
SCLogDebug("ssn %p: not updating state as packet is before next_seq", ssn);
|
|
} else if (TCP_GET_SEQ(p) != ssn->client.next_seq && TCP_GET_SEQ(p) != ssn->client.next_seq + 1) {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_LASTACK_ACK_WRONG_SEQ);
|
|
return -1;
|
|
} else {
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
|
|
|
|
}
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
}
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to handle the TCP_TIME_WAIT state. Upon arrival of ACK
|
|
* the connection goes to TCP_CLOSED state and stream memory is
|
|
* returned back to pool.
|
|
*
|
|
* \param tv Thread Variable containig input/output queue, cpu affinity
|
|
* \param p Packet which has to be handled in this TCP state.
|
|
* \param stt Strean Thread module registered to handle the stream handling
|
|
*/
|
|
|
|
static int StreamTcpPacketStateTimeWait(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
if (!StreamTcpValidateRst(ssn, p))
|
|
return -1;
|
|
|
|
StreamTcpCloseSsnWithReset(p, ssn);
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->server, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
} else {
|
|
if ((p->tcph->th_flags & TH_ACK) && StreamTcpValidateAck(ssn, &ssn->client, p) == 0)
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client,
|
|
StreamTcpResetGetMaxAck(&ssn->client, TCP_GET_ACK(p)));
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server,
|
|
StreamTcpResetGetMaxAck(&ssn->server, TCP_GET_SEQ(p)));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
}
|
|
|
|
} else if (p->tcph->th_flags & TH_FIN) {
|
|
/** \todo */
|
|
|
|
} else if (p->tcph->th_flags & TH_SYN) {
|
|
SCLogDebug("ssn (%p): SYN pkt on TimeWait", ssn);
|
|
StreamTcpSetEvent(p, STREAM_SHUTDOWN_SYN_RESEND);
|
|
return -1;
|
|
|
|
} else if (p->tcph->th_flags & TH_ACK) {
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p))
|
|
return -1;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to server: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->client, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
|
|
} else if (TCP_GET_SEQ(p) != ssn->client.next_seq && TCP_GET_SEQ(p) != ssn->client.next_seq+1) {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->client.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_TIMEWAIT_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_TIMEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
|
|
|
|
ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->server, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->server.next_seq, TCP_GET_ACK(p)))
|
|
ssn->server.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->client.next_seq,
|
|
ssn->server.last_ack);
|
|
} else {
|
|
SCLogDebug("ssn %p: pkt (%" PRIu32 ") is to client: SEQ "
|
|
"%" PRIu32 ", ACK %" PRIu32 "", ssn, p->payload_len,
|
|
TCP_GET_SEQ(p), TCP_GET_ACK(p));
|
|
int retransmission = 0;
|
|
if (StreamTcpPacketIsRetransmission(&ssn->server, p)) {
|
|
SCLogDebug("ssn %p: packet is retransmission", ssn);
|
|
retransmission = 1;
|
|
} else if (TCP_GET_SEQ(p) != ssn->server.next_seq && TCP_GET_SEQ(p) != ssn->server.next_seq+1) {
|
|
if (p->payload_len > 0 && TCP_GET_SEQ(p) == ssn->server.last_ack) {
|
|
SCLogDebug("ssn %p: -> retransmission", ssn);
|
|
SCReturnInt(0);
|
|
} else {
|
|
SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 ""
|
|
" != %" PRIu32 " from stream", ssn,
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
StreamTcpSetEvent(p, STREAM_TIMEWAIT_ACK_WRONG_SEQ);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_TIMEWAIT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
if (!retransmission) {
|
|
StreamTcpPacketSetState(p, ssn, TCP_CLOSED);
|
|
SCLogDebug("ssn %p: state changed to TCP_CLOSED", ssn);
|
|
|
|
ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale;
|
|
}
|
|
|
|
StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p));
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
StreamTcpHandleTimestamp(ssn, p);
|
|
}
|
|
|
|
/* Update the next_seq, in case if we have missed the client
|
|
packet and server has already received and acked it */
|
|
if (SEQ_LT(ssn->client.next_seq, TCP_GET_ACK(p)))
|
|
ssn->client.next_seq = TCP_GET_ACK(p);
|
|
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
SCLogDebug("ssn %p: =+ next SEQ %" PRIu32 ", last ACK "
|
|
"%" PRIu32 "", ssn, ssn->server.next_seq,
|
|
ssn->client.last_ack);
|
|
}
|
|
|
|
} else {
|
|
SCLogDebug("ssn %p: default case", ssn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int StreamTcpPacketStateClosed(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq)
|
|
{
|
|
if (ssn == NULL)
|
|
return -1;
|
|
|
|
if (p->tcph->th_flags & TH_RST) {
|
|
SCLogDebug("RST on closed state");
|
|
return 0;
|
|
}
|
|
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
SCLogDebug("stream %s ostream %s",
|
|
stream->flags & STREAMTCP_STREAM_FLAG_RST_RECV?"true":"false",
|
|
ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV ? "true":"false");
|
|
|
|
/* if we've seen a RST on our direction, but not on the other
|
|
* see if we perhaps need to continue processing anyway. */
|
|
if ((stream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) == 0) {
|
|
if (ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) {
|
|
if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->pstate) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void StreamTcpPacketCheckPostRst(TcpSession *ssn, Packet *p)
|
|
{
|
|
if (p->flags & PKT_PSEUDO_STREAM_END) {
|
|
return;
|
|
}
|
|
/* more RSTs are not unusual */
|
|
if ((p->tcph->th_flags & (TH_RST)) != 0) {
|
|
return;
|
|
}
|
|
|
|
TcpStream *ostream = NULL;
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
ostream = &ssn->server;
|
|
} else {
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
if (ostream->flags & STREAMTCP_STREAM_FLAG_RST_RECV) {
|
|
SCLogDebug("regular packet %"PRIu64" from same sender as "
|
|
"the previous RST. Looks like it injected!", p->pcap_cnt);
|
|
ostream->flags &= ~STREAMTCP_STREAM_FLAG_RST_RECV;
|
|
ssn->flags &= ~STREAMTCP_FLAG_CLOSED_BY_RST;
|
|
StreamTcpSetEvent(p, STREAM_SUSPECTED_RST_INJECT);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* \retval 1 packet is a keep alive pkt
|
|
* \retval 0 packet is not a keep alive pkt
|
|
*/
|
|
static int StreamTcpPacketIsKeepAlive(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
uint32_t seq;
|
|
uint32_t ack;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return 0;
|
|
|
|
/*
|
|
rfc 1122:
|
|
An implementation SHOULD send a keep-alive segment with no
|
|
data; however, it MAY be configurable to send a keep-alive
|
|
segment containing one garbage octet, for compatibility with
|
|
erroneous TCP implementations.
|
|
*/
|
|
if (p->payload_len > 1)
|
|
return 0;
|
|
|
|
if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
seq = TCP_GET_SEQ(p);
|
|
ack = TCP_GET_ACK(p);
|
|
|
|
if (ack == ostream->last_ack && seq == (stream->next_seq - 1)) {
|
|
SCLogDebug("packet is TCP keep-alive: %"PRIu64, p->pcap_cnt);
|
|
stream->flags |= STREAMTCP_STREAM_FLAG_KEEPALIVE;
|
|
return 1;
|
|
}
|
|
SCLogDebug("seq %u (%u), ack %u (%u)", seq, (stream->next_seq - 1), ack, ostream->last_ack);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \retval 1 packet is a keep alive ACK pkt
|
|
* \retval 0 packet is not a keep alive ACK pkt
|
|
*/
|
|
static int StreamTcpPacketIsKeepAliveACK(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
uint32_t seq;
|
|
uint32_t ack;
|
|
uint32_t pkt_win;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return 0;
|
|
/* should get a normal ACK to a Keep Alive */
|
|
if (p->payload_len > 0)
|
|
return 0;
|
|
|
|
if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0)
|
|
return 0;
|
|
|
|
if (TCP_GET_WINDOW(p) == 0)
|
|
return 0;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
seq = TCP_GET_SEQ(p);
|
|
ack = TCP_GET_ACK(p);
|
|
|
|
pkt_win = TCP_GET_WINDOW(p) << ostream->wscale;
|
|
if (pkt_win != ostream->window)
|
|
return 0;
|
|
|
|
if ((ostream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE) && ack == ostream->last_ack && seq == stream->next_seq) {
|
|
SCLogDebug("packet is TCP keep-aliveACK: %"PRIu64, p->pcap_cnt);
|
|
ostream->flags &= ~STREAMTCP_STREAM_FLAG_KEEPALIVE;
|
|
return 1;
|
|
}
|
|
SCLogDebug("seq %u (%u), ack %u (%u) FLAG_KEEPALIVE: %s", seq, stream->next_seq, ack, ostream->last_ack,
|
|
ostream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE ? "set" : "not set");
|
|
return 0;
|
|
}
|
|
|
|
static void StreamTcpClearKeepAliveFlag(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
} else {
|
|
stream = &ssn->server;
|
|
}
|
|
|
|
if (stream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE) {
|
|
stream->flags &= ~STREAMTCP_STREAM_FLAG_KEEPALIVE;
|
|
SCLogDebug("FLAG_KEEPALIVE cleared");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \retval 1 packet is a window update pkt
|
|
* \retval 0 packet is not a window update pkt
|
|
*/
|
|
static int StreamTcpPacketIsWindowUpdate(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
uint32_t seq;
|
|
uint32_t ack;
|
|
uint32_t pkt_win;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return 0;
|
|
|
|
if (ssn->state < TCP_ESTABLISHED)
|
|
return 0;
|
|
|
|
if (p->payload_len > 0)
|
|
return 0;
|
|
|
|
if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0)
|
|
return 0;
|
|
|
|
if (TCP_GET_WINDOW(p) == 0)
|
|
return 0;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
seq = TCP_GET_SEQ(p);
|
|
ack = TCP_GET_ACK(p);
|
|
|
|
pkt_win = TCP_GET_WINDOW(p) << ostream->wscale;
|
|
if (pkt_win == ostream->window)
|
|
return 0;
|
|
|
|
if (ack == ostream->last_ack && seq == stream->next_seq) {
|
|
SCLogDebug("packet is TCP window update: %"PRIu64, p->pcap_cnt);
|
|
return 1;
|
|
}
|
|
SCLogDebug("seq %u (%u), ack %u (%u)", seq, stream->next_seq, ack, ostream->last_ack);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Try to detect whether a packet is a valid FIN 4whs final ack.
|
|
*
|
|
*/
|
|
static int StreamTcpPacketIsFinShutdownAck(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
uint32_t seq;
|
|
uint32_t ack;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return 0;
|
|
if (!(ssn->state == TCP_TIME_WAIT || ssn->state == TCP_CLOSE_WAIT || ssn->state == TCP_LAST_ACK))
|
|
return 0;
|
|
if (p->tcph->th_flags != TH_ACK)
|
|
return 0;
|
|
if (p->payload_len != 0)
|
|
return 0;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
seq = TCP_GET_SEQ(p);
|
|
ack = TCP_GET_ACK(p);
|
|
|
|
SCLogDebug("%"PRIu64", seq %u ack %u stream->next_seq %u ostream->next_seq %u",
|
|
p->pcap_cnt, seq, ack, stream->next_seq, ostream->next_seq);
|
|
|
|
if (SEQ_EQ(stream->next_seq + 1, seq) && SEQ_EQ(ack, ostream->next_seq + 1)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Try to detect packets doing bad window updates
|
|
*
|
|
* See bug 1238.
|
|
*
|
|
* Find packets that are unexpected, and shrink the window to the point
|
|
* where the packets we do expect are rejected for being out of window.
|
|
*
|
|
* The logic we use here is:
|
|
* - packet seq > next_seq
|
|
* - packet ack > next_seq (packet acks unseen data)
|
|
* - packet shrinks window more than it's own data size
|
|
* - packet shrinks window more than the diff between it's ack and the
|
|
* last_ack value
|
|
*
|
|
* Packets coming in after packet loss can look quite a bit like this.
|
|
*/
|
|
static int StreamTcpPacketIsBadWindowUpdate(TcpSession *ssn, Packet *p)
|
|
{
|
|
TcpStream *stream = NULL, *ostream = NULL;
|
|
uint32_t seq;
|
|
uint32_t ack;
|
|
uint32_t pkt_win;
|
|
|
|
if (p->flags & PKT_PSEUDO_STREAM_END)
|
|
return 0;
|
|
|
|
if (ssn->state < TCP_ESTABLISHED || ssn->state == TCP_CLOSED)
|
|
return 0;
|
|
|
|
if ((p->tcph->th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0)
|
|
return 0;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
stream = &ssn->client;
|
|
ostream = &ssn->server;
|
|
} else {
|
|
stream = &ssn->server;
|
|
ostream = &ssn->client;
|
|
}
|
|
|
|
seq = TCP_GET_SEQ(p);
|
|
ack = TCP_GET_ACK(p);
|
|
|
|
pkt_win = TCP_GET_WINDOW(p) << ostream->wscale;
|
|
|
|
if (pkt_win < ostream->window) {
|
|
uint32_t diff = ostream->window - pkt_win;
|
|
if (diff > p->payload_len &&
|
|
SEQ_GT(ack, ostream->next_seq) &&
|
|
SEQ_GT(seq, stream->next_seq))
|
|
{
|
|
SCLogDebug("%"PRIu64", pkt_win %u, stream win %u, diff %u, dsize %u",
|
|
p->pcap_cnt, pkt_win, ostream->window, diff, p->payload_len);
|
|
SCLogDebug("%"PRIu64", pkt_win %u, stream win %u",
|
|
p->pcap_cnt, pkt_win, ostream->window);
|
|
SCLogDebug("%"PRIu64", seq %u ack %u ostream->next_seq %u ostream->last_ack %u, ostream->next_win %u, diff %u (%u)",
|
|
p->pcap_cnt, seq, ack, ostream->next_seq, ostream->last_ack, ostream->next_win,
|
|
ostream->next_seq - ostream->last_ack, stream->next_seq - stream->last_ack);
|
|
|
|
/* get the expected window shrinking from looking at ack vs last_ack.
|
|
* Observed a lot of just a little overrunning that value. So added some
|
|
* margin that is still ok. To make sure this isn't a loophole to still
|
|
* close the window, this is limited to windows above 1024. Both values
|
|
* are rather arbitrary. */
|
|
uint32_t adiff = ack - ostream->last_ack;
|
|
if (((pkt_win > 1024) && (diff > (adiff + 32))) ||
|
|
((pkt_win <= 1024) && (diff > adiff)))
|
|
{
|
|
SCLogDebug("pkt ACK %u is %u bytes beyond last_ack %u, shrinks window by %u "
|
|
"(allowing 32 bytes extra): pkt WIN %u", ack, adiff, ostream->last_ack, diff, pkt_win);
|
|
SCLogDebug("%u - %u = %u (state %u)", diff, adiff, diff - adiff, ssn->state);
|
|
StreamTcpSetEvent(p, STREAM_PKT_BAD_WINDOW_UPDATE);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
SCLogDebug("seq %u (%u), ack %u (%u)", seq, stream->next_seq, ack, ostream->last_ack);
|
|
return 0;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief call packet handling function for 'state'
|
|
* \param state current TCP state
|
|
*/
|
|
static inline int StreamTcpStateDispatch(ThreadVars *tv, Packet *p,
|
|
StreamTcpThread *stt, TcpSession *ssn, PacketQueueNoLock *pq,
|
|
const uint8_t state)
|
|
{
|
|
SCLogDebug("ssn: %p", ssn);
|
|
switch (state) {
|
|
case TCP_SYN_SENT:
|
|
if (StreamTcpPacketStateSynSent(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_SYN_RECV:
|
|
if (StreamTcpPacketStateSynRecv(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_ESTABLISHED:
|
|
if (StreamTcpPacketStateEstablished(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_FIN_WAIT1:
|
|
SCLogDebug("packet received on TCP_FIN_WAIT1 state");
|
|
if (StreamTcpPacketStateFinWait1(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_FIN_WAIT2:
|
|
SCLogDebug("packet received on TCP_FIN_WAIT2 state");
|
|
if (StreamTcpPacketStateFinWait2(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_CLOSING:
|
|
SCLogDebug("packet received on TCP_CLOSING state");
|
|
if (StreamTcpPacketStateClosing(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_CLOSE_WAIT:
|
|
SCLogDebug("packet received on TCP_CLOSE_WAIT state");
|
|
if (StreamTcpPacketStateCloseWait(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_LAST_ACK:
|
|
SCLogDebug("packet received on TCP_LAST_ACK state");
|
|
if (StreamTcpPacketStateLastAck(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_TIME_WAIT:
|
|
SCLogDebug("packet received on TCP_TIME_WAIT state");
|
|
if (StreamTcpPacketStateTimeWait(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case TCP_CLOSED:
|
|
/* TCP session memory is not returned to pool until timeout. */
|
|
SCLogDebug("packet received on closed state");
|
|
|
|
if (StreamTcpPacketStateClosed(tv, p, stt, ssn, pq)) {
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
SCLogDebug("packet received on default state");
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline void HandleThreadId(ThreadVars *tv, Packet *p, StreamTcpThread *stt)
|
|
{
|
|
const int idx = (!(PKT_IS_TOSERVER(p)));
|
|
|
|
/* assign the thread id to the flow */
|
|
if (unlikely(p->flow->thread_id[idx] == 0)) {
|
|
p->flow->thread_id[idx] = (FlowThreadId)tv->id;
|
|
} else if (unlikely((FlowThreadId)tv->id != p->flow->thread_id[idx])) {
|
|
SCLogDebug("wrong thread: flow has %u, we are %d", p->flow->thread_id[idx], tv->id);
|
|
if (p->pkt_src == PKT_SRC_WIRE) {
|
|
StatsIncr(tv, stt->counter_tcp_wrong_thread);
|
|
if ((p->flow->flags & FLOW_WRONG_THREAD) == 0) {
|
|
p->flow->flags |= FLOW_WRONG_THREAD;
|
|
StreamTcpSetEvent(p, STREAM_WRONG_THREAD);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* flow is and stays locked */
|
|
int StreamTcpPacket (ThreadVars *tv, Packet *p, StreamTcpThread *stt,
|
|
PacketQueueNoLock *pq)
|
|
{
|
|
SCEnter();
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(p->flow);
|
|
|
|
SCLogDebug("p->pcap_cnt %"PRIu64, p->pcap_cnt);
|
|
|
|
HandleThreadId(tv, p, stt);
|
|
|
|
TcpSession *ssn = (TcpSession *)p->flow->protoctx;
|
|
|
|
/* track TCP flags */
|
|
if (ssn != NULL) {
|
|
ssn->tcp_packet_flags |= p->tcph->th_flags;
|
|
if (PKT_IS_TOSERVER(p))
|
|
ssn->client.tcp_flags |= p->tcph->th_flags;
|
|
else if (PKT_IS_TOCLIENT(p))
|
|
ssn->server.tcp_flags |= p->tcph->th_flags;
|
|
|
|
/* check if we need to unset the ASYNC flag */
|
|
if (ssn->flags & STREAMTCP_FLAG_ASYNC &&
|
|
ssn->client.tcp_flags != 0 &&
|
|
ssn->server.tcp_flags != 0)
|
|
{
|
|
SCLogDebug("ssn %p: removing ASYNC flag as we have packets on both sides", ssn);
|
|
ssn->flags &= ~STREAMTCP_FLAG_ASYNC;
|
|
}
|
|
}
|
|
|
|
/* update counters */
|
|
if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
|
|
StatsIncr(tv, stt->counter_tcp_synack);
|
|
} else if (p->tcph->th_flags & (TH_SYN)) {
|
|
StatsIncr(tv, stt->counter_tcp_syn);
|
|
}
|
|
if (p->tcph->th_flags & (TH_RST)) {
|
|
StatsIncr(tv, stt->counter_tcp_rst);
|
|
}
|
|
|
|
/* broken TCP http://ask.wireshark.org/questions/3183/acknowledgment-number-broken-tcp-the-acknowledge-field-is-nonzero-while-the-ack-flag-is-not-set */
|
|
if (!(p->tcph->th_flags & TH_ACK) && TCP_GET_ACK(p) != 0) {
|
|
StreamTcpSetEvent(p, STREAM_PKT_BROKEN_ACK);
|
|
goto error;
|
|
}
|
|
|
|
/* If we are on IPS mode, and got a drop action triggered from
|
|
* the IP only module, or from a reassembled msg and/or from an
|
|
* applayer detection, then drop the rest of the packets of the
|
|
* same stream and avoid inspecting it any further */
|
|
if (StreamTcpCheckFlowDrops(p) == 1) {
|
|
SCLogDebug("This flow/stream triggered a drop rule");
|
|
FlowSetNoPacketInspectionFlag(p->flow);
|
|
DecodeSetNoPacketInspectionFlag(p);
|
|
StreamTcpDisableAppLayer(p->flow);
|
|
PACKET_DROP(p);
|
|
/* return the segments to the pool */
|
|
StreamTcpSessionPktFree(p);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
if (ssn == NULL || ssn->state == TCP_NONE) {
|
|
if (StreamTcpPacketStateNone(tv, p, stt, ssn, &stt->pseudo_queue) == -1) {
|
|
goto error;
|
|
}
|
|
|
|
if (ssn != NULL)
|
|
SCLogDebug("ssn->alproto %"PRIu16"", p->flow->alproto);
|
|
} else {
|
|
/* special case for PKT_PSEUDO_STREAM_END packets:
|
|
* bypass the state handling and various packet checks,
|
|
* we care about reassembly here. */
|
|
if (p->flags & PKT_PSEUDO_STREAM_END) {
|
|
if (PKT_IS_TOCLIENT(p)) {
|
|
ssn->client.last_ack = TCP_GET_ACK(p);
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, p, pq);
|
|
} else {
|
|
ssn->server.last_ack = TCP_GET_ACK(p);
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, p, pq);
|
|
}
|
|
/* straight to 'skip' as we already handled reassembly */
|
|
goto skip;
|
|
}
|
|
|
|
if (p->flow->flags & FLOW_WRONG_THREAD) {
|
|
/* Stream and/or session in known bad condition. Block events
|
|
* from being set. */
|
|
p->flags |= PKT_STREAM_NO_EVENTS;
|
|
}
|
|
|
|
if (StreamTcpPacketIsKeepAlive(ssn, p) == 1) {
|
|
goto skip;
|
|
}
|
|
if (StreamTcpPacketIsKeepAliveACK(ssn, p) == 1) {
|
|
StreamTcpClearKeepAliveFlag(ssn, p);
|
|
goto skip;
|
|
}
|
|
StreamTcpClearKeepAliveFlag(ssn, p);
|
|
|
|
/* if packet is not a valid window update, check if it is perhaps
|
|
* a bad window update that we should ignore (and alert on) */
|
|
if (StreamTcpPacketIsFinShutdownAck(ssn, p) == 0)
|
|
if (StreamTcpPacketIsWindowUpdate(ssn, p) == 0)
|
|
if (StreamTcpPacketIsBadWindowUpdate(ssn,p))
|
|
goto skip;
|
|
|
|
/* handle the per 'state' logic */
|
|
if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->state) < 0)
|
|
goto error;
|
|
|
|
skip:
|
|
StreamTcpPacketCheckPostRst(ssn, p);
|
|
|
|
if (ssn->state >= TCP_ESTABLISHED) {
|
|
p->flags |= PKT_STREAM_EST;
|
|
}
|
|
}
|
|
|
|
/* deal with a pseudo packet that is created upon receiving a RST
|
|
* segment. To be sure we process both sides of the connection, we
|
|
* inject a fake packet into the system, forcing reassembly of the
|
|
* opposing direction.
|
|
* There should be only one, but to be sure we do a while loop. */
|
|
if (ssn != NULL) {
|
|
while (stt->pseudo_queue.len > 0) {
|
|
SCLogDebug("processing pseudo packet / stream end");
|
|
Packet *np = PacketDequeueNoLock(&stt->pseudo_queue);
|
|
if (np != NULL) {
|
|
/* process the opposing direction of the original packet */
|
|
if (PKT_IS_TOSERVER(np)) {
|
|
SCLogDebug("pseudo packet is to server");
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->client, np, NULL);
|
|
} else {
|
|
SCLogDebug("pseudo packet is to client");
|
|
StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn,
|
|
&ssn->server, np, NULL);
|
|
}
|
|
|
|
/* enqueue this packet so we inspect it in detect etc */
|
|
PacketEnqueueNoLock(pq, np);
|
|
}
|
|
SCLogDebug("processing pseudo packet / stream end done");
|
|
}
|
|
|
|
/* recalc the csum on the packet if it was modified */
|
|
if (p->flags & PKT_STREAM_MODIFIED) {
|
|
ReCalculateChecksum(p);
|
|
}
|
|
/* check for conditions that may make us not want to log this packet */
|
|
|
|
/* streams that hit depth */
|
|
if ((ssn->client.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) ||
|
|
(ssn->server.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED))
|
|
{
|
|
/* we can call bypass callback, if enabled */
|
|
if (StreamTcpBypassEnabled()) {
|
|
PacketBypassCallback(p);
|
|
}
|
|
}
|
|
|
|
if ((ssn->client.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED) ||
|
|
(ssn->server.flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED))
|
|
{
|
|
p->flags |= PKT_STREAM_NOPCAPLOG;
|
|
}
|
|
|
|
/* encrypted packets */
|
|
if ((PKT_IS_TOSERVER(p) && (ssn->client.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) ||
|
|
(PKT_IS_TOCLIENT(p) && (ssn->server.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)))
|
|
{
|
|
p->flags |= PKT_STREAM_NOPCAPLOG;
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_BYPASS) {
|
|
/* we can call bypass callback, if enabled */
|
|
if (StreamTcpBypassEnabled()) {
|
|
PacketBypassCallback(p);
|
|
}
|
|
|
|
/* if stream is dead and we have no detect engine at all, bypass. */
|
|
} else if (g_detect_disabled &&
|
|
(ssn->client.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) &&
|
|
(ssn->server.flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY) &&
|
|
StreamTcpBypassEnabled())
|
|
{
|
|
SCLogDebug("bypass as stream is dead and we have no rules");
|
|
PacketBypassCallback(p);
|
|
}
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
|
|
error:
|
|
/* make sure we don't leave packets in our pseudo queue */
|
|
while (stt->pseudo_queue.len > 0) {
|
|
Packet *np = PacketDequeueNoLock(&stt->pseudo_queue);
|
|
if (np != NULL) {
|
|
PacketEnqueueNoLock(pq, np);
|
|
}
|
|
}
|
|
|
|
/* recalc the csum on the packet if it was modified */
|
|
if (p->flags & PKT_STREAM_MODIFIED) {
|
|
ReCalculateChecksum(p);
|
|
}
|
|
|
|
if (StreamTcpInlineDropInvalid()) {
|
|
/* disable payload inspection as we're dropping this packet
|
|
* anyway. Doesn't disable all detection, so we can still
|
|
* match on the stream event that was set. */
|
|
DecodeSetNoPayloadInspectionFlag(p);
|
|
PACKET_DROP(p);
|
|
}
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to validate the checksum of the received packet. If the
|
|
* checksum is invalid, packet will be dropped, as the end system will
|
|
* also drop the packet.
|
|
*
|
|
* \param p Packet of which checksum has to be validated
|
|
* \retval 1 if the checksum is valid, otherwise 0
|
|
*/
|
|
static inline int StreamTcpValidateChecksum(Packet *p)
|
|
{
|
|
int ret = 1;
|
|
|
|
if (p->flags & PKT_IGNORE_CHECKSUM)
|
|
return ret;
|
|
|
|
if (p->level4_comp_csum == -1) {
|
|
if (PKT_IS_IPV4(p)) {
|
|
p->level4_comp_csum = TCPChecksum(p->ip4h->s_ip_addrs,
|
|
(uint16_t *)p->tcph,
|
|
(p->payload_len +
|
|
TCP_GET_HLEN(p)),
|
|
p->tcph->th_sum);
|
|
} else if (PKT_IS_IPV6(p)) {
|
|
p->level4_comp_csum = TCPV6Checksum(p->ip6h->s_ip6_addrs,
|
|
(uint16_t *)p->tcph,
|
|
(p->payload_len +
|
|
TCP_GET_HLEN(p)),
|
|
p->tcph->th_sum);
|
|
}
|
|
}
|
|
|
|
if (p->level4_comp_csum != 0) {
|
|
ret = 0;
|
|
if (p->livedev) {
|
|
(void) SC_ATOMIC_ADD(p->livedev->invalid_checksums, 1);
|
|
} else if (p->pcap_cnt) {
|
|
PcapIncreaseInvalidChecksum();
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief check if a packet is a valid stream started
|
|
* \retval bool true/false */
|
|
static int TcpSessionPacketIsStreamStarter(const Packet *p)
|
|
{
|
|
if (p->tcph->th_flags == TH_SYN) {
|
|
SCLogDebug("packet %"PRIu64" is a stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
|
|
return 1;
|
|
}
|
|
|
|
if (stream_config.midstream || stream_config.async_oneside == TRUE) {
|
|
if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
|
|
SCLogDebug("packet %"PRIu64" is a midstream stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Check if Flow and TCP SSN allow this flow/tuple to be reused
|
|
* \retval bool true yes reuse, false no keep tracking old ssn */
|
|
static int TcpSessionReuseDoneEnoughSyn(const Packet *p, const Flow *f, const TcpSession *ssn)
|
|
{
|
|
if (FlowGetPacketDirection(f, p) == TOSERVER) {
|
|
if (ssn == NULL) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p null. No reuse.", p->pcap_cnt, ssn);
|
|
return 0;
|
|
}
|
|
if (SEQ_EQ(ssn->client.isn, TCP_GET_SEQ(p))) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p. Packet SEQ == Stream ISN. Retransmission. Don't reuse.", p->pcap_cnt, ssn);
|
|
return 0;
|
|
}
|
|
if (ssn->state >= TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state == TCP_NONE) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state < TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 0;
|
|
}
|
|
|
|
} else {
|
|
if (ssn == NULL) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p null. Reuse.", p->pcap_cnt, ssn);
|
|
return 1;
|
|
}
|
|
if (ssn->state >= TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state == TCP_NONE) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state < TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SCLogDebug("default: how did we get here?");
|
|
return 0;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief check if ssn is done enough for reuse by syn/ack
|
|
* \note should only be called if midstream is enabled
|
|
*/
|
|
static int TcpSessionReuseDoneEnoughSynAck(const Packet *p, const Flow *f, const TcpSession *ssn)
|
|
{
|
|
if (FlowGetPacketDirection(f, p) == TOCLIENT) {
|
|
if (ssn == NULL) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p null. No reuse.", p->pcap_cnt, ssn);
|
|
return 0;
|
|
}
|
|
if (SEQ_EQ(ssn->server.isn, TCP_GET_SEQ(p))) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p. Packet SEQ == Stream ISN. Retransmission. Don't reuse.", p->pcap_cnt, ssn);
|
|
return 0;
|
|
}
|
|
if (ssn->state >= TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state == TCP_NONE) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state < TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 0;
|
|
}
|
|
|
|
} else {
|
|
if (ssn == NULL) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p null. Reuse.", p->pcap_cnt, ssn);
|
|
return 1;
|
|
}
|
|
if (ssn->state >= TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state >= TCP_LAST_ACK (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state == TCP_NONE) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state == TCP_NONE (%u). Reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 1;
|
|
}
|
|
if (ssn->state < TCP_LAST_ACK) {
|
|
SCLogDebug("steam starter packet %"PRIu64", ssn %p state < TCP_LAST_ACK (%u). Don't reuse.", p->pcap_cnt, ssn, ssn->state);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SCLogDebug("default: how did we get here?");
|
|
return 0;
|
|
}
|
|
|
|
/** \brief Check if SSN is done enough for reuse
|
|
*
|
|
* Reuse means a new TCP session reuses the tuple (flow in suri)
|
|
*
|
|
* \retval bool true if ssn can be reused, false if not */
|
|
static int TcpSessionReuseDoneEnough(const Packet *p, const Flow *f, const TcpSession *ssn)
|
|
{
|
|
if (p->tcph->th_flags == TH_SYN) {
|
|
return TcpSessionReuseDoneEnoughSyn(p, f, ssn);
|
|
}
|
|
|
|
if (stream_config.midstream || stream_config.async_oneside == TRUE) {
|
|
if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
|
|
return TcpSessionReuseDoneEnoughSynAck(p, f, ssn);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TcpSessionPacketSsnReuse(const Packet *p, const Flow *f, const void *tcp_ssn)
|
|
{
|
|
if (p->proto == IPPROTO_TCP && p->tcph != NULL) {
|
|
if (TcpSessionPacketIsStreamStarter(p) == 1) {
|
|
if (TcpSessionReuseDoneEnough(p, f, tcp_ssn) == 1) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TmEcode StreamTcp (ThreadVars *tv, Packet *p, void *data, PacketQueueNoLock *pq)
|
|
{
|
|
StreamTcpThread *stt = (StreamTcpThread *)data;
|
|
|
|
SCLogDebug("p->pcap_cnt %"PRIu64, p->pcap_cnt);
|
|
|
|
if (!(PKT_IS_TCP(p))) {
|
|
return TM_ECODE_OK;
|
|
}
|
|
|
|
if (p->flow == NULL) {
|
|
StatsIncr(tv, stt->counter_tcp_no_flow);
|
|
return TM_ECODE_OK;
|
|
}
|
|
|
|
/* only TCP packets with a flow from here */
|
|
|
|
if (!(p->flags & PKT_PSEUDO_STREAM_END)) {
|
|
if (stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION) {
|
|
if (StreamTcpValidateChecksum(p) == 0) {
|
|
StatsIncr(tv, stt->counter_tcp_invalid_checksum);
|
|
return TM_ECODE_OK;
|
|
}
|
|
} else {
|
|
p->flags |= PKT_IGNORE_CHECKSUM;
|
|
}
|
|
} else {
|
|
p->flags |= PKT_IGNORE_CHECKSUM; //TODO check that this is set at creation
|
|
}
|
|
AppLayerProfilingReset(stt->ra_ctx->app_tctx);
|
|
|
|
(void)StreamTcpPacket(tv, p, stt, pq);
|
|
|
|
return TM_ECODE_OK;
|
|
}
|
|
|
|
TmEcode StreamTcpThreadInit(ThreadVars *tv, void *initdata, void **data)
|
|
{
|
|
SCEnter();
|
|
StreamTcpThread *stt = SCMalloc(sizeof(StreamTcpThread));
|
|
if (unlikely(stt == NULL))
|
|
SCReturnInt(TM_ECODE_FAILED);
|
|
memset(stt, 0, sizeof(StreamTcpThread));
|
|
stt->ssn_pool_id = -1;
|
|
|
|
*data = (void *)stt;
|
|
|
|
stt->counter_tcp_sessions = StatsRegisterCounter("tcp.sessions", tv);
|
|
stt->counter_tcp_ssn_memcap = StatsRegisterCounter("tcp.ssn_memcap_drop", tv);
|
|
stt->counter_tcp_pseudo = StatsRegisterCounter("tcp.pseudo", tv);
|
|
stt->counter_tcp_pseudo_failed = StatsRegisterCounter("tcp.pseudo_failed", tv);
|
|
stt->counter_tcp_invalid_checksum = StatsRegisterCounter("tcp.invalid_checksum", tv);
|
|
stt->counter_tcp_no_flow = StatsRegisterCounter("tcp.no_flow", tv);
|
|
stt->counter_tcp_syn = StatsRegisterCounter("tcp.syn", tv);
|
|
stt->counter_tcp_synack = StatsRegisterCounter("tcp.synack", tv);
|
|
stt->counter_tcp_rst = StatsRegisterCounter("tcp.rst", tv);
|
|
stt->counter_tcp_midstream_pickups = StatsRegisterCounter("tcp.midstream_pickups", tv);
|
|
stt->counter_tcp_wrong_thread = StatsRegisterCounter("tcp.pkt_on_wrong_thread", tv);
|
|
|
|
/* init reassembly ctx */
|
|
stt->ra_ctx = StreamTcpReassembleInitThreadCtx(tv);
|
|
if (stt->ra_ctx == NULL)
|
|
SCReturnInt(TM_ECODE_FAILED);
|
|
|
|
stt->ra_ctx->counter_tcp_segment_memcap = StatsRegisterCounter("tcp.segment_memcap_drop", tv);
|
|
stt->ra_ctx->counter_tcp_stream_depth = StatsRegisterCounter("tcp.stream_depth_reached", tv);
|
|
stt->ra_ctx->counter_tcp_reass_gap = StatsRegisterCounter("tcp.reassembly_gap", tv);
|
|
stt->ra_ctx->counter_tcp_reass_overlap = StatsRegisterCounter("tcp.overlap", tv);
|
|
stt->ra_ctx->counter_tcp_reass_overlap_diff_data = StatsRegisterCounter("tcp.overlap_diff_data", tv);
|
|
|
|
stt->ra_ctx->counter_tcp_reass_data_normal_fail = StatsRegisterCounter("tcp.insert_data_normal_fail", tv);
|
|
stt->ra_ctx->counter_tcp_reass_data_overlap_fail = StatsRegisterCounter("tcp.insert_data_overlap_fail", tv);
|
|
stt->ra_ctx->counter_tcp_reass_list_fail = StatsRegisterCounter("tcp.insert_list_fail", tv);
|
|
|
|
|
|
SCLogDebug("StreamTcp thread specific ctx online at %p, reassembly ctx %p",
|
|
stt, stt->ra_ctx);
|
|
|
|
SCMutexLock(&ssn_pool_mutex);
|
|
if (ssn_pool == NULL) {
|
|
ssn_pool = PoolThreadInit(1, /* thread */
|
|
0, /* unlimited */
|
|
stream_config.prealloc_sessions,
|
|
sizeof(TcpSession),
|
|
StreamTcpSessionPoolAlloc,
|
|
StreamTcpSessionPoolInit, NULL,
|
|
StreamTcpSessionPoolCleanup, NULL);
|
|
stt->ssn_pool_id = 0;
|
|
SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id);
|
|
} else {
|
|
/* grow ssn_pool until we have a element for our thread id */
|
|
stt->ssn_pool_id = PoolThreadExpand(ssn_pool);
|
|
SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id);
|
|
}
|
|
SCMutexUnlock(&ssn_pool_mutex);
|
|
if (stt->ssn_pool_id < 0 || ssn_pool == NULL) {
|
|
SCLogError(SC_ERR_MEM_ALLOC, "failed to setup/expand stream session pool. Expand stream.memcap?");
|
|
SCReturnInt(TM_ECODE_FAILED);
|
|
}
|
|
|
|
SCReturnInt(TM_ECODE_OK);
|
|
}
|
|
|
|
TmEcode StreamTcpThreadDeinit(ThreadVars *tv, void *data)
|
|
{
|
|
SCEnter();
|
|
StreamTcpThread *stt = (StreamTcpThread *)data;
|
|
if (stt == NULL) {
|
|
return TM_ECODE_OK;
|
|
}
|
|
|
|
/* XXX */
|
|
|
|
/* free reassembly ctx */
|
|
StreamTcpReassembleFreeThreadCtx(stt->ra_ctx);
|
|
|
|
/* clear memory */
|
|
memset(stt, 0, sizeof(StreamTcpThread));
|
|
|
|
SCFree(stt);
|
|
SCReturnInt(TM_ECODE_OK);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to check the validity of the RST packets based on the
|
|
* target OS of the given packet.
|
|
*
|
|
* \param ssn TCP session to which the given packet belongs
|
|
* \param p Packet which has to be checked for its validity
|
|
*
|
|
* \retval 0 unacceptable RST
|
|
* \retval 1 acceptable RST
|
|
*
|
|
* WebSense sends RST packets that are:
|
|
* - RST flag, win 0, ack 0, seq = nextseq
|
|
*
|
|
*/
|
|
|
|
static int StreamTcpValidateRst(TcpSession *ssn, Packet *p)
|
|
{
|
|
|
|
uint8_t os_policy;
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
|
|
if (!StreamTcpValidateTimestamp(ssn, p)) {
|
|
SCReturnInt(0);
|
|
}
|
|
}
|
|
|
|
/* Set up the os_policy to be used in validating the RST packets based on
|
|
target system */
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if (ssn->server.os_policy == 0)
|
|
StreamTcpSetOSPolicy(&ssn->server, p);
|
|
|
|
os_policy = ssn->server.os_policy;
|
|
|
|
if (p->tcph->th_flags & TH_ACK &&
|
|
TCP_GET_ACK(p) && StreamTcpValidateAck(ssn, &ssn->server, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_RST_INVALID_ACK);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
} else {
|
|
if (ssn->client.os_policy == 0)
|
|
StreamTcpSetOSPolicy(&ssn->client, p);
|
|
|
|
os_policy = ssn->client.os_policy;
|
|
|
|
if (p->tcph->th_flags & TH_ACK &&
|
|
TCP_GET_ACK(p) && StreamTcpValidateAck(ssn, &ssn->client, p) == -1) {
|
|
SCLogDebug("ssn %p: rejecting because of invalid ack value", ssn);
|
|
StreamTcpSetEvent(p, STREAM_RST_INVALID_ACK);
|
|
SCReturnInt(0);
|
|
}
|
|
}
|
|
|
|
if (ssn->flags & STREAMTCP_FLAG_ASYNC) {
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
if (SEQ_GEQ(TCP_GET_SEQ(p), ssn->client.next_seq)) {
|
|
SCLogDebug("ssn %p: ASYNC accept RST", ssn);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (SEQ_GEQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
|
|
SCLogDebug("ssn %p: ASYNC accept RST", ssn);
|
|
return 1;
|
|
}
|
|
}
|
|
SCLogDebug("ssn %p: ASYNC reject RST", ssn);
|
|
return 0;
|
|
}
|
|
|
|
switch (os_policy) {
|
|
case OS_POLICY_HPUX11:
|
|
if(PKT_IS_TOSERVER(p)){
|
|
if(SEQ_GEQ(TCP_GET_SEQ(p), ssn->client.next_seq)) {
|
|
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p));
|
|
return 1;
|
|
} else {
|
|
SCLogDebug("reset is not Valid! Packet SEQ: %" PRIu32 " "
|
|
"and server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
|
|
ssn->client.next_seq);
|
|
return 0;
|
|
}
|
|
} else { /* implied to client */
|
|
if(SEQ_GEQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
|
|
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p));
|
|
return 1;
|
|
} else {
|
|
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " "
|
|
"and client SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
|
|
ssn->server.next_seq);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
case OS_POLICY_OLD_LINUX:
|
|
case OS_POLICY_LINUX:
|
|
case OS_POLICY_SOLARIS:
|
|
if(PKT_IS_TOSERVER(p)){
|
|
if(SEQ_GEQ((TCP_GET_SEQ(p)+p->payload_len),
|
|
ssn->client.last_ack))
|
|
{ /*window base is needed !!*/
|
|
if(SEQ_LT(TCP_GET_SEQ(p),
|
|
(ssn->client.next_seq + ssn->client.window)))
|
|
{
|
|
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p));
|
|
return 1;
|
|
}
|
|
} else {
|
|
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
|
|
" server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
|
|
ssn->client.next_seq);
|
|
return 0;
|
|
}
|
|
} else { /* implied to client */
|
|
if(SEQ_GEQ((TCP_GET_SEQ(p) + p->payload_len),
|
|
ssn->server.last_ack))
|
|
{ /*window base is needed !!*/
|
|
if(SEQ_LT(TCP_GET_SEQ(p),
|
|
(ssn->server.next_seq + ssn->server.window)))
|
|
{
|
|
SCLogDebug("reset is Valid! Packet SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p));
|
|
return 1;
|
|
}
|
|
} else {
|
|
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
|
|
" client SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
|
|
ssn->server.next_seq);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
case OS_POLICY_BSD:
|
|
case OS_POLICY_FIRST:
|
|
case OS_POLICY_HPUX10:
|
|
case OS_POLICY_IRIX:
|
|
case OS_POLICY_MACOS:
|
|
case OS_POLICY_LAST:
|
|
case OS_POLICY_WINDOWS:
|
|
case OS_POLICY_WINDOWS2K3:
|
|
case OS_POLICY_VISTA:
|
|
if(PKT_IS_TOSERVER(p)) {
|
|
if(SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq)) {
|
|
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p));
|
|
return 1;
|
|
} else {
|
|
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " "
|
|
"and server SEQ: %" PRIu32 "", TCP_GET_SEQ(p),
|
|
ssn->client.next_seq);
|
|
return 0;
|
|
}
|
|
} else { /* implied to client */
|
|
if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) {
|
|
SCLogDebug("reset is valid! Packet SEQ: %" PRIu32 " Stream %u",
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
return 1;
|
|
} else {
|
|
SCLogDebug("reset is not valid! Packet SEQ: %" PRIu32 " and"
|
|
" client SEQ: %" PRIu32 "",
|
|
TCP_GET_SEQ(p), ssn->server.next_seq);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Function to check the validity of the received timestamp based on
|
|
* the target OS of the given stream.
|
|
*
|
|
* It's passive except for:
|
|
* 1. it sets the os policy on the stream if necessary
|
|
* 2. it sets an event in the packet if necessary
|
|
*
|
|
* \param ssn TCP session to which the given packet belongs
|
|
* \param p Packet which has to be checked for its validity
|
|
*
|
|
* \retval 1 if the timestamp is valid
|
|
* \retval 0 if the timestamp is invalid
|
|
*/
|
|
static int StreamTcpValidateTimestamp (TcpSession *ssn, Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
TcpStream *sender_stream;
|
|
TcpStream *receiver_stream;
|
|
uint8_t ret = 1;
|
|
uint8_t check_ts = 1;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
sender_stream = &ssn->client;
|
|
receiver_stream = &ssn->server;
|
|
} else {
|
|
sender_stream = &ssn->server;
|
|
receiver_stream = &ssn->client;
|
|
}
|
|
|
|
/* Set up the os_policy to be used in validating the timestamps based on
|
|
the target system */
|
|
if (receiver_stream->os_policy == 0) {
|
|
StreamTcpSetOSPolicy(receiver_stream, p);
|
|
}
|
|
|
|
if (TCP_HAS_TS(p)) {
|
|
uint32_t ts = TCP_GET_TSVAL(p);
|
|
uint32_t last_pkt_ts = sender_stream->last_pkt_ts;
|
|
uint32_t last_ts = sender_stream->last_ts;
|
|
|
|
if (sender_stream->flags & STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP) {
|
|
/* The 3whs used the timestamp with 0 value. */
|
|
switch (receiver_stream->os_policy) {
|
|
case OS_POLICY_LINUX:
|
|
case OS_POLICY_WINDOWS2K3:
|
|
/* Linux and windows 2003 does not allow the use of 0 as
|
|
* timestamp in the 3whs. */
|
|
check_ts = 0;
|
|
break;
|
|
|
|
case OS_POLICY_OLD_LINUX:
|
|
case OS_POLICY_WINDOWS:
|
|
case OS_POLICY_VISTA:
|
|
if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) {
|
|
last_ts = ts;
|
|
check_ts = 0; /*next packet will be checked for validity
|
|
and stream TS has been updated with this
|
|
one.*/
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (receiver_stream->os_policy == OS_POLICY_HPUX11) {
|
|
/* HPUX11 igoners the timestamp of out of order packets */
|
|
if (!SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
|
|
check_ts = 0;
|
|
}
|
|
|
|
if (ts == 0) {
|
|
switch (receiver_stream->os_policy) {
|
|
case OS_POLICY_OLD_LINUX:
|
|
case OS_POLICY_WINDOWS:
|
|
case OS_POLICY_WINDOWS2K3:
|
|
case OS_POLICY_VISTA:
|
|
case OS_POLICY_SOLARIS:
|
|
/* Old Linux and windows allowed packet with 0 timestamp. */
|
|
break;
|
|
default:
|
|
/* other OS simply drop the pakcet with 0 timestamp, when
|
|
* 3whs has valid timestamp*/
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (check_ts) {
|
|
int32_t result = 0;
|
|
|
|
SCLogDebug("ts %"PRIu32", last_ts %"PRIu32"", ts, last_ts);
|
|
|
|
if (receiver_stream->os_policy == OS_POLICY_LINUX) {
|
|
/* Linux accepts TS which are off by one.*/
|
|
result = (int32_t) ((ts - last_ts) + 1);
|
|
} else {
|
|
result = (int32_t) (ts - last_ts);
|
|
}
|
|
|
|
SCLogDebug("result %"PRIi32", p->ts.tv_sec %"PRIuMAX"", result, (uintmax_t)p->ts.tv_sec);
|
|
|
|
if (last_pkt_ts == 0 &&
|
|
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM))
|
|
{
|
|
last_pkt_ts = p->ts.tv_sec;
|
|
}
|
|
|
|
if (result < 0) {
|
|
SCLogDebug("timestamp is not valid last_ts "
|
|
"%" PRIu32 " p->tcpvars->ts %" PRIu32 " result "
|
|
"%" PRId32 "", last_ts, ts, result);
|
|
/* candidate for rejection */
|
|
ret = 0;
|
|
} else if ((sender_stream->last_ts != 0) &&
|
|
(((uint32_t) p->ts.tv_sec) >
|
|
last_pkt_ts + PAWS_24DAYS))
|
|
{
|
|
SCLogDebug("packet is not valid last_pkt_ts "
|
|
"%" PRIu32 " p->ts.tv_sec %" PRIu32 "",
|
|
last_pkt_ts, (uint32_t) p->ts.tv_sec);
|
|
/* candidate for rejection */
|
|
ret = 0;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
/* if the timestamp of packet is not valid then, check if the
|
|
* current stream timestamp is not so old. if so then we need to
|
|
* accept the packet and update the stream->last_ts (RFC 1323)*/
|
|
if ((SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) &&
|
|
(((uint32_t) p->ts.tv_sec > (last_pkt_ts + PAWS_24DAYS))))
|
|
{
|
|
SCLogDebug("timestamp considered valid anyway");
|
|
} else {
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SCReturnInt(1);
|
|
|
|
invalid:
|
|
StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to check the validity of the received timestamp based on
|
|
* the target OS of the given stream and update the session.
|
|
*
|
|
* \param ssn TCP session to which the given packet belongs
|
|
* \param p Packet which has to be checked for its validity
|
|
*
|
|
* \retval 1 if the timestamp is valid
|
|
* \retval 0 if the timestamp is invalid
|
|
*/
|
|
static int StreamTcpHandleTimestamp (TcpSession *ssn, Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
TcpStream *sender_stream;
|
|
TcpStream *receiver_stream;
|
|
uint8_t ret = 1;
|
|
uint8_t check_ts = 1;
|
|
|
|
if (PKT_IS_TOSERVER(p)) {
|
|
sender_stream = &ssn->client;
|
|
receiver_stream = &ssn->server;
|
|
} else {
|
|
sender_stream = &ssn->server;
|
|
receiver_stream = &ssn->client;
|
|
}
|
|
|
|
/* Set up the os_policy to be used in validating the timestamps based on
|
|
the target system */
|
|
if (receiver_stream->os_policy == 0) {
|
|
StreamTcpSetOSPolicy(receiver_stream, p);
|
|
}
|
|
|
|
if (TCP_HAS_TS(p)) {
|
|
uint32_t ts = TCP_GET_TSVAL(p);
|
|
|
|
if (sender_stream->flags & STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP) {
|
|
/* The 3whs used the timestamp with 0 value. */
|
|
switch (receiver_stream->os_policy) {
|
|
case OS_POLICY_LINUX:
|
|
case OS_POLICY_WINDOWS2K3:
|
|
/* Linux and windows 2003 does not allow the use of 0 as
|
|
* timestamp in the 3whs. */
|
|
ssn->flags &= ~STREAMTCP_FLAG_TIMESTAMP;
|
|
check_ts = 0;
|
|
break;
|
|
|
|
case OS_POLICY_OLD_LINUX:
|
|
case OS_POLICY_WINDOWS:
|
|
case OS_POLICY_VISTA:
|
|
sender_stream->flags &= ~STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP;
|
|
if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) {
|
|
sender_stream->last_ts = ts;
|
|
check_ts = 0; /*next packet will be checked for validity
|
|
and stream TS has been updated with this
|
|
one.*/
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (receiver_stream->os_policy == OS_POLICY_HPUX11) {
|
|
/*HPUX11 igoners the timestamp of out of order packets*/
|
|
if (!SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
|
|
check_ts = 0;
|
|
}
|
|
|
|
if (ts == 0) {
|
|
switch (receiver_stream->os_policy) {
|
|
case OS_POLICY_OLD_LINUX:
|
|
case OS_POLICY_WINDOWS:
|
|
case OS_POLICY_WINDOWS2K3:
|
|
case OS_POLICY_VISTA:
|
|
case OS_POLICY_SOLARIS:
|
|
/* Old Linux and windows allowed packet with 0 timestamp. */
|
|
break;
|
|
default:
|
|
/* other OS simply drop the pakcet with 0 timestamp, when
|
|
* 3whs has valid timestamp*/
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (check_ts) {
|
|
int32_t result = 0;
|
|
|
|
SCLogDebug("ts %"PRIu32", last_ts %"PRIu32"", ts, sender_stream->last_ts);
|
|
|
|
if (receiver_stream->os_policy == OS_POLICY_LINUX) {
|
|
/* Linux accepts TS which are off by one.*/
|
|
result = (int32_t) ((ts - sender_stream->last_ts) + 1);
|
|
} else {
|
|
result = (int32_t) (ts - sender_stream->last_ts);
|
|
}
|
|
|
|
SCLogDebug("result %"PRIi32", p->ts.tv_sec %"PRIuMAX"", result, (uintmax_t)p->ts.tv_sec);
|
|
|
|
if (sender_stream->last_pkt_ts == 0 &&
|
|
(ssn->flags & STREAMTCP_FLAG_MIDSTREAM))
|
|
{
|
|
sender_stream->last_pkt_ts = p->ts.tv_sec;
|
|
}
|
|
|
|
if (result < 0) {
|
|
SCLogDebug("timestamp is not valid sender_stream->last_ts "
|
|
"%" PRIu32 " p->tcpvars->ts %" PRIu32 " result "
|
|
"%" PRId32 "", sender_stream->last_ts, ts, result);
|
|
/* candidate for rejection */
|
|
ret = 0;
|
|
} else if ((sender_stream->last_ts != 0) &&
|
|
(((uint32_t) p->ts.tv_sec) >
|
|
sender_stream->last_pkt_ts + PAWS_24DAYS))
|
|
{
|
|
SCLogDebug("packet is not valid sender_stream->last_pkt_ts "
|
|
"%" PRIu32 " p->ts.tv_sec %" PRIu32 "",
|
|
sender_stream->last_pkt_ts, (uint32_t) p->ts.tv_sec);
|
|
/* candidate for rejection */
|
|
ret = 0;
|
|
}
|
|
|
|
if (ret == 1) {
|
|
/* Update the timestamp and last seen packet time for this
|
|
* stream */
|
|
if (SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p)))
|
|
sender_stream->last_ts = ts;
|
|
|
|
sender_stream->last_pkt_ts = p->ts.tv_sec;
|
|
|
|
} else if (ret == 0) {
|
|
/* if the timestamp of packet is not valid then, check if the
|
|
* current stream timestamp is not so old. if so then we need to
|
|
* accept the packet and update the stream->last_ts (RFC 1323)*/
|
|
if ((SEQ_EQ(sender_stream->next_seq, TCP_GET_SEQ(p))) &&
|
|
(((uint32_t) p->ts.tv_sec > (sender_stream->last_pkt_ts + PAWS_24DAYS))))
|
|
{
|
|
sender_stream->last_ts = ts;
|
|
sender_stream->last_pkt_ts = p->ts.tv_sec;
|
|
|
|
SCLogDebug("timestamp considered valid anyway");
|
|
} else {
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* Solaris stops using timestamps if a packet is received
|
|
without a timestamp and timestamps were used on that stream. */
|
|
if (receiver_stream->os_policy == OS_POLICY_SOLARIS)
|
|
ssn->flags &= ~STREAMTCP_FLAG_TIMESTAMP;
|
|
}
|
|
|
|
SCReturnInt(1);
|
|
|
|
invalid:
|
|
StreamTcpSetEvent(p, STREAM_PKT_INVALID_TIMESTAMP);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Function to test the received ACK values against the stream window
|
|
* and previous ack value. ACK values should be higher than previous
|
|
* ACK value and less than the next_win value.
|
|
*
|
|
* \param ssn TcpSession for state access
|
|
* \param stream TcpStream of which last_ack needs to be tested
|
|
* \param p Packet which is used to test the last_ack
|
|
*
|
|
* \retval 0 ACK is valid, last_ack is updated if ACK was higher
|
|
* \retval -1 ACK is invalid
|
|
*/
|
|
static inline int StreamTcpValidateAck(TcpSession *ssn, TcpStream *stream, Packet *p)
|
|
{
|
|
SCEnter();
|
|
|
|
uint32_t ack = TCP_GET_ACK(p);
|
|
|
|
/* fast track */
|
|
if (SEQ_GT(ack, stream->last_ack) && SEQ_LEQ(ack, stream->next_win))
|
|
{
|
|
SCLogDebug("ACK in bounds");
|
|
SCReturnInt(0);
|
|
}
|
|
/* fast track */
|
|
else if (SEQ_EQ(ack, stream->last_ack)) {
|
|
SCLogDebug("pkt ACK %"PRIu32" == stream last ACK %"PRIu32, TCP_GET_ACK(p), stream->last_ack);
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/* exception handling */
|
|
if (SEQ_LT(ack, stream->last_ack)) {
|
|
SCLogDebug("pkt ACK %"PRIu32" < stream last ACK %"PRIu32, TCP_GET_ACK(p), stream->last_ack);
|
|
|
|
/* This is an attempt to get a 'left edge' value that we can check against.
|
|
* It doesn't work when the window is 0, need to think of a better way. */
|
|
|
|
if (stream->window != 0 && SEQ_LT(ack, (stream->last_ack - stream->window))) {
|
|
SCLogDebug("ACK %"PRIu32" is before last_ack %"PRIu32" - window "
|
|
"%"PRIu32" = %"PRIu32, ack, stream->last_ack,
|
|
stream->window, stream->last_ack - stream->window);
|
|
goto invalid;
|
|
}
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/* no further checks possible for ASYNC */
|
|
if ((ssn->flags & STREAMTCP_FLAG_ASYNC) != 0) {
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
if (ssn->state > TCP_SYN_SENT && SEQ_GT(ack, stream->next_win)) {
|
|
SCLogDebug("ACK %"PRIu32" is after next_win %"PRIu32, ack, stream->next_win);
|
|
goto invalid;
|
|
/* a toclient RST as a reponse to SYN, next_win is 0, ack will be isn+1, just like
|
|
* the syn ack */
|
|
} else if (ssn->state == TCP_SYN_SENT && PKT_IS_TOCLIENT(p) &&
|
|
p->tcph->th_flags & TH_RST &&
|
|
SEQ_EQ(ack, stream->isn + 1)) {
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
SCLogDebug("default path leading to invalid: ACK %"PRIu32", last_ack %"PRIu32
|
|
" next_win %"PRIu32, ack, stream->last_ack, stream->next_win);
|
|
invalid:
|
|
StreamTcpSetEvent(p, STREAM_PKT_INVALID_ACK);
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
/** \brief update reassembly progress
|
|
|
|
* \param ssn TCP Session
|
|
* \param direction direction to set the flag in: 0 toserver, 1 toclient
|
|
*/
|
|
void StreamTcpUpdateAppLayerProgress(TcpSession *ssn, char direction,
|
|
const uint32_t progress)
|
|
{
|
|
if (direction) {
|
|
ssn->server.app_progress_rel += progress;
|
|
} else {
|
|
ssn->client.app_progress_rel += progress;
|
|
}
|
|
}
|
|
|
|
/** \brief disable reassembly
|
|
|
|
* Disable app layer and set raw inspect to no longer accept new data.
|
|
* Stream engine will then fully disable raw after last inspection.
|
|
*
|
|
* \param ssn TCP Session to set the flag in
|
|
* \param direction direction to set the flag in: 0 toserver, 1 toclient
|
|
*/
|
|
void StreamTcpSetSessionNoReassemblyFlag(TcpSession *ssn, char direction)
|
|
{
|
|
ssn->flags |= STREAMTCP_FLAG_APP_LAYER_DISABLED;
|
|
if (direction) {
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED;
|
|
} else {
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED;
|
|
}
|
|
}
|
|
|
|
/** \brief Set the No reassembly flag for the given direction in given TCP
|
|
* session.
|
|
*
|
|
* \param ssn TCP Session to set the flag in
|
|
* \param direction direction to set the flag in: 0 toserver, 1 toclient
|
|
*/
|
|
void StreamTcpSetDisableRawReassemblyFlag(TcpSession *ssn, char direction)
|
|
{
|
|
direction ? (ssn->server.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED) :
|
|
(ssn->client.flags |= STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED);
|
|
}
|
|
|
|
/** \brief enable bypass
|
|
*
|
|
* \param ssn TCP Session to set the flag in
|
|
* \param direction direction to set the flag in: 0 toserver, 1 toclient
|
|
*/
|
|
void StreamTcpSetSessionBypassFlag(TcpSession *ssn)
|
|
{
|
|
ssn->flags |= STREAMTCP_FLAG_BYPASS;
|
|
}
|
|
|
|
#define PSEUDO_PKT_SET_IPV4HDR(nipv4h,ipv4h) do { \
|
|
IPV4_SET_RAW_VER(nipv4h, IPV4_GET_RAW_VER(ipv4h)); \
|
|
IPV4_SET_RAW_HLEN(nipv4h, IPV4_GET_RAW_HLEN(ipv4h)); \
|
|
IPV4_SET_RAW_IPLEN(nipv4h, IPV4_GET_RAW_IPLEN(ipv4h)); \
|
|
IPV4_SET_RAW_IPTOS(nipv4h, IPV4_GET_RAW_IPTOS(ipv4h)); \
|
|
IPV4_SET_RAW_IPPROTO(nipv4h, IPV4_GET_RAW_IPPROTO(ipv4h)); \
|
|
(nipv4h)->s_ip_src = IPV4_GET_RAW_IPDST(ipv4h); \
|
|
(nipv4h)->s_ip_dst = IPV4_GET_RAW_IPSRC(ipv4h); \
|
|
} while (0)
|
|
|
|
#define PSEUDO_PKT_SET_IPV6HDR(nipv6h,ipv6h) do { \
|
|
(nipv6h)->s_ip6_src[0] = (ipv6h)->s_ip6_dst[0]; \
|
|
(nipv6h)->s_ip6_src[1] = (ipv6h)->s_ip6_dst[1]; \
|
|
(nipv6h)->s_ip6_src[2] = (ipv6h)->s_ip6_dst[2]; \
|
|
(nipv6h)->s_ip6_src[3] = (ipv6h)->s_ip6_dst[3]; \
|
|
(nipv6h)->s_ip6_dst[0] = (ipv6h)->s_ip6_src[0]; \
|
|
(nipv6h)->s_ip6_dst[1] = (ipv6h)->s_ip6_src[1]; \
|
|
(nipv6h)->s_ip6_dst[2] = (ipv6h)->s_ip6_src[2]; \
|
|
(nipv6h)->s_ip6_dst[3] = (ipv6h)->s_ip6_src[3]; \
|
|
IPV6_SET_RAW_NH(nipv6h, IPV6_GET_RAW_NH(ipv6h)); \
|
|
} while (0)
|
|
|
|
#define PSEUDO_PKT_SET_TCPHDR(ntcph,tcph) do { \
|
|
COPY_PORT((tcph)->th_dport, (ntcph)->th_sport); \
|
|
COPY_PORT((tcph)->th_sport, (ntcph)->th_dport); \
|
|
(ntcph)->th_seq = (tcph)->th_ack; \
|
|
(ntcph)->th_ack = (tcph)->th_seq; \
|
|
} while (0)
|
|
|
|
/**
|
|
* \brief Function to fetch a packet from the packet allocation queue for
|
|
* creation of the pseudo packet from the reassembled stream.
|
|
*
|
|
* @param parent Pointer to the parent of the pseudo packet
|
|
* @param pkt pointer to the raw packet of the parent
|
|
* @param len length of the packet
|
|
* @return upon success returns the pointer to the new pseudo packet
|
|
* otherwise NULL
|
|
*/
|
|
Packet *StreamTcpPseudoSetup(Packet *parent, uint8_t *pkt, uint32_t len)
|
|
{
|
|
SCEnter();
|
|
|
|
if (len == 0) {
|
|
SCReturnPtr(NULL, "Packet");
|
|
}
|
|
|
|
Packet *p = PacketGetFromQueueOrAlloc();
|
|
if (p == NULL) {
|
|
SCReturnPtr(NULL, "Packet");
|
|
}
|
|
|
|
/* set the root ptr to the lowest layer */
|
|
if (parent->root != NULL)
|
|
p->root = parent->root;
|
|
else
|
|
p->root = parent;
|
|
|
|
/* copy packet and set lenght, proto */
|
|
p->proto = parent->proto;
|
|
p->datalink = parent->datalink;
|
|
|
|
PacketCopyData(p, pkt, len);
|
|
p->recursion_level = parent->recursion_level + 1;
|
|
p->ts.tv_sec = parent->ts.tv_sec;
|
|
p->ts.tv_usec = parent->ts.tv_usec;
|
|
|
|
FlowReference(&p->flow, parent->flow);
|
|
/* set tunnel flags */
|
|
|
|
/* tell new packet it's part of a tunnel */
|
|
SET_TUNNEL_PKT(p);
|
|
/* tell parent packet it's part of a tunnel */
|
|
SET_TUNNEL_PKT(parent);
|
|
|
|
/* increment tunnel packet refcnt in the root packet */
|
|
TUNNEL_INCR_PKT_TPR(p);
|
|
|
|
return p;
|
|
}
|
|
|
|
/** \brief Create a pseudo packet injected into the engine to signal the
|
|
* opposing direction of this stream trigger detection/logging.
|
|
*
|
|
* \param parent real packet
|
|
* \param pq packet queue to store the new pseudo packet in
|
|
* \param dir 0 ts 1 tc
|
|
*/
|
|
static void StreamTcpPseudoPacketCreateDetectLogFlush(ThreadVars *tv,
|
|
StreamTcpThread *stt, Packet *parent,
|
|
TcpSession *ssn, PacketQueueNoLock *pq, int dir)
|
|
{
|
|
SCEnter();
|
|
Flow *f = parent->flow;
|
|
|
|
if (parent->flags & PKT_PSEUDO_DETECTLOG_FLUSH) {
|
|
SCReturn;
|
|
}
|
|
|
|
Packet *np = PacketPoolGetPacket();
|
|
if (np == NULL) {
|
|
SCReturn;
|
|
}
|
|
PKT_SET_SRC(np, PKT_SRC_STREAM_TCP_DETECTLOG_FLUSH);
|
|
|
|
np->tenant_id = f->tenant_id;
|
|
np->datalink = DLT_RAW;
|
|
np->proto = IPPROTO_TCP;
|
|
FlowReference(&np->flow, f);
|
|
np->flags |= PKT_STREAM_EST;
|
|
np->flags |= PKT_HAS_FLOW;
|
|
np->flags |= PKT_IGNORE_CHECKSUM;
|
|
np->flags |= PKT_PSEUDO_DETECTLOG_FLUSH;
|
|
np->vlan_id[0] = f->vlan_id[0];
|
|
np->vlan_id[1] = f->vlan_id[1];
|
|
np->vlan_idx = f->vlan_idx;
|
|
np->livedev = (struct LiveDevice_ *)f->livedev;
|
|
|
|
if (f->flags & FLOW_NOPACKET_INSPECTION) {
|
|
DecodeSetNoPacketInspectionFlag(np);
|
|
}
|
|
if (f->flags & FLOW_NOPAYLOAD_INSPECTION) {
|
|
DecodeSetNoPayloadInspectionFlag(np);
|
|
}
|
|
|
|
if (dir == 0) {
|
|
SCLogDebug("pseudo is to_server");
|
|
np->flowflags |= FLOW_PKT_TOSERVER;
|
|
} else {
|
|
SCLogDebug("pseudo is to_client");
|
|
np->flowflags |= FLOW_PKT_TOCLIENT;
|
|
}
|
|
np->flowflags |= FLOW_PKT_ESTABLISHED;
|
|
np->payload = NULL;
|
|
np->payload_len = 0;
|
|
|
|
if (FLOW_IS_IPV4(f)) {
|
|
if (dir == 0) {
|
|
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &np->src);
|
|
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &np->dst);
|
|
np->sp = f->sp;
|
|
np->dp = f->dp;
|
|
} else {
|
|
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &np->dst);
|
|
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &np->src);
|
|
np->sp = f->dp;
|
|
np->dp = f->sp;
|
|
}
|
|
|
|
/* Check if we have enough room in direct data. We need ipv4 hdr + tcp hdr.
|
|
* Force an allocation if it is not the case.
|
|
*/
|
|
if (GET_PKT_DIRECT_MAX_SIZE(np) < 40) {
|
|
if (PacketCallocExtPkt(np, 40) == -1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* set the ip header */
|
|
np->ip4h = (IPV4Hdr *)GET_PKT_DATA(np);
|
|
/* version 4 and length 20 bytes for the tcp header */
|
|
np->ip4h->ip_verhl = 0x45;
|
|
np->ip4h->ip_tos = 0;
|
|
np->ip4h->ip_len = htons(40);
|
|
np->ip4h->ip_id = 0;
|
|
np->ip4h->ip_off = 0;
|
|
np->ip4h->ip_ttl = 64;
|
|
np->ip4h->ip_proto = IPPROTO_TCP;
|
|
if (dir == 0) {
|
|
np->ip4h->s_ip_src.s_addr = f->src.addr_data32[0];
|
|
np->ip4h->s_ip_dst.s_addr = f->dst.addr_data32[0];
|
|
} else {
|
|
np->ip4h->s_ip_src.s_addr = f->dst.addr_data32[0];
|
|
np->ip4h->s_ip_dst.s_addr = f->src.addr_data32[0];
|
|
}
|
|
|
|
/* set the tcp header */
|
|
np->tcph = (TCPHdr *)((uint8_t *)GET_PKT_DATA(np) + 20);
|
|
|
|
SET_PKT_LEN(np, 40); /* ipv4 hdr + tcp hdr */
|
|
|
|
} else if (FLOW_IS_IPV6(f)) {
|
|
if (dir == 0) {
|
|
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->src, &np->src);
|
|
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->dst, &np->dst);
|
|
np->sp = f->sp;
|
|
np->dp = f->dp;
|
|
} else {
|
|
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->src, &np->dst);
|
|
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->dst, &np->src);
|
|
np->sp = f->dp;
|
|
np->dp = f->sp;
|
|
}
|
|
|
|
/* Check if we have enough room in direct data. We need ipv6 hdr + tcp hdr.
|
|
* Force an allocation if it is not the case.
|
|
*/
|
|
if (GET_PKT_DIRECT_MAX_SIZE(np) < 60) {
|
|
if (PacketCallocExtPkt(np, 60) == -1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* set the ip header */
|
|
np->ip6h = (IPV6Hdr *)GET_PKT_DATA(np);
|
|
/* version 6 */
|
|
np->ip6h->s_ip6_vfc = 0x60;
|
|
np->ip6h->s_ip6_flow = 0;
|
|
np->ip6h->s_ip6_nxt = IPPROTO_TCP;
|
|
np->ip6h->s_ip6_plen = htons(20);
|
|
np->ip6h->s_ip6_hlim = 64;
|
|
if (dir == 0) {
|
|
np->ip6h->s_ip6_src[0] = f->src.addr_data32[0];
|
|
np->ip6h->s_ip6_src[1] = f->src.addr_data32[1];
|
|
np->ip6h->s_ip6_src[2] = f->src.addr_data32[2];
|
|
np->ip6h->s_ip6_src[3] = f->src.addr_data32[3];
|
|
np->ip6h->s_ip6_dst[0] = f->dst.addr_data32[0];
|
|
np->ip6h->s_ip6_dst[1] = f->dst.addr_data32[1];
|
|
np->ip6h->s_ip6_dst[2] = f->dst.addr_data32[2];
|
|
np->ip6h->s_ip6_dst[3] = f->dst.addr_data32[3];
|
|
} else {
|
|
np->ip6h->s_ip6_src[0] = f->dst.addr_data32[0];
|
|
np->ip6h->s_ip6_src[1] = f->dst.addr_data32[1];
|
|
np->ip6h->s_ip6_src[2] = f->dst.addr_data32[2];
|
|
np->ip6h->s_ip6_src[3] = f->dst.addr_data32[3];
|
|
np->ip6h->s_ip6_dst[0] = f->src.addr_data32[0];
|
|
np->ip6h->s_ip6_dst[1] = f->src.addr_data32[1];
|
|
np->ip6h->s_ip6_dst[2] = f->src.addr_data32[2];
|
|
np->ip6h->s_ip6_dst[3] = f->src.addr_data32[3];
|
|
}
|
|
|
|
/* set the tcp header */
|
|
np->tcph = (TCPHdr *)((uint8_t *)GET_PKT_DATA(np) + 40);
|
|
|
|
SET_PKT_LEN(np, 60); /* ipv6 hdr + tcp hdr */
|
|
}
|
|
|
|
np->tcph->th_offx2 = 0x50;
|
|
np->tcph->th_flags |= TH_ACK;
|
|
np->tcph->th_win = 10;
|
|
np->tcph->th_urp = 0;
|
|
|
|
/* to server */
|
|
if (dir == 0) {
|
|
np->tcph->th_sport = htons(f->sp);
|
|
np->tcph->th_dport = htons(f->dp);
|
|
|
|
np->tcph->th_seq = htonl(ssn->client.next_seq);
|
|
np->tcph->th_ack = htonl(ssn->server.last_ack);
|
|
|
|
/* to client */
|
|
} else {
|
|
np->tcph->th_sport = htons(f->dp);
|
|
np->tcph->th_dport = htons(f->sp);
|
|
|
|
np->tcph->th_seq = htonl(ssn->server.next_seq);
|
|
np->tcph->th_ack = htonl(ssn->client.last_ack);
|
|
}
|
|
|
|
/* use parent time stamp */
|
|
memcpy(&np->ts, &parent->ts, sizeof(struct timeval));
|
|
|
|
SCLogDebug("np %p", np);
|
|
PacketEnqueueNoLock(pq, np);
|
|
|
|
StatsIncr(tv, stt->counter_tcp_pseudo);
|
|
SCReturn;
|
|
error:
|
|
FlowDeReference(&np->flow);
|
|
SCReturn;
|
|
}
|
|
|
|
/** \brief create packets in both directions to flush out logging
|
|
* and detection before switching protocols.
|
|
* In IDS mode, create first in packet dir, 2nd in opposing
|
|
* In IPS mode, do the reverse.
|
|
* Flag TCP engine that data needs to be inspected regardless
|
|
* of how far we are wrt inspect limits.
|
|
*/
|
|
void StreamTcpDetectLogFlush(ThreadVars *tv, StreamTcpThread *stt, Flow *f, Packet *p,
|
|
PacketQueueNoLock *pq)
|
|
{
|
|
TcpSession *ssn = f->protoctx;
|
|
ssn->client.flags |= STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
ssn->server.flags |= STREAMTCP_STREAM_FLAG_TRIGGER_RAW;
|
|
bool ts = PKT_IS_TOSERVER(p) ? true : false;
|
|
ts ^= StreamTcpInlineMode();
|
|
StreamTcpPseudoPacketCreateDetectLogFlush(tv, stt, p, ssn, pq, ts^0);
|
|
StreamTcpPseudoPacketCreateDetectLogFlush(tv, stt, p, ssn, pq, ts^1);
|
|
}
|
|
|
|
/**
|
|
* \brief Run callback function on each TCP segment
|
|
*
|
|
* \note when stream engine is running in inline mode all segments are used,
|
|
* in IDS/non-inline mode only ack'd segments are iterated.
|
|
*
|
|
* \note Must be called under flow lock.
|
|
*
|
|
* \return -1 in case of error, the number of segment in case of success
|
|
*
|
|
*/
|
|
int StreamTcpSegmentForEach(const Packet *p, uint8_t flag, StreamSegmentCallback CallbackFunc, void *data)
|
|
{
|
|
TcpSession *ssn = NULL;
|
|
TcpStream *stream = NULL;
|
|
int ret = 0;
|
|
int cnt = 0;
|
|
|
|
if (p->flow == NULL)
|
|
return 0;
|
|
|
|
ssn = (TcpSession *)p->flow->protoctx;
|
|
|
|
if (ssn == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (flag & FLOW_PKT_TOSERVER) {
|
|
stream = &(ssn->server);
|
|
} else {
|
|
stream = &(ssn->client);
|
|
}
|
|
|
|
/* for IDS, return ack'd segments. For IPS all. */
|
|
TcpSegment *seg;
|
|
RB_FOREACH(seg, TCPSEG, &stream->seg_tree) {
|
|
if (!((stream_config.flags & STREAMTCP_INIT_FLAG_INLINE)
|
|
|| SEQ_LT(seg->seq, stream->last_ack)))
|
|
break;
|
|
|
|
const uint8_t *seg_data;
|
|
uint32_t seg_datalen;
|
|
StreamingBufferSegmentGetData(&stream->sb, &seg->sbseg, &seg_data, &seg_datalen);
|
|
|
|
ret = CallbackFunc(p, data, seg_data, seg_datalen);
|
|
if (ret != 1) {
|
|
SCLogDebug("Callback function has failed");
|
|
return -1;
|
|
}
|
|
|
|
cnt++;
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
int StreamTcpBypassEnabled(void)
|
|
{
|
|
return (stream_config.flags & STREAMTCP_INIT_FLAG_BYPASS);
|
|
}
|
|
|
|
/**
|
|
* \brief See if stream engine is operating in inline mode
|
|
*
|
|
* \retval 0 no
|
|
* \retval 1 yes
|
|
*/
|
|
int StreamTcpInlineMode(void)
|
|
{
|
|
return (stream_config.flags & STREAMTCP_INIT_FLAG_INLINE) ? 1 : 0;
|
|
}
|
|
|
|
|
|
void TcpSessionSetReassemblyDepth(TcpSession *ssn, uint32_t size)
|
|
{
|
|
if (size > ssn->reassembly_depth || size == 0) {
|
|
ssn->reassembly_depth = size;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const char *StreamTcpStateAsString(const enum TcpState state)
|
|
{
|
|
const char *tcp_state = NULL;
|
|
switch (state) {
|
|
case TCP_NONE:
|
|
tcp_state = "none";
|
|
break;
|
|
case TCP_LISTEN:
|
|
tcp_state = "listen";
|
|
break;
|
|
case TCP_SYN_SENT:
|
|
tcp_state = "syn_sent";
|
|
break;
|
|
case TCP_SYN_RECV:
|
|
tcp_state = "syn_recv";
|
|
break;
|
|
case TCP_ESTABLISHED:
|
|
tcp_state = "established";
|
|
break;
|
|
case TCP_FIN_WAIT1:
|
|
tcp_state = "fin_wait1";
|
|
break;
|
|
case TCP_FIN_WAIT2:
|
|
tcp_state = "fin_wait2";
|
|
break;
|
|
case TCP_TIME_WAIT:
|
|
tcp_state = "time_wait";
|
|
break;
|
|
case TCP_LAST_ACK:
|
|
tcp_state = "last_ack";
|
|
break;
|
|
case TCP_CLOSE_WAIT:
|
|
tcp_state = "close_wait";
|
|
break;
|
|
case TCP_CLOSING:
|
|
tcp_state = "closing";
|
|
break;
|
|
case TCP_CLOSED:
|
|
tcp_state = "closed";
|
|
break;
|
|
}
|
|
return tcp_state;
|
|
}
|
|
|
|
const char *StreamTcpSsnStateAsString(const TcpSession *ssn)
|
|
{
|
|
if (ssn == NULL)
|
|
return NULL;
|
|
return StreamTcpStateAsString(ssn->state);
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
#include "tests/stream-tcp.c"
|
|
#endif
|