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.
suricata/src/app-layer-dnp3.c

2638 lines
76 KiB
C

/* Copyright (C) 2015-2025 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* DNP3 protocol implementation
*/
#include "suricata-common.h"
#include "suricata.h"
#include "util-unittest.h"
#include "util-spm-bs.h"
#include "util-enum.h"
#include "app-layer.h"
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "app-layer-detect-proto.h"
#include "app-layer-events.h"
#include "app-layer-dnp3.h"
#include "app-layer-dnp3-objects.h"
/* Default number of unreplied requests to be considered a flood. */
#define DNP3_DEFAULT_REQ_FLOOD_COUNT 500
#define DNP3_DEFAULT_PORT "20000"
/* Expected values for the start bytes. */
#define DNP3_START_BYTE0 0x05
#define DNP3_START_BYTE1 0x64
/* Minimum length for a DNP3 frame. */
#define DNP3_MIN_LEN 5
/* Length of each CRC. */
#define DNP3_CRC_LEN 2
/* DNP3 block size. After the link header a CRC is inserted after
* after 16 bytes of data. */
#define DNP3_BLOCK_SIZE 16
/* Maximum transport layer sequence number. */
#define DNP3_MAX_TRAN_SEQNO 64
/* Maximum application layer sequence number. */
// unused #define DNP3_MAX_APP_SEQNO 16
/* The number of bytes in the header that are counted as part of the
* header length field. */
#define DNP3_LINK_HDR_LEN 5
/* Link function codes. */
enum {
DNP3_LINK_FC_CONFIRMED_USER_DATA = 3,
DNP3_LINK_FC_UNCONFIRMED_USER_DATA
};
/* Reserved addresses. */
// unused #define DNP3_RESERVED_ADDR_MIN 0xfff0
// unused #define DNP3_RESERVED_ADDR_MAX 0xfffb
/* Source addresses must be < 0xfff0. */
// unused #define DNP3_SRC_ADDR_MAX 0xfff0
/* Extract the prefix code from the object qualifier. */
#define DNP3_OBJ_PREFIX(x) ((x >> 4) & 0x7)
/* Extract the range code from the object qualifier. */
#define DNP3_OBJ_RANGE(x) (x & 0xf)
/* Decoder event map. */
SCEnumCharMap dnp3_decoder_event_table[] = {
{"FLOODED", DNP3_DECODER_EVENT_FLOODED},
{"LEN_TOO_SMALL", DNP3_DECODER_EVENT_LEN_TOO_SMALL},
{"BAD_LINK_CRC", DNP3_DECODER_EVENT_BAD_LINK_CRC},
{"BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC},
{"MALFORMED", DNP3_DECODER_EVENT_MALFORMED},
{"UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT},
{NULL, -1},
};
/* Calculate the next transport sequence number. */
#define NEXT_TH_SEQNO(current) ((current + 1) % DNP3_MAX_TRAN_SEQNO)
/* CRC table generated by pycrc - http://github.com/tpircher/pycrc.
* - Polynomial: 0x3d65. */
static const uint16_t crc_table[256] = {
0x0000, 0x365e, 0x6cbc, 0x5ae2, 0xd978, 0xef26, 0xb5c4, 0x839a,
0xff89, 0xc9d7, 0x9335, 0xa56b, 0x26f1, 0x10af, 0x4a4d, 0x7c13,
0xb26b, 0x8435, 0xded7, 0xe889, 0x6b13, 0x5d4d, 0x07af, 0x31f1,
0x4de2, 0x7bbc, 0x215e, 0x1700, 0x949a, 0xa2c4, 0xf826, 0xce78,
0x29af, 0x1ff1, 0x4513, 0x734d, 0xf0d7, 0xc689, 0x9c6b, 0xaa35,
0xd626, 0xe078, 0xba9a, 0x8cc4, 0x0f5e, 0x3900, 0x63e2, 0x55bc,
0x9bc4, 0xad9a, 0xf778, 0xc126, 0x42bc, 0x74e2, 0x2e00, 0x185e,
0x644d, 0x5213, 0x08f1, 0x3eaf, 0xbd35, 0x8b6b, 0xd189, 0xe7d7,
0x535e, 0x6500, 0x3fe2, 0x09bc, 0x8a26, 0xbc78, 0xe69a, 0xd0c4,
0xacd7, 0x9a89, 0xc06b, 0xf635, 0x75af, 0x43f1, 0x1913, 0x2f4d,
0xe135, 0xd76b, 0x8d89, 0xbbd7, 0x384d, 0x0e13, 0x54f1, 0x62af,
0x1ebc, 0x28e2, 0x7200, 0x445e, 0xc7c4, 0xf19a, 0xab78, 0x9d26,
0x7af1, 0x4caf, 0x164d, 0x2013, 0xa389, 0x95d7, 0xcf35, 0xf96b,
0x8578, 0xb326, 0xe9c4, 0xdf9a, 0x5c00, 0x6a5e, 0x30bc, 0x06e2,
0xc89a, 0xfec4, 0xa426, 0x9278, 0x11e2, 0x27bc, 0x7d5e, 0x4b00,
0x3713, 0x014d, 0x5baf, 0x6df1, 0xee6b, 0xd835, 0x82d7, 0xb489,
0xa6bc, 0x90e2, 0xca00, 0xfc5e, 0x7fc4, 0x499a, 0x1378, 0x2526,
0x5935, 0x6f6b, 0x3589, 0x03d7, 0x804d, 0xb613, 0xecf1, 0xdaaf,
0x14d7, 0x2289, 0x786b, 0x4e35, 0xcdaf, 0xfbf1, 0xa113, 0x974d,
0xeb5e, 0xdd00, 0x87e2, 0xb1bc, 0x3226, 0x0478, 0x5e9a, 0x68c4,
0x8f13, 0xb94d, 0xe3af, 0xd5f1, 0x566b, 0x6035, 0x3ad7, 0x0c89,
0x709a, 0x46c4, 0x1c26, 0x2a78, 0xa9e2, 0x9fbc, 0xc55e, 0xf300,
0x3d78, 0x0b26, 0x51c4, 0x679a, 0xe400, 0xd25e, 0x88bc, 0xbee2,
0xc2f1, 0xf4af, 0xae4d, 0x9813, 0x1b89, 0x2dd7, 0x7735, 0x416b,
0xf5e2, 0xc3bc, 0x995e, 0xaf00, 0x2c9a, 0x1ac4, 0x4026, 0x7678,
0x0a6b, 0x3c35, 0x66d7, 0x5089, 0xd313, 0xe54d, 0xbfaf, 0x89f1,
0x4789, 0x71d7, 0x2b35, 0x1d6b, 0x9ef1, 0xa8af, 0xf24d, 0xc413,
0xb800, 0x8e5e, 0xd4bc, 0xe2e2, 0x6178, 0x5726, 0x0dc4, 0x3b9a,
0xdc4d, 0xea13, 0xb0f1, 0x86af, 0x0535, 0x336b, 0x6989, 0x5fd7,
0x23c4, 0x159a, 0x4f78, 0x7926, 0xfabc, 0xcce2, 0x9600, 0xa05e,
0x6e26, 0x5878, 0x029a, 0x34c4, 0xb75e, 0x8100, 0xdbe2, 0xedbc,
0x91af, 0xa7f1, 0xfd13, 0xcb4d, 0x48d7, 0x7e89, 0x246b, 0x1235
};
/**
* \brief Compute the CRC for a buffer.
*
* \param buf Buffer to create CRC from.
* \param len Length of buffer (number of bytes to use for CRC).
*/
static uint16_t DNP3ComputeCRC(const uint8_t *buf, uint32_t len)
{
const uint8_t *byte = buf;
uint16_t crc = 0;
int idx;
while (len--) {
idx = (crc ^ *byte) & 0xff;
crc = (crc_table[idx] ^ (crc >> 8)) & 0xffff;
byte++;
}
return ~crc & 0xffff;
}
/**
* \brief Check the CRC of a block.
*
* \param block The block of data with CRC to be checked.
* \param len The size of the data block.
*
* \retval 1 if CRC is OK, otherwise 0.
*/
static int DNP3CheckCRC(const uint8_t *block, uint32_t len)
{
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
return 1;
#endif
uint32_t crc_offset;
uint16_t crc;
/* Need at least one byte plus the CRC. */
if (len < DNP3_CRC_LEN + 1) {
return 0;
}
crc_offset = len - DNP3_CRC_LEN;
crc = DNP3ComputeCRC(block, len - DNP3_CRC_LEN);
if (((crc & 0xff) == block[crc_offset]) &&
((crc >> 8) == block[crc_offset + 1])) {
return 1;
}
return 0;
}
/**
* \brief Check the CRC of the link header.
*
* \param header Point to the link header.
*
* \retval 1 if header CRC is OK, otherwise 0.
*/
static int DNP3CheckLinkHeaderCRC(const DNP3LinkHeader *header)
{
return DNP3CheckCRC((uint8_t *)header, sizeof(DNP3LinkHeader));
}
/**
* \brief Check user data CRCs.
*
* \param data Pointer to user data.
* \param len Length of user data.
*
* \retval 1 if CRCs are OK, otherwise 0.
*/
static int DNP3CheckUserDataCRCs(const uint8_t *data, uint32_t len)
{
uint32_t offset = 0;
uint32_t block_size;
while (offset < len) {
if (len - offset >= DNP3_BLOCK_SIZE + DNP3_CRC_LEN) {
block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN;
}
else {
block_size = len - offset;
}
if (!DNP3CheckCRC(data + offset, block_size)) {
/* Once failed, may as well return immediately. */
return 0;
}
offset += block_size;
}
return 1;
}
/**
* \brief Check the DNP3 frame start bytes.
*
* \retval 1 if valid, 0 if not.
*/
static int DNP3CheckStartBytes(const DNP3LinkHeader *header)
{
return header->start_byte0 == DNP3_START_BYTE0 &&
header->start_byte1 == DNP3_START_BYTE1;
}
/* Some DNP3 servers start with a banner. */
#define DNP3_BANNER "DNP3"
/**
* \brief Check if a frame contains a banner.
*
* Some servers (outstations) appear to send back a banner that fails
* the normal frame checks. So first check for a banner.
*
* \retval 1 if a banner is found, 0 if not.
*/
static int DNP3ContainsBanner(const uint8_t *input, uint32_t len)
{
return BasicSearch(input, len, (uint8_t *)DNP3_BANNER, strlen(DNP3_BANNER)) != NULL;
}
/**
* \brief DNP3 probing parser.
*/
static uint16_t DNP3ProbingParser(
const Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
{
const DNP3LinkHeader *const hdr = (const DNP3LinkHeader *)input;
const bool toserver = (direction & STREAM_TOSERVER) != 0;
/* May be a banner. */
if (DNP3ContainsBanner(input, len)) {
SCLogDebug("Packet contains a DNP3 banner.");
bool is_banner = true;
// magic 0x100 = 256 seems good enough
for (uint32_t i = 0; i < len && i < 0x100; i++) {
if (!isprint(input[i])) {
is_banner = false;
break;
}
}
if (is_banner) {
if (toserver) {
*rdir = STREAM_TOCLIENT;
}
return ALPROTO_DNP3;
}
}
/* Check that we have the minimum amount of bytes. */
if (len < sizeof(DNP3LinkHeader)) {
SCLogDebug("Length too small to be a DNP3 header.");
return ALPROTO_UNKNOWN;
}
/* Verify start value (from AN2013-004b). */
if (!DNP3CheckStartBytes(hdr)) {
SCLogDebug("Invalid start bytes.");
return ALPROTO_FAILED;
}
/* Verify minimum length. */
if (hdr->len < DNP3_MIN_LEN) {
SCLogDebug("Packet too small to be a valid DNP3 fragment.");
return ALPROTO_FAILED;
}
// Test compatibility between direction and dnp3.ctl.direction
if ((DNP3_LINK_DIR(hdr->control) != 0) != toserver) {
*rdir = toserver ? STREAM_TOCLIENT : STREAM_TOSERVER;
}
SCLogDebug("Detected DNP3.");
return ALPROTO_DNP3;
}
/**
* \brief Calculate the length of the transport layer with CRCs removed.
*
* \param input_len The length of the transport layer buffer.
*
* \retval The length of the buffer after CRCs are removed.
*/
static int DNP3CalculateTransportLengthWithoutCRCs(uint32_t input_len)
{
/* Too small. */
if (input_len < DNP3_CRC_LEN) {
return -1;
}
/* Get the number of complete blocks. */
int blocks = input_len / (DNP3_BLOCK_SIZE + DNP3_CRC_LEN);
/* And the number of bytes in the last block. */
int rem = input_len - (blocks * (DNP3_BLOCK_SIZE + DNP3_CRC_LEN));
if (rem) {
if (rem < DNP3_CRC_LEN) {
return -1;
}
return (blocks * DNP3_BLOCK_SIZE) + (rem - DNP3_CRC_LEN);
}
else {
return (blocks * DNP3_BLOCK_SIZE);
}
}
/**
* \brief Reassemble the application layer by stripping the CRCs.
*
* Remove the CRCs from the user data blocks. The output is the user
* data with the CRCs removed as well as the transport header removed,
* but the input data still needs to include the transport header as
* its part of the first user data block.
*
* If the output length passed in is non-null, the new input data will
* be appended, and the output length pointer incremented as needed.
*
* \param input Input buffer starting at the transport header (which
* will be removed from the output).
* \param input_len Length of the input buffer.
* \param output Pointer to output buffer (may be realloc'd).
* \param output_len Pointer to output length.
*
* \retval 1 if reassembly was successful, otherwise 0.
*/
static int DNP3ReassembleApplicationLayer(const uint8_t *input,
uint32_t input_len, uint8_t **output, uint32_t *output_len)
{
int len = DNP3CalculateTransportLengthWithoutCRCs(input_len);
if (len <= 0) {
return 0;
}
/* Remove one byte for the transport header and make sure we have
* at least one byte of user data. */
if (--len < 1) {
return 0;
}
if (*output == NULL) {
*output = SCCalloc(1, len);
if (unlikely(*output == NULL)) {
return 0;
}
}
else {
uint8_t *ptr = SCRealloc(*output, (size_t)(*output_len + len));
if (unlikely(ptr == NULL)) {
return 0;
}
*output = ptr;
}
int offset = 0, block_size;
while ((uint32_t)offset < input_len) {
if (input_len - offset > DNP3_BLOCK_SIZE + DNP3_CRC_LEN) {
block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN;
}
else {
block_size = input_len - offset;
}
/* If handling the first block (offset is 0), trim off the
* first byte which is the transport header, and not part of
* the application data. */
if (offset == 0) {
offset++;
block_size--;
}
/* Need at least 3 bytes to continue. One for application
* data, and 2 for the CRC. If not, return failure for
* malformed frame. */
if (block_size < DNP3_CRC_LEN + 1) {
SCLogDebug("Not enough data to continue.");
return 0;
}
/* Make sure there is enough space to write into. */
if (block_size - DNP3_CRC_LEN > len) {
SCLogDebug("Not enough data to continue.");
return 0;
}
memcpy(*output + *output_len, input + offset,
block_size - DNP3_CRC_LEN);
*output_len += block_size - DNP3_CRC_LEN;
offset += block_size;
len -= block_size - DNP3_CRC_LEN;
}
return 1;
}
/**
* \brief Allocate a DNP3 state object.
*
* The DNP3 state object represents a single DNP3 TCP session.
*/
static void *DNP3StateAlloc(void *orig_state, AppProto proto_orig)
{
SCEnter();
DNP3State *dnp3;
dnp3 = (DNP3State *)SCCalloc(1, sizeof(DNP3State));
if (unlikely(dnp3 == NULL)) {
return NULL;
}
TAILQ_INIT(&dnp3->tx_list);
SCReturnPtr(dnp3, "void");
}
/**
* \brief Set a DNP3 application layer event.
*
* Sets an event on the current transaction object.
*/
static void DNP3SetEvent(DNP3State *dnp3, uint8_t event)
{
if (dnp3 && dnp3->curr) {
AppLayerDecoderEventsSetEventRaw(&dnp3->curr->tx_data.events, event);
dnp3->events++;
}
else {
SCLogWarning("Failed to set event, state or tx pointer was NULL.");
}
}
/**
* \brief Set a DNP3 application layer event on a transaction.
*/
static void DNP3SetEventTx(DNP3Transaction *tx, uint8_t event)
{
AppLayerDecoderEventsSetEventRaw(&tx->tx_data.events, event);
tx->dnp3->events++;
}
/**
* \brief Allocation a DNP3 transaction.
*/
static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3, bool request)
{
DNP3Transaction *tx = SCCalloc(1, sizeof(DNP3Transaction));
if (unlikely(tx == NULL)) {
return NULL;
}
dnp3->transaction_max++;
dnp3->unreplied++;
dnp3->curr = tx;
tx->dnp3 = dnp3;
tx->tx_num = dnp3->transaction_max;
tx->is_request = request;
if (tx->is_request) {
tx->tx_data.flags = APP_LAYER_TX_SKIP_INSPECT_TC;
} else {
tx->tx_data.flags = APP_LAYER_TX_SKIP_INSPECT_TS;
}
TAILQ_INIT(&tx->objects);
TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);
/* Check for flood state. */
if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED);
dnp3->flooded = 1;
}
return tx;
}
/**
* \brief Calculate the length of a link frame with CRCs.
*
* This is required as the length parameter in the DNP3 header does not
* include the added CRCs.
*
* \param length The length from the DNP3 link header.
*
* \retval The length of the frame with CRCs included or 0 if the length isn't
* long enough to be a valid DNP3 frame.
*/
static uint32_t DNP3CalculateLinkLength(uint8_t length)
{
uint32_t frame_len = 0;
int rem;
/* Fail early if the length is less than the minimum size. */
if (length < DNP3_LINK_HDR_LEN) {
return 0;
}
/* Subtract the 5 bytes of the header that are included in the
* length. */
length -= DNP3_LINK_HDR_LEN;
rem = length % DNP3_BLOCK_SIZE;
frame_len = (length / DNP3_BLOCK_SIZE) * (DNP3_BLOCK_SIZE + DNP3_CRC_LEN);
if (rem) {
frame_len += rem + DNP3_CRC_LEN;
}
return frame_len + sizeof(DNP3LinkHeader);
}
/**
* \brief Check if the link function code specifies user data.
*
* \param header Point to link header.
*
* \retval 1 if frame contains user data, otherwise 0.
*/
static int DNP3IsUserData(const DNP3LinkHeader *header)
{
switch (DNP3_LINK_FC(header->control)) {
case DNP3_LINK_FC_CONFIRMED_USER_DATA:
case DNP3_LINK_FC_UNCONFIRMED_USER_DATA:
return 1;
default:
return 0;
}
}
/**
* \brief Check if the frame has user data.
*
* Check if the DNP3 frame actually has user data by checking if data
* exists after the headers.
*
* \retval 1 if user data exists, otherwise 0.
*/
static int DNP3HasUserData(const DNP3LinkHeader *header, uint8_t direction)
{
if (direction == STREAM_TOSERVER) {
return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) +
sizeof(DNP3ApplicationHeader);
}
else {
return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) +
sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd);
}
}
/**
* \brief Reset a DNP3Buffer.
*/
static void DNP3BufferReset(DNP3Buffer *buffer)
{
buffer->offset = 0;
buffer->len = 0;
}
/**
* \brief Add data to a DNP3 buffer, enlarging the buffer if required.
*
* \param buffer Buffer to add data data.
* \param data Data to be added to buffer.
* \param len Size of data to be added to buffer.
*
* \param 1 if data was added successful, otherwise 0.
*/
static int DNP3BufferAdd(DNP3Buffer *buffer, const uint8_t *data, uint32_t len)
{
if (buffer->size == 0) {
buffer->buffer = SCCalloc(1, len);
if (unlikely(buffer->buffer == NULL)) {
return 0;
}
buffer->size = len;
}
else if (buffer->len + len > buffer->size) {
uint8_t *tmp = SCRealloc(buffer->buffer, buffer->len + len);
if (unlikely(tmp == NULL)) {
return 0;
}
buffer->buffer = tmp;
buffer->size = buffer->len + len;
}
memcpy(buffer->buffer + buffer->len, data, len);
buffer->len += len;
return 1;
}
/**
* \brief Trim a DNP3 buffer.
*
* Trimming a buffer moves the data in the buffer up to the front of
* the buffer freeing up room at the end for more incoming data.
*
* \param buffer The buffer to trim.
*/
static void DNP3BufferTrim(DNP3Buffer *buffer)
{
if (buffer->offset == buffer->len) {
DNP3BufferReset(buffer);
}
else if (buffer->offset > 0) {
memmove(buffer->buffer, buffer->buffer + buffer->offset,
buffer->len - buffer->offset);
buffer->len = buffer->len - buffer->offset;
buffer->offset = 0;
}
}
/**
* \brief Free a DNP3 object.
*/
static void DNP3ObjectFree(DNP3Object *object)
{
if (object->points != NULL) {
DNP3FreeObjectPointList(object->group, object->variation,
object->points);
}
SCFree(object);
}
/**
* \brief Allocate a DNP3 object.
*/
static DNP3Object *DNP3ObjectAlloc(void)
{
DNP3Object *object = SCCalloc(1, sizeof(*object));
if (unlikely(object == NULL)) {
return NULL;
}
object->points = DNP3PointListAlloc();
if (object->points == NULL) {
DNP3ObjectFree(object);
return NULL;
}
return object;
}
/**
* \brief Decode DNP3 application objects.
*
* This function decoded known DNP3 application objects. As the
* protocol isn't self describing, we can only decode the buffer while
* the application objects are known. As soon as an unknown
* group/variation is hit, we must stop processing.
*
* \param buf the input buffer
* \param len length of the input buffer
* \param objects pointer to list where decoded objects will be stored.
*
* \retval 1 if all objects decoded, 0 if all objects could not be decoded (
* unknown group/variations)
*/
static int DNP3DecodeApplicationObjects(DNP3Transaction *tx, const uint8_t *buf,
uint32_t len, DNP3ObjectList *objects)
{
int retval = 0;
if (buf == NULL || len == 0) {
return 1;
}
while (len) {
uint32_t offset = 0;
if (len < sizeof(DNP3ObjHeader)) {
goto done;
}
DNP3ObjHeader *header = (DNP3ObjHeader *)buf;
offset += sizeof(DNP3ObjHeader);
DNP3Object *object = DNP3ObjectAlloc();
if (unlikely(object == NULL)) {
goto done;
}
TAILQ_INSERT_TAIL(objects, object, next);
object->group = header->group;
object->variation = header->variation;
object->qualifier = header->qualifier;
object->prefix_code = DNP3_OBJ_PREFIX(header->qualifier);
object->range_code = DNP3_OBJ_RANGE(header->qualifier);
/* IEEE 1815-2012, Table 4-5. */
switch (object->range_code) {
case 0x00:
case 0x03: {
/* 1 octet start and stop indexes OR 1 octet start and
* stop virtual addresses. */
if (offset + (sizeof(uint8_t) * 2) > len) {
/* Not enough data. */
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->start = buf[offset++];
object->stop = buf[offset++];
object->count = object->stop - object->start + 1;
break;
}
case 0x01:
case 0x04: {
/* 2 octet start and stop indexes OR 2 octect start
* and stop virtual addresses. */
if (offset + (sizeof(uint16_t) * 2) > len) {
/* Not enough data. */
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->start = DNP3_SWAP16(*(uint16_t *)(buf + offset));
offset += sizeof(uint16_t);
object->stop = DNP3_SWAP16(*(uint16_t *)(buf + offset));
offset += sizeof(uint16_t);
object->count = object->stop - object->start + 1;
break;
}
case 0x02:
case 0x05: {
/* 4 octet start and stop indexes OR 4 octect start
* and stop virtual addresses. */
if (offset + (sizeof(uint32_t) * 2) > len) {
/* Not enough data. */
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->start = DNP3_SWAP32(*(uint32_t *)(buf + offset));
offset += sizeof(uint32_t);
object->stop = DNP3_SWAP32(*(uint32_t *)(buf + offset));
offset += sizeof(uint32_t);
object->count = object->stop - object->start + 1;
break;
}
case 0x06:
/* No range field. */
object->count = 0;
break;
case 0x07:
/* 1 octet count of objects. */
if (offset + sizeof(uint8_t) > len) {
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->count = buf[offset];
offset += sizeof(uint8_t);
break;
case 0x08: {
/* 2 octet count of objects. */
if (offset + sizeof(uint16_t) > len) {
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->count = DNP3_SWAP16(*(uint16_t *)(buf + offset));
offset += sizeof(uint16_t);
break;
}
case 0x09: {
/* 4 octet count of objects. */
if (offset + sizeof(uint32_t) > len) {
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->count = DNP3_SWAP32(*(uint32_t *)(buf + offset));
offset += sizeof(uint32_t);
break;
}
case 0x0b: {
if (offset + sizeof(uint8_t) > len) {
/* Not enough data. */
SCLogDebug("Not enough data.");
goto not_enough_data;
}
object->count = *(uint8_t *)(buf + offset);
offset += sizeof(uint8_t);
break;
}
default:
SCLogDebug("Range code 0x%02x is reserved.",
object->range_code);
goto done;
}
buf += offset;
len -= offset;
if (object->variation == 0 || object->count == 0) {
goto next;
}
int event = DNP3DecodeObject(header->group, header->variation, &buf,
&len, object->prefix_code, object->start, object->count,
object->points);
if (event) {
DNP3SetEventTx(tx, DNP3_DECODER_EVENT_UNKNOWN_OBJECT);
goto done;
}
next:
continue;
}
/* All objects were decoded. */
retval = 1;
not_enough_data:
done:
return retval;
}
/**
* \brief Handle DNP3 request user data.
*
* \param dnp3 the current DNP3State
* \param input pointer to the DNP3 frame (starting with link header)
* \param input_len length of the input frame
*/
static void DNP3HandleUserDataRequest(
Flow *f, DNP3State *dnp3, const uint8_t *input, uint32_t input_len)
{
DNP3LinkHeader *lh;
DNP3TransportHeader th;
DNP3ApplicationHeader *ah;
DNP3Transaction *tx = NULL, *ttx;
lh = (DNP3LinkHeader *)input;
if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader),
input_len - sizeof(DNP3LinkHeader))) {
return;
}
th = input[sizeof(DNP3LinkHeader)];
if (!DNP3_TH_FIR(th)) {
TAILQ_FOREACH(ttx, &dnp3->tx_list, next) {
if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && ttx->is_request && !ttx->done &&
NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) {
tx = ttx;
break;
}
}
if (tx == NULL) {
return;
}
/* Update the saved transport header so subsequent segments
* will be matched to this sequence number. */
tx->th = th;
tx->tx_data.updated_ts = true;
}
else {
ah = (DNP3ApplicationHeader *)(input + sizeof(DNP3LinkHeader) +
sizeof(DNP3TransportHeader));
/* Ignore confirms - for now. */
if (ah->function_code == DNP3_APP_FC_CONFIRM) {
return;
}
/* Create a transaction. */
tx = DNP3TxAlloc(dnp3, true);
if (unlikely(tx == NULL)) {
return;
}
tx->tx_data.updated_ts = true;
tx->lh = *lh;
tx->th = th;
tx->ah = *ah;
}
if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader),
input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) {
/* Malformed, set event and mark as done. */
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED);
tx->done = 1;
return;
}
/* If this is not the final segment, just return. */
if (!DNP3_TH_FIN(th)) {
return;
}
tx->done = 1;
if (DNP3DecodeApplicationObjects(tx, tx->buffer + sizeof(DNP3ApplicationHeader),
tx->buffer_len - sizeof(DNP3ApplicationHeader), &tx->objects)) {
tx->complete = 1;
}
if (f != NULL) {
AppLayerParserTriggerRawStreamInspection(f, STREAM_TOSERVER);
}
}
static void DNP3HandleUserDataResponse(
Flow *f, DNP3State *dnp3, const uint8_t *input, uint32_t input_len)
{
DNP3LinkHeader *lh;
DNP3TransportHeader th;
DNP3ApplicationHeader *ah;
DNP3InternalInd *iin;
DNP3Transaction *tx = NULL, *ttx;
uint32_t offset = 0;
lh = (DNP3LinkHeader *)input;
offset += sizeof(DNP3LinkHeader);
if (!DNP3CheckUserDataCRCs(input + offset, input_len - offset)) {
return;
}
th = input[offset++];
if (!DNP3_TH_FIR(th)) {
TAILQ_FOREACH(ttx, &dnp3->tx_list, next) {
if (ttx->lh.src == lh->src && ttx->lh.dst == lh->dst && !ttx->is_request &&
!ttx->done && NEXT_TH_SEQNO(DNP3_TH_SEQ(ttx->th)) == DNP3_TH_SEQ(th)) {
tx = ttx;
break;
}
}
if (tx == NULL) {
return;
}
/* Replace the transport header in the transaction with this
* one in case there are more frames. */
tx->th = th;
tx->tx_data.updated_tc = true;
}
else {
ah = (DNP3ApplicationHeader *)(input + offset);
offset += sizeof(DNP3ApplicationHeader);
iin = (DNP3InternalInd *)(input + offset);
tx = DNP3TxAlloc(dnp3, false);
if (unlikely(tx == NULL)) {
return;
}
tx->tx_data.updated_tc = true;
tx->lh = *lh;
tx->th = th;
tx->ah = *ah;
tx->iin = *iin;
}
BUG_ON(tx->is_request);
if (!DNP3ReassembleApplicationLayer(input + sizeof(DNP3LinkHeader),
input_len - sizeof(DNP3LinkHeader), &tx->buffer, &tx->buffer_len)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_MALFORMED);
return;
}
if (!DNP3_TH_FIN(th)) {
return;
}
tx->done = 1;
offset = sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd);
if (DNP3DecodeApplicationObjects(
tx, tx->buffer + offset, tx->buffer_len - offset, &tx->objects)) {
tx->complete = 1;
}
if (f != NULL) {
AppLayerParserTriggerRawStreamInspection(f, STREAM_TOCLIENT);
}
}
/**
* \brief Decode the DNP3 request link layer.
*
* \retval number of bytes processed or -1 if the data stream does not look
* like DNP3.
*/
static int DNP3HandleRequestLinkLayer(
Flow *f, DNP3State *dnp3, const uint8_t *input, uint32_t input_len)
{
SCEnter();
uint32_t processed = 0;
while (input_len) {
/* Need at least enough bytes for a DNP3 header. */
if (input_len < sizeof(DNP3LinkHeader)) {
break;
}
DNP3LinkHeader *header = (DNP3LinkHeader *)input;
if (!DNP3CheckStartBytes(header)) {
goto error;
}
if (!DNP3CheckLinkHeaderCRC(header)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_LINK_CRC);
goto error;
}
uint32_t frame_len = DNP3CalculateLinkLength(header->len);
if (frame_len == 0) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL);
goto error;
}
if (input_len < frame_len) {
/* Insufficient data, just break - will wait for more data. */
break;
}
/* Ignore non-user data for now. */
if (!DNP3IsUserData(header)) {
goto next;
}
/* Make sure the header length is large enough for transport and
* application headers. */
if (!DNP3HasUserData(header, STREAM_TOSERVER)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL);
goto next;
}
if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader),
frame_len - sizeof(DNP3LinkHeader))) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC);
goto next;
}
DNP3HandleUserDataRequest(f, dnp3, input, frame_len);
next:
/* Advance the input buffer. */
input += frame_len;
input_len -= frame_len;
processed += frame_len;
}
SCReturnInt(processed);
error:
/* Error out. Should only happen if this doesn't look like a DNP3
* frame. */
SCReturnInt(-1);
}
/**
* \brief Handle incoming request data.
*
* The actual request PDU parsing is done in
* DNP3HandleRequestLinkLayer. This function takes care of buffering TCP
* date if a segment does not contain a complete frame (or contains
* multiple frames, but not the complete final frame).
*/
static AppLayerResult DNP3ParseRequest(Flow *f, void *state, AppLayerParserState *pstate,
StreamSlice stream_slice, void *local_data)
{
SCEnter();
DNP3State *dnp3 = (DNP3State *)state;
DNP3Buffer *buffer = &dnp3->request_buffer;
int processed = 0;
const uint8_t *input = StreamSliceGetData(&stream_slice);
uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
if (input_len == 0) {
SCReturnStruct(APP_LAYER_OK);
}
if (buffer->len) {
if (!DNP3BufferAdd(buffer, input, input_len)) {
goto error;
}
processed = DNP3HandleRequestLinkLayer(
f, dnp3, buffer->buffer + buffer->offset, buffer->len - buffer->offset);
if (processed < 0) {
goto error;
}
buffer->offset += processed;
DNP3BufferTrim(buffer);
}
else {
processed = DNP3HandleRequestLinkLayer(f, dnp3, input, input_len);
if (processed < 0) {
SCLogDebug("Failed to process request link layer.");
goto error;
}
input += processed;
input_len -= processed;
/* Not all data was processed, buffer it. */
if (input_len) {
if (!DNP3BufferAdd(buffer, input, input_len)) {
goto error;
}
}
}
SCReturnStruct(APP_LAYER_OK);
error:
/* Reset the buffer. */
DNP3BufferReset(buffer);
SCReturnStruct(APP_LAYER_ERROR);
}
/**
* \brief Decode the DNP3 response link layer.
*
* \retval number of bytes processed or -1 if the data stream does not
* like look DNP3.
*/
static int DNP3HandleResponseLinkLayer(
Flow *f, DNP3State *dnp3, const uint8_t *input, uint32_t input_len)
{
SCEnter();
uint32_t processed = 0;
while (input_len) {
/* Need at least enough bytes for a DNP3 header. */
if (input_len < sizeof(DNP3LinkHeader)) {
break;
}
DNP3LinkHeader *header = (DNP3LinkHeader *)input;
if (!DNP3CheckStartBytes(header)) {
goto error;
}
if (!DNP3CheckLinkHeaderCRC(header)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_LINK_CRC);
goto error;
}
/* Calculate the number of bytes needed to for this frame. */
uint32_t frame_len = DNP3CalculateLinkLength(header->len);
if (frame_len == 0) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL);
goto error;
}
if (input_len < frame_len) {
/* Insufficient data, just break - will wait for more data. */
break;
}
/* Only handle user data frames for now. */
if (!DNP3IsUserData(header)) {
goto next;
}
/* Make sure the header length is large enough for transport and
* application headers. */
if (!DNP3HasUserData(header, STREAM_TOCLIENT)) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_LEN_TOO_SMALL);
goto error;
}
if (!DNP3CheckUserDataCRCs(input + sizeof(DNP3LinkHeader),
frame_len - sizeof(DNP3LinkHeader))) {
DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC);
goto next;
}
DNP3HandleUserDataResponse(f, dnp3, input, frame_len);
next:
/* Advance the input buffer. */
input += frame_len;
input_len -= frame_len;
processed += frame_len;
}
SCReturnInt(processed);
error:
/* Error out. Should only happen if the data stream no longer
* looks like DNP3. */
SCReturnInt(-1);
}
/**
* \brief Parse incoming data.
*
* This is the entry function for DNP3 application layer data. Its
* main responsibility is buffering incoming data that cannot be
* processed.
*
* See DNP3ParseResponsePDUs for DNP3 frame handling.
*/
static AppLayerResult DNP3ParseResponse(Flow *f, void *state, AppLayerParserState *pstate,
StreamSlice stream_slice, void *local_data)
{
SCEnter();
DNP3State *dnp3 = (DNP3State *)state;
DNP3Buffer *buffer = &dnp3->response_buffer;
int processed;
const uint8_t *input = StreamSliceGetData(&stream_slice);
uint32_t input_len = StreamSliceGetDataLen(&stream_slice);
if (buffer->len) {
if (!DNP3BufferAdd(buffer, input, input_len)) {
goto error;
}
processed = DNP3HandleResponseLinkLayer(
f, dnp3, buffer->buffer + buffer->offset, buffer->len - buffer->offset);
if (processed < 0) {
goto error;
}
buffer->offset += processed;
DNP3BufferTrim(buffer);
}
else {
/* Check if this is a banner, ignore if it is. */
if (DNP3ContainsBanner(input, input_len)) {
goto done;
}
processed = DNP3HandleResponseLinkLayer(f, dnp3, input, input_len);
if (processed < 0) {
goto error;
}
input += processed;
input_len -= processed;
/* Not all data was processed, buffer it. */
if (input_len) {
if (!DNP3BufferAdd(buffer, input, input_len)) {
goto error;
}
}
}
done:
SCReturnStruct(APP_LAYER_OK);
error:
/* An error occurred while processing DNP3 frames. Dump the
* buffer as we can't be assured that they are valid anymore. */
DNP3BufferReset(buffer);
SCReturnStruct(APP_LAYER_ERROR);
}
static void *DNP3GetTx(void *alstate, uint64_t tx_id)
{
SCEnter();
DNP3State *dnp3 = (DNP3State *)alstate;
DNP3Transaction *tx = NULL;
uint64_t tx_num = tx_id + 1;
if (dnp3->curr && dnp3->curr->tx_num == (tx_num)) {
SCReturnPtr(dnp3->curr, "void");
}
TAILQ_FOREACH(tx, &dnp3->tx_list, next) {
if (tx_num != tx->tx_num) {
continue;
}
SCReturnPtr(tx, "void");
}
SCReturnPtr(NULL, "void");
}
static uint64_t DNP3GetTxCnt(void *state)
{
SCEnter();
uint64_t count = ((uint64_t)((DNP3State *)state)->transaction_max);
SCReturnUInt(count);
}
/**
* \brief Free all the objects in a DNP3ObjectList.
*/
static void DNP3TxFreeObjectList(DNP3ObjectList *objects)
{
DNP3Object *object;
while ((object = TAILQ_FIRST(objects)) != NULL) {
TAILQ_REMOVE(objects, object, next);
DNP3ObjectFree(object);
}
}
/**
* \brief Free a DNP3 transaction.
*/
static void DNP3TxFree(DNP3Transaction *tx)
{
SCEnter();
if (tx->buffer != NULL) {
SCFree(tx->buffer);
}
SCAppLayerTxDataCleanup(&tx->tx_data);
DNP3TxFreeObjectList(&tx->objects);
SCFree(tx);
SCReturn;
}
/**
* \brief Free a transaction by ID on a specific DNP3 state.
*
* This function is called by the app-layer to free a transaction on a
* specific DNP3 state object.
*/
static void DNP3StateTxFree(void *state, uint64_t tx_id)
{
SCEnter();
DNP3State *dnp3 = state;
DNP3Transaction *tx = NULL, *ttx;
uint64_t tx_num = tx_id + 1;
TAILQ_FOREACH_SAFE(tx, &dnp3->tx_list, next, ttx) {
if (tx->tx_num != tx_num) {
continue;
}
if (tx == dnp3->curr) {
dnp3->curr = NULL;
}
if (tx->tx_data.events != NULL) {
if (tx->tx_data.events->cnt <= dnp3->events) {
dnp3->events -= tx->tx_data.events->cnt;
} else {
dnp3->events = 0;
}
}
dnp3->unreplied--;
/* Check flood state. */
if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) {
dnp3->flooded = 0;
}
TAILQ_REMOVE(&dnp3->tx_list, tx, next);
DNP3TxFree(tx);
break;
}
SCReturn;
}
/**
* \brief Free a DNP3 state.
*/
static void DNP3StateFree(void *state)
{
SCEnter();
DNP3State *dnp3 = state;
DNP3Transaction *tx;
if (state != NULL) {
while ((tx = TAILQ_FIRST(&dnp3->tx_list)) != NULL) {
TAILQ_REMOVE(&dnp3->tx_list, tx, next);
DNP3TxFree(tx);
}
if (dnp3->request_buffer.buffer != NULL) {
SCFree(dnp3->request_buffer.buffer);
}
if (dnp3->response_buffer.buffer != NULL) {
SCFree(dnp3->response_buffer.buffer);
}
SCFree(dnp3);
}
SCReturn;
}
/**
* \brief Called by the app-layer to get the state progress.
*/
static int DNP3GetAlstateProgress(void *tx, uint8_t direction)
{
DNP3Transaction *dnp3tx = (DNP3Transaction *)tx;
DNP3State *dnp3 = dnp3tx->dnp3;
int retval = 0;
/* If flooded, "ack" old transactions. */
if (dnp3->flooded && (dnp3->transaction_max -
dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) {
SCLogDebug("flooded: returning tx as done.");
SCReturnInt(1);
}
if (dnp3tx->complete)
retval = 1;
SCReturnInt(retval);
}
/**
* \brief App-layer support.
*/
static int DNP3StateGetEventInfo(
const char *event_name, uint8_t *event_id, AppLayerEventType *event_type)
{
if (SCAppLayerGetEventIdByName(event_name, dnp3_decoder_event_table, event_id) == 0) {
*event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
return 0;
}
return -1;
}
/**
* \brief App-layer support.
*/
static int DNP3StateGetEventInfoById(
uint8_t event_id, const char **event_name, AppLayerEventType *event_type)
{
*event_name = SCMapEnumValueToName(event_id, dnp3_decoder_event_table);
if (*event_name == NULL) {
SCLogError("Event \"%d\" not present in "
"the DNP3 enum event map table.",
event_id);
return -1;
}
*event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
return 0;
}
static AppLayerTxData *DNP3GetTxData(void *vtx)
{
DNP3Transaction *tx = (DNP3Transaction *)vtx;
return &tx->tx_data;
}
static AppLayerStateData *DNP3GetStateData(void *vstate)
{
DNP3State *state = (DNP3State *)vstate;
return &state->state_data;
}
/**
* \brief Check if the prefix code is a size prefix.
*
* \retval 1 if the prefix_code specifies a size prefix, 0 if not.
*/
int DNP3PrefixIsSize(uint8_t prefix_code)
{
switch (prefix_code) {
case 0x04:
case 0x05:
case 0x06:
return 1;
break;
default:
return 0;
}
}
static AppLayerGetTxIterTuple DNP3GetTxIterator(const uint8_t ipproto, const AppProto alproto,
void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state)
{
DNP3State *dnp_state = (DNP3State *)alstate;
AppLayerGetTxIterTuple no_tuple = { NULL, 0, false };
if (dnp_state) {
DNP3Transaction *tx_ptr;
if (state->un.ptr == NULL) {
tx_ptr = TAILQ_FIRST(&dnp_state->tx_list);
} else {
tx_ptr = (DNP3Transaction *)state->un.ptr;
}
if (tx_ptr) {
while (tx_ptr->tx_num < min_tx_id + 1) {
tx_ptr = TAILQ_NEXT(tx_ptr, next);
if (!tx_ptr) {
return no_tuple;
}
}
if (tx_ptr->tx_num >= max_tx_id + 1) {
return no_tuple;
}
state->un.ptr = TAILQ_NEXT(tx_ptr, next);
AppLayerGetTxIterTuple tuple = {
.tx_ptr = tx_ptr,
.tx_id = tx_ptr->tx_num - 1,
.has_next = (state->un.ptr != NULL),
};
return tuple;
}
}
return no_tuple;
}
/**
* \brief Register the DNP3 application protocol parser.
*/
void RegisterDNP3Parsers(void)
{
SCEnter();
const char *proto_name = "dnp3";
if (SCAppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) {
AppLayerProtoDetectRegisterProtocol(ALPROTO_DNP3, proto_name);
if (RunmodeIsUnittests()) {
SCAppLayerProtoDetectPPRegister(IPPROTO_TCP, DNP3_DEFAULT_PORT, ALPROTO_DNP3, 0,
sizeof(DNP3LinkHeader), STREAM_TOSERVER, DNP3ProbingParser, DNP3ProbingParser);
}
else {
if (!SCAppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, proto_name, ALPROTO_DNP3,
0, sizeof(DNP3LinkHeader), DNP3ProbingParser, DNP3ProbingParser)) {
return;
}
}
} else {
SCLogConfig("Protocol detection and parser disabled for DNP3.");
SCReturn;
}
if (SCAppLayerParserConfParserEnabled("tcp", proto_name)) {
SCLogConfig("Registering DNP3/tcp parsers.");
AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNP3, STREAM_TOSERVER,
DNP3ParseRequest);
AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNP3, STREAM_TOCLIENT,
DNP3ParseResponse);
AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_DNP3,
DNP3StateAlloc, DNP3StateFree);
AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTx);
AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxIterator);
AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxCnt);
AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_DNP3,
DNP3StateTxFree);
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_DNP3,
DNP3GetAlstateProgress);
AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_DNP3, 1, 1);
AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNP3,
DNP3StateGetEventInfo);
AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_DNP3,
DNP3StateGetEventInfoById);
AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3,
DNP3GetTxData);
AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData);
} else {
SCLogConfig("Parser disabled for protocol %s. "
"Protocol detection still on.", proto_name);
}
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_DNP3,
DNP3ParserRegisterTests);
#endif
SCReturn;
}
#ifdef UNITTESTS
#include "flow-util.h"
#include "stream-tcp.h"
/**
* \brief Utility function to fix CRCs when mangling a frame.
*/
static void DNP3FixCrc(uint8_t *data, uint32_t len)
{
uint32_t block_size;
while (len) {
if (len >= DNP3_BLOCK_SIZE + DNP3_CRC_LEN) {
block_size = DNP3_BLOCK_SIZE;
} else {
block_size = len - DNP3_CRC_LEN;
}
uint16_t crc = DNP3ComputeCRC(data, block_size);
data[block_size + 1] = (crc >> 8) & 0xff;
data[block_size] = crc & 0xff;
data += block_size + DNP3_CRC_LEN;
len -= block_size + DNP3_CRC_LEN;
}
}
/**
* \test Test CRC checking on partial and full blocks.
*/
static int DNP3ParserTestCheckCRC(void)
{
uint8_t request[] = {
/* DNP3 start. */
0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00,
0xa5, 0xe9,
/* Transport header. */
0xff,
/* Application layer - segment 1. */
0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72,
0xef,
/* Application layer - segment 2. */
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};
/* Check link header CRC. */
FAIL_IF(!DNP3CheckCRC(request, sizeof(DNP3LinkHeader)));
/* Check first application layer segment. */
FAIL_IF(!DNP3CheckCRC(request + sizeof(DNP3LinkHeader),
DNP3_BLOCK_SIZE + DNP3_CRC_LEN));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* Change a byte in link header, should fail now. */
request[2]++;
FAIL_IF(DNP3CheckCRC(request, sizeof(DNP3LinkHeader)));
/* Change a byte in the first application segment, should fail
* now. */
request[sizeof(DNP3LinkHeader) + 3]++;
FAIL_IF(DNP3CheckCRC(request + sizeof(DNP3LinkHeader),
DNP3_BLOCK_SIZE + DNP3_CRC_LEN));
#endif
PASS;
}
/**
* \test Test validation of all CRCs in user data.
*/
static int DNP3CheckUserDataCRCsTest(void)
{
/* Multi-block data with valid CRCs. */
uint8_t data_valid[] = {
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0x00, 0x00, 0x00, 0x00,
0x00,
0xff, 0xff, /* CRC. */
};
FAIL_IF(!DNP3CheckUserDataCRCs(data_valid, sizeof(data_valid)));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* Multi-block data with one non-crc byte altered. */
uint8_t data_invalid[] = {
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0x00, 0x00, 0x00, 0x00,
0x01, /* Invalid byte. */
0xff, 0xff, /* CRC. */
};
FAIL_IF(DNP3CheckUserDataCRCs(data_invalid, sizeof(data_invalid)));
/* 1 byte - need at least 3. */
uint8_t one_byte_nocrc[] = { 0x01 };
FAIL_IF(DNP3CheckUserDataCRCs(one_byte_nocrc, sizeof(one_byte_nocrc)));
/* 2 bytes - need at least 3. */
uint8_t two_byte_nocrc[] = { 0x01, 0x02 };
FAIL_IF(DNP3CheckUserDataCRCs(two_byte_nocrc, sizeof(two_byte_nocrc)));
#endif
/* 3 bytes, valid CRC. */
uint8_t three_bytes_good_crc[] = { 0x00, 0x00, 0x00 };
*(uint16_t *)(three_bytes_good_crc + 1) = DNP3ComputeCRC(
three_bytes_good_crc, 1);
FAIL_IF(!DNP3CheckUserDataCRCs(three_bytes_good_crc,
sizeof(three_bytes_good_crc)));
PASS;
}
/**
* \test Test the link layer length calculation.
*
* Test the calculation that converts the link provided in the DNP3
* header to the actual length of the frame. That is the length with
* CRCs as the length in the header does not include CRCs.
*/
static int DNP3CalculateLinkLengthTest(void)
{
/* These are invalid. */
FAIL_IF(DNP3CalculateLinkLength(0) != 0);
FAIL_IF(DNP3CalculateLinkLength(1) != 0);
FAIL_IF(DNP3CalculateLinkLength(2) != 0);
FAIL_IF(DNP3CalculateLinkLength(3) != 0);
FAIL_IF(DNP3CalculateLinkLength(4) != 0);
/* This is the minimum size. */
FAIL_IF(DNP3CalculateLinkLength(5) != 10);
/* 1 full user data blocks of data. */
FAIL_IF(DNP3CalculateLinkLength(21) != 28);
/* 2 full user data blocks of data. */
FAIL_IF(DNP3CalculateLinkLength(37) != 46);
/* 2 full user data blocks, plus one more byte. */
/* 2 full user data blocks of data. */
FAIL_IF(DNP3CalculateLinkLength(38) != 49);
/* The maximum size. */
FAIL_IF(DNP3CalculateLinkLength(255) != 292);
PASS;
}
/**
* \test The conversion of length with CRCs to the length without
* CRCs.
*/
static int DNP3CalculateTransportLengthWithoutCRCsTest(void)
{
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(0) != -1);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(1) != -1);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(2) != 0);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(3) != 1);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(16) != 14);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(17) != 15);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(18) != 16);
/* 19 bytes is not enough for a second block. */
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(19) != -1);
/* 20 bytes really isn't enough either, but is large enough to
* satisfy the CRC on the second block. */
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(20) != 16);
FAIL_IF(DNP3CalculateTransportLengthWithoutCRCs(21) != 17);
PASS;
}
/**
* \test Test the validation of the link header CRC.
*/
static int DNP3ParserCheckLinkHeaderCRC(void)
{
/* DNP3 frame with valid headers and CRCs. */
uint8_t request[] = {
/* DNP3 start. */
0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00,
0xa5, 0xe9,
/* Transport header. */
0xff,
/* Application layer. */
0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};
DNP3LinkHeader *header = (DNP3LinkHeader *)request;
FAIL_IF(!DNP3CheckLinkHeaderCRC(header));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* Alter a byte in the header. */
request[4] = 0;
FAIL_IF(DNP3CheckLinkHeaderCRC(header));
#endif
PASS;
}
/**
* \test Test removal of CRCs from user data.
*/
static int DNP3ReassembleApplicationLayerTest01(void)
{
uint32_t reassembled_len = 0;
uint8_t *output = NULL;
uint8_t payload[] = {
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0x00, 0x00, 0x00, 0x00,
0x00,
0xff, 0xff, /* CRC. */
};
uint8_t expected[] = {
0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
/* CRC removed. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
/* CRC removed. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
/* CRC removed. */
0x00, 0x00, 0x00, 0x00,
0x00
/* CRC removed. */
};
/* Valid frame. */
FAIL_IF(!DNP3ReassembleApplicationLayer(payload,
sizeof(payload), &output, &reassembled_len));
FAIL_IF(output == NULL);
FAIL_IF(reassembled_len != sizeof(expected));
FAIL_IF(memcmp(expected, output, reassembled_len));
SCFree(output);
/* 1 byte, invalid. */
reassembled_len = 0;
output = NULL;
FAIL_IF(DNP3ReassembleApplicationLayer(payload, 1, &output,
&reassembled_len));
FAIL_IF(output != NULL);
FAIL_IF(reassembled_len != 0);
/* 2 bytes, invalid. */
reassembled_len = 0;
output = NULL;
FAIL_IF(DNP3ReassembleApplicationLayer(payload, 2, &output,
&reassembled_len));
FAIL_IF(output != NULL);
FAIL_IF(reassembled_len != 0);
/* 3 bytes, minimum - but that would only be the transport header
* which isn't included in the output. */
reassembled_len = 0;
output = NULL;
FAIL_IF(DNP3ReassembleApplicationLayer(payload, 3, &output,
&reassembled_len));
FAIL_IF(output != NULL);
FAIL_IF(reassembled_len != 0);
/* 4 bytes is the minimum to get any reassembled data. */
reassembled_len = 0;
output = NULL;
FAIL_IF(!DNP3ReassembleApplicationLayer(payload, 4, &output,
&reassembled_len));
FAIL_IF(output == NULL);
FAIL_IF(reassembled_len != 1);
/* Last block too short (by 1 byte) for data + CRC. */
uint8_t short_payload1[] = {
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0x00, 0x00
};
reassembled_len = 0;
FAIL_IF(DNP3ReassembleApplicationLayer(short_payload1,
sizeof(short_payload1), &output, &reassembled_len));
/* Last block too short (by 2 bytes) for data + CRC. */
uint8_t short_payload2[] = {
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0xff, 0xc9, 0x05, 0x0c,
0x01, 0x28, 0x01, 0x00,
0x00, 0x00, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00,
0x72, 0xef, /* CRC. */
0x00,
};
reassembled_len = 0;
FAIL_IF(DNP3ReassembleApplicationLayer(short_payload2,
sizeof(short_payload2), &output, &reassembled_len));
PASS;
}
/**
* \test Test the probing parser.
*/
static int DNP3ProbingParserTest(void)
{
uint8_t pkt[] = {
0x05, 0x64, 0x05, 0xc9, 0x03, 0x00, 0x04, 0x00,
0xbd, 0x71
};
proto-detect: improve midstream support When Suricata picks up a flow it assumes the first packet is toserver. In a perfect world without packet loss and where all sessions neatly start after Suricata itself started, this would be true. However, in reality we have to account for packet loss and Suricata starting to get packets for flows already active be for Suricata is (re)started. The protocol records on the wire would often be able to tell us more though. For example in SMB1 and SMB2 records there is a flag that indicates whether the record is a request or a response. This patch is enabling the procotol detection engine to utilize this information to 'reverse' the flow. There are three ways in which this is supported in this patch: 1. patterns for detection are registered per direction. If the proto was not recognized in the traffic direction, and midstream is enabled, the pattern set for the opposing direction is also evaluated. If that matches, the flow is considered to be in the wrong direction and is reversed. 2. probing parsers now have a way to feed back their understanding of the flow direction. They are now passed the direction as Suricata sees the traffic when calling the probing parsers. The parser can then see if its own observation matches that, and pass back it's own view to the caller. 3. a new pattern + probing parser set up: probing parsers can now be registered with a pattern, so that when the pattern matches the probing parser is called as well. The probing parser can then provide the protocol detection engine with the direction of the traffic. The process of reversing takes a multi step approach as well: a. reverse the current packets direction b. reverse most of the flows direction sensitive flags c. tag the flow as 'reversed'. This is because the 5 tuple is *not* reversed, since it is immutable after the flows creation. Most of the currently registered parsers benefit already: - HTTP/SMTP/FTP/TLS patterns are registered per direction already so they will benefit from the pattern midstream logic in (1) above. - the Rust based SMB parser uses a mix of pattern + probing parser as described in (3) above. - the NFS detection is purely done by probing parser and is updated to consider the direction in that parser. Other protocols, such as DNS, are still to do. Ticket: #2572
6 years ago
uint8_t rdir = 0;
/* Valid frame. */
proto-detect: improve midstream support When Suricata picks up a flow it assumes the first packet is toserver. In a perfect world without packet loss and where all sessions neatly start after Suricata itself started, this would be true. However, in reality we have to account for packet loss and Suricata starting to get packets for flows already active be for Suricata is (re)started. The protocol records on the wire would often be able to tell us more though. For example in SMB1 and SMB2 records there is a flag that indicates whether the record is a request or a response. This patch is enabling the procotol detection engine to utilize this information to 'reverse' the flow. There are three ways in which this is supported in this patch: 1. patterns for detection are registered per direction. If the proto was not recognized in the traffic direction, and midstream is enabled, the pattern set for the opposing direction is also evaluated. If that matches, the flow is considered to be in the wrong direction and is reversed. 2. probing parsers now have a way to feed back their understanding of the flow direction. They are now passed the direction as Suricata sees the traffic when calling the probing parsers. The parser can then see if its own observation matches that, and pass back it's own view to the caller. 3. a new pattern + probing parser set up: probing parsers can now be registered with a pattern, so that when the pattern matches the probing parser is called as well. The probing parser can then provide the protocol detection engine with the direction of the traffic. The process of reversing takes a multi step approach as well: a. reverse the current packets direction b. reverse most of the flows direction sensitive flags c. tag the flow as 'reversed'. This is because the 5 tuple is *not* reversed, since it is immutable after the flows creation. Most of the currently registered parsers benefit already: - HTTP/SMTP/FTP/TLS patterns are registered per direction already so they will benefit from the pattern midstream logic in (1) above. - the Rust based SMB parser uses a mix of pattern + probing parser as described in (3) above. - the NFS detection is purely done by probing parser and is updated to consider the direction in that parser. Other protocols, such as DNS, are still to do. Ticket: #2572
6 years ago
FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_DNP3);
/* Send too little bytes. */
proto-detect: improve midstream support When Suricata picks up a flow it assumes the first packet is toserver. In a perfect world without packet loss and where all sessions neatly start after Suricata itself started, this would be true. However, in reality we have to account for packet loss and Suricata starting to get packets for flows already active be for Suricata is (re)started. The protocol records on the wire would often be able to tell us more though. For example in SMB1 and SMB2 records there is a flag that indicates whether the record is a request or a response. This patch is enabling the procotol detection engine to utilize this information to 'reverse' the flow. There are three ways in which this is supported in this patch: 1. patterns for detection are registered per direction. If the proto was not recognized in the traffic direction, and midstream is enabled, the pattern set for the opposing direction is also evaluated. If that matches, the flow is considered to be in the wrong direction and is reversed. 2. probing parsers now have a way to feed back their understanding of the flow direction. They are now passed the direction as Suricata sees the traffic when calling the probing parsers. The parser can then see if its own observation matches that, and pass back it's own view to the caller. 3. a new pattern + probing parser set up: probing parsers can now be registered with a pattern, so that when the pattern matches the probing parser is called as well. The probing parser can then provide the protocol detection engine with the direction of the traffic. The process of reversing takes a multi step approach as well: a. reverse the current packets direction b. reverse most of the flows direction sensitive flags c. tag the flow as 'reversed'. This is because the 5 tuple is *not* reversed, since it is immutable after the flows creation. Most of the currently registered parsers benefit already: - HTTP/SMTP/FTP/TLS patterns are registered per direction already so they will benefit from the pattern midstream logic in (1) above. - the Rust based SMB parser uses a mix of pattern + probing parser as described in (3) above. - the NFS detection is purely done by probing parser and is updated to consider the direction in that parser. Other protocols, such as DNS, are still to do. Ticket: #2572
6 years ago
FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(DNP3LinkHeader) - 1, &rdir) != ALPROTO_UNKNOWN);
/* Bad start bytes. */
pkt[0] = 0x06;
proto-detect: improve midstream support When Suricata picks up a flow it assumes the first packet is toserver. In a perfect world without packet loss and where all sessions neatly start after Suricata itself started, this would be true. However, in reality we have to account for packet loss and Suricata starting to get packets for flows already active be for Suricata is (re)started. The protocol records on the wire would often be able to tell us more though. For example in SMB1 and SMB2 records there is a flag that indicates whether the record is a request or a response. This patch is enabling the procotol detection engine to utilize this information to 'reverse' the flow. There are three ways in which this is supported in this patch: 1. patterns for detection are registered per direction. If the proto was not recognized in the traffic direction, and midstream is enabled, the pattern set for the opposing direction is also evaluated. If that matches, the flow is considered to be in the wrong direction and is reversed. 2. probing parsers now have a way to feed back their understanding of the flow direction. They are now passed the direction as Suricata sees the traffic when calling the probing parsers. The parser can then see if its own observation matches that, and pass back it's own view to the caller. 3. a new pattern + probing parser set up: probing parsers can now be registered with a pattern, so that when the pattern matches the probing parser is called as well. The probing parser can then provide the protocol detection engine with the direction of the traffic. The process of reversing takes a multi step approach as well: a. reverse the current packets direction b. reverse most of the flows direction sensitive flags c. tag the flow as 'reversed'. This is because the 5 tuple is *not* reversed, since it is immutable after the flows creation. Most of the currently registered parsers benefit already: - HTTP/SMTP/FTP/TLS patterns are registered per direction already so they will benefit from the pattern midstream logic in (1) above. - the Rust based SMB parser uses a mix of pattern + probing parser as described in (3) above. - the NFS detection is purely done by probing parser and is updated to consider the direction in that parser. Other protocols, such as DNS, are still to do. Ticket: #2572
6 years ago
FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_FAILED);
/* Restore start byte. */
pkt[0] = 0x05;
/* Set the length to a value less than the minimum length of 5. */
pkt[2] = 0x03;
proto-detect: improve midstream support When Suricata picks up a flow it assumes the first packet is toserver. In a perfect world without packet loss and where all sessions neatly start after Suricata itself started, this would be true. However, in reality we have to account for packet loss and Suricata starting to get packets for flows already active be for Suricata is (re)started. The protocol records on the wire would often be able to tell us more though. For example in SMB1 and SMB2 records there is a flag that indicates whether the record is a request or a response. This patch is enabling the procotol detection engine to utilize this information to 'reverse' the flow. There are three ways in which this is supported in this patch: 1. patterns for detection are registered per direction. If the proto was not recognized in the traffic direction, and midstream is enabled, the pattern set for the opposing direction is also evaluated. If that matches, the flow is considered to be in the wrong direction and is reversed. 2. probing parsers now have a way to feed back their understanding of the flow direction. They are now passed the direction as Suricata sees the traffic when calling the probing parsers. The parser can then see if its own observation matches that, and pass back it's own view to the caller. 3. a new pattern + probing parser set up: probing parsers can now be registered with a pattern, so that when the pattern matches the probing parser is called as well. The probing parser can then provide the protocol detection engine with the direction of the traffic. The process of reversing takes a multi step approach as well: a. reverse the current packets direction b. reverse most of the flows direction sensitive flags c. tag the flow as 'reversed'. This is because the 5 tuple is *not* reversed, since it is immutable after the flows creation. Most of the currently registered parsers benefit already: - HTTP/SMTP/FTP/TLS patterns are registered per direction already so they will benefit from the pattern midstream logic in (1) above. - the Rust based SMB parser uses a mix of pattern + probing parser as described in (3) above. - the NFS detection is purely done by probing parser and is updated to consider the direction in that parser. Other protocols, such as DNS, are still to do. Ticket: #2572
6 years ago
FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, pkt, sizeof(pkt), &rdir) != ALPROTO_FAILED);
/* Send a banner. */
char mybanner[] = "Welcome to DNP3 SCADA.";
FAIL_IF(DNP3ProbingParser(NULL, STREAM_TOSERVER, (uint8_t *)mybanner, sizeof(mybanner) - 1,
&rdir) != ALPROTO_DNP3);
FAIL_IF(rdir != STREAM_TOCLIENT);
PASS;
}
/**
* \test Test a basic request/response.
*/
static int DNP3ParserTestRequestResponse(void)
{
DNP3State *state = NULL;
uint8_t request[] = {
/* DNP3 start. */
0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00,
0xa5, 0xe9,
/* Transport header. */
0xff,
/* Application layer. */
0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};
uint8_t response[] = {
/* DNP3 start. */
0x05, 0x64, 0x1c, 0x44, 0x01, 0x00, 0x02, 0x00,
0xe2, 0x59,
/* Transport header. */
0xc3,
/* Application layer. */
0xc9, 0x81, 0x00, 0x00, 0x0c, 0x01, 0x28, 0x01,
0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x7a,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff
};
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request, sizeof(request)));
SCMutexUnlock(&flow.m);
state = flow.alstate;
FAIL_IF(state == NULL);
DNP3Transaction *tx = DNP3GetTx(state, 0);
FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr);
FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->buffer_len != 20);
FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, response, sizeof(response)));
SCMutexUnlock(&flow.m);
DNP3Transaction *tx0 = DNP3GetTx(state, 1);
FAIL_IF(tx0 == NULL);
FAIL_IF(tx0 == tx);
FAIL_IF(!tx0->done);
FAIL_IF(tx0->buffer == NULL);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
/**
* \test Test an unsolicited response from an outstation.
*
* This is kind of like a request initiated from the "server".
*/
static int DNP3ParserTestUnsolicitedResponseConfirm(void)
{
DNP3State *state = NULL;
/* Unsolicited response with confirm bit set. */
uint8_t response[] = {
0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00,
0x89, 0xe5, 0xc4, 0xfa, 0x82, 0x00, 0x00, 0x02,
0x02, 0x17, 0x01, 0x01, 0x81, 0xa7, 0x75, 0xd8,
0x32, 0x4c, 0x81, 0x3e, 0x01, 0xa1, 0xc9
};
/* Confirm. */
uint8_t confirm[] = {
0x05, 0x64, 0x08, 0xc4, 0x02, 0x00,
0x01, 0x00, 0xd3, 0xb7, 0xc0, 0xda, 0x00, 0x6a,
0x3d
};
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, response, sizeof(response)));
SCMutexUnlock(&flow.m);
state = flow.alstate;
FAIL_IF(state == NULL);
DNP3Transaction *tx = DNP3GetTx(state, 0);
FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr);
FAIL_IF(!tx->done);
FAIL_IF(tx->ah.function_code != DNP3_APP_FC_UNSOLICITED_RESP);
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, confirm, sizeof(confirm)));
SCMutexUnlock(&flow.m);
/* Confirms are ignored currently. With the move to
unidirectional transactions it might be easy to support these
now. */
DNP3Transaction *resptx = DNP3GetTx(state, 1);
FAIL_IF(resptx);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
/**
* \test Test flood state.
*
* Note that flood state needs to revisited with the modification to a
* unidirectional protocol.
*/
static int DNP3ParserTestFlooded(void)
{
DNP3State *state = NULL;
uint8_t request[] = {
/* DNP3 start. */
0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00,
0xa5, 0xe9,
/* Transport header. */
0xff,
/* Application layer. */
0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request, sizeof(request)));
SCMutexUnlock(&flow.m);
state = flow.alstate;
FAIL_IF(state == NULL);
DNP3Transaction *tx = DNP3GetTx(state, 0);
FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr);
FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->buffer_len != 20);
FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
FAIL_IF_NOT(tx->done);
FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) {
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request, sizeof(request)));
SCMutexUnlock(&flow.m);
}
FAIL_IF(state->flooded);
FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
/* One more request should trip us into flooded state. */
SCMutexLock(&flow.m);
FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request, sizeof(request)));
SCMutexUnlock(&flow.m);
FAIL_IF(!state->flooded);
/* Progress for the oldest tx should return 1. */
FAIL_IF(!DNP3GetAlstateProgress(tx, 0));
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
/**
* \test Test parsing of partial frames.
*
* As DNP3 operates over TCP, it is possible that a partial DNP3 frame
* is received. Test that the partial frame will be buffered until the
* remainder is seen.
*/
static int DNP3ParserTestPartialFrame(void)
{
DNP3State *state = NULL;
DNP3Transaction *tx;
int r;
uint8_t request_partial1[] = {
/* DNP3 start. */
0x05, 0x64, 0x1a, 0xc4, 0x02, 0x00, 0x01, 0x00,
0xa5, 0xe9,
/* Transport header. */
0xff,
/* Application layer. */
0xc9, 0x05, 0x0c, 0x01, 0x28, 0x01, 0x00, 0x00,
};
uint8_t request_partial2[] = {
/* Remainder of application layer. */
0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x72,
0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
};
uint8_t response_partial1[] = {
/* DNP3 start. */
0x05, 0x64, 0x1c, 0x44, 0x01, 0x00, 0x02, 0x00,
0xe2, 0x59,
/* Transport header. */
0xc3,
/* Application layer. */
0xc9, 0x81, 0x00, 0x00, 0x0c, 0x01, 0x28, 0x01,
};
uint8_t response_partial2[] = {
0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x7a,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff
};
/* Boiler plate for app layer setup. */
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
/* Pass in the first partial frame. */
SCMutexLock(&flow.m);
r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request_partial1, sizeof(request_partial1));
SCMutexUnlock(&flow.m);
FAIL_IF(r != 0);
/* Frame should just be buffered, but not yet processed. */
state = flow.alstate;
FAIL_IF(state == NULL);
FAIL_IF(state->request_buffer.len != sizeof(request_partial1));
FAIL_IF(state->request_buffer.offset != 0);
FAIL_IF(memcmp(state->request_buffer.buffer, request_partial1,
sizeof(request_partial1)));
/* There should not be a transaction yet. */
FAIL_IF(state->transaction_max != 0);
FAIL_IF(DNP3GetTx(state, 0) != NULL);
/* Send the second partial. */
SCMutexLock(&flow.m);
r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOSERVER, request_partial2, sizeof(request_partial2));
SCMutexUnlock(&flow.m);
FAIL_IF(r != 0);
/* The second partial completed the frame, the buffer should now
* be clear. */
FAIL_IF(state->request_buffer.len != 0);
FAIL_IF(state->request_buffer.offset != 0);
/* Should now have a complete transaction. */
tx = DNP3GetTx(state, 0);
FAIL_IF(tx == NULL);
FAIL_IF(tx->tx_num != 1);
FAIL_IF(tx != state->curr);
FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->buffer_len != 20);
FAIL_IF(tx->ah.function_code != DNP3_APP_FC_DIR_OPERATE);
/* Send partial response. */
SCMutexLock(&flow.m);
r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, response_partial1, sizeof(response_partial1));
SCMutexUnlock(&flow.m);
FAIL_IF(r != 0);
FAIL_IF(state->response_buffer.len != sizeof(response_partial1));
FAIL_IF(state->response_buffer.offset != 0);
tx = DNP3GetTx(state, 1);
FAIL_IF_NOT_NULL(tx);
/* Send rest of response. */
SCMutexLock(&flow.m);
r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, response_partial2, sizeof(response_partial2));
SCMutexUnlock(&flow.m);
FAIL_IF(r != 0);
/* Buffer should now be empty. */
FAIL_IF(state->response_buffer.len != 0);
FAIL_IF(state->response_buffer.offset != 0);
/* There should now be a response transaction. */
tx = DNP3GetTx(state, 1);
FAIL_IF_NULL(tx);
FAIL_IF(tx->buffer == NULL);
FAIL_IF(tx->buffer_len == 0);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
/**
* \test Test multiple DNP3 frames in one TCP read.
*/
static int DNP3ParserTestMultiFrame(void)
{
DNP3State *state = NULL;
/* Unsolicited response 1. */
uint8_t unsol_response1[] = {
0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00,
0x89, 0xe5, 0xc4, 0xfa, 0x82, 0x00, 0x00, 0x02,
0x02, 0x17, 0x01, 0x01, 0x81, 0xa7, 0x75, 0xd8,
0x32, 0x4c, 0x81, 0x3e, 0x01, 0xa1, 0xc9,
};
/* Unsolicited response 2. */
uint8_t unsol_response2[] = {
0x05, 0x64, 0x16, 0x44, 0x01, 0x00, 0x02, 0x00,
0x89, 0xe5, 0xc5, 0xfb, 0x82, 0x00, 0x00, 0x02,
0x02, 0x17, 0x01, 0x0c, 0x01, 0xd8, 0x75, 0xd8,
0x32, 0x4c, 0xc9, 0x3c, 0x01, 0xa1, 0xc9,
};
uint8_t combined[sizeof(unsol_response1) + sizeof(unsol_response2)];
memcpy(combined, unsol_response1, sizeof(unsol_response1));
memcpy(combined + sizeof(unsol_response1), unsol_response2,
sizeof(unsol_response2));
/* Setup. */
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
int r;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
SCMutexLock(&flow.m);
r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, combined, sizeof(combined));
SCMutexUnlock(&flow.m);
FAIL_IF(r != 0);
state = flow.alstate;
FAIL_IF(state == NULL);
FAIL_IF(state->transaction_max != 2);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
/**
* \test Test the parsing of a request PDU.
*
* The PDU under test contains a single read request object:
* - Group: 1
* - Variation: 0
* - Count: 0
*/
static int DNP3ParserTestParsePDU01(void)
{
/* Frame to be tested. This frame is a DNP3 request with one read
* request data object, group 1, variation 0. */
const uint8_t pkt[] = {
0x05, 0x64,
0x0b, 0xc4, 0x17, 0x00, 0xef, 0xff, 0xc4, 0x8f,
0xe1, 0xc8, 0x01, 0x01, 0x00, 0x06, 0x77, 0x6e
};
DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN);
int pdus = DNP3HandleRequestLinkLayer(NULL, dnp3state, pkt, sizeof(pkt));
FAIL_IF(pdus < 1);
DNP3Transaction *dnp3tx = DNP3GetTx(dnp3state, 0);
FAIL_IF_NULL(dnp3tx);
FAIL_IF(!dnp3tx->is_request);
FAIL_IF(TAILQ_EMPTY(&dnp3tx->objects));
DNP3Object *object = TAILQ_FIRST(&dnp3tx->objects);
FAIL_IF(object->group != 1 || object->variation != 0);
FAIL_IF(object->count != 0);
DNP3StateFree(dnp3state);
PASS;
}
/**
* \test Test the decode of a DNP3 fragment with a single 70:3 object.
*/
static int DNP3ParserDecodeG70V3Test(void)
{
const uint8_t pkt[] = {
0x05, 0x64,
0x63, 0xc4, 0x04, 0x00, 0x03, 0x00, 0xc7, 0xee,
0xc7, 0xc9, 0x1b, 0x46, 0x03, 0x5b, 0x01, 0x55,
0x00, 0x1a, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00,
0x9e, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff, 0x00, 0x1e, 0x00, 0x43,
0x3a, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x2f, 0x44,
0x4e, 0x50, 0x44, 0x65, 0x67, 0x7d, 0x76, 0x69,
0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x93, 0x0c,
0x6e, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65,
0x6e, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x6d,
0x35, 0x20, 0x6f, 0x74, 0x65, 0x20, 0x44, 0x65,
0x76, 0x69, 0x63, 0x65, 0x2e, 0x78, 0x6d, 0x6c,
0xc4, 0x8b
};
DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN);
FAIL_IF_NULL(dnp3state);
int bytes = DNP3HandleRequestLinkLayer(NULL, dnp3state, pkt, sizeof(pkt));
FAIL_IF(bytes != sizeof(pkt));
DNP3Transaction *tx = DNP3GetTx(dnp3state, 0);
FAIL_IF_NULL(tx);
FAIL_IF_NOT(tx->is_request);
DNP3Object *obj = TAILQ_FIRST(&tx->objects);
FAIL_IF_NULL(obj);
FAIL_IF_NOT(obj->group == 70);
FAIL_IF_NOT(obj->variation == 3);
FAIL_IF_NOT(obj->prefix_code == 0x5);
FAIL_IF_NOT(obj->range_code == 0xb);
FAIL_IF_NOT(obj->count == 1);
DNP3Point *point = TAILQ_FIRST(obj->points);
FAIL_IF_NULL(point);
FAIL_IF_NOT(point->prefix == 85);
FAIL_IF_NOT(point->size == 85);
FAIL_IF_NULL(point->data);
DNP3ObjectG70V3 *data = point->data;
FAIL_IF_NOT(strcmp(
data->filename,
"C:/temp/DNPDeviceConfiguration written to Remote Device.xml") == 0);
DNP3StateFree(dnp3state);
PASS;
}
/**
* \brief Test that an alert is raised on an unknown object.
*/
static int DNP3ParserUnknownEventAlertTest(void)
{
/* Valid DNP3 frame with 70:3 object. */
uint8_t pkt[] = {
0x05, 0x64, 0x63, 0xc4, 0x04, 0x00, 0x03, 0x00,
0xc7, 0xee,
0xc7, 0xc9, 0x1b,
/* Object and variation. Originally 70:3, now 70:99, an
* unknown object. */
0x46, 0x63,
0x5b, 0x01, 0x55,
0x00, 0x1a, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00,
0x9e, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff, 0x00, 0x1e, 0x00, 0x43,
0x3a, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x2f, 0x44,
0x4e, 0x50, 0x44, 0x65, 0x67, 0x7d, 0x76, 0x69,
0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x93, 0x0c,
0x6e, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65,
0x6e, 0x20, 0x74, 0x6f, 0x20, 0x52, 0x65, 0x6d,
0x35, 0x20, 0x6f, 0x74, 0x65, 0x20, 0x44, 0x65,
0x76, 0x69, 0x63, 0x65, 0x2e, 0x78, 0x6d, 0x6c,
0xc4, 0x8b
};
DNP3FixCrc(pkt + 10, sizeof(pkt) - 10);
DNP3State *dnp3state = DNP3StateAlloc(NULL, ALPROTO_UNKNOWN);
FAIL_IF_NULL(dnp3state);
int bytes = DNP3HandleRequestLinkLayer(NULL, dnp3state, pkt, sizeof(pkt));
FAIL_IF(bytes != sizeof(pkt));
DNP3StateFree(dnp3state);
PASS;
}
/**
* \brief Test that an alert is raised on incorrect data.
*/
static int DNP3ParserIncorrectUserData(void)
{
uint8_t packet_bytes[] = {
0x05, 0x64, 0x08, 0xc4, 0x03, 0x00, 0x04, 0x00,
0xbf, 0xe9, 0xc1, 0xc1, 0x82, 0xc5, 0xee
};
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow flow;
TcpSession ssn;
memset(&flow, 0, sizeof(flow));
memset(&ssn, 0, sizeof(ssn));
flow.protoctx = (void *)&ssn;
flow.proto = IPPROTO_TCP;
flow.alproto = ALPROTO_DNP3;
StreamTcpInitConfig(true);
int r = AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
STREAM_TOCLIENT, packet_bytes, sizeof(packet_bytes));
FAIL_IF(r == 0);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(true);
FLOW_DESTROY(&flow);
PASS;
}
#endif
void DNP3ParserRegisterTests(void)
{
#ifdef UNITTESTS
UtRegisterTest("DNP3ParserTestCheckCRC", DNP3ParserTestCheckCRC);
UtRegisterTest("DNP3ParserCheckLinkHeaderCRC",
DNP3ParserCheckLinkHeaderCRC);
UtRegisterTest("DNP3CheckUserDataCRCsTest", DNP3CheckUserDataCRCsTest);
UtRegisterTest("DNP3CalculateLinkLengthTest", DNP3CalculateLinkLengthTest);
UtRegisterTest("DNP3CalculateTransportLengthWithoutCRCsTest",
DNP3CalculateTransportLengthWithoutCRCsTest);
UtRegisterTest("DNP3ReassembleApplicationLayerTest01",
DNP3ReassembleApplicationLayerTest01);
UtRegisterTest("DNP3ProbingParserTest", DNP3ProbingParserTest);
UtRegisterTest("DNP3ParserTestRequestResponse",
DNP3ParserTestRequestResponse);
UtRegisterTest("DNP3ParserTestUnsolicitedResponseConfirm",
DNP3ParserTestUnsolicitedResponseConfirm);
UtRegisterTest("DNP3ParserTestPartialFrame", DNP3ParserTestPartialFrame);
UtRegisterTest("DNP3ParserTestMultiFrame", DNP3ParserTestMultiFrame);
UtRegisterTest("DNP3ParserTestFlooded", DNP3ParserTestFlooded);
UtRegisterTest("DNP3ParserTestParsePDU01", DNP3ParserTestParsePDU01);
UtRegisterTest("DNP3ParserDecodeG70V3Test", DNP3ParserDecodeG70V3Test);
UtRegisterTest("DNP3ParserUnknownEventAlertTest",
DNP3ParserUnknownEventAlertTest);
UtRegisterTest("DNP3ParserIncorrectUserData", DNP3ParserIncorrectUserData);
#endif
}