dnp3: fixups to work with unified json tx logger

Update DNP3 to work with a single TX logger, and just register one
logger instead of 2.

This primarily creates a TX per message instead of correlating replies
to requests, which fits the DNP3 model better, but we didn't really have
this concept nailed down when DNP3 was written.
pull/8101/head
Jason Ish 3 years ago
parent 2f9ca8bb34
commit 27672c950c

@ -490,7 +490,7 @@ static void DNP3SetEventTx(DNP3Transaction *tx, uint8_t event)
/** /**
* \brief Allocation a DNP3 transaction. * \brief Allocation a DNP3 transaction.
*/ */
static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3) static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3, bool request)
{ {
DNP3Transaction *tx = SCCalloc(1, sizeof(DNP3Transaction)); DNP3Transaction *tx = SCCalloc(1, sizeof(DNP3Transaction));
if (unlikely(tx == NULL)) { if (unlikely(tx == NULL)) {
@ -501,8 +501,8 @@ static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3)
dnp3->curr = tx; dnp3->curr = tx;
tx->dnp3 = dnp3; tx->dnp3 = dnp3;
tx->tx_num = dnp3->transaction_max; tx->tx_num = dnp3->transaction_max;
TAILQ_INIT(&tx->request_objects); tx->is_request = request;
TAILQ_INIT(&tx->response_objects); TAILQ_INIT(&tx->objects);
TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next); TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);
/* Check for flood state. */ /* Check for flood state. */
@ -872,12 +872,8 @@ static void DNP3HandleUserDataRequest(DNP3State *dnp3, const uint8_t *input,
if (!DNP3_TH_FIR(th)) { if (!DNP3_TH_FIR(th)) {
TAILQ_FOREACH(ttx, &dnp3->tx_list, next) { TAILQ_FOREACH(ttx, &dnp3->tx_list, next) {
if (ttx->request_lh.src == lh->src && if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && ttx->is_request && !ttx->done &&
ttx->request_lh.dst == lh->dst && NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) {
ttx->has_request &&
!ttx->request_done &&
NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->request_th)) == DNP3_TH_SEQ(th))
{
tx = ttx; tx = ttx;
break; break;
} }
@ -889,7 +885,7 @@ static void DNP3HandleUserDataRequest(DNP3State *dnp3, const uint8_t *input,
/* Update the saved transport header so subsequent segments /* Update the saved transport header so subsequent segments
* will be matched to this sequence number. */ * will be matched to this sequence number. */
tx->response_th = th; tx->th = th;
} }
else { else {
ah = (DNP3ApplicationHeader *)(input + sizeof(DNP3LinkHeader) + ah = (DNP3ApplicationHeader *)(input + sizeof(DNP3LinkHeader) +
@ -901,24 +897,21 @@ static void DNP3HandleUserDataRequest(DNP3State *dnp3, const uint8_t *input,
} }
/* Create a transaction. */ /* Create a transaction. */
tx = DNP3TxAlloc(dnp3); tx = DNP3TxAlloc(dnp3, true);
if (unlikely(tx == NULL)) { if (unlikely(tx == NULL)) {
return; return;
} }
tx->request_lh = *lh; tx->lh = *lh;
tx->request_th = th; tx->th = th;
tx->request_ah = *ah; tx->ah = *ah;
tx->has_request = 1;
} }
if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader), if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader),
input_len - sizeof(DNP3LinkHeader), input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) {
&tx->request_buffer, &tx->request_buffer_len)) {
/* Malformed, set event and mark as done. */ /* Malformed, set event and mark as done. */
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED); DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED);
tx->request_done = 1; tx->done = 1;
return; return;
} }
@ -927,26 +920,11 @@ static void DNP3HandleUserDataRequest(DNP3State *dnp3, const uint8_t *input,
return; return;
} }
tx->request_done = 1; tx->done = 1;
/* Some function codes do not expect a reply. */
switch (tx->request_ah.function_code) {
case DNP3_APP_FC_CONFIRM:
case DNP3_APP_FC_DIR_OPERATE_NR:
case DNP3_APP_FC_FREEZE_NR:
case DNP3_APP_FC_FREEZE_CLEAR_NR:
case DNP3_APP_FC_FREEZE_AT_TIME_NR:
case DNP3_APP_FC_AUTH_REQ_NR:
tx->response_done = 1;
default:
break;
}
if (DNP3DecodeApplicationObjects( if (DNP3DecodeApplicationObjects(tx, tx->buffer + sizeof(DNP3ApplicationHeader),
tx, tx->request_buffer + sizeof(DNP3ApplicationHeader), tx->buffer_len - sizeof(DNP3ApplicationHeader), &tx->objects)) {
tx->request_buffer_len - sizeof(DNP3ApplicationHeader), tx->complete = 1;
&tx->request_objects)) {
tx->request_complete = 1;
} }
} }
@ -971,11 +949,8 @@ static void DNP3HandleUserDataResponse(DNP3State *dnp3, const uint8_t *input,
if (!DNP3_TH_FIR(th)) { if (!DNP3_TH_FIR(th)) {
TAILQ_FOREACH(ttx, &dnp3->tx_list, next) { TAILQ_FOREACH(ttx, &dnp3->tx_list, next) {
if (ttx->response_lh.src == lh->src && if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && !ttx->is_request &&
ttx->response_lh.dst == lh->dst && !ttx->done && NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) {
ttx->has_response && !ttx->response_done &&
NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->response_th)) == DNP3_TH_SEQ(th))
{
tx = ttx; tx = ttx;
break; break;
} }
@ -987,53 +962,27 @@ static void DNP3HandleUserDataResponse(DNP3State *dnp3, const uint8_t *input,
/* Replace the transport header in the transaction with this /* Replace the transport header in the transaction with this
* one in case there are more frames. */ * one in case there are more frames. */
tx->response_th = th; tx->th = th;
} }
else { else {
ah = (DNP3ApplicationHeader *)(input + offset); ah = (DNP3ApplicationHeader *)(input + offset);
offset += sizeof(DNP3ApplicationHeader); offset += sizeof(DNP3ApplicationHeader);
iin = (DNP3InternalInd *)(input + offset); iin = (DNP3InternalInd *)(input + offset);
if (ah->function_code == DNP3_APP_FC_UNSOLICITED_RESP) { tx = DNP3TxAlloc(dnp3, false);
tx = DNP3TxAlloc(dnp3); if (unlikely(tx == NULL)) {
if (unlikely(tx == NULL)) { return;
return;
}
/* There is no request associated with an unsolicited
* response, so mark the request done as far as
* transaction state handling is concerned. */
tx->request_done = 1;
}
else {
/* Find transaction. */
TAILQ_FOREACH(ttx, &dnp3->tx_list, next) {
if (ttx->has_request &&
ttx->request_done &&
ttx->request_lh.src == lh->dst &&
ttx->request_lh.dst == lh->src &&
!ttx->has_response &&
!ttx->response_done &&
DNP3_APP_SEQ(ttx->request_ah.control) == DNP3_APP_SEQ(ah->control)) {
tx = ttx;
break;
}
}
if (tx == NULL) {
return;
}
} }
tx->lh = *lh;
tx->has_response = 1; tx->th = th;
tx->response_lh = *lh; tx->ah = *ah;
tx->response_th = th; tx->iin = *iin;
tx->response_ah = *ah;
tx->response_iin = *iin;
} }
BUG_ON(tx->is_request);
if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader), if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader),
input_len - sizeof(DNP3LinkHeader), input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) {
&tx->response_buffer, &tx->response_buffer_len)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED); DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED);
return; return;
} }
@ -1042,13 +991,12 @@ static void DNP3HandleUserDataResponse(DNP3State *dnp3, const uint8_t *input,
return; return;
} }
tx->response_done = 1; tx->done = 1;
offset = sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd); offset = sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd);
if (DNP3DecodeApplicationObjects(tx, tx->response_buffer + offset, if (DNP3DecodeApplicationObjects(
tx->response_buffer_len - offset, tx, tx->buffer + offset, tx->buffer_len - offset, &tx->objects)) {
&tx->response_objects)) { tx->complete = 1;
tx->response_complete = 1;
} }
} }
@ -1385,12 +1333,8 @@ static void DNP3TxFree(DNP3Transaction *tx)
{ {
SCEnter(); SCEnter();
if (tx->request_buffer != NULL) { if (tx->buffer != NULL) {
SCFree(tx->request_buffer); SCFree(tx->buffer);
}
if (tx->response_buffer != NULL) {
SCFree(tx->response_buffer);
} }
AppLayerDecoderEventsFreeEvents(&tx->tx_data.events); AppLayerDecoderEventsFreeEvents(&tx->tx_data.events);
@ -1399,8 +1343,7 @@ static void DNP3TxFree(DNP3Transaction *tx)
DetectEngineStateFree(tx->tx_data.de_state); DetectEngineStateFree(tx->tx_data.de_state);
} }
DNP3TxFreeObjectList(&tx->request_objects); DNP3TxFreeObjectList(&tx->objects);
DNP3TxFreeObjectList(&tx->response_objects);
SCFree(tx); SCFree(tx);
SCReturn; SCReturn;
@ -1491,11 +1434,30 @@ static int DNP3GetAlstateProgress(void *tx, uint8_t direction)
SCReturnInt(1); SCReturnInt(1);
} }
if (direction & STREAM_TOCLIENT && dnp3tx->response_done) { /* This is a unidirectional protocol.
retval = 1; *
} * If progress is being checked in the TOSERVER (request)
else if (direction & STREAM_TOSERVER && dnp3tx->request_done) { * direction, always return complete if the message is not a
retval = 1; * request, as there will never be replies on transactions created
* in the TOSERVER direction.
*
* Like wise, if progress is being checked in the TOCLIENT
* direction, requests will never be seen. So always return
* complete if the transaction is not a reply.
*
* Otherwise, if TOSERVER and transaction is a request, return
* complete if the transaction is complete. And if TOCLIENT and
* transaction is a response, return complete if the transaction
* is complete.
*/
if (direction & STREAM_TOSERVER) {
if (!dnp3tx->is_request || dnp3tx->complete) {
retval = 1;
}
} else if (direction & STREAM_TOCLIENT) {
if (dnp3tx->is_request || dnp3tx->complete) {
retval = 1;
}
} }
SCReturnInt(retval); SCReturnInt(retval);
@ -1626,6 +1588,12 @@ void RegisterDNP3Parsers(void)
AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3, AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3,
DNP3GetTxData); DNP3GetTxData);
AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData); AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData);
#if 0
/* While this parser is now fully unidirectional. setting this
* flag breaks detection at this time. */
AppLayerParserRegisterOptionFlags(
IPPROTO_TCP, ALPROTO_DNP3, APP_LAYER_PARSER_OPT_UNIDIR_TXS);
#endif
} }
else { else {
SCLogConfig("Parser disabled for protocol %s. " SCLogConfig("Parser disabled for protocol %s. "
@ -2130,17 +2098,19 @@ static int DNP3ParserTestRequestResponse(void)
FAIL_IF(tx == NULL); FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1); FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr); FAIL_IF(tx != state->curr);
FAIL_IF(tx->request_buffer == NULL); FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->request_buffer_len != 20); FAIL_IF(tx->buffer_len != 20);
FAIL_IF(tx->request_ah.function_code != DNP3_APP_FC_DIR_OPERATE); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, response, sizeof(response))); STREAM_TOCLIENT, response, sizeof(response)));
SCMutexUnlock(&flow.m); SCMutexUnlock(&flow.m);
FAIL_IF(DNP3GetTx(state, 0) != tx); DNP3Transaction *tx0 = DNP3GetTx(state, 1);
FAIL_IF(!tx->response_done); FAIL_IF(tx0 == NULL);
FAIL_IF(tx->response_buffer == NULL); FAIL_IF(tx0 == tx);
FAIL_IF(!tx0->done);
FAIL_IF(tx0->buffer == NULL);
AppLayerParserThreadCtxFree(alp_tctx); AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true); StreamTcpFreeConfig(true);
@ -2197,19 +2167,19 @@ static int DNP3ParserTestUnsolicitedResponseConfirm(void)
FAIL_IF(tx == NULL); FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1); FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr); FAIL_IF(tx != state->curr);
FAIL_IF(tx->request_buffer != NULL); FAIL_IF(!tx->done);
FAIL_IF(tx->response_buffer == NULL); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_UNSOLICITED_RESP);
FAIL_IF(!tx->response_done);
FAIL_IF(tx->response_ah.function_code != DNP3_APP_FC_UNSOLICITED_RESP);
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, confirm, sizeof(confirm))); STREAM_TOSERVER, confirm, sizeof(confirm)));
SCMutexUnlock(&flow.m); SCMutexUnlock(&flow.m);
FAIL_IF(DNP3GetTx(state, 0) != tx);
FAIL_IF(!tx->response_done); /* Confirms are ignored currently. With the move to
FAIL_IF(tx->response_buffer == NULL); unidirectional transactions it might be easy to support these
/* FAIL_IF(tx->iin1 != 0 || tx->iin2 != 0); */ now. */
DNP3Transaction *resptx = DNP3GetTx(state, 1);
FAIL_IF(resptx);
AppLayerParserThreadCtxFree(alp_tctx); AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true); StreamTcpFreeConfig(true);
@ -2219,6 +2189,9 @@ static int DNP3ParserTestUnsolicitedResponseConfirm(void)
/** /**
* \test Test flood state. * \test Test flood state.
*
* Note that flood state needs to revisited with the modification to a
* unidirectional protocol.
*/ */
static int DNP3ParserTestFlooded(void) static int DNP3ParserTestFlooded(void)
{ {
@ -2263,10 +2236,11 @@ static int DNP3ParserTestFlooded(void)
FAIL_IF(tx == NULL); FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1); FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr); FAIL_IF(tx != state->curr);
FAIL_IF(tx->request_buffer == NULL); FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->request_buffer_len != 20); FAIL_IF(tx->buffer_len != 20);
/* FAIL_IF(tx->app_function_code != DNP3_APP_FC_DIR_OPERATE); */ FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
FAIL_IF(tx->response_done); FAIL_IF_NOT(tx->done);
FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) { for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) {
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
@ -2275,7 +2249,7 @@ static int DNP3ParserTestFlooded(void)
SCMutexUnlock(&flow.m); SCMutexUnlock(&flow.m);
} }
FAIL_IF(state->flooded); FAIL_IF(state->flooded);
FAIL_IF(DNP3GetAlstateProgress(tx, 0)); FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
/* One more request should trip us into flooded state. */ /* One more request should trip us into flooded state. */
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
@ -2287,9 +2261,6 @@ static int DNP3ParserTestFlooded(void)
/* Progress for the oldest tx should return 1. */ /* Progress for the oldest tx should return 1. */
FAIL_IF(!DNP3GetAlstateProgress(tx, 0)); FAIL_IF(!DNP3GetAlstateProgress(tx, 0));
/* But progress for the current state should still return 0. */
FAIL_IF(DNP3GetAlstateProgress(state->curr, 0));
AppLayerParserThreadCtxFree(alp_tctx); AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true); StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow); FLOW_DESTROY(&flow);
@ -2393,9 +2364,9 @@ static int DNP3ParserTestPartialFrame(void)
FAIL_IF(tx == NULL); FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1); FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr); FAIL_IF(tx != state->curr);
FAIL_IF(tx->request_buffer == NULL); FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->request_buffer_len != 20); FAIL_IF(tx->buffer_len != 20);
FAIL_IF(tx->request_ah.function_code != DNP3_APP_FC_DIR_OPERATE); FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
/* Send partial response. */ /* Send partial response. */
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
@ -2405,7 +2376,8 @@ static int DNP3ParserTestPartialFrame(void)
FAIL_IF(r != 0); FAIL_IF(r != 0);
FAIL_IF(state->response_buffer.len != sizeof(response_partial1)); FAIL_IF(state->response_buffer.len != sizeof(response_partial1));
FAIL_IF(state->response_buffer.offset != 0); FAIL_IF(state->response_buffer.offset != 0);
FAIL_IF(tx->response_done); tx = DNP3GetTx(state, 1);
FAIL_IF_NOT_NULL(tx);
/* Send rest of response. */ /* Send rest of response. */
SCMutexLock(&flow.m); SCMutexLock(&flow.m);
@ -2418,10 +2390,11 @@ static int DNP3ParserTestPartialFrame(void)
FAIL_IF(state->response_buffer.len != 0); FAIL_IF(state->response_buffer.len != 0);
FAIL_IF(state->response_buffer.offset != 0); FAIL_IF(state->response_buffer.offset != 0);
/* Transaction should be replied to now. */ /* There should now be a response transaction. */
FAIL_IF(!tx->response_done); tx = DNP3GetTx(state, 1);
FAIL_IF(tx->response_buffer == NULL); FAIL_IF_NULL(tx);
FAIL_IF(tx->response_buffer_len == 0); FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->buffer_len == 0);
AppLayerParserThreadCtxFree(alp_tctx); AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true); StreamTcpFreeConfig(true);
@ -2508,9 +2481,9 @@ static int DNP3ParserTestParsePDU01(void)
FAIL_IF(pdus < 1); FAIL_IF(pdus < 1);
DNP3Transaction *dnp3tx = DNP3GetTx(dnp3state, 0); DNP3Transaction *dnp3tx = DNP3GetTx(dnp3state, 0);
FAIL_IF_NULL(dnp3tx); FAIL_IF_NULL(dnp3tx);
FAIL_IF(!dnp3tx->has_request); FAIL_IF(!dnp3tx->is_request);
FAIL_IF(TAILQ_EMPTY(&dnp3tx->request_objects)); FAIL_IF(TAILQ_EMPTY(&dnp3tx->objects));
DNP3Object *object = TAILQ_FIRST(&dnp3tx->request_objects); DNP3Object *object = TAILQ_FIRST(&dnp3tx->objects);
FAIL_IF(object->group != 1 || object->variation != 0); FAIL_IF(object->group != 1 || object->variation != 0);
FAIL_IF(object->count != 0); FAIL_IF(object->count != 0);
@ -2548,8 +2521,8 @@ static int DNP3ParserDecodeG70V3Test(void)
FAIL_IF(bytes != sizeof(pkt)); FAIL_IF(bytes != sizeof(pkt));
DNP3Transaction *tx = DNP3GetTx(dnp3state, 0); DNP3Transaction *tx = DNP3GetTx(dnp3state, 0);
FAIL_IF_NULL(tx); FAIL_IF_NULL(tx);
FAIL_IF_NOT(tx->has_request); FAIL_IF_NOT(tx->is_request);
DNP3Object *obj = TAILQ_FIRST(&tx->request_objects); DNP3Object *obj = TAILQ_FIRST(&tx->objects);
FAIL_IF_NULL(obj); FAIL_IF_NULL(obj);
FAIL_IF_NOT(obj->group == 70); FAIL_IF_NOT(obj->group == 70);
FAIL_IF_NOT(obj->variation == 3); FAIL_IF_NOT(obj->variation == 3);

@ -208,37 +208,20 @@ typedef struct DNP3Transaction_ {
AppLayerTxData tx_data; AppLayerTxData tx_data;
uint64_t tx_num; /**< Internal transaction ID. */ uint64_t tx_num; /**< Internal transaction ID. */
bool is_request; /**< Is this tx a request? */
struct DNP3State_ *dnp3; struct DNP3State_ *dnp3;
uint8_t has_request; uint8_t *buffer; /**< Reassembled request buffer. */
uint8_t request_done; uint32_t buffer_len;
DNP3LinkHeader request_lh; DNP3ObjectList objects;
DNP3TransportHeader request_th; DNP3LinkHeader lh;
DNP3ApplicationHeader request_ah; DNP3TransportHeader th;
uint8_t *request_buffer; /**< Reassembled request DNP3ApplicationHeader ah;
* buffer. */ DNP3InternalInd iin;
uint32_t request_buffer_len; uint8_t done;
uint8_t request_complete; /**< Was the decode uint8_t complete; /**< Was the decode complete. It will not be
* complete. It will not be complete if we hit objects we do not know. */
* complete if we hit objects
* we do not know. */
DNP3ObjectList request_objects;
uint8_t has_response;
uint8_t response_done;
DNP3LinkHeader response_lh;
DNP3TransportHeader response_th;
DNP3ApplicationHeader response_ah;
DNP3InternalInd response_iin;
uint8_t *response_buffer; /**< Reassembed response
* buffer. */
uint32_t response_buffer_len;
uint8_t response_complete; /**< Was the decode
* complete. It will not be
* complete if we hit objects
* we do not know. */
DNP3ObjectList response_objects;
TAILQ_ENTRY(DNP3Transaction_) next; TAILQ_ENTRY(DNP3Transaction_) next;
} DNP3Transaction; } DNP3Transaction;

@ -156,21 +156,17 @@ static InspectionBuffer *GetDNP3Data(DetectEngineThreadCtx *det_ctx,
DNP3Transaction *tx = (DNP3Transaction *)txv; DNP3Transaction *tx = (DNP3Transaction *)txv;
SCLogDebug("tx %p", tx); SCLogDebug("tx %p", tx);
const uint8_t *data = NULL; if ((flow_flags & STREAM_TOSERVER && !tx->is_request) ||
uint32_t data_len = 0; (flow_flags & STREAM_TOCLIENT && tx->is_request)) {
return NULL;
if (flow_flags & STREAM_TOSERVER) {
data = tx->request_buffer;
data_len = tx->request_buffer_len;
} else if (flow_flags & STREAM_TOCLIENT) {
data = tx->response_buffer;
data_len = tx->response_buffer_len;
} }
if (data == NULL || data_len == 0)
if (tx->buffer == NULL || tx->buffer_len == 0) {
return NULL; return NULL;
}
SCLogDebug("tx %p data %p data_len %u", tx, data, data_len); SCLogDebug("tx %p data %p data_len %u", tx, tx->buffer, tx->buffer_len);
InspectionBufferSetup(det_ctx, list_id, buffer, data, data_len); InspectionBufferSetup(det_ctx, list_id, buffer, tx->buffer, tx->buffer_len);
InspectionBufferApplyTransforms(buffer, transforms); InspectionBufferApplyTransforms(buffer, transforms);
} }
return buffer; return buffer;
@ -425,11 +421,10 @@ static int DetectDNP3FuncMatch(DetectEngineThreadCtx *det_ctx,
DetectDNP3 *detect = (DetectDNP3 *)ctx; DetectDNP3 *detect = (DetectDNP3 *)ctx;
int match = 0; int match = 0;
if (flags & STREAM_TOSERVER) { if (flags & STREAM_TOSERVER && tx->is_request) {
match = detect->function_code == tx->request_ah.function_code; match = detect->function_code == tx->ah.function_code;
} } else if (flags & STREAM_TOCLIENT && !tx->is_request) {
else if (flags & STREAM_TOCLIENT) { match = detect->function_code == tx->ah.function_code;
match = detect->function_code == tx->response_ah.function_code;
} }
return match; return match;
@ -443,11 +438,10 @@ static int DetectDNP3ObjMatch(DetectEngineThreadCtx *det_ctx,
DetectDNP3 *detect = (DetectDNP3 *)ctx; DetectDNP3 *detect = (DetectDNP3 *)ctx;
DNP3ObjectList *objects = NULL; DNP3ObjectList *objects = NULL;
if (flags & STREAM_TOSERVER) { if (flags & STREAM_TOSERVER && tx->is_request) {
objects = &tx->request_objects; objects = &tx->objects;
} } else if (flags & STREAM_TOCLIENT && !tx->is_request) {
else if (flags & STREAM_TOCLIENT) { objects = &tx->objects;
objects = &tx->response_objects;
} }
if (objects != NULL) { if (objects != NULL) {
@ -471,8 +465,8 @@ static int DetectDNP3IndMatch(DetectEngineThreadCtx *det_ctx,
DetectDNP3 *detect = (DetectDNP3 *)ctx; DetectDNP3 *detect = (DetectDNP3 *)ctx;
if (flags & STREAM_TOCLIENT) { if (flags & STREAM_TOCLIENT) {
if ((tx->response_iin.iin1 & (detect->ind_flags >> 8)) || if ((tx->iin.iin1 & (detect->ind_flags >> 8)) ||
(tx->response_iin.iin2 & (detect->ind_flags & 0xf))) { (tx->iin.iin2 & (detect->ind_flags & 0xf))) {
return 1; return 1;
} }
} }

@ -198,13 +198,13 @@ static void AlertJsonDnp3(const Flow *f, const uint64_t tx_id, JsonBuilder *js)
jb_get_mark(js, &mark); jb_get_mark(js, &mark);
bool logged = false; bool logged = false;
jb_open_object(js, "dnp3"); jb_open_object(js, "dnp3");
if (tx->has_request && tx->request_done) { if (tx->is_request && tx->done) {
jb_open_object(js, "request"); jb_open_object(js, "request");
JsonDNP3LogRequest(js, tx); JsonDNP3LogRequest(js, tx);
jb_close(js); jb_close(js);
logged = true; logged = true;
} }
if (tx->has_response && tx->response_done) { if (!tx->is_request && tx->done) {
jb_open_object(js, "response"); jb_open_object(js, "response");
JsonDNP3LogResponse(js, tx); JsonDNP3LogResponse(js, tx);
jb_close(js); jb_close(js);

@ -145,27 +145,27 @@ void JsonDNP3LogRequest(JsonBuilder *js, DNP3Transaction *dnp3tx)
JB_SET_STRING(js, "type", "request"); JB_SET_STRING(js, "type", "request");
jb_open_object(js, "control"); jb_open_object(js, "control");
JsonDNP3LogLinkControl(js, dnp3tx->request_lh.control); JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
jb_close(js); jb_close(js);
jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->request_lh.src)); jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->request_lh.dst)); jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
jb_open_object(js, "application"); jb_open_object(js, "application");
jb_open_object(js, "control"); jb_open_object(js, "control");
JsonDNP3LogApplicationControl(js, dnp3tx->request_ah.control); JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
jb_close(js); jb_close(js);
jb_set_uint(js, "function_code", dnp3tx->request_ah.function_code); jb_set_uint(js, "function_code", dnp3tx->ah.function_code);
if (!TAILQ_EMPTY(&dnp3tx->request_objects)) { if (!TAILQ_EMPTY(&dnp3tx->objects)) {
jb_open_array(js, "objects"); jb_open_array(js, "objects");
JsonDNP3LogObjects(js, &dnp3tx->request_objects); JsonDNP3LogObjects(js, &dnp3tx->objects);
jb_close(js); jb_close(js);
} }
jb_set_bool(js, "complete", dnp3tx->request_complete); jb_set_bool(js, "complete", dnp3tx->complete);
/* Close application. */ /* Close application. */
jb_close(js); jb_close(js);
@ -173,41 +173,40 @@ void JsonDNP3LogRequest(JsonBuilder *js, DNP3Transaction *dnp3tx)
void JsonDNP3LogResponse(JsonBuilder *js, DNP3Transaction *dnp3tx) void JsonDNP3LogResponse(JsonBuilder *js, DNP3Transaction *dnp3tx)
{ {
if (dnp3tx->response_ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) { if (dnp3tx->ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) {
JB_SET_STRING(js, "type", "unsolicited_response"); JB_SET_STRING(js, "type", "unsolicited_response");
} } else {
else {
JB_SET_STRING(js, "type", "response"); JB_SET_STRING(js, "type", "response");
} }
jb_open_object(js, "control"); jb_open_object(js, "control");
JsonDNP3LogLinkControl(js, dnp3tx->response_lh.control); JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
jb_close(js); jb_close(js);
jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->response_lh.src)); jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->response_lh.dst)); jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
jb_open_object(js, "application"); jb_open_object(js, "application");
jb_open_object(js, "control"); jb_open_object(js, "control");
JsonDNP3LogApplicationControl(js, dnp3tx->response_ah.control); JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
jb_close(js); jb_close(js);
jb_set_uint(js, "function_code", dnp3tx->response_ah.function_code); jb_set_uint(js, "function_code", dnp3tx->ah.function_code);
if (!TAILQ_EMPTY(&dnp3tx->response_objects)) { if (!TAILQ_EMPTY(&dnp3tx->objects)) {
jb_open_array(js, "objects"); jb_open_array(js, "objects");
JsonDNP3LogObjects(js, &dnp3tx->response_objects); JsonDNP3LogObjects(js, &dnp3tx->objects);
jb_close(js); jb_close(js);
} }
jb_set_bool(js, "complete", dnp3tx->response_complete); jb_set_bool(js, "complete", dnp3tx->complete);
/* Close application. */ /* Close application. */
jb_close(js); jb_close(js);
jb_open_object(js, "iin"); jb_open_object(js, "iin");
JsonDNP3LogIin(js, (uint16_t)(dnp3tx->response_iin.iin1 << 8 | dnp3tx->response_iin.iin2)); JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2));
jb_close(js); jb_close(js);
} }
@ -218,20 +217,17 @@ static int JsonDNP3LoggerToServer(ThreadVars *tv, void *thread_data,
LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
DNP3Transaction *tx = vtx; DNP3Transaction *tx = vtx;
if (tx->has_request && tx->request_done) { JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
JsonBuilder *js = if (unlikely(js == NULL)) {
CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); return TM_ECODE_OK;
if (unlikely(js == NULL)) {
return TM_ECODE_OK;
}
jb_open_object(js, "dnp3");
JsonDNP3LogRequest(js, tx);
jb_close(js);
OutputJsonBuilderBuffer(js, thread->ctx);
jb_free(js);
} }
jb_open_object(js, "dnp3");
JsonDNP3LogRequest(js, tx);
jb_close(js);
OutputJsonBuilderBuffer(js, thread->ctx);
jb_free(js);
SCReturnInt(TM_ECODE_OK); SCReturnInt(TM_ECODE_OK);
} }
@ -242,20 +238,32 @@ static int JsonDNP3LoggerToClient(ThreadVars *tv, void *thread_data,
LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data; LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
DNP3Transaction *tx = vtx; DNP3Transaction *tx = vtx;
if (tx->has_response && tx->response_done) { JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
JsonBuilder *js = if (unlikely(js == NULL)) {
CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx); return TM_ECODE_OK;
if (unlikely(js == NULL)) {
return TM_ECODE_OK;
}
jb_open_object(js, "dnp3");
JsonDNP3LogResponse(js, tx);
jb_close(js);
OutputJsonBuilderBuffer(js, thread->ctx);
jb_free(js);
} }
jb_open_object(js, "dnp3");
JsonDNP3LogResponse(js, tx);
jb_close(js);
OutputJsonBuilderBuffer(js, thread->ctx);
jb_free(js);
SCReturnInt(TM_ECODE_OK);
}
static int JsonDNP3Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state,
void *vtx, uint64_t tx_id)
{
SCEnter();
DNP3Transaction *tx = vtx;
static int count = 0;
if (tx->is_request && tx->done) {
JsonDNP3LoggerToServer(tv, thread_data, p, f, state, vtx, tx_id);
} else if (!tx->is_request && tx->done) {
JsonDNP3LoggerToClient(tv, thread_data, p, f, state, vtx, tx_id);
}
SCLogNotice("count = %d", ++count);
SCReturnInt(TM_ECODE_OK); SCReturnInt(TM_ECODE_OK);
} }
@ -338,13 +346,7 @@ static TmEcode JsonDNP3LogThreadDeinit(ThreadVars *t, void *data)
void JsonDNP3LogRegister(void) void JsonDNP3LogRegister(void)
{ {
/* Register direction aware eve sub-modules. */ OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDNP3Log", "eve-log.dnp3",
OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_DNP3_TS, "eve-log", OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3Logger, JsonDNP3LogThreadInit,
"JsonDNP3Log", "eve-log.dnp3", OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3LogThreadDeinit, NULL);
JsonDNP3LoggerToServer, 0, 1, JsonDNP3LogThreadInit,
JsonDNP3LogThreadDeinit, NULL);
OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_DNP3_TC, "eve-log",
"JsonDNP3Log", "eve-log.dnp3", OutputDNP3LogInitSub, ALPROTO_DNP3,
JsonDNP3LoggerToClient, 1, 1, JsonDNP3LogThreadInit,
JsonDNP3LogThreadDeinit, NULL);
} }

@ -446,13 +446,6 @@ typedef enum {
LOGGER_TLS_STORE, LOGGER_TLS_STORE,
LOGGER_TLS, LOGGER_TLS,
LOGGER_JSON_TX, LOGGER_JSON_TX,
/* DNP3 loggers. These ones don't fit the common model of a
transaction logger yet so are left with their own IDs for
now. */
LOGGER_JSON_DNP3_TS,
LOGGER_JSON_DNP3_TC,
LOGGER_FILE, LOGGER_FILE,
LOGGER_FILEDATA, LOGGER_FILEDATA,

@ -106,21 +106,21 @@ static void DNP3PushRequest(lua_State *luastate, DNP3Transaction *tx)
/* Link header. */ /* Link header. */
lua_pushliteral(luastate, "link_header"); lua_pushliteral(luastate, "link_header");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushLinkHeader(luastate, &tx->request_lh); DNP3PushLinkHeader(luastate, &tx->lh);
lua_settable(luastate, -3); lua_settable(luastate, -3);
/* Transport header. */ /* Transport header. */
LUA_PUSHT_INT(luastate, "transport_header", tx->request_th); LUA_PUSHT_INT(luastate, "transport_header", tx->th);
/* Application header. */ /* Application header. */
lua_pushliteral(luastate, "application_header"); lua_pushliteral(luastate, "application_header");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushApplicationHeader(luastate, &tx->request_ah); DNP3PushApplicationHeader(luastate, &tx->ah);
lua_settable(luastate, -3); lua_settable(luastate, -3);
lua_pushliteral(luastate, "objects"); lua_pushliteral(luastate, "objects");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushObjects(luastate, &tx->request_objects); DNP3PushObjects(luastate, &tx->objects);
lua_settable(luastate, -3); lua_settable(luastate, -3);
} }
@ -129,25 +129,24 @@ static void DNP3PushResponse(lua_State *luastate, DNP3Transaction *tx)
/* Link header. */ /* Link header. */
lua_pushliteral(luastate, "link_header"); lua_pushliteral(luastate, "link_header");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushLinkHeader(luastate, &tx->response_lh); DNP3PushLinkHeader(luastate, &tx->lh);
lua_settable(luastate, -3); lua_settable(luastate, -3);
/* Transport header. */ /* Transport header. */
LUA_PUSHT_INT(luastate, "transport_header", tx->response_th); LUA_PUSHT_INT(luastate, "transport_header", tx->th);
/* Application header. */ /* Application header. */
lua_pushliteral(luastate, "application_header"); lua_pushliteral(luastate, "application_header");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushApplicationHeader(luastate, &tx->response_ah); DNP3PushApplicationHeader(luastate, &tx->ah);
lua_settable(luastate, -3); lua_settable(luastate, -3);
/* Internal indicators. */ /* Internal indicators. */
LUA_PUSHT_INT(luastate, "indicators", LUA_PUSHT_INT(luastate, "indicators", tx->iin.iin1 << 8 | tx->iin.iin2);
tx->response_iin.iin1 << 8 | tx->response_iin.iin2);
lua_pushliteral(luastate, "objects"); lua_pushliteral(luastate, "objects");
lua_newtable(luastate); lua_newtable(luastate);
DNP3PushObjects(luastate, &tx->response_objects); DNP3PushObjects(luastate, &tx->objects);
lua_settable(luastate, -3); lua_settable(luastate, -3);
} }
@ -168,22 +167,22 @@ static int DNP3GetTx(lua_State *luastate)
lua_pushinteger(luastate, tx->tx_num); lua_pushinteger(luastate, tx->tx_num);
lua_settable(luastate, -3); lua_settable(luastate, -3);
LUA_PUSHT_INT(luastate, "has_request", tx->has_request); LUA_PUSHT_INT(luastate, "has_request", tx->is_request ? 1 : 0);
if (tx->has_request) { if (tx->is_request) {
lua_pushliteral(luastate, "request"); lua_pushliteral(luastate, "request");
lua_newtable(luastate); lua_newtable(luastate);
LUA_PUSHT_INT(luastate, "done", tx->request_done); LUA_PUSHT_INT(luastate, "done", tx->done);
LUA_PUSHT_INT(luastate, "complete", tx->request_complete); LUA_PUSHT_INT(luastate, "complete", tx->complete);
DNP3PushRequest(luastate, tx); DNP3PushRequest(luastate, tx);
lua_settable(luastate, -3); lua_settable(luastate, -3);
} }
LUA_PUSHT_INT(luastate, "has_response", tx->has_response); LUA_PUSHT_INT(luastate, "has_response", tx->is_request ? 0 : 1);
if (tx->has_response) { if (!tx->is_request) {
lua_pushliteral(luastate, "response"); lua_pushliteral(luastate, "response");
lua_newtable(luastate); lua_newtable(luastate);
LUA_PUSHT_INT(luastate, "done", tx->response_done); LUA_PUSHT_INT(luastate, "done", tx->done);
LUA_PUSHT_INT(luastate, "complete", tx->response_complete); LUA_PUSHT_INT(luastate, "complete", tx->complete);
DNP3PushResponse(luastate, tx); DNP3PushResponse(luastate, tx);
lua_settable(luastate, -3); lua_settable(luastate, -3);
} }

@ -1262,8 +1262,6 @@ const char * PacketProfileLoggertIdToString(LoggerId id)
CASE_CODE(LOGGER_TLS_STORE); CASE_CODE(LOGGER_TLS_STORE);
CASE_CODE(LOGGER_TLS); CASE_CODE(LOGGER_TLS);
CASE_CODE(LOGGER_JSON_TX); CASE_CODE(LOGGER_JSON_TX);
CASE_CODE(LOGGER_JSON_DNP3_TS);
CASE_CODE(LOGGER_JSON_DNP3_TC);
CASE_CODE(LOGGER_FILE); CASE_CODE(LOGGER_FILE);
CASE_CODE(LOGGER_FILEDATA); CASE_CODE(LOGGER_FILEDATA);
CASE_CODE (LOGGER_ALERT_DEBUG); CASE_CODE (LOGGER_ALERT_DEBUG);

Loading…
Cancel
Save