app-layer-expectation: expectation system

This patch provides a working expectation system. This will allow
suricata to have a way to identify parallel connections opened by
a protocol such as FTP.

Expectation are a chained list and there is a cleaning by timeout
of the entries.

This patch also defined a counter of expectations that is also
used to check if we need to query IPPairs. This way we only query
the IPPairs store if we have an expectation.
pull/3108/head
Eric Leblond 8 years ago committed by Victor Julien
parent 31a0783865
commit 140f8baed9

@ -26,6 +26,7 @@ app-layer-dns-udp-rust.c app-layer-dns-udp-rust.h \
app-layer-enip.c app-layer-enip.h \
app-layer-enip-common.c app-layer-enip-common.h \
app-layer-events.c app-layer-events.h \
app-layer-expectation.c app-layer-expectation.h \
app-layer-ftp.c app-layer-ftp.h \
app-layer-htp-body.c app-layer-htp-body.h \
app-layer-htp.c app-layer-htp.h \

@ -55,6 +55,7 @@
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "app-layer-detect-proto.h"
#include "app-layer-expectation.h"
#include "conf.h"
#include "util-memcmp.h"
@ -170,6 +171,9 @@ struct AppLayerProtoDetectThreadCtx_ {
/* The global app layer proto detection context. */
static AppLayerProtoDetectCtx alpd_ctx;
static void AppLayerProtoDetectPEGetIpprotos(AppProto alproto,
uint8_t *ipprotos);
/***** Static Internal Calls: Protocol Retrieval *****/
/** \internal
@ -314,6 +318,24 @@ static AppLayerProtoDetectProbingParserPort *AppLayerProtoDetectGetProbingParser
SCReturnPtr(pp_port, "AppLayerProtoDetectProbingParserPort *");
}
/**
* \brief Call the probing expectation to see if there is some for this flow.
*
*/
static AppProto AppLayerProtoDetectPEGetProto(Flow *f, uint8_t ipproto,
uint8_t direction)
{
AppProto alproto = ALPROTO_UNKNOWN;
SCLogDebug("expectation check for %p (dir %d)", f, direction);
FLOW_SET_PE_DONE(f, direction);
alproto = AppLayerExpectationHandle(f, direction);
return alproto;
}
/**
* \brief Call the probing parser if it exists for this flow.
*
@ -1329,8 +1351,17 @@ AppProto AppLayerProtoDetectGetProto(AppLayerProtoDetectThreadCtx *tctx,
}
}
if (!FLOW_IS_PP_DONE(f, direction))
alproto = AppLayerProtoDetectPPGetProto(f, buf, buflen, ipproto, direction);
if (!FLOW_IS_PP_DONE(f, direction)) {
alproto = AppLayerProtoDetectPPGetProto(f, buf, buflen,
ipproto, direction);
if (alproto != ALPROTO_UNKNOWN)
goto end;
}
/* Look if flow can be found in expectation list */
if (!FLOW_IS_PE_DONE(f, direction)) {
alproto = AppLayerProtoDetectPEGetProto(f, ipproto, direction);
}
end:
SCReturnUInt(alproto);
@ -1573,6 +1604,9 @@ int AppLayerProtoDetectSetup(void)
MpmInitCtx(&alpd_ctx.ctx_ipp[i].ctx_pm[j].mpm_ctx, mpm_matcher);
}
}
AppLayerExpectationSetup();
SCReturnInt(0);
}
@ -1662,6 +1696,8 @@ void AppLayerProtoDetectReset(Flow *f)
FLOW_RESET_PM_DONE(f, STREAM_TOCLIENT);
FLOW_RESET_PP_DONE(f, STREAM_TOSERVER);
FLOW_RESET_PP_DONE(f, STREAM_TOCLIENT);
FLOW_RESET_PE_DONE(f, STREAM_TOSERVER);
FLOW_RESET_PE_DONE(f, STREAM_TOCLIENT);
f->probing_parser_toserver_alproto_masks = 0;
f->probing_parser_toclient_alproto_masks = 0;
@ -1827,6 +1863,7 @@ void AppLayerProtoDetectSupportedIpprotos(AppProto alproto, uint8_t *ipprotos)
AppLayerProtoDetectPMGetIpprotos(alproto, ipprotos);
AppLayerProtoDetectPPGetIpprotos(alproto, ipprotos);
AppLayerProtoDetectPEGetIpprotos(alproto, ipprotos);
SCReturn;
}
@ -1869,6 +1906,30 @@ void AppLayerProtoDetectSupportedAppProtocols(AppProto *alprotos)
SCReturn;
}
uint8_t expectation_proto[ALPROTO_MAX];
static void AppLayerProtoDetectPEGetIpprotos(AppProto alproto,
uint8_t *ipprotos)
{
if (expectation_proto[alproto] == IPPROTO_TCP) {
ipprotos[IPPROTO_TCP / 8] |= 1 << (IPPROTO_TCP % 8);
}
if (expectation_proto[alproto] == IPPROTO_UDP) {
ipprotos[IPPROTO_UDP / 8] |= 1 << (IPPROTO_UDP % 8);
}
}
void AppLayerRegisterExpectationProto(uint8_t proto, AppProto alproto)
{
if (expectation_proto[alproto]) {
if (proto != expectation_proto[alproto]) {
SCLogError(SC_ERR_NOT_SUPPORTED,
"Expectation on 2 IP protocols are not supported");
}
}
expectation_proto[alproto] = proto;
}
/***** Unittests *****/
#ifdef UNITTESTS

@ -181,6 +181,8 @@ AppProto AppLayerProtoDetectGetProtoByName(const char *alproto_name);
const char *AppLayerProtoDetectGetProtoName(AppProto alproto);
void AppLayerProtoDetectSupportedAppProtocols(AppProto *alprotos);
void AppLayerRegisterExpectationProto(uint8_t proto, AppProto alproto);
/***** Unittests *****/
#ifdef UNITTESTS

@ -0,0 +1,331 @@
/* Copyright (C) 2017 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.
*/
/**
* \defgroup applayerexpectation Application Layer Expectation
*
* Handling of dynamic parallel connection for application layer similar
* to FTP.
*
* @{
*
* Some protocols like FTP create dynamic parallel flow (called expectation). In
* order to assign a application layer protocol to these expectation, Suricata
* needs to parse message of the initial protocol and create and maintain a list
* of expected flow.
*
* Application layers must use the here described API to implement this mechanism.
*
* When parsing a application layer message describing a parallel flow, the
* application layer can call AppLayerExpectationCreate() to declare an
* expectation. By doing that the next flow coming with corresponding IP parameters
* will be assigned the specified application layer. The resulting Flow will
* also have a Flow storage set that can be retrieved at index
* AppLayerExpectationGetDataId():
*
* ```
* data = (char *)FlowGetStorageById(f, AppLayerExpectationGetDataId());
* ```
* This storage can be used to store information that are only available in the
* parent connection and could be useful in the parent connection. For instance
* this is used by the FTP protocol to propagate information such as file name
* and ftp operation to the FTP data connection.
*/
/**
* \file
*
* \author Eric Leblond <eric@regit.org>
*/
#include "suricata-common.h"
#include "debug.h"
#include "ippair-storage.h"
#include "flow-storage.h"
#include "app-layer-expectation.h"
#include "util-print.h"
static int g_expectation_id = -1;
static int g_expectation_data_id = -1;
SC_ATOMIC_DECLARE(uint32_t, expectation_count);
#define EXPECTATION_TIMEOUT 30
typedef struct Expectation_ {
struct timeval ts;
Port sp;
Port dp;
AppProto alproto;
int direction;
void *data;
struct Expectation_ *next;
} Expectation;
typedef struct ExpectationData_ {
/** Start of Expectation Data structure must be a pointer
* to free function. Set to NULL to use SCFree() */
void (*DFree)(void *);
} ExpectationData;
static void ExpectationDataFree(void *e)
{
SCLogDebug("Free expectation data");
ExpectationData *ed = (ExpectationData *) e;
if (ed->DFree) {
ed->DFree(e);
} else {
SCFree(e);
}
}
static void ExpectationListFree(void *e)
{
Expectation *exp = (Expectation *)e;
Expectation *lexp;
while (exp) {
lexp = exp->next;
if (exp->data) {
ExpectationData *expdata = (ExpectationData *) exp->data;
if (expdata->DFree) {
expdata->DFree(exp->data);
} else {
SCFree(exp->data);
}
}
SCFree(exp);
exp = lexp;
}
}
uint64_t ExpectationGetCounter(void)
{
uint64_t x = SC_ATOMIC_GET(expectation_count);
return x;
}
void AppLayerExpectationSetup(void)
{
g_expectation_id = IPPairStorageRegister("expectation", sizeof(void *), NULL, ExpectationListFree);
g_expectation_data_id = FlowStorageRegister("expectation", sizeof(void *), NULL, ExpectationDataFree);
SC_ATOMIC_INIT(expectation_count);
}
static inline int GetFlowAddresses(Flow *f, Address *ip_src, Address *ip_dst)
{
memset(ip_src, 0, sizeof(*ip_src));
memset(ip_dst, 0, sizeof(*ip_dst));
if (FLOW_IS_IPV4(f)) {
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, ip_src);
FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, ip_dst);
} else if (FLOW_IS_IPV6(f)) {
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->src, ip_src);
FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->dst, ip_dst);
} else {
return -1;
}
return 0;
}
static Expectation *AppLayerExpectationLookup(Flow *f, int direction, IPPair **ipp)
{
Address ip_src, ip_dst;
if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1)
return NULL;
*ipp = IPPairLookupIPPairFromHash(&ip_src, &ip_dst);
if (*ipp == NULL) {
return NULL;
}
return IPPairGetStorageById(*ipp, g_expectation_id);
}
/**
* Create an entry in expectation list
*
* Create a expectation from an existing Flow. Currently, only Flow between
* the two original IP addresses are supported.
*
* \param f a pointer to the original Flow
* \param direction the direction of the data in the expectation flow
* \param src source port of the expected flow, use 0 for any
* \param dst destination port of the expected flow, use 0 for any
* \param alproto the protocol that need to be set on the expected flow
* \param data pointer to data that will be attached to the expected flow
*
* \return -1 if error
* \return 0 if success
*/
int AppLayerExpectationCreate(Flow *f, int direction, Port src, Port dst,
AppProto alproto, void *data)
{
Expectation *iexp = NULL;
IPPair *ipp;
Address ip_src, ip_dst;
Expectation *exp = SCCalloc(1, sizeof(*exp));
if (exp == NULL)
return -1;
exp->sp = src;
exp->dp = dst;
exp->alproto = alproto;
exp->ts = f->lastts;
exp->data = data;
exp->direction = direction;
if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1)
goto error;
ipp = IPPairGetIPPairFromHash(&ip_src, &ip_dst);
if (ipp == NULL)
goto error;
iexp = IPPairGetStorageById(ipp, g_expectation_id);
exp->next = iexp;
IPPairSetStorageById(ipp, g_expectation_id, exp);
SC_ATOMIC_ADD(expectation_count, 1);
/* As we are creating the expectation, we release lock on IPPair without
* setting the ref count to 0. This way the IPPair will be kept till
* cleanup */
IPPairUnlock(ipp);
return 0;
error:
SCFree(exp);
return -1;
}
/**
* Return Flow storage identifier corresponding to expectation data
*
* \return expectation data identifier
*/
int AppLayerExpectationGetDataId(void)
{
return g_expectation_data_id;
}
/**
*
* Remove expectation and return next one
*
* \param ipp an IPPair
* \param pexp pointer to previous Expectation
* \param exp pointer to Expectation to remove
* \param lexp pointer to head of Expectation ist
* \return expectation
*/
static Expectation * RemoveExpectationAndGetNext(IPPair *ipp,
Expectation *pexp, Expectation *exp,
Expectation *lexp)
{
/* we remove the object so we get ref count down by 1 to remove reference
* hold by the expectation
*/
(void) IPPairDecrUsecnt(ipp);
SC_ATOMIC_SUB(expectation_count, 1);
if (pexp == NULL) {
IPPairSetStorageById(ipp, g_expectation_id, lexp);
} else {
pexp->next = lexp;
}
if (exp->data) {
ExpectationData *expdata = (ExpectationData *)exp->data;
if (expdata->DFree) {
expdata->DFree(exp->data);
} else {
SCFree(exp->data);
}
}
SCFree(exp);
return lexp;
}
/**
* Function doing a lookup in expectation list and updating Flow if needed.
*
* This function lookup for a existing expectation that could match the Flow.
* If found and if the expectation contains data it store the data in the
* expectation storage of the Flow.
*
* \return an AppProto value if found
* \return ALPROTO_UNKNOWN if not found
*/
AppProto AppLayerExpectationHandle(Flow *f, int direction)
{
AppProto alproto = ALPROTO_UNKNOWN;
IPPair *ipp = NULL;
Expectation *lexp = NULL;
Expectation *pexp = NULL;
int x = SC_ATOMIC_GET(expectation_count);
if (x == 0) {
return ALPROTO_UNKNOWN;
}
/* Call will take reference of the ip pair in 'ipp' */
Expectation *exp = AppLayerExpectationLookup(f, direction, &ipp);
if (exp == NULL)
goto out;
time_t ctime = f->lastts.tv_sec;
pexp = NULL;
while (exp) {
lexp = exp->next;
if ( (exp->direction & direction) &&
((exp->sp == 0) || (exp->sp == f->sp)) &&
((exp->dp == 0) || (exp->dp == f->dp))) {
alproto = exp->alproto;
f->alproto_ts = alproto;
f->alproto_tc = alproto;
void *fdata = FlowGetStorageById(f, g_expectation_id);
if (fdata) {
/* We already have an expectation so let's clean this one */
ExpectationDataFree(exp->data);
} else {
/* Transfer ownership of Expectation data to the Flow */
if (FlowSetStorageById(f, g_expectation_data_id, exp->data) != 0) {
SCLogDebug("Unable to set flow storage");
}
}
exp->data = NULL;
exp = RemoveExpectationAndGetNext(ipp, pexp, exp, lexp);
continue;
}
/* Cleaning remove old entries */
if (exp && (ctime > exp->ts.tv_sec + EXPECTATION_TIMEOUT)) {
exp = RemoveExpectationAndGetNext(ipp, pexp, exp, lexp);
continue;
}
pexp = exp;
exp = lexp;
}
out:
if (ipp)
IPPairRelease(ipp);
return alproto;
}
/**
* @}
*/

@ -0,0 +1,35 @@
/* Copyright (C) 2017 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 Eric Leblond <eric@regit.org>
*/
#ifndef __APP_LAYER_EXPECTATION__H__
#define __APP_LAYER_EXPECTATION__H__
void AppLayerExpectationSetup(void);
int AppLayerExpectationCreate(Flow *f, int direction, Port src, Port dst,
AppProto alproto, void *data);
AppProto AppLayerExpectationHandle(Flow *f, int direction);
int AppLayerExpectationGetDataId(void);
uint64_t ExpectationGetCounter(void);
#endif /* __APP_LAYER_EXPECTATION__H__ */

@ -29,6 +29,7 @@
#include "app-layer.h"
#include "app-layer-parser.h"
#include "app-layer-protos.h"
#include "app-layer-expectation.h"
#include "app-layer-detect-proto.h"
#include "stream-tcp-reassemble.h"
#include "stream-tcp-private.h"
@ -37,6 +38,7 @@
#include "flow.h"
#include "flow-util.h"
#include "flow-private.h"
#include "ippair.h"
#include "util-debug.h"
#include "util-print.h"
@ -415,6 +417,7 @@ static int TCPProtoDetect(ThreadVars *tv,
StreamTcpResetStreamFlagAppProtoDetectionCompleted(stream);
FLOW_RESET_PP_DONE(f, flags);
FLOW_RESET_PM_DONE(f, flags);
FLOW_RESET_PE_DONE(f, flags);
goto failure;
}
}
@ -830,6 +833,7 @@ void AppLayerRegisterGlobalCounters(void)
StatsRegisterGlobalCounter("dns.memcap_global", DNSMemcapGetMemcapGlobalCounter);
StatsRegisterGlobalCounter("http.memuse", HTPMemuseGlobalCounter);
StatsRegisterGlobalCounter("http.memcap", HTPMemcapGlobalCounter);
StatsRegisterGlobalCounter("app_layer.expectations", ExpectationGetCounter);
}
#define IPPROTOS_MAX 2
@ -948,6 +952,7 @@ void AppLayerDeSetupCounters()
p->tcph = &tcph;\
\
StreamTcpInitConfig(TRUE);\
IPPairInitConfig(TRUE); \
StreamTcpThreadInit(&tv, NULL, (void **)&stt);\
\
/* handshake */\

@ -79,22 +79,26 @@ typedef struct AppLayerParserState_ AppLayerParserState;
#define FLOW_TS_PM_ALPROTO_DETECT_DONE BIT_U32(13)
/** Probing parser alproto detection done */
#define FLOW_TS_PP_ALPROTO_DETECT_DONE BIT_U32(14)
/** Expectation alproto detection done */
#define FLOW_TS_PE_ALPROTO_DETECT_DONE BIT_U32(15)
/** Pattern matcher alproto detection done */
#define FLOW_TC_PM_ALPROTO_DETECT_DONE BIT_U32(15)
#define FLOW_TC_PM_ALPROTO_DETECT_DONE BIT_U32(16)
/** Probing parser alproto detection done */
#define FLOW_TC_PP_ALPROTO_DETECT_DONE BIT_U32(16)
#define FLOW_TIMEOUT_REASSEMBLY_DONE BIT_U32(17)
#define FLOW_TC_PP_ALPROTO_DETECT_DONE BIT_U32(17)
/** Expectation alproto detection done */
#define FLOW_TC_PE_ALPROTO_DETECT_DONE BIT_U32(18)
#define FLOW_TIMEOUT_REASSEMBLY_DONE BIT_U32(19)
/** flow is ipv4 */
#define FLOW_IPV4 BIT_U32(18)
#define FLOW_IPV4 BIT_U32(20)
/** flow is ipv6 */
#define FLOW_IPV6 BIT_U32(19)
#define FLOW_IPV6 BIT_U32(21)
#define FLOW_PROTO_DETECT_TS_DONE BIT_U32(20)
#define FLOW_PROTO_DETECT_TC_DONE BIT_U32(21)
#define FLOW_PROTO_DETECT_TS_DONE BIT_U32(22)
#define FLOW_PROTO_DETECT_TC_DONE BIT_U32(23)
/** Indicate that alproto detection for flow should be done again */
#define FLOW_CHANGE_PROTO BIT_U32(22)
#define FLOW_CHANGE_PROTO BIT_U32(24)
/* File flags */
@ -233,12 +237,15 @@ typedef struct AppLayerParserState_ AppLayerParserState;
#define FLOW_IS_PM_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags & FLOW_TS_PM_ALPROTO_DETECT_DONE) : ((f)->flags & FLOW_TC_PM_ALPROTO_DETECT_DONE))
#define FLOW_IS_PP_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags & FLOW_TS_PP_ALPROTO_DETECT_DONE) : ((f)->flags & FLOW_TC_PP_ALPROTO_DETECT_DONE))
#define FLOW_IS_PE_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags & FLOW_TS_PE_ALPROTO_DETECT_DONE) : ((f)->flags & FLOW_TC_PE_ALPROTO_DETECT_DONE))
#define FLOW_SET_PM_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags |= FLOW_TS_PM_ALPROTO_DETECT_DONE) : ((f)->flags |= FLOW_TC_PM_ALPROTO_DETECT_DONE))
#define FLOW_SET_PP_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags |= FLOW_TS_PP_ALPROTO_DETECT_DONE) : ((f)->flags |= FLOW_TC_PP_ALPROTO_DETECT_DONE))
#define FLOW_SET_PE_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags |= FLOW_TS_PE_ALPROTO_DETECT_DONE) : ((f)->flags |= FLOW_TC_PE_ALPROTO_DETECT_DONE))
#define FLOW_RESET_PM_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags &= ~FLOW_TS_PM_ALPROTO_DETECT_DONE) : ((f)->flags &= ~FLOW_TC_PM_ALPROTO_DETECT_DONE))
#define FLOW_RESET_PP_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags &= ~FLOW_TS_PP_ALPROTO_DETECT_DONE) : ((f)->flags &= ~FLOW_TC_PP_ALPROTO_DETECT_DONE))
#define FLOW_RESET_PE_DONE(f, dir) (((dir) & STREAM_TOSERVER) ? ((f)->flags &= ~FLOW_TS_PE_ALPROTO_DETECT_DONE) : ((f)->flags &= ~FLOW_TC_PE_ALPROTO_DETECT_DONE))
/* global flow config */
typedef struct FlowCnf_

@ -35,6 +35,7 @@
#include "util-unittest.h"
#include "util-unittest-helper.h"
#include "ippair.h"
#ifdef UNITTESTS
@ -43,6 +44,7 @@
void StreamTcpUTInit(TcpReassemblyThreadCtx **ra_ctx)
{
StreamTcpInitConfig(TRUE);
IPPairInitConfig(TRUE);
*ra_ctx = StreamTcpReassembleInitThreadCtx(NULL);
}

Loading…
Cancel
Save