summaryrefslogtreecommitdiffstats
path: root/src/app-layer-enip.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /src/app-layer-enip.c
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/app-layer-enip.c')
-rw-r--r--src/app-layer-enip.c712
1 files changed, 712 insertions, 0 deletions
diff --git a/src/app-layer-enip.c b/src/app-layer-enip.c
new file mode 100644
index 0000000..94c707c
--- /dev/null
+++ b/src/app-layer-enip.c
@@ -0,0 +1,712 @@
+/* Copyright (C) 2015 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 Kevin Wong <kwong@solananetworks.com>
+ *
+ * App-layer parser for ENIP protocol
+ *
+ */
+
+#include "suricata-common.h"
+#include "suricata.h"
+
+#include "util-debug.h"
+#include "util-byte.h"
+#include "util-enum.h"
+#include "util-mem.h"
+#include "util-misc.h"
+
+#include "stream.h"
+
+#include "app-layer.h"
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+#include "app-layer-enip.h"
+#include "app-layer-enip-common.h"
+
+#include "app-layer-detect-proto.h"
+
+#include "conf.h"
+#include "decode.h"
+
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "pkt-var.h"
+#include "util-profiling.h"
+
+
+SCEnumCharMap enip_decoder_event_table[ ] = {
+ { NULL, -1 },
+};
+
+/** \brief get value for 'complete' status in ENIP
+ *
+ * For ENIP we use a simple bool.
+ */
+static int ENIPGetAlstateProgress(void *tx, uint8_t direction)
+{
+ return 1;
+}
+
+static AppLayerTxData *ENIPGetTxData(void *vtx)
+{
+ ENIPTransaction *tx = (ENIPTransaction *)vtx;
+ return &tx->tx_data;
+}
+
+static AppLayerStateData *ENIPGetStateData(void *vstate)
+{
+ ENIPState *state = (ENIPState *)vstate;
+ return &state->state_data;
+}
+
+static void *ENIPGetTx(void *alstate, uint64_t tx_id)
+{
+ ENIPState *enip = (ENIPState *) alstate;
+ ENIPTransaction *tx = NULL;
+
+ if (enip->curr && enip->curr->tx_num == tx_id + 1)
+ return enip->curr;
+
+ TAILQ_FOREACH(tx, &enip->tx_list, next) {
+ if (tx->tx_num != (tx_id+1))
+ continue;
+
+ SCLogDebug("returning tx %p", tx);
+ return tx;
+ }
+
+ return NULL;
+}
+
+static uint64_t ENIPGetTxCnt(void *alstate)
+{
+ return ((ENIPState *)alstate)->transaction_max;
+}
+
+static int ENIPStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type)
+{
+ *event_id = SCMapEnumNameToValue(event_name, enip_decoder_event_table);
+
+ if (*event_id == -1) {
+ SCLogError("event \"%s\" not present in "
+ "enip's enum map table.",
+ event_name);
+ /* yes this is fatal */
+ return -1;
+ }
+
+ *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+
+ return 0;
+}
+
+static int ENIPStateGetEventInfoById(int event_id, const char **event_name,
+ AppLayerEventType *event_type)
+{
+ *event_name = SCMapEnumValueToName(event_id, enip_decoder_event_table);
+ if (*event_name == NULL) {
+ SCLogError("event \"%d\" not present in "
+ "enip's enum map table.",
+ event_id);
+ /* yes this is fatal */
+ return -1;
+ }
+
+ *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+
+ return 0;
+}
+
+/** \brief Allocate enip state
+ *
+ * return state
+ */
+static void *ENIPStateAlloc(void *orig_state, AppProto proto_orig)
+{
+ SCLogDebug("ENIPStateAlloc");
+ void *s = SCMalloc(sizeof(ENIPState));
+ if (unlikely(s == NULL))
+ return NULL;
+
+ memset(s, 0, sizeof(ENIPState));
+
+ ENIPState *enip_state = (ENIPState *) s;
+
+ TAILQ_INIT(&enip_state->tx_list);
+ return s;
+}
+
+/** \internal
+ * \brief Free a ENIP TX
+ * \param tx ENIP TX to free */
+static void ENIPTransactionFree(ENIPTransaction *tx, ENIPState *state)
+{
+ SCEnter();
+ SCLogDebug("ENIPTransactionFree");
+ CIPServiceEntry *svc = NULL;
+ while ((svc = TAILQ_FIRST(&tx->service_list)))
+ {
+ TAILQ_REMOVE(&tx->service_list, svc, next);
+
+ SegmentEntry *seg = NULL;
+ while ((seg = TAILQ_FIRST(&svc->segment_list)))
+ {
+ TAILQ_REMOVE(&svc->segment_list, seg, next);
+ SCFree(seg);
+ }
+
+ AttributeEntry *attr = NULL;
+ while ((attr = TAILQ_FIRST(&svc->attrib_list)))
+ {
+ TAILQ_REMOVE(&svc->attrib_list, attr, next);
+ SCFree(attr);
+ }
+
+ SCFree(svc);
+ }
+
+ AppLayerDecoderEventsFreeEvents(&tx->tx_data.events);
+
+ if (tx->tx_data.de_state != NULL) {
+ DetectEngineStateFree(tx->tx_data.de_state);
+
+ state->tx_with_detect_state_cnt--;
+ }
+
+ if (state->iter == tx)
+ state->iter = NULL;
+
+ SCFree(tx);
+ SCReturn;
+}
+
+/** \brief Free enip state
+ *
+ */
+static void ENIPStateFree(void *s)
+{
+ SCEnter();
+ SCLogDebug("ENIPStateFree");
+ if (s)
+ {
+ ENIPState *enip_state = (ENIPState *) s;
+
+ ENIPTransaction *tx = NULL;
+ while ((tx = TAILQ_FIRST(&enip_state->tx_list)))
+ {
+ TAILQ_REMOVE(&enip_state->tx_list, tx, next);
+ ENIPTransactionFree(tx, enip_state);
+ }
+
+ if (enip_state->buffer != NULL)
+ {
+ SCFree(enip_state->buffer);
+ }
+
+ SCFree(s);
+ }
+ SCReturn;
+}
+
+/** \internal
+ * \brief Allocate a ENIP TX
+ * \retval tx or NULL */
+static ENIPTransaction *ENIPTransactionAlloc(ENIPState *state)
+{
+ SCLogDebug("ENIPStateTransactionAlloc");
+ ENIPTransaction *tx = (ENIPTransaction *) SCCalloc(1,
+ sizeof(ENIPTransaction));
+ if (unlikely(tx == NULL))
+ return NULL;
+
+ state->curr = tx;
+ state->transaction_max++;
+
+ memset(tx, 0x00, sizeof(ENIPTransaction));
+ TAILQ_INIT(&tx->service_list);
+
+ tx->enip = state;
+ tx->tx_num = state->transaction_max;
+ tx->service_count = 0;
+
+ TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
+
+ return tx;
+}
+
+/**
+ * \brief enip transaction cleanup callback
+ */
+static void ENIPStateTransactionFree(void *state, uint64_t tx_id)
+{
+ SCEnter();
+ SCLogDebug("ENIPStateTransactionFree");
+ ENIPState *enip_state = state;
+ ENIPTransaction *tx = NULL;
+ TAILQ_FOREACH(tx, &enip_state->tx_list, next)
+ {
+
+ if ((tx_id+1) < tx->tx_num)
+ break;
+ else if ((tx_id+1) > tx->tx_num)
+ continue;
+
+ if (tx == enip_state->curr)
+ enip_state->curr = NULL;
+
+ if (tx->tx_data.events != NULL) {
+ if (tx->tx_data.events->cnt <= enip_state->events)
+ enip_state->events -= tx->tx_data.events->cnt;
+ else
+ enip_state->events = 0;
+ }
+
+ TAILQ_REMOVE(&enip_state->tx_list, tx, next);
+ ENIPTransactionFree(tx, state);
+ break;
+ }
+ SCReturn;
+}
+
+/** \internal
+ *
+ * \brief This function is called to retrieve a ENIP
+ *
+ * \param state ENIP state structure for the parser
+ * \param input Input line of the command
+ * \param input_len Length of the request
+ *
+ * \retval 1 when the command is parsed, 0 otherwise
+ */
+static AppLayerResult ENIPParse(Flow *f, void *state, AppLayerParserState *pstate,
+ StreamSlice stream_slice, void *local_data, uint8_t direction)
+{
+ SCEnter();
+ ENIPState *enip = (ENIPState *) state;
+ ENIPTransaction *tx;
+
+ const uint8_t *input = StreamSliceGetData(&stream_slice);
+ uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
+
+ if (input == NULL && AppLayerParserStateIssetFlag(pstate,
+ APP_LAYER_PARSER_EOF_TS|APP_LAYER_PARSER_EOF_TC))
+ {
+ SCReturnStruct(APP_LAYER_OK);
+ } else if (input == NULL && input_len != 0) {
+ // GAP
+ SCReturnStruct(APP_LAYER_OK);
+ } else if (input == NULL || input_len == 0)
+ {
+ SCReturnStruct(APP_LAYER_ERROR);
+ }
+
+ while (input_len > 0)
+ {
+ tx = ENIPTransactionAlloc(enip);
+ if (tx == NULL)
+ SCReturnStruct(APP_LAYER_OK);
+
+ if (direction == STREAM_TOCLIENT)
+ tx->tx_data.detect_flags_ts |= APP_LAYER_TX_SKIP_INSPECT_FLAG;
+ else
+ tx->tx_data.detect_flags_tc |= APP_LAYER_TX_SKIP_INSPECT_FLAG;
+
+ SCLogDebug("ENIPParse input len %d", input_len);
+ DecodeENIPPDU(input, input_len, tx);
+ uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr);
+ SCLogDebug("ENIPParse packet len %d", pkt_len);
+ if (pkt_len > input_len)
+ {
+ SCLogDebug("Invalid packet length");
+ break;
+ }
+
+ input += pkt_len;
+ input_len -= pkt_len;
+ //SCLogDebug("remaining %d", input_len);
+
+ if (input_len < sizeof(ENIPEncapHdr))
+ {
+ //SCLogDebug("Not enough data"); //not enough data for ENIP
+ break;
+ }
+ }
+
+ SCReturnStruct(APP_LAYER_OK);
+}
+
+static AppLayerResult ENIPParseRequest(Flow *f, void *state, AppLayerParserState *pstate,
+ StreamSlice stream_slice, void *local_data)
+{
+ return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOSERVER);
+}
+
+static AppLayerResult ENIPParseResponse(Flow *f, void *state, AppLayerParserState *pstate,
+ StreamSlice stream_slice, void *local_data)
+{
+ return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOCLIENT);
+}
+
+#define ENIP_LEN_REGISTER_SESSION 4 // protocol u16, options u16
+
+static uint16_t ENIPProbingParser(Flow *f, uint8_t direction,
+ const uint8_t *input, uint32_t input_len, uint8_t *rdir)
+{
+ // SCLogDebug("ENIPProbingParser %d", input_len);
+ if (input_len < sizeof(ENIPEncapHdr))
+ {
+ SCLogDebug("length too small to be a ENIP header");
+ return ALPROTO_UNKNOWN;
+ }
+ uint16_t cmd;
+ uint16_t enip_len;
+ uint32_t status;
+ uint32_t option;
+ uint16_t nbitems;
+
+ int ret = ByteExtractUint32(
+ &status, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 8));
+ if (ret < 0) {
+ return ALPROTO_FAILED;
+ }
+ switch (status) {
+ case SUCCESS:
+ case INVALID_CMD:
+ case NO_RESOURCES:
+ case INCORRECT_DATA:
+ case INVALID_SESSION:
+ case INVALID_LENGTH:
+ case UNSUPPORTED_PROT_REV:
+ case ENCAP_HEADER_ERROR:
+ break;
+ default:
+ return ALPROTO_FAILED;
+ }
+ ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input));
+ if(ret < 0) {
+ return ALPROTO_FAILED;
+ }
+ ret = ByteExtractUint32(
+ &option, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 20));
+ if (ret < 0) {
+ return ALPROTO_FAILED;
+ }
+ ret = ByteExtractUint16(
+ &enip_len, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + 2));
+ if (ret < 0) {
+ return ALPROTO_FAILED;
+ }
+
+ //ok for all the known commands
+ switch(cmd) {
+ case NOP:
+ if (option != 0) {
+ return ALPROTO_FAILED;
+ }
+ break;
+ case REGISTER_SESSION:
+ if (enip_len != ENIP_LEN_REGISTER_SESSION) {
+ return ALPROTO_FAILED;
+ }
+ break;
+ case UNREGISTER_SESSION:
+ if (enip_len != ENIP_LEN_REGISTER_SESSION && enip_len != 0) {
+ // 0 for request and 4 for response
+ return ALPROTO_FAILED;
+ }
+ break;
+ case LIST_SERVICES:
+ case LIST_IDENTITY:
+ case SEND_RR_DATA:
+ case SEND_UNIT_DATA:
+ case INDICATE_STATUS:
+ case CANCEL:
+ break;
+ case LIST_INTERFACES:
+ if (input_len < sizeof(ENIPEncapHdr) + 2) {
+ SCLogDebug("length too small to be a ENIP LIST_INTERFACES");
+ return ALPROTO_UNKNOWN;
+ }
+ ret = ByteExtractUint16(
+ &nbitems, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input));
+ if(ret < 0) {
+ return ALPROTO_FAILED;
+ }
+ if (enip_len < sizeof(ENIPEncapHdr) + 2 * (size_t)nbitems) {
+ return ALPROTO_FAILED;
+ }
+ break;
+ default:
+ return ALPROTO_FAILED;
+ }
+ return ALPROTO_ENIP;
+}
+
+static AppLayerGetTxIterTuple ENIPGetTxIterator(const uint8_t ipproto, const AppProto alproto,
+ void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state)
+{
+ ENIPState *enip_state = (ENIPState *)alstate;
+ AppLayerGetTxIterTuple no_tuple = { NULL, 0, false };
+ if (enip_state) {
+ ENIPTransaction *tx_ptr;
+ if (state->un.ptr == NULL) {
+ tx_ptr = TAILQ_FIRST(&enip_state->tx_list);
+ } else {
+ tx_ptr = (ENIPTransaction *)state->un.ptr;
+ }
+ if (tx_ptr) {
+ while (tx_ptr->tx_num < min_tx_id + 1) {
+ tx_ptr = TAILQ_NEXT(tx_ptr, next);
+ if (!tx_ptr) {
+ return no_tuple;
+ }
+ }
+ if (tx_ptr->tx_num >= max_tx_id + 1) {
+ return no_tuple;
+ }
+ state->un.ptr = TAILQ_NEXT(tx_ptr, next);
+ AppLayerGetTxIterTuple tuple = {
+ .tx_ptr = tx_ptr,
+ .tx_id = tx_ptr->tx_num - 1,
+ .has_next = (state->un.ptr != NULL),
+ };
+ return tuple;
+ }
+ }
+ return no_tuple;
+}
+
+/**
+ * \brief Function to register the ENIP protocol parsers and other functions
+ */
+void RegisterENIPUDPParsers(void)
+{
+ SCEnter();
+ const char *proto_name = "enip";
+
+ if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("udp", proto_name, false)) {
+ AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name);
+
+ if (RunmodeIsUnittests())
+ {
+ AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP,
+ 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL);
+
+ AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP,
+ 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL);
+
+ } else
+ {
+ if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP,
+ proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr),
+ ENIPProbingParser, ENIPProbingParser))
+ {
+ SCLogDebug(
+ "no ENIP UDP config found enabling ENIP detection on port 44818.");
+
+ AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818",
+ ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER,
+ ENIPProbingParser, NULL);
+
+ AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818",
+ ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT,
+ ENIPProbingParser, NULL);
+ }
+ }
+
+ } else {
+ SCLogConfig("Protocol detection and parser disabled for %s protocol.",
+ proto_name);
+ return;
+ }
+
+ if (AppLayerParserConfParserEnabled("udp", proto_name))
+ {
+ AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest);
+ AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse);
+
+ AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP,
+ ENIPStateAlloc, ENIPStateFree);
+
+ AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx);
+ AppLayerParserRegisterGetTxIterator(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxIterator);
+ AppLayerParserRegisterTxDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxData);
+ AppLayerParserRegisterStateDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetStateData);
+ AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt);
+ AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree);
+
+ AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress);
+ AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1);
+
+ AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo);
+ AppLayerParserRegisterGetEventInfoById(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfoById);
+
+ AppLayerParserRegisterParserAcceptableDataDirection(
+ IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT);
+ } else
+ {
+ SCLogInfo(
+ "Parsed disabled for %s protocol. Protocol detection" "still on.",
+ proto_name);
+ }
+
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_ENIP, ENIPParserRegisterTests);
+#endif
+
+ SCReturn;
+}
+
+/**
+ * \brief Function to register the ENIP protocol parsers and other functions
+ */
+void RegisterENIPTCPParsers(void)
+{
+ SCEnter();
+ const char *proto_name = "enip";
+
+ if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) {
+ AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name);
+
+ if (RunmodeIsUnittests())
+ {
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP,
+ 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL);
+
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP,
+ 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL);
+
+ } else
+ {
+ if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
+ proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr),
+ ENIPProbingParser, ENIPProbingParser))
+ {
+ return;
+ }
+ }
+
+ } else {
+ SCLogDebug("Protocol detection and parser disabled for %s protocol.",
+ proto_name);
+ return;
+ }
+
+ if (AppLayerParserConfParserEnabled("tcp", proto_name))
+ {
+ AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest);
+ AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse);
+ AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP,
+ ENIPStateAlloc, ENIPStateFree);
+
+ AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx);
+ AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxIterator);
+ AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxData);
+ AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetStateData);
+ AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt);
+ AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree);
+
+ AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress);
+ AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1);
+
+ AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo);
+
+ AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP,
+ ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT);
+
+ /* This parser accepts gaps. */
+ AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP,
+ APP_LAYER_PARSER_OPT_ACCEPT_GAPS);
+
+ AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, 0);
+ } else
+ {
+ SCLogConfig("Parser disabled for %s protocol. Protocol detection still on.",
+ proto_name);
+ }
+
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests);
+#endif
+
+ SCReturn;
+}
+
+/* UNITTESTS */
+#ifdef UNITTESTS
+#include "flow-util.h"
+#include "stream-tcp.h"
+
+static uint8_t listIdentity[] = {/* List ID */ 0x63, 0x00,
+ /* Length */ 0x00, 0x00,
+ /* Session */ 0x00, 0x00, 0x00, 0x00,
+ /* Status */ 0x00, 0x00, 0x00, 0x00,
+ /* Delay*/ 0x00,
+ /* Context */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Quantity of coils */ 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/**
+ * \brief Test if ENIP Packet matches signature
+ */
+static int ALDecodeENIPTest(void)
+{
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_ENIP;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_ENIP, STREAM_TOSERVER,
+ listIdentity, sizeof(listIdentity));
+ FAIL_IF(r != 0);
+
+ ENIPState *enip_state = f.alstate;
+ FAIL_IF_NULL(enip_state);
+
+ ENIPTransaction *tx = ENIPGetTx(enip_state, 0);
+ FAIL_IF_NULL(tx);
+
+ FAIL_IF(tx->header.command != 99);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+void ENIPParserRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("ALDecodeENIPTest", ALDecodeENIPTest);
+#endif /* UNITTESTS */
+}