aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/suricata/src/app-layer-modbus.c
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
commit8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch)
treec7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/app-layer-modbus.c
parent13d05bc8458758ee39cb829098241e89616717ee (diff)
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/app-layer-modbus.c')
-rw-r--r--framework/src/suricata/src/app-layer-modbus.c2671
1 files changed, 2671 insertions, 0 deletions
diff --git a/framework/src/suricata/src/app-layer-modbus.c b/framework/src/suricata/src/app-layer-modbus.c
new file mode 100644
index 00000000..fa965135
--- /dev/null
+++ b/framework/src/suricata/src/app-layer-modbus.c
@@ -0,0 +1,2671 @@
+/*
+ * Copyright (C) 2014 ANSSI
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * \author David DIALLO <diallo@et.esiea.fr>
+ *
+ * App-layer parser for Modbus protocol
+ *
+ */
+
+#include "suricata-common.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-protos.h"
+#include "app-layer-parser.h"
+#include "app-layer-modbus.h"
+
+#include "app-layer-detect-proto.h"
+
+#include "conf.h"
+#include "decode.h"
+
+SCEnumCharMap modbus_decoder_event_table[ ] = {
+ /* Modbus Application Data Unit messages - ADU Modbus */
+ { "INVALID_PROTOCOL_ID", MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID },
+ { "UNSOLICITED_RESPONSE", MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE },
+ { "INVALID_LENGTH", MODBUS_DECODER_EVENT_INVALID_LENGTH },
+ { "INVALID_UNIT_IDENTIFIER", MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER},
+
+ /* Modbus Protocol Data Unit messages - PDU Modbus */
+ { "INVALID_FUNCTION_CODE", MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE },
+ { "INVALID_VALUE", MODBUS_DECODER_EVENT_INVALID_VALUE },
+ { "INVALID_EXCEPTION_CODE", MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE },
+ { "VALUE_MISMATCH", MODBUS_DECODER_EVENT_VALUE_MISMATCH },
+
+ /* Modbus Decoder event */
+ { "FLOODED", MODBUS_DECODER_EVENT_FLOODED},
+ { NULL, -1 },
+};
+
+/* Modbus Application Data Unit (ADU) length range. */
+#define MODBUS_MIN_ADU_LEN 2
+#define MODBUS_MAX_ADU_LEN 254
+
+/* Modbus Protocol version. */
+#define MODBUS_PROTOCOL_VER 0
+
+/* Modbus Unit Identifier range. */
+#define MODBUS_MIN_INVALID_UNIT_ID 247
+#define MODBUS_MAX_INVALID_UNIT_ID 255
+
+/* Modbus Quantity range. */
+#define MODBUS_MIN_QUANTITY 0
+#define MODBUS_MAX_QUANTITY_IN_BIT_ACCESS 2000
+#define MODBUS_MAX_QUANTITY_IN_WORD_ACCESS 125
+
+/* Modbus Count range. */
+#define MODBUS_MIN_COUNT 1
+#define MODBUS_MAX_COUNT 250
+
+/* Modbus Function Code. */
+#define MODBUS_FUNC_NONE 0x00
+#define MODBUS_FUNC_READCOILS 0x01
+#define MODBUS_FUNC_READDISCINPUTS 0x02
+#define MODBUS_FUNC_READHOLDREGS 0x03
+#define MODBUS_FUNC_READINPUTREGS 0x04
+#define MODBUS_FUNC_WRITESINGLECOIL 0x05
+#define MODBUS_FUNC_WRITESINGLEREG 0x06
+#define MODBUS_FUNC_READEXCSTATUS 0x07
+#define MODBUS_FUNC_DIAGNOSTIC 0x08
+#define MODBUS_FUNC_GETCOMEVTCOUNTER 0x0b
+#define MODBUS_FUNC_GETCOMEVTLOG 0x0c
+#define MODBUS_FUNC_WRITEMULTCOILS 0x0f
+#define MODBUS_FUNC_WRITEMULTREGS 0x10
+#define MODBUS_FUNC_REPORTSERVERID 0x11
+#define MODBUS_FUNC_READFILERECORD 0x14
+#define MODBUS_FUNC_WRITEFILERECORD 0x15
+#define MODBUS_FUNC_MASKWRITEREG 0x16
+#define MODBUS_FUNC_READWRITEMULTREGS 0x17
+#define MODBUS_FUNC_READFIFOQUEUE 0x18
+#define MODBUS_FUNC_ENCAPINTTRANS 0x2b
+#define MODBUS_FUNC_MASK 0x7f
+#define MODBUS_FUNC_ERRORMASK 0x80
+
+/* Modbus Diagnostic functions: Subfunction Code. */
+#define MODBUS_SUBFUNC_QUERY_DATA 0x00
+#define MODBUS_SUBFUNC_RESTART_COM 0x01
+#define MODBUS_SUBFUNC_DIAG_REGS 0x02
+#define MODBUS_SUBFUNC_CHANGE_DELIMITER 0x03
+#define MODBUS_SUBFUNC_LISTEN_MODE 0x04
+#define MODBUS_SUBFUNC_CLEAR_REGS 0x0a
+#define MODBUS_SUBFUNC_BUS_MSG_COUNT 0x0b
+#define MODBUS_SUBFUNC_COM_ERR_COUNT 0x0c
+#define MODBUS_SUBFUNC_EXCEPT_ERR_COUNT 0x0d
+#define MODBUS_SUBFUNC_SERVER_MSG_COUNT 0x0e
+#define MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT 0x0f
+#define MODBUS_SUBFUNC_SERVER_NAK_COUNT 0x10
+#define MODBUS_SUBFUNC_SERVER_BUSY_COUNT 0x11
+#define MODBUS_SUBFUNC_SERVER_CHAR_COUNT 0x12
+#define MODBUS_SUBFUNC_CLEAR_COUNT 0x14
+
+/* Modbus Encapsulated Interface Transport function: MEI type. */
+#define MODBUS_MEI_ENCAPINTTRANS_CAN 0x0d
+#define MODBUS_MEI_ENCAPINTTRANS_READ 0x0e
+
+/* Modbus Exception Codes. */
+#define MODBUS_ERROR_CODE_ILLEGAL_FUNCTION 0x01
+#define MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS 0x02
+#define MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE 0x03
+#define MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE 0x04
+#define MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR 0x08
+
+/* Modbus Application Protocol (MBAP) header. */
+struct ModbusHeader_ {
+ uint16_t transactionId;
+ uint16_t protocolId;
+ uint16_t length;
+ uint8_t unitId;
+} __attribute__((__packed__));
+typedef struct ModbusHeader_ ModbusHeader;
+
+/* Modbus Read/Write function and Access Types. */
+#define MODBUS_TYP_WRITE_SINGLE (MODBUS_TYP_WRITE | MODBUS_TYP_SINGLE)
+#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
+#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
+
+/* Macro to convert quantity value (in bit) into count value (in word): count = Ceil(quantity/8) */
+#define CEIL(quantity) (((quantity) + 7)>>3)
+
+/* Modbus Default unreplied Modbus requests are considered a flood */
+#define MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD 500
+
+static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD;
+
+int ModbusStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) {
+ *event_id = SCMapEnumNameToValue(event_name, modbus_decoder_event_table);
+
+ if (*event_id == -1) {
+ SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
+ "modbus's enum map table.", event_name);
+ /* yes this is fatal */
+ return -1;
+ }
+
+ *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+
+ return 0;
+}
+
+void ModbusSetEvent(ModbusState *modbus, uint8_t e) {
+ if (modbus && modbus->curr) {
+ SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
+ AppLayerDecoderEventsSetEventRaw(&modbus->curr->decoder_events, e);
+ SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
+ modbus->events++;
+ } else
+ SCLogDebug("couldn't set event %u", e);
+}
+
+AppLayerDecoderEvents *ModbusGetEvents(void *state, uint64_t id) {
+ ModbusState *modbus = (ModbusState *) state;
+ ModbusTransaction *tx;
+
+ if (modbus->curr && modbus->curr->tx_num == (id + 1))
+ return modbus->curr->decoder_events;
+
+ TAILQ_FOREACH(tx, &modbus->tx_list, next) {
+ if (tx->tx_num == (id+1))
+ return tx->decoder_events;
+ }
+
+ return NULL;
+}
+
+int ModbusHasEvents(void *state) {
+ return (((ModbusState *) state)->events > 0);
+}
+
+int ModbusGetAlstateProgress(void *modbus_tx, uint8_t direction) {
+ ModbusTransaction *tx = (ModbusTransaction *) modbus_tx;
+ ModbusState *modbus = tx->modbus;
+
+ if (tx->replied == 1)
+ return 1;
+
+ /* Check flood limit */
+ if ((modbus->givenup == 1) &&
+ ((modbus->transaction_max - tx->tx_num) > request_flood))
+ return 1;
+
+ return 0;
+}
+
+/** \brief Get value for 'complete' status in Modbus
+ */
+int ModbusGetAlstateProgressCompletionStatus(uint8_t direction) {
+ return 1;
+}
+
+void *ModbusGetTx(void *alstate, uint64_t tx_id) {
+ ModbusState *modbus = (ModbusState *) alstate;
+ ModbusTransaction *tx = NULL;
+
+ if (modbus->curr && modbus->curr->tx_num == tx_id + 1)
+ return modbus->curr;
+
+ TAILQ_FOREACH(tx, &modbus->tx_list, next) {
+ SCLogDebug("tx->tx_num %"PRIu64", tx_id %"PRIu64, tx->tx_num, (tx_id+1));
+ if (tx->tx_num != (tx_id+1))
+ continue;
+
+ SCLogDebug("returning tx %p", tx);
+ return tx;
+ }
+
+ return NULL;
+}
+
+uint64_t ModbusGetTxCnt(void *alstate) {
+ return ((uint64_t) ((ModbusState *) alstate)->transaction_max);
+}
+
+/** \internal
+ * \brief Find the Modbus Transaction in the state based on Transaction ID.
+ *
+ * \param modbus Pointer to Modbus state structure
+ * \param transactionId Transaction ID of the transaction
+ *
+ * \retval tx or NULL if not found
+ */
+static ModbusTransaction *ModbusTxFindByTransaction(const ModbusState *modbus,
+ const uint16_t transactionId) {
+ ModbusTransaction *tx = NULL;
+
+ if (modbus->curr == NULL)
+ return NULL;
+
+ /* fast path */
+ if ((modbus->curr->transactionId == transactionId) &&
+ !(modbus->curr->replied)) {
+ return modbus->curr;
+ /* slow path, iterate list */
+ } else {
+ TAILQ_FOREACH(tx, &modbus->tx_list, next) {
+ if ((tx->transactionId == transactionId) &&
+ !(modbus->curr->replied))
+ return tx;
+ }
+ }
+ /* not found */
+ return NULL;
+}
+
+/** \internal
+ * \brief Allocate a Modbus Transaction and
+ * add it into Transaction list of Modbus State
+ *
+ * \param modbus Pointer to Modbus state structure
+ *
+ * \retval Pointer to Transaction or NULL pointer
+ */
+static ModbusTransaction *ModbusTxAlloc(ModbusState *modbus) {
+ ModbusTransaction *tx;
+
+ tx = (ModbusTransaction *) SCCalloc(1, sizeof(ModbusTransaction));
+ if (unlikely(tx == NULL))
+ return NULL;
+
+ modbus->transaction_max++;
+ modbus->unreplied_cnt++;
+
+ /* Check flood limit */
+ if ((request_flood != 0) && (modbus->unreplied_cnt > request_flood)) {
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_FLOODED);
+ modbus->givenup = 1;
+ }
+
+ modbus->curr = tx;
+
+ SCLogDebug("modbus->transaction_max updated to %"PRIu64, modbus->transaction_max);
+
+ TAILQ_INSERT_TAIL(&modbus->tx_list, tx, next);
+
+ tx->modbus = modbus;
+ tx->tx_num = modbus->transaction_max;
+
+ return tx;
+}
+
+/** \internal
+ * \brief Free a Modbus Transaction
+ *
+ * \retval Pointer to Transaction or NULL pointer
+ */
+static void ModbusTxFree(ModbusTransaction *tx) {
+ SCEnter();
+ if (tx->data != NULL)
+ SCFree(tx->data);
+
+ AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
+
+ if (tx->de_state != NULL)
+ DetectEngineStateFree(tx->de_state);
+
+ SCFree(tx);
+ SCReturn;
+}
+
+/**
+ * \brief Modbus transaction cleanup callback
+ */
+void ModbusStateTxFree(void *state, uint64_t tx_id) {
+ SCEnter();
+ ModbusState *modbus = (ModbusState *) state;
+ ModbusTransaction *tx = NULL, *ttx;
+
+ SCLogDebug("state %p, id %"PRIu64, modbus, tx_id);
+
+ TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
+ SCLogDebug("tx %p tx->tx_num %"PRIu64", tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1));
+
+ if (tx->tx_num != (tx_id+1))
+ continue;
+
+ if (tx == modbus->curr)
+ modbus->curr = NULL;
+
+ if (tx->decoder_events != NULL) {
+ if (tx->decoder_events->cnt <= modbus->events)
+ modbus->events -= tx->decoder_events->cnt;
+ else
+ modbus->events = 0;
+ }
+
+ modbus->unreplied_cnt--;
+
+ /* Check flood limit */
+ if ((modbus->givenup == 1) &&
+ (request_flood != 0) &&
+ (modbus->unreplied_cnt < request_flood) )
+ modbus->givenup = 0;
+
+ TAILQ_REMOVE(&modbus->tx_list, tx, next);
+ ModbusTxFree(tx);
+ break;
+ }
+ SCReturn;
+}
+
+/** \internal
+ * \brief Extract 8bits data from pointer the received input data
+ *
+ * \param res Pointer to the result
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static int ModbusExtractUint8(ModbusState *modbus,
+ uint8_t *res,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset) {
+ SCEnter();
+ if (input_len < (uint32_t) (*offset + sizeof(uint8_t))) {
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
+ SCReturnInt(-1);
+ }
+
+ *res = *(input + *offset);
+ *offset += sizeof(uint8_t);
+ SCReturnInt(0);
+}
+
+/** \internal
+ * \brief Extract 16bits data from pointer the received input data
+ *
+ * \param res Pointer to the result
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static int ModbusExtractUint16(ModbusState *modbus,
+ uint16_t *res,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset) {
+ SCEnter();
+ if (input_len < (uint32_t) (*offset + sizeof(uint16_t))) {
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
+ SCReturnInt(-1);
+ }
+
+ ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset));
+ *offset += sizeof(uint16_t);
+ SCReturnInt(0);
+}
+
+/** \internal
+ * \brief Check length field in Modbus header according to code function
+ *
+ * \param modbus Pointer to Modbus state structure
+ * \param length Length field in Modbus Header
+ * \param len Length according to code functio
+ */
+static int ModbusCheckHeaderLength(ModbusState *modbus,
+ uint16_t length,
+ uint16_t len) {
+ SCEnter();
+ if (length != len) {
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
+ SCReturnInt(-1);
+ }
+ SCReturnInt(0);
+}
+
+/** \internal
+ * \brief Check Modbus header
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param header Pointer to Modbus header state in which the value to be stored
+ */
+static void ModbusCheckHeader(ModbusState *modbus,
+ ModbusHeader *header)
+{
+ SCEnter();
+ /* MODBUS protocol is identified by the value 0. */
+ if (header->protocolId != MODBUS_PROTOCOL_VER)
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID);
+
+ /* Check Length field that is a byte count of the following fields */
+ if ((header->length < MODBUS_MIN_ADU_LEN) ||
+ (header->length > MODBUS_MAX_ADU_LEN) )
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
+
+ /* Check Unit Identifier field that is not in invalid range */
+ if ((header->unitId > MODBUS_MIN_INVALID_UNIT_ID) &&
+ (header->unitId < MODBUS_MAX_INVALID_UNIT_ID) )
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER);
+
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse Exception Response and verify protocol compliance.
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static void ModbusExceptionResponse(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint8_t exception;
+
+ /* Exception code (1 byte) */
+ if (ModbusExtractUint8(modbus, &exception, input, input_len, offset))
+ SCReturn;
+
+ switch (exception) {
+ case MODBUS_ERROR_CODE_ILLEGAL_FUNCTION:
+ case MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE:
+ break;
+ case MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE:
+ if (tx->function == MODBUS_FUNC_DIAGNOSTIC) {
+ break;
+ }
+ /* Fallthrough */
+ case MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS:
+ if ( (tx->type & MODBUS_TYP_ACCESS_FUNCTION_MASK) ||
+ (tx->function == MODBUS_FUNC_READFIFOQUEUE) ||
+ (tx->function == MODBUS_FUNC_ENCAPINTTRANS)) {
+ break;
+ }
+ /* Fallthrough */
+ case MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR:
+ if ( (tx->function == MODBUS_FUNC_READFILERECORD) ||
+ (tx->function == MODBUS_FUNC_WRITEFILERECORD) ) {
+ break;
+ }
+ /* Fallthrough */
+ default:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE);
+ break;
+ }
+
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse Read data Request, complete Transaction structure
+ * and verify protocol compliance.
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static void ModbusParseReadRequest(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint16_t quantity;
+ uint8_t type = tx->type;
+
+ /* Starting Address (2 bytes) */
+ if (ModbusExtractUint16(modbus, &(tx->read.address), input, input_len, offset))
+ goto end;
+
+ /* Quantity (2 bytes) */
+ if (ModbusExtractUint16(modbus, &(tx->read.quantity), input, input_len, offset))
+ goto end;
+ quantity = tx->read.quantity;
+
+ /* Check Quantity range */
+ if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
+ goto error;
+ } else {
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
+ goto error;
+ }
+
+ if (~type & MODBUS_TYP_WRITE)
+ /* Except from Read/Write Multiple Registers function (code 23) */
+ /* The length of all Read Data function requests is 6 bytes */
+ /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */
+ ModbusCheckHeaderLength(modbus, tx->length, 6);
+
+ goto end;
+
+error:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
+end:
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse Read data Response and verify protocol compliance
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static void ModbusParseReadResponse(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint8_t count;
+
+ /* Count (1 bytes) */
+ if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
+ goto end;
+
+ /* Check Count range and value according to the request */
+ if ((tx->type) & MODBUS_TYP_BIT_ACCESS_MASK) {
+ if ( (count < MODBUS_MIN_COUNT) ||
+ (count > MODBUS_MAX_COUNT) ||
+ (count != CEIL(tx->read.quantity)))
+ goto error;
+ } else {
+ if ( (count == MODBUS_MIN_COUNT) ||
+ (count > MODBUS_MAX_COUNT) ||
+ (count != (2 * (tx->read.quantity))))
+ goto error;
+ }
+
+ /* Except from Read/Write Multiple Registers function (code 23) */
+ /* The length of all Read Data function responses is (3 bytes + count) */
+ /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */
+ ModbusCheckHeaderLength(modbus, tx->length, 3 + count);
+ goto end;
+
+error:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
+end:
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse Write data Request, complete Transaction structure
+ * and verify protocol compliance.
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ *
+ * \retval On success returns 0 or on failure returns -1.
+ */
+static int ModbusParseWriteRequest(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint16_t quantity = 1, word;
+ uint8_t byte, count = 1, type = tx->type;
+
+ int i = 0;
+
+ /* Starting/Output/Register Address (2 bytes) */
+ if (ModbusExtractUint16(modbus, &(tx->write.address), input, input_len, offset))
+ goto end;
+
+ if (type & MODBUS_TYP_SINGLE) {
+ /* The length of Write Single Coil (code 5) and */
+ /* Write Single Register (code 6) requests is 6 bytes */
+ /* Modbus Application Protocol Specification V1.1b3 6.5 and 6.6 */
+ if (ModbusCheckHeaderLength(modbus, tx->length, 6))
+ goto end;
+ } else if (type & MODBUS_TYP_MULTIPLE) {
+ /* Quantity (2 bytes) */
+ if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
+ goto end;
+ tx->write.quantity = quantity;
+
+ /* Count (1 bytes) */
+ if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
+ goto end;
+ tx->write.count = count;
+
+ if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
+ /* Check Quantity range and conversion in byte (count) */
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS) ||
+ (quantity != CEIL(count)))
+ goto error;
+
+ /* The length of Write Multiple Coils (code 15) request is (7 + count) */
+ /* Modbus Application Protocol Specification V1.1b3 6.11 */
+ if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
+ goto end;
+ } else {
+ /* Check Quantity range and conversion in byte (count) */
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS) ||
+ (count != (2 * quantity)))
+ goto error;
+
+ if (type & MODBUS_TYP_READ) {
+ /* The length of Read/Write Multiple Registers function (code 23) */
+ /* request is (11 bytes + count) */
+ /* Modbus Application Protocol Specification V1.1b3 6.17 */
+ if (ModbusCheckHeaderLength(modbus, tx->length, 11 + count))
+ goto end;
+ } else {
+ /* The length of Write Multiple Coils (code 15) and */
+ /* Write Multiple Registers (code 16) functions requests is (7 bytes + count) */
+ /* Modbus Application Protocol Specification V1.1b3 from 6.11 and 6.12 */
+ if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
+ goto end;
+ }
+ }
+ } else {
+ /* Mask Write Register function (And_Mask and Or_Mask) */
+ quantity = 2;
+
+ /* The length of Mask Write Register (code 22) function request is 8 */
+ /* Modbus Application Protocol Specification V1.1b3 6.16 */
+ if (ModbusCheckHeaderLength(modbus, tx->length, 8))
+ goto end;
+ }
+
+ if (type & MODBUS_TYP_COILS) {
+ /* Output value (data block) unit is count */
+ tx->data = (uint16_t *) SCCalloc(1, count * sizeof(uint16_t));
+ if (unlikely(tx->data == NULL))
+ SCReturnInt(-1);
+
+ if (type & MODBUS_TYP_SINGLE) {
+ /* Outputs value (2 bytes) */
+ if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
+ goto end;
+ tx->data[i] = word;
+
+ if ((word != 0x00) && (word != 0xFF00))
+ goto error;
+ } else {
+ for (i = 0; i < count; i++) {
+ /* Outputs value (1 byte) */
+ if (ModbusExtractUint8(modbus, &byte, input, input_len, offset))
+ goto end;
+ tx->data[i] = (uint16_t) byte;
+ }
+ }
+ } else {
+ /* Registers value (data block) unit is quantity */
+ tx->data = (uint16_t *) SCCalloc(1, quantity * sizeof(uint16_t));
+ if (unlikely(tx->data == NULL))
+ SCReturnInt(-1);
+
+ for (i = 0; i < quantity; i++) {
+ /* Outputs/Registers value (2 bytes) */
+ if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
+ goto end;
+ tx->data[i] = word;
+ }
+ }
+ goto end;
+
+error:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
+end:
+ SCReturnInt(0);
+}
+
+/** \internal
+ * \brief Parse Write data Response and verify protocol compliance
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static void ModbusParseWriteResponse(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint16_t address, quantity, word;
+ uint8_t type = tx->type;
+
+ /* Starting Address (2 bytes) */
+ if (ModbusExtractUint16(modbus, &address, input, input_len, offset))
+ goto end;
+
+ if (address != tx->write.address)
+ goto error;
+
+ if (type & MODBUS_TYP_SINGLE) {
+ /* Outputs/Registers value (2 bytes) */
+ if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
+ goto end;
+
+ /* Check with Outputs/Registers from request */
+ if (word != tx->data[0])
+ goto error;
+ } else if (type & MODBUS_TYP_MULTIPLE) {
+ /* Quantity (2 bytes) */
+ if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
+ goto end;
+
+ /* Check Quantity range */
+ if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
+ goto error;
+ } else {
+ if ((quantity == MODBUS_MIN_QUANTITY) ||
+ (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
+ goto error;
+ }
+
+ /* Check Quantity value according to the request */
+ if (quantity != tx->write.quantity)
+ goto error;
+ } else {
+ /* And_Mask value (2 bytes) */
+ if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
+ goto end;
+
+ /* Check And_Mask value according to the request */
+ if (word != tx->data[0])
+ goto error;
+
+ /* And_Or_Mask value (2 bytes) */
+ if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
+
+ /* Check Or_Mask value according to the request */
+ if (word != tx->data[1])
+ goto error;
+
+ /* The length of Mask Write Register (code 22) function response is 8 */
+ /* Modbus Application Protocol Specification V1.1b3 6.16 */
+ ModbusCheckHeaderLength(modbus, tx->length, 8);
+ goto end;
+ }
+
+ /* Except from Mask Write Register (code 22) */
+ /* The length of all Write Data function responses is 6 */
+ /* Modbus Application Protocol Specification V1.1b3 6.5, 6.6, 6.11, 6.12 and 6.17 */
+ ModbusCheckHeaderLength(modbus, tx->length, 6);
+ goto end;
+
+error:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
+end:
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse Diagnostic Request, complete Transaction
+ * structure (Category) and verify protocol compliance.
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer to Modbus state structure
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ *
+ * \retval Reserved category function returns 1 otherwise returns 0.
+ */
+static int ModbusParseDiagnosticRequest(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len,
+ uint16_t *offset)
+{
+ SCEnter();
+ uint16_t data;
+
+ /* Sub-function (2 bytes) */
+ if (ModbusExtractUint16(modbus, &(tx->subFunction), input, input_len, offset))
+ goto end;
+
+ /* Data (2 bytes) */
+ if (ModbusExtractUint16(modbus, &data, input, input_len, offset))
+ goto end;
+
+ if (tx->subFunction != MODBUS_SUBFUNC_QUERY_DATA) {
+ switch (tx->subFunction) {
+ case MODBUS_SUBFUNC_RESTART_COM:
+ if ((data != 0x00) && (data != 0xFF00))
+ goto error;
+ break;
+
+ case MODBUS_SUBFUNC_CHANGE_DELIMITER:
+ if ((data & 0xFF) != 0x00)
+ goto error;
+ break;
+
+ case MODBUS_SUBFUNC_LISTEN_MODE:
+ /* No answer is expected then mark tx as completed. */
+ tx->replied = 1;
+ /* Fallthrough */
+ case MODBUS_SUBFUNC_DIAG_REGS:
+ case MODBUS_SUBFUNC_CLEAR_REGS:
+ case MODBUS_SUBFUNC_BUS_MSG_COUNT:
+ case MODBUS_SUBFUNC_COM_ERR_COUNT:
+ case MODBUS_SUBFUNC_EXCEPT_ERR_COUNT:
+ case MODBUS_SUBFUNC_SERVER_MSG_COUNT:
+ case MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT:
+ case MODBUS_SUBFUNC_SERVER_NAK_COUNT:
+ case MODBUS_SUBFUNC_SERVER_BUSY_COUNT:
+ case MODBUS_SUBFUNC_SERVER_CHAR_COUNT:
+ case MODBUS_SUBFUNC_CLEAR_COUNT:
+ if (data != 0x00)
+ goto error;
+ break;
+
+ default:
+ /* Set function code category */
+ tx->category = MODBUS_CAT_RESERVED;
+ SCReturnInt(1);
+ }
+
+ /* The length of all Diagnostic Requests is 6 */
+ /* Modbus Application Protocol Specification V1.1b3 6.8 */
+ ModbusCheckHeaderLength(modbus, tx->length, 6);
+ }
+
+ goto end;
+
+error:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
+end:
+ SCReturnInt(0);
+}
+
+/* Modbus Function Code Categories structure. */
+typedef struct ModbusFunctionCodeRange_ {
+ uint8_t function;
+ uint8_t category;
+} ModbusFunctionCodeRange;
+
+/* Modbus Function Code Categories table. */
+static ModbusFunctionCodeRange modbusFunctionCodeRanges[] = {
+ { 0, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 9, MODBUS_CAT_RESERVED },
+ { 15, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 41, MODBUS_CAT_RESERVED },
+ { 43, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 65, MODBUS_CAT_USER_DEFINED },
+ { 73, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 90, MODBUS_CAT_RESERVED },
+ { 92, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 100, MODBUS_CAT_USER_DEFINED },
+ { 111, MODBUS_CAT_PUBLIC_UNASSIGNED},
+ { 125, MODBUS_CAT_RESERVED },
+ { 128, MODBUS_CAT_NONE }
+};
+
+/** \internal
+ * \brief Parse the Modbus Protocol Data Unit (PDU) Request
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param ModbusPdu Pointer the Modbus PDU state in which the value to be stored
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ */
+static void ModbusParseRequestPDU(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len)
+{
+ SCEnter();
+ uint16_t offset = (uint16_t) sizeof(ModbusHeader);
+ uint8_t count;
+
+ int i = 0;
+
+ /* Standard function codes used on MODBUS application layer protocol (1 byte) */
+ if (ModbusExtractUint8(modbus, &(tx->function), input, input_len, &offset))
+ goto end;
+
+ /* Set default function code category */
+ tx->category = MODBUS_CAT_NONE;
+
+ /* Set default function primary table */
+ tx->type = MODBUS_TYP_NONE;
+
+ switch (tx->function) {
+ case MODBUS_FUNC_NONE:
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
+ break;
+
+ case MODBUS_FUNC_READCOILS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_READ);
+ break;
+
+ case MODBUS_FUNC_READDISCINPUTS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_DISCRETES | MODBUS_TYP_READ);
+ break;
+
+ case MODBUS_FUNC_READHOLDREGS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ);
+ break;
+
+ case MODBUS_FUNC_READINPUTREGS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_INPUT | MODBUS_TYP_READ);
+ break;
+
+ case MODBUS_FUNC_WRITESINGLECOIL:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_SINGLE);
+ break;
+
+ case MODBUS_FUNC_WRITESINGLEREG:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_SINGLE);
+ break;
+
+ case MODBUS_FUNC_WRITEMULTCOILS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_MULTIPLE);
+ break;
+
+ case MODBUS_FUNC_WRITEMULTREGS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_MULTIPLE);
+ break;
+
+ case MODBUS_FUNC_MASKWRITEREG:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE);
+ break;
+
+ case MODBUS_FUNC_READWRITEMULTREGS:
+ /* Set function type */
+ tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ_WRITE_MULTIPLE);
+ break;
+
+ case MODBUS_FUNC_READFILERECORD:
+ case MODBUS_FUNC_WRITEFILERECORD:
+ /* Count/length (1 bytes) */
+ if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
+ goto end;
+
+ /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */
+ ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
+ break;
+
+ case MODBUS_FUNC_DIAGNOSTIC:
+ if(ModbusParseDiagnosticRequest(tx, modbus, input, input_len, &offset))
+ goto end;
+ break;
+
+ case MODBUS_FUNC_READEXCSTATUS:
+ case MODBUS_FUNC_GETCOMEVTCOUNTER:
+ case MODBUS_FUNC_GETCOMEVTLOG:
+ case MODBUS_FUNC_REPORTSERVERID:
+ /* Modbus Application Protocol Specification V1.1b3 6.7, 6.9, 6.10 and 6.13 */
+ ModbusCheckHeaderLength(modbus, tx->length, 2);
+ break;
+
+ case MODBUS_FUNC_READFIFOQUEUE:
+ /* Modbus Application Protocol Specification V1.1b3 6.18 */
+ ModbusCheckHeaderLength(modbus, tx->length, 4);
+ break;
+
+ case MODBUS_FUNC_ENCAPINTTRANS:
+ /* MEI type (1 byte) */
+ if (ModbusExtractUint8(modbus, &(tx->mei), input, input_len, &offset))
+ goto end;
+
+ if (tx->mei == MODBUS_MEI_ENCAPINTTRANS_READ) {
+ /* Modbus Application Protocol Specification V1.1b3 6.21 */
+ ModbusCheckHeaderLength(modbus, tx->length, 5);
+ } else if (tx->mei != MODBUS_MEI_ENCAPINTTRANS_CAN) {
+ /* Set function code category */
+ tx->category = MODBUS_CAT_RESERVED;
+ goto end;
+ }
+ break;
+
+ default:
+ /* Check if request is error. */
+ if (tx->function & MODBUS_FUNC_ERRORMASK) {
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
+ goto end;
+ }
+
+ /* Get and store function code category */
+ for (i = 0; modbusFunctionCodeRanges[i].category != MODBUS_CAT_NONE; i++) {
+ if (tx->function <= modbusFunctionCodeRanges[i].function)
+ break;
+ tx->category = modbusFunctionCodeRanges[i].category;
+ }
+ goto end;
+ }
+
+ /* Set function code category */
+ tx->category = MODBUS_CAT_PUBLIC_ASSIGNED;
+
+ if (tx->type & MODBUS_TYP_READ)
+ ModbusParseReadRequest(tx, modbus, input, input_len, &offset);
+
+ if (tx->type & MODBUS_TYP_WRITE)
+ ModbusParseWriteRequest(tx, modbus, input, input_len, &offset);
+
+end:
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse the Modbus Protocol Data Unit (PDU) Response
+ *
+ * \param tx Pointer to Modbus Transaction structure
+ * \param modbus Pointer the Modbus PDU state in which the value to be stored
+ * \param input Pointer the received input data
+ * \param input_len Length of the received input data
+ * \param offset Offset of the received input data pointer
+ */
+static void ModbusParseResponsePDU(ModbusTransaction *tx,
+ ModbusState *modbus,
+ uint8_t *input,
+ uint32_t input_len)
+{
+ SCEnter();
+ uint16_t offset = (uint16_t) sizeof(ModbusHeader);
+ uint8_t count, error = FALSE, function, mei;
+
+ /* Standard function codes used on MODBUS application layer protocol (1 byte) */
+ if (ModbusExtractUint8(modbus, &function, input, input_len, &offset))
+ goto end;
+
+ /* Check if response is error */
+ if(function & MODBUS_FUNC_ERRORMASK) {
+ function &= MODBUS_FUNC_MASK;
+ error = TRUE;
+ }
+
+ if (tx->category == MODBUS_CAT_PUBLIC_ASSIGNED) {
+ /* Check if response is error. */
+ if (error) {
+ ModbusExceptionResponse(tx, modbus, input, input_len, &offset);
+ } else {
+ switch(function) {
+ case MODBUS_FUNC_READEXCSTATUS:
+ /* Modbus Application Protocol Specification V1.1b3 6.7 */
+ ModbusCheckHeaderLength(modbus, tx->length, 3);
+ goto end;
+
+ case MODBUS_FUNC_GETCOMEVTCOUNTER:
+ /* Modbus Application Protocol Specification V1.1b3 6.9 */
+ ModbusCheckHeaderLength(modbus, tx->length, 6);
+ goto end;
+
+ case MODBUS_FUNC_READFILERECORD:
+ case MODBUS_FUNC_WRITEFILERECORD:
+ /* Count/length (1 bytes) */
+ if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
+ goto end;
+
+ /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */
+ ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
+ goto end;
+
+ case MODBUS_FUNC_ENCAPINTTRANS:
+ /* MEI type (1 byte) */
+ if (ModbusExtractUint8(modbus, &mei, input, input_len, &offset))
+ goto end;
+
+ if (mei != tx->mei)
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
+ goto end;
+ }
+
+ if (tx->type & MODBUS_TYP_READ)
+ ModbusParseReadResponse(tx, modbus, input, input_len, &offset);
+ /* Read/Write response contents none write response part */
+ else if (tx->type & MODBUS_TYP_WRITE)
+ ModbusParseWriteResponse(tx, modbus, input, input_len, &offset);
+ }
+ }
+
+end:
+ SCReturn;
+}
+
+/** \internal
+ * \brief Parse the Modbus Application Protocol (MBAP) header
+ *
+ * \param header Pointer the Modbus header state in which the value to be stored
+ * \param input Pointer the received input data
+ */
+static int ModbusParseHeader(ModbusState *modbus,
+ ModbusHeader *header,
+ uint8_t *input,
+ uint32_t input_len)
+{
+ SCEnter();
+ uint16_t offset = 0;
+
+ /* Transaction Identifier (2 bytes) */
+ if (ModbusExtractUint16(modbus, &(header->transactionId), input, input_len, &offset) ||
+ /* Protocol Identifier (2 bytes) */
+ ModbusExtractUint16(modbus, &(header->protocolId), input, input_len, &offset) ||
+ /* Length (2 bytes) */
+ ModbusExtractUint16(modbus, &(header->length), input, input_len, &offset) ||
+ /* Unit Identifier (1 byte) */
+ ModbusExtractUint8(modbus, &(header->unitId), input, input_len, &offset))
+ SCReturnInt(-1);
+
+ SCReturnInt(0);
+}
+
+/** \internal
+ *
+ * \brief This function is called to retrieve a Modbus Request
+ *
+ * \param state Modbus 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 int ModbusParseRequest(Flow *f,
+ void *state,
+ AppLayerParserState *pstate,
+ uint8_t *input,
+ uint32_t input_len,
+ void *local_data)
+{
+ SCEnter();
+ ModbusState *modbus = (ModbusState *) state;
+ ModbusTransaction *tx;
+ ModbusHeader header;
+
+ if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
+ SCReturnInt(1);
+ } else if (input == NULL || input_len == 0) {
+ SCReturnInt(-1);
+ }
+
+ while (input_len > 0) {
+ uint32_t adu_len = input_len;
+ uint8_t *adu = input;
+
+ /* Extract MODBUS Header */
+ if (ModbusParseHeader(modbus, &header, adu, adu_len))
+ SCReturnInt(0);
+
+ /* Update ADU length with length in Modbus header. */
+ adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
+ if (adu_len > input_len)
+ SCReturnInt(0);
+
+ /* Allocate a Transaction Context and add it to Transaction list */
+ tx = ModbusTxAlloc(modbus);
+ if (tx == NULL)
+ SCReturnInt(0);
+
+ /* Check MODBUS Header */
+ ModbusCheckHeader(modbus, &header);
+
+ /* Store Transaction ID & PDU length */
+ tx->transactionId = header.transactionId;
+ tx->length = header.length;
+
+ /* Extract MODBUS PDU and fill Transaction Context */
+ ModbusParseRequestPDU(tx, modbus, adu, adu_len);
+
+ /* Update input line and remaining input length of the command */
+ input += adu_len;
+ input_len -= adu_len;
+ }
+
+ SCReturnInt(1);
+}
+
+/** \internal
+ * \brief This function is called to retrieve a Modbus response
+ *
+ * \param state Pointer to Modbus 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 int ModbusParseResponse(Flow *f,
+ void *state,
+ AppLayerParserState *pstate,
+ uint8_t *input,
+ uint32_t input_len,
+ void *local_data)
+{
+ SCEnter();
+ ModbusHeader header;
+ ModbusState *modbus = (ModbusState *) state;
+ ModbusTransaction *tx;
+
+ if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
+ SCReturnInt(1);
+ } else if (input == NULL || input_len == 0) {
+ SCReturnInt(-1);
+ }
+
+ while (input_len > 0) {
+ uint32_t adu_len = input_len;
+ uint8_t *adu = input;
+
+ /* Extract MODBUS Header */
+ if (ModbusParseHeader(modbus, &header, adu, adu_len))
+ SCReturnInt(0);
+
+ /* Update ADU length with length in Modbus header. */
+ adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
+ if (adu_len > input_len)
+ SCReturnInt(0);
+
+ /* Find the transaction context thanks to transaction ID (and function code) */
+ tx = ModbusTxFindByTransaction(modbus, header.transactionId);
+ if (tx == NULL) {
+ /* Allocate a Transaction Context if not previous request */
+ /* and add it to Transaction list */
+ tx = ModbusTxAlloc(modbus);
+ if (tx == NULL)
+ SCReturnInt(0);
+
+ SCLogDebug("MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE");
+ ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE);
+ } else {
+ /* Store PDU length */
+ tx->length = header.length;
+
+ /* Extract MODBUS PDU and fill Transaction Context */
+ ModbusParseResponsePDU(tx, modbus, adu, adu_len);
+ }
+
+ /* Check and store MODBUS Header */
+ ModbusCheckHeader(modbus, &header);
+
+ /* Mark as completed */
+ tx->replied = 1;
+
+ /* Update input line and remaining input length of the command */
+ input += adu_len;
+ input_len -= adu_len;
+ }
+
+ SCReturnInt(1);
+}
+
+/** \internal
+ * \brief Function to allocate the Modbus state memory
+ */
+static void *ModbusStateAlloc(void)
+{
+ ModbusState *modbus;
+
+ modbus = (ModbusState *) SCCalloc(1, sizeof(ModbusState));
+ if (unlikely(modbus == NULL))
+ return NULL;
+
+ TAILQ_INIT(&modbus->tx_list);
+
+ return (void *) modbus;
+}
+
+/** \internal
+ * \brief Function to free the Modbus state memory
+ */
+static void ModbusStateFree(void *state)
+{
+ SCEnter();
+ ModbusState *modbus = (ModbusState *) state;
+ ModbusTransaction *tx = NULL, *ttx;
+
+ if (state) {
+ TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
+ ModbusTxFree(tx);
+ }
+
+ SCFree(state);
+ }
+ SCReturn;
+}
+
+static uint16_t ModbusProbingParser(uint8_t *input,
+ uint32_t input_len,
+ uint32_t *offset)
+{
+ ModbusHeader *header = (ModbusHeader *) input;
+
+ /* Modbus header is 7 bytes long */
+ if (input_len < sizeof(ModbusHeader))
+ return ALPROTO_UNKNOWN;
+
+ /* MODBUS protocol is identified by the value 0. */
+ if (header->protocolId != 0)
+ return ALPROTO_FAILED;
+
+ return ALPROTO_MODBUS;
+}
+
+DetectEngineState *ModbusGetTxDetectState(void *vtx)
+{
+ ModbusTransaction *tx = (ModbusTransaction *)vtx;
+ return tx->de_state;
+}
+
+int ModbusSetTxDetectState(void *state, void *vtx, DetectEngineState *s)
+{
+ ModbusTransaction *tx = (ModbusTransaction *)vtx;
+ tx->de_state = s;
+ return 0;
+}
+
+/**
+ * \brief Function to register the Modbus protocol parsers and other functions
+ */
+void RegisterModbusParsers(void)
+{
+ SCEnter();
+ char *proto_name = "modbus";
+
+ /* Modbus application protocol V1.1b3 */
+ if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
+ AppLayerProtoDetectRegisterProtocol(ALPROTO_MODBUS, proto_name);
+
+ if (RunmodeIsUnittests()) {
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP,
+ "502",
+ ALPROTO_MODBUS,
+ 0, sizeof(ModbusHeader),
+ STREAM_TOSERVER,
+ ModbusProbingParser);
+ } else {
+ /* if we have no config, we enable the default port 502 */
+ if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
+ proto_name, ALPROTO_MODBUS,
+ 0, sizeof(ModbusHeader),
+ ModbusProbingParser)) {
+ SCLogWarning(SC_ERR_MODBUS_CONFIG, "no Modbus TCP config found, "
+ "enabling Modbus detection on "
+ "port 502.");
+
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP,
+ "502",
+ ALPROTO_MODBUS,
+ 0, sizeof(ModbusHeader),
+ STREAM_TOSERVER,
+ ModbusProbingParser);
+ }
+ }
+
+ ConfNode *p = ConfGetNode("app-layer.protocols.modbus.request-flood");
+ if (p != NULL) {
+ uint32_t value;
+ if (ParseSizeStringU32(p->val, &value) < 0) {
+ SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for request-flood %s", p->val);
+ } else {
+ request_flood = value;
+ }
+ }
+ SCLogInfo("Modbus request flood protection level: %u", request_flood);
+ } else {
+ SCLogInfo("Protocol detection and parser disabled for %s protocol.", proto_name);
+ return;
+ }
+
+ if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
+ AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER, ModbusParseRequest);
+ AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOCLIENT, ModbusParseResponse);
+ AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateAlloc, ModbusStateFree);
+
+ AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetEvents);
+ AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusHasEvents);
+ AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, NULL,
+ ModbusGetTxDetectState, ModbusSetTxDetectState);
+
+ AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTx);
+ AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxCnt);
+ AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree);
+
+ AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress);
+ AppLayerParserRegisterGetStateProgressCompletionStatus(IPPROTO_TCP, ALPROTO_MODBUS,
+ ModbusGetAlstateProgressCompletionStatus);
+
+ AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo);
+
+ AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER);
+ } else {
+ SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name);
+ }
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests);
+#endif
+
+ SCReturn;
+}
+
+/* UNITTESTS */
+#ifdef UNITTESTS
+#include "detect.h"
+#include "detect-engine.h"
+#include "detect-parse.h"
+
+#include "flow-util.h"
+
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#include "stream-tcp.h"
+#include "stream-tcp-private.h"
+
+/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
+/* Example of a request to read discrete outputs 20-38 */
+static uint8_t readCoilsReq[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Starting Address */ 0x78, 0x90,
+ /* Quantity of coils */ 0x00, 0x13 };
+
+static uint8_t readCoilsRsp[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Byte count */ 0x03,
+ /* Coil Status */ 0xCD, 0x6B, 0x05 };
+
+static uint8_t readCoilsErrorRsp[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x03,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x81,
+ /* Exception code */ 0x05};
+
+/* Modbus Application Protocol Specification V1.1b3 6.12: Write Multiple registers */
+/* Example of a request to write two registers starting at 2 to 00 0A and 01 02 hex */
+static uint8_t writeMultipleRegistersReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x0B,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02,
+ /* Byte count */ 0x04,
+ /* Registers Value */ 0x00, 0x0A,
+ 0x01, 0x02};
+
+static uint8_t writeMultipleRegistersRsp[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02};
+
+/* Modbus Application Protocol Specification V1.1b3 6.17: Read/Write Multiple registers */
+/* Example of a request to read six registers starting at register 4, */
+/* and to write three registers starting at register 15 */
+static uint8_t readWriteMultipleRegistersReq[] = {/* Transaction ID */ 0x12, 0x34,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x11,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x17,
+ /* Read Starting Address */ 0x00, 0x03,
+ /* Quantity to Read */ 0x00, 0x06,
+ /* Write Starting Address */ 0x00, 0x0E,
+ /* Quantity to Write */ 0x00, 0x03,
+ /* Write Byte count */ 0x06,
+ /* Write Registers Value */ 0x12, 0x34,
+ 0x56, 0x78,
+ 0x9A, 0xBC};
+
+/* Mismatch value in Byte count 0x0B instead of 0x0C */
+static uint8_t readWriteMultipleRegistersRsp[] = {/* Transaction ID */ 0x12, 0x34,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x0E,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x17,
+ /* Byte count */ 0x0B,
+ /* Read Registers Value */ 0x00, 0xFE,
+ 0x0A, 0xCD,
+ 0x00, 0x01,
+ 0x00, 0x03,
+ 0x00, 0x0D,
+ 0x00};
+
+/* Modbus Application Protocol Specification V1.1b3 6.8.1: 04 Force Listen Only Mode */
+/* Example of a request to to remote device to its Listen Only MOde for Modbus Communications. */
+static uint8_t forceListenOnlyMode[] = {/* Transaction ID */ 0x0A, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x08,
+ /* Sub-function code */ 0x00, 0x04,
+ /* Data */ 0x00, 0x00};
+
+static uint8_t invalidProtocolIdReq[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x01,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Starting Address */ 0x78, 0x90,
+ /* Quantity of coils */ 0x00, 0x13 };
+
+static uint8_t invalidLengthWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x09,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02,
+ /* Byte count */ 0x04,
+ /* Registers Value */ 0x00, 0x0A,
+ 0x01, 0x02};
+
+static uint8_t exceededLengthWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0xff, 0xfa,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x7f, 0xf9,
+ /* Byte count */ 0xff};
+
+static uint8_t invalidLengthPDUWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x02,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10};
+
+/** \test Send Modbus Read Coils request/response. */
+static int ModbusParserTest01(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ int result = 0;
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+
+ StreamTcpInitConfig(TRUE);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ readCoilsReq, sizeof(readCoilsReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function);
+ printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address);
+ printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity);
+ goto end;
+ }
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ readCoilsRsp, sizeof(readCoilsRsp));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ if (modbus_state->transaction_max !=1) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max);
+ goto end;
+ }
+
+ result = 1;
+end:
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ return result;
+}
+
+/** \test Send Modbus Write Multiple registers request/response. */
+static int ModbusParserTest02(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ int result = 0;
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+
+ StreamTcpInitConfig(TRUE);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 16) || (tx->write.address != 0x01) || (tx->write.quantity != 2) ||
+ (tx->write.count != 4) || (tx->data[0] != 0x000A) || (tx->data[1] != 0x0102)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 16, tx->function);
+ printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x01, tx->write.address);
+ printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 2, tx->write.quantity);
+ printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 4, tx->write.count);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x000A, tx->data[0]);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x0102, tx->data[1]);
+ goto end;
+ }
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ writeMultipleRegistersRsp, sizeof(writeMultipleRegistersRsp));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ if (modbus_state->transaction_max !=1) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max);
+ goto end;
+ }
+
+ result = 1;
+end:
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ return result;
+}
+
+/** \test Send Modbus Read/Write Multiple registers request/response with mismatch value. */
+static int ModbusParserTest03(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus Data mismatch\"; "
+ "app-layer-event: "
+ "modbus.value_mismatch; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ readWriteMultipleRegistersReq, sizeof(readWriteMultipleRegistersReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 23) || (tx->read.address != 0x03) || (tx->read.quantity != 6) ||
+ (tx->write.address != 0x0E) || (tx->write.quantity != 3) || (tx->write.count != 6) ||
+ (tx->data[0] != 0x1234) || (tx->data[1] != 0x5678) || (tx->data[2] != 0x9ABC)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 23, tx->function);
+ printf("expected read address %" PRIu8 ", got %" PRIu8 ": ", 0x03, tx->read.address);
+ printf("expected read quantity %" PRIu8 ", got %" PRIu8 ": ", 6, tx->read.quantity);
+ printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x0E, tx->write.address);
+ printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 3, tx->write.quantity);
+ printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 6, tx->write.count);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x1234, tx->data[0]);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x5678, tx->data[1]);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x9ABC, tx->data[2]);
+ goto end;
+ }
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ readWriteMultipleRegistersRsp, sizeof(readWriteMultipleRegistersRsp));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ if (modbus_state->transaction_max !=1) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max);
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Send Modbus Force Listen Only Mode request. */
+static int ModbusParserTest04(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ int result = 0;
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+
+ StreamTcpInitConfig(TRUE);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ forceListenOnlyMode, sizeof(forceListenOnlyMode));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 8) || (tx->subFunction != 4)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 8, tx->function);
+ printf("expected sub-function %" PRIu8 ", got %" PRIu8 ": ", 0x04, tx->subFunction);
+ goto end;
+ }
+
+ result = 1;
+end:
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ return result;
+}
+
+/** \test Send Modbus invalid Protocol version in request. */
+static int ModbusParserTest05(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Protocol version\"; "
+ "app-layer-event: "
+ "modbus.invalid_protocol_id; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ invalidProtocolIdReq, sizeof(invalidProtocolIdReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Send Modbus unsolicited response. */
+static int ModbusParserTest06(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus unsolicited response\"; "
+ "app-layer-event: "
+ "modbus.unsolicited_response; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ readCoilsRsp, sizeof(readCoilsRsp));
+ if (r != 0) {
+ printf("toclient chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Send Modbus invalid Length request. */
+static int ModbusParserTest07(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ invalidLengthWriteMultipleRegistersReq,
+ sizeof(invalidLengthWriteMultipleRegistersReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Send Modbus Read Coils request and error response with Exception code invalid. */
+static int ModbusParserTest08(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus Exception code invalid\"; "
+ "app-layer-event: "
+ "modbus.invalid_exception_code; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ readCoilsReq, sizeof(readCoilsReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function);
+ printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address);
+ printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity);
+ goto end;
+ }
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ readCoilsErrorRsp, sizeof(readCoilsErrorRsp));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ if (modbus_state->transaction_max !=1) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max);
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Modbus fragmentation - 1 ADU over 2 TCP packets. */
+static int ModbusParserTest09(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ uint32_t input_len = sizeof(readCoilsReq), part2_len = 3;
+ uint8_t *input = readCoilsReq;
+
+ int result = 0;
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+
+ StreamTcpInitConfig(TRUE);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ input, input_len - part2_len);
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ input, input_len);
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+
+ if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function);
+ printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address);
+ printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity);
+ goto end;
+ }
+
+ input_len = sizeof(readCoilsRsp);
+ part2_len = 10;
+ input = readCoilsRsp;
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ input, input_len - part2_len);
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ input, input_len);
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ if (modbus_state->transaction_max !=1) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max);
+ goto end;
+ }
+
+ result = 1;
+end:
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ return result;
+}
+
+/** \test Modbus fragmentation - 2 ADU in 1 TCP packet. */
+static int ModbusParserTest10(void) {
+ uint32_t input_len = sizeof(readCoilsReq) + sizeof(writeMultipleRegistersReq);
+ uint8_t *input, *ptr;
+
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ int result = 0;
+
+ input = (uint8_t *) SCMalloc (input_len * sizeof(uint8_t));
+ if (unlikely(input == NULL))
+ goto end;
+
+ memcpy(input, readCoilsReq, sizeof(readCoilsReq));
+ memcpy(input + sizeof(readCoilsReq), writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq));
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+
+ StreamTcpInitConfig(TRUE);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ input, input_len);
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ if (modbus_state->transaction_max !=2) {
+ printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 2, modbus_state->transaction_max);
+ goto end;
+ }
+
+ ModbusTransaction *tx = ModbusGetTx(modbus_state, 1);
+
+ if ((tx->function != 16) || (tx->write.address != 0x01) || (tx->write.quantity != 2) ||
+ (tx->write.count != 4) || (tx->data[0] != 0x000A) || (tx->data[1] != 0x0102)) {
+ printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 16, tx->function);
+ printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x01, tx->write.address);
+ printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 2, tx->write.quantity);
+ printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 4, tx->write.count);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x000A, tx->data[0]);
+ printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x0102, tx->data[1]);
+ goto end;
+ }
+
+ input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp);
+
+ ptr = (uint8_t *) SCRealloc (input, input_len * sizeof(uint8_t));
+ if (unlikely(ptr == NULL))
+ goto end;
+ input = ptr;
+
+ memcpy(input, readCoilsRsp, sizeof(readCoilsRsp));
+ memcpy(input + sizeof(readCoilsRsp), writeMultipleRegistersRsp, sizeof(writeMultipleRegistersRsp));
+
+ SCMutexLock(&f.m);
+ r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ input, sizeof(input_len));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ result = 1;
+end:
+ if (input != NULL)
+ SCFree(input);
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ return result;
+}
+
+/** \test Send Modbus exceed Length request. */
+static int ModbusParserTest11(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ exceededLengthWriteMultipleRegistersReq,
+ sizeof(exceededLengthWriteMultipleRegistersReq) + 65523 /* header.length - 7 */ * sizeof(uint8_t));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+
+/** \test Send Modbus invalid PDU Length. */
+static int ModbusParserTest12(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ int result = 0;
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(TRUE);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ if (de_ctx == NULL)
+ goto end;
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ if (s == NULL)
+ goto end;
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ SCMutexLock(&f.m);
+ int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ invalidLengthPDUWriteMultipleRegistersReq,
+ sizeof(invalidLengthPDUWriteMultipleRegistersReq));
+ if (r != 0) {
+ printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+ SCMutexUnlock(&f.m);
+ goto end;
+ }
+ SCMutexUnlock(&f.m);
+
+ ModbusState *modbus_state = f.alstate;
+ if (modbus_state == NULL) {
+ printf("no modbus state: ");
+ goto end;
+ }
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ if (!PacketAlertCheck(p, 1)) {
+ printf("sid 1 didn't match. Should have matched: ");
+ goto end;
+ }
+
+ result = 1;
+end:
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ if (alp_tctx != NULL)
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(TRUE);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ return result;
+}
+#endif /* UNITTESTS */
+
+void ModbusParserRegisterTests(void) {
+#ifdef UNITTESTS
+ UtRegisterTest("ModbusParserTest01 - Modbus Read Coils request", ModbusParserTest01, 1);
+ UtRegisterTest("ModbusParserTest02 - Modbus Write Multiple registers request", ModbusParserTest02, 1);
+ UtRegisterTest("ModbusParserTest03 - Modbus Read/Write Multiple registers request", ModbusParserTest03, 1);
+ UtRegisterTest("ModbusParserTest04 - Modbus Force Listen Only Mode request", ModbusParserTest04, 1);
+ UtRegisterTest("ModbusParserTest05 - Modbus invalid Protocol version", ModbusParserTest05, 1);
+ UtRegisterTest("ModbusParserTest06 - Modbus unsolicited response", ModbusParserTest06, 1);
+ UtRegisterTest("ModbusParserTest07 - Modbus invalid Length request", ModbusParserTest07, 1);
+ UtRegisterTest("ModbusParserTest08 - Modbus Exception code invalid", ModbusParserTest08, 1);
+ UtRegisterTest("ModbusParserTest09 - Modbus fragmentation - 1 ADU in 2 TCP packets", ModbusParserTest09, 1);
+ UtRegisterTest("ModbusParserTest10 - Modbus fragmentation - 2 ADU in 1 TCP packet", ModbusParserTest10, 1);
+ UtRegisterTest("ModbusParserTest11 - Modbus exceeded Length request", ModbusParserTest11, 1);
+ UtRegisterTest("ModbusParserTest12 - Modbus invalid PDU Length", ModbusParserTest12, 1);
+#endif /* UNITTESTS */
+}