Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

app-layer gap handling - v6.1 #2696

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app-layer-dns-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ typedef struct DNSState_ {
uint16_t offset;
uint16_t record_len;
uint8_t *buffer;
uint8_t gap; /**< Flag set when a gap has occurred. */
} DNSState;

#define DNS_CONFIG_DEFAULT_REQUEST_FLOOD 500
Expand Down
46 changes: 46 additions & 0 deletions src/app-layer-dns-tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ struct DNSTcpHeader_ {
} __attribute__((__packed__));
typedef struct DNSTcpHeader_ DNSTcpHeader;

static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen,
uint32_t *offset);

/** \internal
* \param input_len at least enough for the DNSTcpHeader
*/
Expand Down Expand Up @@ -288,6 +291,13 @@ static int DNSTCPRequestParse(Flow *f, void *dstate,
DNSState *dns_state = (DNSState *)dstate;
SCLogDebug("starting %u", input_len);

if (input == NULL && input_len > 0) {
SCLogDebug("Input is NULL, but len is %"PRIu32": must be a gap.",
input_len);
dns_state->gap = 1;
SCReturnInt(1);
}

if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
SCReturnInt(1);
}
Expand All @@ -301,6 +311,18 @@ static int DNSTCPRequestParse(Flow *f, void *dstate,
goto insufficient_data;
}

/* Clear gap state. */
if (dns_state->gap) {
if (DNSTcpProbingParser(input, input_len, NULL) == ALPROTO_DNS) {
SCLogDebug("New data probed as DNS, clearing gap state.");
BufferReset(dns_state);
dns_state->gap = 0;
} else {
SCLogDebug("Unable to sync DNS parser, leaving gap state.");
SCReturnInt(1);
}
}

next_record:
/* if this is the beginning of a record, we need at least the header */
if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) {
Expand Down Expand Up @@ -508,6 +530,13 @@ static int DNSTCPResponseParse(Flow *f, void *dstate,
{
DNSState *dns_state = (DNSState *)dstate;

if (input == NULL && input_len > 0) {
SCLogDebug("Input is NULL, but len is %"PRIu32": must be a gap.",
input_len);
dns_state->gap = 1;
SCReturnInt(1);
}

if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
SCReturnInt(1);
}
Expand All @@ -521,6 +550,18 @@ static int DNSTCPResponseParse(Flow *f, void *dstate,
goto insufficient_data;
}

/* Clear gap state. */
if (dns_state->gap) {
if (DNSTcpProbingParser(input, input_len, NULL) == ALPROTO_DNS) {
SCLogDebug("New data probed as DNS, clearing gap state.");
BufferReset(dns_state);
dns_state->gap = 0;
} else {
SCLogDebug("Unable to sync DNS parser, leaving gap state.");
SCReturnInt(1);
}
}

next_record:
/* if this is the beginning of a record, we need at least the header */
if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) {
Expand Down Expand Up @@ -712,6 +753,11 @@ void RegisterDNSTCPParsers(void)
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_DNS,
DNSGetAlstateProgressCompletionStatus);
DNSAppLayerRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNS);

/* This parser accepts gaps. */
AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_DNS,
APP_LAYER_PARSER_OPT_ACCEPT_GAPS);

} else {
SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
"still on.", proto_name);
Expand Down
28 changes: 21 additions & 7 deletions src/app-layer-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ typedef struct AppLayerParserProtoCtx_
* STREAM_TOSERVER, STREAM_TOCLIENT */
uint8_t first_data_dir;

/* Option flags such as supporting gaps or not. */
uint64_t flags;

#ifdef UNITTESTS
void (*RegisterUnittests)(void);
#endif
Expand Down Expand Up @@ -364,6 +367,16 @@ void AppLayerParserRegisterParserAcceptableDataDirection(uint8_t ipproto, AppPro
SCReturn;
}

void AppLayerParserRegisterOptionFlags(uint8_t ipproto, AppProto alproto,
uint64_t flags)
{
SCEnter();

alp_ctx.ctxs[FlowGetProtoMapping(ipproto)][alproto].flags |= flags;

SCReturn;
}

void AppLayerParserRegisterStateFuncs(uint8_t ipproto, AppProto alproto,
void *(*StateAlloc)(void),
void (*StateFree)(void *))
Expand Down Expand Up @@ -980,14 +993,15 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow
if (p->StateAlloc == NULL)
goto end;

/* Do this check before calling AppLayerParse */
if (flags & STREAM_GAP) {
SCLogDebug("stream gap detected (missing packets), "
"this is not yet supported.");

if (f->alstate != NULL)
AppLayerParserStreamTruncated(f->proto, alproto, f->alstate, flags);
goto error;
if (!(p->flags & APP_LAYER_PARSER_OPT_ACCEPT_GAPS)) {
SCLogDebug("app-layer parser does not accept gaps");
if (f->alstate != NULL) {
AppLayerParserStreamTruncated(f->proto, alproto, f->alstate,
flags);
}
goto error;
}
}

/* Get the parser state (if any) */
Expand Down
6 changes: 6 additions & 0 deletions src/app-layer-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@
#include "util-file.h"
#include "stream-tcp-private.h"

/* Flags for AppLayerParserState. */
#define APP_LAYER_PARSER_EOF 0x01
#define APP_LAYER_PARSER_NO_INSPECTION 0x02
#define APP_LAYER_PARSER_NO_REASSEMBLY 0x04
#define APP_LAYER_PARSER_NO_INSPECTION_PAYLOAD 0x08

/* Flags for AppLayerParserProtoCtx. */
#define APP_LAYER_PARSER_OPT_ACCEPT_GAPS BIT_U64(0)

int AppLayerParserProtoIsRegistered(uint8_t ipproto, AppProto alproto);

/***** transaction handling *****/
Expand Down Expand Up @@ -115,6 +119,8 @@ int AppLayerParserRegisterParser(uint8_t ipproto, AppProto alproto,
void AppLayerParserRegisterParserAcceptableDataDirection(uint8_t ipproto,
AppProto alproto,
uint8_t direction);
void AppLayerParserRegisterOptionFlags(uint8_t ipproto, AppProto alproto,
uint64_t flags);
void AppLayerParserRegisterStateFuncs(uint8_t ipproto, AppProto alproto,
void *(*StateAlloc)(void),
void (*StateFree)(void *));
Expand Down
36 changes: 22 additions & 14 deletions src/stream-tcp-reassemble.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,12 @@ static void GetAppBuffer(TcpStream *stream, const uint8_t **data, uint32_t *data
}
}

static inline bool CheckGap(TcpStream *stream, Packet *p)
/**
* \brief Check if there is a gap in the application data.
*
* \retval the size of the gap in bytes, 0 if no gap.
*/
static inline uint32_t CheckGap(TcpStream *stream, Packet *p)
{
const uint64_t app_progress = STREAM_APP_PROGRESS(stream);
uint64_t last_ack_abs = STREAM_BASE_OFFSET(stream);
Expand All @@ -915,16 +920,16 @@ static inline bool CheckGap(TcpStream *stream, Packet *p)
if (next_seq_abs > app_progress+1) {
/* fall through */
} else {
return false;
return 0;
}
}
}

SCLogDebug("packet %u GAP! last_ack_abs %u > app_progress %u, at no data.", (uint)p->pcap_cnt, (uint)last_ack_abs, (uint)app_progress);
return true;
return delta;
}
}
return false;
return 0;
}

/** \internal
Expand All @@ -934,9 +939,9 @@ static inline bool CheckGap(TcpStream *stream, Packet *p)
static int ReassembleUpdateAppLayer (ThreadVars *tv,
TcpReassemblyThreadCtx *ra_ctx,
TcpSession *ssn, TcpStream *stream,
Packet *p, enum StreamUpdateDir dir)
Packet *p, enum StreamUpdateDir dir, uint32_t gap)
{
const uint64_t app_progress = STREAM_APP_PROGRESS(stream);
const uint64_t app_progress = STREAM_APP_PROGRESS(stream) + gap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of adding the gap here I think it makes more sense to update app_progress right after the GAP call to the applayer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll have to rebase against your changes then make this change.

Currently I am never able to get the gap check below this to trigger. It was also the gap check before this which triggered, so the gap had to be used here to get at the next data (as the gap call had already been made to the app-layer before calling ReassembleUpdateAppLayer).


SCLogDebug("app progress %"PRIu64, app_progress);
SCLogDebug("last_ack %u, base_seq %u", stream->last_ack, stream->base_seq);
Expand All @@ -945,11 +950,12 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
uint32_t mydata_len;
GetAppBuffer(stream, &mydata, &mydata_len, app_progress);
if (mydata == NULL || mydata_len == 0) {
if (CheckGap(stream, p)) {
/* Only check for gap and signal if not already done. */
if (gap == 0 && ((gap = CheckGap(stream, p) > 0))) {
/* send gap signal */
SCLogDebug("sending GAP to app-layer");
AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
NULL, 0,
NULL, gap,
StreamGetAppLayerFlags(ssn, stream, p, dir)|STREAM_GAP);
AppLayerProfilingStore(ra_ctx->app_tctx, p);

Expand Down Expand Up @@ -1005,7 +1011,7 @@ static int ReassembleUpdateAppLayer (ThreadVars *tv,
SCLogDebug("app progress %"PRIu64" increasing with data len %u to %"PRIu64,
app_progress, mydata_len, app_progress + mydata_len);

stream->app_progress_rel += mydata_len;
stream->app_progress_rel += mydata_len + gap;
SCLogDebug("app progress now %"PRIu64, STREAM_APP_PROGRESS(stream));
} else {
SCLogDebug("NOT UPDATED app progress still %"PRIu64, app_progress);
Expand All @@ -1032,6 +1038,8 @@ int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
{
SCEnter();

uint32_t gap = 0;

/* this function can be directly called by app layer protocol
* detection. */
if ((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) ||
Expand All @@ -1043,7 +1051,6 @@ int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
SCReturnInt(0);
}


SCLogDebug("stream->seg_list %p", stream->seg_list);
#ifdef DEBUG
PrintList(stream->seg_list);
Expand Down Expand Up @@ -1081,12 +1088,15 @@ int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
"(base %u, isn %u, last_ack %u => diff %u) p %"PRIu64,
stream->base_seq, stream->isn, stream->last_ack,
stream->last_ack - (stream->isn + ackadd), p->pcap_cnt);
gap = stream->last_ack - (stream->isn + ackadd);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an issue when we hit this case. The seg_list continues to be NULL, so its never passed to the app-layer. Not sure yet where I should be looking here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a preview/test PR #2702 I have removed this seg_list based gap check. I think that makes your PR easier to implement as well.

} else {
gap = stream->seg_list->seq - stream->base_seq;
}

/* send gap signal */
SCLogDebug("sending GAP to app-layer");
AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream,
NULL, 0,
NULL, gap,
StreamGetAppLayerFlags(ssn, stream, p, dir)|STREAM_GAP);
AppLayerProfilingStore(ra_ctx->app_tctx, p);

Expand All @@ -1096,8 +1106,6 @@ int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,

StreamTcpSetEvent(p, STREAM_REASSEMBLY_SEQ_GAP);
StatsIncr(tv, ra_ctx->counter_tcp_reass_gap);

SCReturnInt(0);
}
}

Expand Down Expand Up @@ -1128,7 +1136,7 @@ int StreamTcpReassembleAppLayer (ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
}

/* with all that out of the way, lets update the app-layer */
return ReassembleUpdateAppLayer(tv, ra_ctx, ssn, stream, p, dir);
return ReassembleUpdateAppLayer(tv, ra_ctx, ssn, stream, p, dir, gap);
}

/** \internal
Expand Down