mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3297 lines
114 KiB
C
3297 lines
114 KiB
C
/*
|
|
* 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 "stream-tcp.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 "conf-yaml-loader.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
|
|
|
|
/* Modbus default stream reassembly depth */
|
|
#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
|
|
|
|
static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD;
|
|
static uint32_t stream_depth = MODBUS_CONFIG_DEFAULT_STREAM_DEPTH;
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int ModbusHasEvents(void *state)
|
|
{
|
|
return (((ModbusState *) state)->events > 0);
|
|
}
|
|
|
|
static 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
|
|
*/
|
|
static int ModbusGetAlstateProgressCompletionStatus(uint8_t direction)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static void ModbusSetTxLogged(void *alstate, void *vtx, uint32_t logger)
|
|
{
|
|
ModbusTransaction *tx = (ModbusTransaction *)vtx;
|
|
tx->logged |= logger;
|
|
}
|
|
|
|
static int ModbusGetTxLogged(void *alstate, void *vtx, uint32_t logger)
|
|
{
|
|
ModbusTransaction *tx = (ModbusTransaction *)vtx;
|
|
if (tx->logged & logger)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static 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
|
|
*/
|
|
static 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 = 0;
|
|
|
|
/* 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 = 0;
|
|
|
|
/* 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 = 0;
|
|
uint8_t byte = 0, 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 = 0, quantity = 0, word = 0;
|
|
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) {
|
|
/* Check if Outputs/Registers value has been stored */
|
|
if (tx->data != NULL)
|
|
{
|
|
/* 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 {
|
|
/* Check if And_Mask and Or_Mask values have been stored */
|
|
if (tx->data != NULL)
|
|
{
|
|
/* 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))
|
|
goto end;
|
|
|
|
/* 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 = 0;
|
|
|
|
/* 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 = 0;
|
|
|
|
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 = 0, error = FALSE, function = 0, mei = 0;
|
|
|
|
/* 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;
|
|
|
|
int r = 0;
|
|
|
|
/* can't pass the header fields directly due to alignment (Bug 2088) */
|
|
uint16_t transaction_id = 0;
|
|
uint16_t protocol_id = 0;
|
|
uint16_t length = 0;
|
|
uint8_t unit_id = 0;
|
|
|
|
/* Transaction Identifier (2 bytes) */
|
|
r = ModbusExtractUint16(modbus, &transaction_id, input, input_len, &offset);
|
|
/* Protocol Identifier (2 bytes) */
|
|
r |= ModbusExtractUint16(modbus, &protocol_id, input, input_len, &offset);
|
|
/* Length (2 bytes) */
|
|
r |= ModbusExtractUint16(modbus, &length, input, input_len, &offset);
|
|
/* Unit Identifier (1 byte) */
|
|
r |= ModbusExtractUint8(modbus, &unit_id, input, input_len, &offset);
|
|
|
|
if (r != 0) {
|
|
SCReturnInt(-1);
|
|
}
|
|
header->transactionId = transaction_id;
|
|
header->protocolId = protocol_id;
|
|
header->length = length;
|
|
header->unitId = unit_id;
|
|
|
|
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;
|
|
}
|
|
|
|
static DetectEngineState *ModbusGetTxDetectState(void *vtx)
|
|
{
|
|
ModbusTransaction *tx = (ModbusTransaction *)vtx;
|
|
return tx->de_state;
|
|
}
|
|
|
|
static 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();
|
|
const 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, NULL);
|
|
} else {
|
|
/* If there is no app-layer section for Modbus, silently
|
|
* leave it disabled. */
|
|
if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
|
|
proto_name, ALPROTO_MODBUS,
|
|
0, sizeof(ModbusHeader),
|
|
ModbusProbingParser, NULL)) {
|
|
#ifndef AFLFUZZ_APPLAYER
|
|
return;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ConfNode *p = NULL;
|
|
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;
|
|
}
|
|
}
|
|
SCLogConfig("Modbus request flood protection level: %u", request_flood);
|
|
|
|
p = ConfGetNode("app-layer.protocols.modbus.stream-depth");
|
|
if (p != NULL) {
|
|
uint32_t value;
|
|
if (ParseSizeStringU32(p->val, &value) < 0) {
|
|
SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for stream-depth %s", p->val);
|
|
} else {
|
|
stream_depth = value;
|
|
}
|
|
}
|
|
SCLogConfig("Modbus stream depth: %u", stream_depth);
|
|
} else {
|
|
#ifndef AFLFUZZ_APPLAYER
|
|
SCLogConfig("Protocol detection and parser disabled for %s protocol.", proto_name);
|
|
return;
|
|
#endif
|
|
}
|
|
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);
|
|
AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxLogged,
|
|
ModbusSetTxLogged);
|
|
AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree);
|
|
|
|
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress);
|
|
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_MODBUS,
|
|
ModbusGetAlstateProgressCompletionStatus);
|
|
|
|
AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo);
|
|
|
|
AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER);
|
|
|
|
AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_MODBUS, stream_depth);
|
|
} else {
|
|
SCLogConfig("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.6: Write Single register */
|
|
/* Example of a request to write register 2 to 00 03 hex */
|
|
static uint8_t writeSingleRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x06,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x06,
|
|
/* Register Address */ 0x00, 0x01,
|
|
/* Register Value */ 0x00, 0x03};
|
|
|
|
static uint8_t invalidWriteSingleRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x04,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x06,
|
|
/* Register Address */ 0x00, 0x01};
|
|
|
|
static uint8_t writeSingleRegisterRsp[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x06,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x06,
|
|
/* Register Address */ 0x00, 0x01,
|
|
/* Register Value */ 0x00, 0x03};
|
|
|
|
/* 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.16: Mask Write Register */
|
|
/* Example of a request to mask write to register 5 */
|
|
static uint8_t maskWriteRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x08,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x16,
|
|
/* Reference Address */ 0x00, 0x04,
|
|
/* And_Mask */ 0x00, 0xF2,
|
|
/* Or_Mask */ 0x00, 0x25};
|
|
|
|
static uint8_t invalidMaskWriteRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x06,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x16,
|
|
/* Reference Address */ 0x00, 0x04,
|
|
/* And_Mask */ 0x00, 0xF2};
|
|
|
|
static uint8_t maskWriteRegisterRsp[] = {/* Transaction ID */ 0x00, 0x0A,
|
|
/* Protocol ID */ 0x00, 0x00,
|
|
/* Length */ 0x00, 0x08,
|
|
/* Unit ID */ 0x00,
|
|
/* Function code */ 0x16,
|
|
/* Reference Address */ 0x00, 0x04,
|
|
/* And_Mask */ 0x00, 0xF2,
|
|
/* Or_Mask */ 0x00, 0x25};
|
|
|
|
/* 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));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, readCoilsReq,
|
|
sizeof(readCoilsReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 1, tx->function);
|
|
printf("expected address %d, got %" PRIu16 ": ", 0x7890, tx->read.address);
|
|
printf("expected quantity %d, got %" PRIu16 ": ", 19, tx->read.quantity);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, readCoilsRsp,
|
|
sizeof(readCoilsRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, writeMultipleRegistersReq,
|
|
sizeof(writeMultipleRegistersReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 16, tx->function);
|
|
printf("expected write address %d, got %" PRIu16 ": ", 0x01, tx->write.address);
|
|
printf("expected write quantity %d, got %" PRIu16 ": ", 2, tx->write.quantity);
|
|
printf("expected write count %d, got %" PRIu8 ": ", 4, tx->write.count);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x000A, tx->data[0]);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x0102, tx->data[1]);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, writeMultipleRegistersRsp,
|
|
sizeof(writeMultipleRegistersRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER,
|
|
readWriteMultipleRegistersReq,
|
|
sizeof(readWriteMultipleRegistersReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 23, tx->function);
|
|
printf("expected read address %d, got %" PRIu16 ": ", 0x03, tx->read.address);
|
|
printf("expected read quantity %d, got %" PRIu16 ": ", 6, tx->read.quantity);
|
|
printf("expected write address %d, got %" PRIu16 ": ", 0x0E, tx->write.address);
|
|
printf("expected write quantity %d, got %" PRIu16 ": ", 3, tx->write.quantity);
|
|
printf("expected write count %d, got %" PRIu8 ": ", 6, tx->write.count);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x1234, tx->data[0]);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x5678, tx->data[1]);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x9ABC, tx->data[2]);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, readWriteMultipleRegistersRsp,
|
|
sizeof(readWriteMultipleRegistersRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, forceListenOnlyMode,
|
|
sizeof(forceListenOnlyMode));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 8, tx->function);
|
|
printf("expected sub-function %d, got %" PRIu16 ": ", 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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, invalidProtocolIdReq,
|
|
sizeof(invalidProtocolIdReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, readCoilsRsp,
|
|
sizeof(readCoilsRsp));
|
|
if (r != 0) {
|
|
printf("toclient chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER,
|
|
invalidLengthWriteMultipleRegistersReq,
|
|
sizeof(invalidLengthWriteMultipleRegistersReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, readCoilsReq,
|
|
sizeof(readCoilsReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 1, tx->function);
|
|
printf("expected address %d, got %" PRIu16 ": ", 0x7890, tx->read.address);
|
|
printf("expected quantity %d, got %" PRIu16 ": ", 19, tx->read.quantity);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, readCoilsErrorRsp,
|
|
sizeof(readCoilsErrorRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, input, input_len - part2_len);
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, input, input_len);
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, got %" PRIu8 ": ", 1, tx->function);
|
|
printf("expected address %d, got %" PRIu16 ": ", 0x7890, tx->read.address);
|
|
printf("expected quantity %d, got %" PRIu16 ": ", 19, tx->read.quantity);
|
|
goto end;
|
|
}
|
|
|
|
input_len = sizeof(readCoilsRsp);
|
|
part2_len = 10;
|
|
input = readCoilsRsp;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, input, input_len - part2_len);
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, input, input_len);
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, input, input_len);
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 %d, 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 %d, got %" PRIu8 ": ", 16, tx->function);
|
|
printf("expected write address %d, got %" PRIu16 ": ", 0x01, tx->write.address);
|
|
printf("expected write quantity %d, got %" PRIu16 ": ", 2, tx->write.quantity);
|
|
printf("expected write count %d, got %" PRIu8 ": ", 4, tx->write.count);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x000A, tx->data[0]);
|
|
printf("expected data %d, got %" PRIu16 ": ", 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));
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, input, sizeof(input_len));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER,
|
|
exceededLengthWriteMultipleRegistersReq,
|
|
sizeof(exceededLengthWriteMultipleRegistersReq) + 65523 * sizeof(uint8_t));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER,
|
|
invalidLengthPDUWriteMultipleRegistersReq,
|
|
sizeof(invalidLengthPDUWriteMultipleRegistersReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
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 Mask Write register request/response. */
|
|
static int ModbusParserTest13(void) {
|
|
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
|
|
Flow f;
|
|
TcpSession ssn;
|
|
|
|
int result = 0;
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, maskWriteRegisterReq,
|
|
sizeof(maskWriteRegisterReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
ModbusState *modbus_state = f.alstate;
|
|
if (modbus_state == NULL) {
|
|
printf("no modbus state: ");
|
|
goto end;
|
|
}
|
|
|
|
ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
|
|
|
|
if ((tx->function != 22) || (tx->data[0] != 0x00F2) || (tx->data[1] != 0x0025)) {
|
|
printf("expected function %d, got %" PRIu8 ": ", 16, tx->function);
|
|
printf("expected And_Mask %d, got %" PRIu16 ": ", 0x00F2, tx->data[0]);
|
|
printf("expected Or_Mask %d, got %" PRIu16 ": ", 0x0025, tx->data[1]);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, maskWriteRegisterRsp,
|
|
sizeof(maskWriteRegisterRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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 single register request/response. */
|
|
static int ModbusParserTest14(void) {
|
|
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
|
|
Flow f;
|
|
TcpSession ssn;
|
|
|
|
int result = 0;
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, writeSingleRegisterReq,
|
|
sizeof(writeSingleRegisterReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
ModbusState *modbus_state = f.alstate;
|
|
if (modbus_state == NULL) {
|
|
printf("no modbus state: ");
|
|
goto end;
|
|
}
|
|
|
|
ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
|
|
|
|
if ((tx->function != 6) || (tx->write.address != 0x0001) || (tx->data[0] != 0x0003)) {
|
|
printf("expected function %d, got %" PRIu8 ": ", 16, tx->function);
|
|
printf("expected write address %d, got %" PRIu16 ": ", 0x01, tx->write.address);
|
|
printf("expected data %d, got %" PRIu16 ": ", 0x03, tx->data[0]);
|
|
goto end;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, writeSingleRegisterRsp,
|
|
sizeof(writeSingleRegisterRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, 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 invalid Modbus Mask Write register request. */
|
|
static int ModbusParserTest15(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(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER, invalidMaskWriteRegisterReq,
|
|
sizeof(invalidMaskWriteRegisterReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
ModbusState *modbus_state = f.alstate;
|
|
if (modbus_state == NULL) {
|
|
printf("no modbus state: ");
|
|
goto end;
|
|
}
|
|
|
|
ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
|
|
|
|
if (tx->function != 22) {
|
|
printf("expected function %d, got %" PRIu8 ": ", 16, tx->function);
|
|
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;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, maskWriteRegisterRsp,
|
|
sizeof(maskWriteRegisterRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
|
|
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 invalid Modbus Mask Write register request. */
|
|
static int ModbusParserTest16(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(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
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);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOSERVER,
|
|
invalidWriteSingleRegisterReq,
|
|
sizeof(invalidWriteSingleRegisterReq));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
ModbusState *modbus_state = f.alstate;
|
|
if (modbus_state == NULL) {
|
|
printf("no modbus state: ");
|
|
goto end;
|
|
}
|
|
|
|
ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
|
|
|
|
if ((tx->function != 6) || (tx->write.address != 0x0001)) {
|
|
printf("expected function %d, got %" PRIu8 ": ", 16, tx->function);
|
|
printf("expected write address %d, got %" PRIu16 ": ", 0x01, tx->write.address);
|
|
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;
|
|
}
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
|
|
STREAM_TOCLIENT, writeSingleRegisterRsp,
|
|
sizeof(writeSingleRegisterRsp));
|
|
if (r != 0) {
|
|
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
goto end;
|
|
}
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
if (modbus_state->transaction_max !=1) {
|
|
printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
|
|
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 Checks if stream_depth is correct */
|
|
static int ModbusParserTest17(void) {
|
|
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
|
|
Flow f;
|
|
TcpSession ssn;
|
|
|
|
FAIL_IF(alp_tctx == NULL);
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
|
|
readCoilsReq, sizeof(readCoilsReq));
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(f.alstate == NULL);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
|
|
readCoilsRsp, sizeof(readCoilsRsp));
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
AppLayerParserThreadCtxFree(alp_tctx);
|
|
StreamTcpFreeConfig(TRUE);
|
|
FLOW_DESTROY(&f);
|
|
PASS;
|
|
}
|
|
|
|
/*/ \test Checks if stream depth is correct over 2 TCP packets */
|
|
static int ModbusParserTest18(void) {
|
|
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
|
|
Flow f;
|
|
TcpSession ssn;
|
|
|
|
uint32_t input_len = sizeof(readCoilsReq), part2_len = 3;
|
|
uint8_t *input = readCoilsReq;
|
|
|
|
FAIL_IF(alp_tctx == NULL);
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
memset(&ssn, 0, sizeof(ssn));
|
|
|
|
FLOW_INITIALIZE(&f);
|
|
f.protoctx = (void *)&ssn;
|
|
f.proto = IPPROTO_TCP;
|
|
|
|
StreamTcpInitConfig(TRUE);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
|
|
input, input_len - part2_len);
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
|
|
input, input_len);
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
FAIL_IF(f.alstate == NULL);
|
|
|
|
input_len = sizeof(readCoilsRsp);
|
|
part2_len = 10;
|
|
input = readCoilsRsp;
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
|
|
input, input_len - part2_len);
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
FLOWLOCK_WRLOCK(&f);
|
|
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
|
|
input, input_len);
|
|
FAIL_IF(r != 0);
|
|
FLOWLOCK_UNLOCK(&f);
|
|
|
|
FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
|
|
|
|
AppLayerParserThreadCtxFree(alp_tctx);
|
|
StreamTcpFreeConfig(TRUE);
|
|
FLOW_DESTROY(&f);
|
|
PASS;
|
|
}
|
|
#endif /* UNITTESTS */
|
|
|
|
void ModbusParserRegisterTests(void) {
|
|
#ifdef UNITTESTS
|
|
UtRegisterTest("ModbusParserTest01 - Modbus Read Coils request",
|
|
ModbusParserTest01);
|
|
UtRegisterTest("ModbusParserTest02 - Modbus Write Multiple registers request",
|
|
ModbusParserTest02);
|
|
UtRegisterTest("ModbusParserTest03 - Modbus Read/Write Multiple registers request",
|
|
ModbusParserTest03);
|
|
UtRegisterTest("ModbusParserTest04 - Modbus Force Listen Only Mode request",
|
|
ModbusParserTest04);
|
|
UtRegisterTest("ModbusParserTest05 - Modbus invalid Protocol version",
|
|
ModbusParserTest05);
|
|
UtRegisterTest("ModbusParserTest06 - Modbus unsolicited response",
|
|
ModbusParserTest06);
|
|
UtRegisterTest("ModbusParserTest07 - Modbus invalid Length request",
|
|
ModbusParserTest07);
|
|
UtRegisterTest("ModbusParserTest08 - Modbus Exception code invalid",
|
|
ModbusParserTest08);
|
|
UtRegisterTest("ModbusParserTest09 - Modbus fragmentation - 1 ADU in 2 TCP packets",
|
|
ModbusParserTest09);
|
|
UtRegisterTest("ModbusParserTest10 - Modbus fragmentation - 2 ADU in 1 TCP packet",
|
|
ModbusParserTest10);
|
|
UtRegisterTest("ModbusParserTest11 - Modbus exceeded Length request",
|
|
ModbusParserTest11);
|
|
UtRegisterTest("ModbusParserTest12 - Modbus invalid PDU Length",
|
|
ModbusParserTest12);
|
|
UtRegisterTest("ModbusParserTest13 - Modbus Mask Write register request",
|
|
ModbusParserTest13);
|
|
UtRegisterTest("ModbusParserTest14 - Modbus Write single register request",
|
|
ModbusParserTest14);
|
|
UtRegisterTest("ModbusParserTest15 - Modbus invalid Mask Write register request",
|
|
ModbusParserTest15);
|
|
UtRegisterTest("ModbusParserTest16 - Modbus invalid Write single register request",
|
|
ModbusParserTest16);
|
|
UtRegisterTest("ModbusParserTest17 - Modbus stream depth",
|
|
ModbusParserTest17);
|
|
UtRegisterTest("ModbusParserTest18 - Modbus stream depth in 2 TCP packets",
|
|
ModbusParserTest18);
|
|
#endif /* UNITTESTS */
|
|
}
|