]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
modbus: Support Unit Identifier
authorDavid DIALLO <david.diallo@gmail.com>
Wed, 21 Feb 2018 23:29:33 +0000 (00:29 +0100)
committerVictor Julien <victor@inliniac.net>
Wed, 14 Mar 2018 21:28:30 +0000 (22:28 +0100)
When destination IP address does not suffice to uniquely identify
the Modbus/TCP device.

Some Modbus/TCP devices act as gateways to other Modbus/TCP devices
that are behind this gateways.

doc/userguide/rules/modbus-keyword.rst
src/app-layer-modbus.c
src/app-layer-modbus.h
src/detect-engine-modbus.c
src/detect-modbus.c
src/detect-modbus.h

index 38a4a26b7bd93881c60bf5e19985d7e4cf765899..a338bb42cae1986fcc99c13dc5b2333310335336 100644 (file)
@@ -4,10 +4,11 @@ Modbus Keyword
 The modbus keyword can be used for matching on various properties of
 Modbus requests.
 
-There are two ways of using this keyword:
+There are three ways of using this keyword:
 
 * matching on functions properties with the setting "function";
-* matching on directly on data access with the setting "access".
+* matching on directly on data access with the setting "access";
+* matching on unit identifier with the setting "unit" only or with the previous setting "function" or "access".
 
 With the setting **function**, you can match on:
 
@@ -72,6 +73,43 @@ Examples::
   modbus: access read discretes, address <100            # Read access at address smaller than 100 of Discretes Input table
   modbus: access write holding, address 500, value >200  # Write value greather than 200 at address 500 of Holding Registers table
 
+With the setting **unit**, you can match on:
+
+* a MODBUS slave address of a remote device connected on the sub-network behind a bridge or a gateway. The destination IP address identifies the bridge itself and the bridge uses the MODBUS unit identifier to forward the request to the right slave device.
+
+Syntax::
+
+  modbus: unit <value>
+  modbus: unit <value>, function <value>
+  modbus: unit <value>, function <value>, subfunction <value>
+  modbus: unit <value>, function [!] <assigned | unassigned | public | user | reserved | all>
+  modbus: unit <value>, access <read | write>
+  modbus: unit <value>, access read <discretes | coils | input | holding>
+  modbus: unit <value>, access read <discretes | coils | input | holding>, address <value>
+  modbus: unit <value>, access write < coils | holding>
+  modbus: unit <value>, access write < coils | holding>, address <value>
+  modbus: unit <value>, access write < coils | holding>, address <value>, value <value>
+
+With _<value>_ setting matches on the address or value as it is being
+accessed or written as follows::
+
+  unit 10     # exactly unit identifier 10
+  unit 10<>20 # greater than unit identifier 10 and smaller than unit identifier 20
+  unit >10    # greater than unit identifier 10
+  unit <10    # smaller than unit identifier 10
+
+Examples::
+
+  modbus: unit 10                                                       # Unit identifier 10
+  modbus: unit 10, function 21                                          # Unit identifier 10 and write File record function
+  modbus: unit 10, function 4, subfunction 4                            # Unit identifier 10 and force Listen Only Mode (Diagnostics) function
+  modbus: unit 10, function assigned                                    # Unit identifier 10 and assigned function
+  modbus: unit 10, function !reserved                                   # Unit identifier 10 and every function but reserved function
+  modbus: unit 10, access read                                          # Unit identifier 10 and Read access
+  modbus: unit 10, access write coils                                   # Unit identifier 10 and Write access to Coils table
+  modbus: unit >10, access read discretes, address <100                 # Greater than unit identifier 10 and Read access at address smaller than 100 of Discretes Input table
+  modbus: unit 10<>20, access write holding, address 500, value >200    # Greater than unit identifier 10 and smaller than unit identifier 20 and Write value greather than 200 at address 500 of Holding Registers table
+
 (cf. http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)
 
 **Note:** Address of read and write are starting at 1. So if your system
@@ -83,6 +121,10 @@ remote device and not to open and close it for each MODBUS/TCP
 transaction. In that case, it is important to set the depth of the
 stream reassembling as unlimited (stream.reassembly.depth: 0)
 
+**Note:** According to MODBUS Messaging on TCP/IP Implementation Guide
+V1.0b, the MODBUS slave device addresses on serial line are assigned from 1 to
+247 (decimal). Address 0 is used as broadcast address.
+
 (cf. http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf)
 
 Paper and presentation (in french) on Modbus support are available :
index 51ada113f74cc6990eb0aadf75477fc9d97c3c2e..b39d527d218ad432962d67b7d223edf065ad46d4 100644 (file)
@@ -94,7 +94,6 @@ SCEnumCharMap modbus_decoder_event_table[ ] = {
 #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
@@ -1301,7 +1300,8 @@ static int ModbusParseRequest(Flow                  *f,
         /* Check MODBUS Header */
         ModbusCheckHeader(modbus, &header);
 
-        /* Store Transaction ID & PDU length */
+        /* Store Unit ID, Transaction ID & PDU length */
+        tx->unit_id         = header.unitId;
         tx->transactionId   = header.transactionId;
         tx->length          = header.length;
 
@@ -1563,6 +1563,13 @@ void RegisterModbusParsers(void)
 #include "stream-tcp.h"
 #include "stream-tcp-private.h"
 
+/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
+static uint8_t invalidFunctionCode[] = {/* Transaction ID */    0x00, 0x00,
+                                         /* Protocol ID */       0x00, 0x01,
+                                         /* Length */            0x00, 0x02,
+                                         /* Unit ID */           0x00,
+                                         /* Function code */     0x00};
+
 /* 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,
@@ -1746,7 +1753,7 @@ static int ModbusParserTest01(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -1762,50 +1769,30 @@ static int ModbusParserTest01(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 1);
+    FAIL_IF_NOT(tx->read.address == 0x7890);
+    FAIL_IF_NOT(tx->read.quantity == 19);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Write Multiple registers request/response. */
@@ -1814,7 +1801,7 @@ static int ModbusParserTest02(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -1830,55 +1817,34 @@ static int ModbusParserTest02(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 16);
+    FAIL_IF_NOT(tx->write.address == 0x01);
+    FAIL_IF_NOT(tx->write.quantity == 2);
+    FAIL_IF_NOT(tx->write.count == 4);
+    FAIL_IF_NOT(tx->data[0] == 0x000A);
+    FAIL_IF_NOT(tx->data[1] == 0x0102);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Read/Write Multiple registers request/response with mismatch value. */
@@ -1891,7 +1857,7 @@ static int ModbusParserTest03(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -1913,8 +1879,7 @@ static int ModbusParserTest03(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -1922,8 +1887,7 @@ static int ModbusParserTest03(void) {
                                       "app-layer-event: "
                                       "modbus.value_mismatch; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -1933,74 +1897,49 @@ static int ModbusParserTest03(void) {
                                 STREAM_TOSERVER,
                                 readWriteMultipleRegistersReq,
                                 sizeof(readWriteMultipleRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 23);
+    FAIL_IF_NOT(tx->read.address == 0x03);
+    FAIL_IF_NOT(tx->read.quantity == 6);
+    FAIL_IF_NOT(tx->write.address == 0x0E);
+    FAIL_IF_NOT(tx->write.quantity == 3);
+    FAIL_IF_NOT(tx->write.count == 6);
+    FAIL_IF_NOT(tx->data[0] == 0x1234);
+    FAIL_IF_NOT(tx->data[1] == 0x5678);
+    FAIL_IF_NOT(tx->data[2] == 0x9ABC);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Force Listen Only Mode request. */
@@ -2009,7 +1948,7 @@ static int ModbusParserTest04(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -2025,34 +1964,21 @@ static int ModbusParserTest04(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 8);
+    FAIL_IF_NOT(tx->subFunction == 4);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus invalid Protocol version in request. */
@@ -2065,7 +1991,7 @@ static int ModbusParserTest05(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2087,8 +2013,7 @@ static int ModbusParserTest05(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2096,8 +2021,7 @@ static int ModbusParserTest05(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_protocol_id; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2106,41 +2030,28 @@ static int ModbusParserTest05(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus unsolicited response. */
@@ -2153,7 +2064,7 @@ static int ModbusParserTest06(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2175,8 +2086,7 @@ static int ModbusParserTest06(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2184,8 +2094,7 @@ static int ModbusParserTest06(void) {
                                       "app-layer-event: "
                                       "modbus.unsolicited_response; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2194,41 +2103,28 @@ static int ModbusParserTest06(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus invalid Length request. */
@@ -2241,7 +2137,7 @@ static int ModbusParserTest07(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2263,8 +2159,7 @@ static int ModbusParserTest07(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2272,8 +2167,7 @@ static int ModbusParserTest07(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_length; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2283,41 +2177,28 @@ static int ModbusParserTest07(void) {
                                 STREAM_TOSERVER,
                                 invalidLengthWriteMultipleRegistersReq,
                                 sizeof(invalidLengthWriteMultipleRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Read Coils request and error response with Exception code invalid. */
@@ -2330,7 +2211,7 @@ static int ModbusParserTest08(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2352,8 +2233,7 @@ static int ModbusParserTest08(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2361,8 +2241,7 @@ static int ModbusParserTest08(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_exception_code; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2371,66 +2250,43 @@ static int ModbusParserTest08(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 1);
+    FAIL_IF_NOT(tx->read.address == 0x7890);
+    FAIL_IF_NOT(tx->read.quantity == 19);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Modbus fragmentation - 1 ADU over 2 TCP packets. */
@@ -2442,7 +2298,7 @@ static int ModbusParserTest09(void) {
     uint32_t    input_len = sizeof(readCoilsReq), part2_len = 3;
     uint8_t     *input = readCoilsReq;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -2457,35 +2313,21 @@ static int ModbusParserTest09(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 1);
+    FAIL_IF_NOT(tx->read.address == 0x7890);
+    FAIL_IF_NOT(tx->read.quantity == 19);
 
     input_len = sizeof(readCoilsRsp);
     part2_len = 10;
@@ -2494,33 +2336,19 @@ static int ModbusParserTest09(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max ==1);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Modbus fragmentation - 2 ADU in 1 TCP packet. */
@@ -2532,11 +2360,10 @@ static int ModbusParserTest10(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     input  = (uint8_t *) SCMalloc (input_len * sizeof(uint8_t));
-    if (unlikely(input == NULL))
-        goto end;
+    FAIL_IF_NULL(input);
 
     memcpy(input, readCoilsReq, sizeof(readCoilsReq));
     memcpy(input + sizeof(readCoilsReq), writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq));
@@ -2554,42 +2381,27 @@ static int ModbusParserTest10(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
-    if (modbus_state->transaction_max !=2) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 2, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 2);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 16);
+    FAIL_IF_NOT(tx->write.address == 0x01);
+    FAIL_IF_NOT(tx->write.quantity == 2);
+    FAIL_IF_NOT(tx->write.count == 4);
+    FAIL_IF_NOT(tx->data[0] == 0x000A);
+    FAIL_IF_NOT(tx->data[1] == 0x0102);
 
     input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp);
 
     ptr = (uint8_t *) SCRealloc (input, input_len * sizeof(uint8_t));
-    if (unlikely(ptr == NULL))
-        goto end;
+    FAIL_IF_NULL(ptr);
     input = ptr;
 
     memcpy(input, readCoilsRsp, sizeof(readCoilsRsp));
@@ -2598,22 +2410,14 @@ static int ModbusParserTest10(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    result = 1;
-end:
-    if (input != NULL)
-        SCFree(input);
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    SCFree(input);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus exceed Length request. */
@@ -2626,7 +2430,7 @@ static int ModbusParserTest11(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF(alp_tctx == NULL);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2648,8 +2452,7 @@ static int ModbusParserTest11(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2657,8 +2460,7 @@ static int ModbusParserTest11(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_length; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2668,41 +2470,28 @@ static int ModbusParserTest11(void) {
                                 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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus invalid PDU Length. */
@@ -2715,7 +2504,7 @@ static int ModbusParserTest12(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -2737,8 +2526,7 @@ static int ModbusParserTest12(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2746,8 +2534,7 @@ static int ModbusParserTest12(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_length; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2757,41 +2544,28 @@ static int ModbusParserTest12(void) {
                                 STREAM_TOSERVER,
                                 invalidLengthPDUWriteMultipleRegistersReq,
                                 sizeof(invalidLengthPDUWriteMultipleRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Mask Write register request/response. */
@@ -2800,7 +2574,7 @@ static int ModbusParserTest13(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -2816,51 +2590,31 @@ static int ModbusParserTest13(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 22);
+    FAIL_IF_NOT(tx->data[0] == 0x00F2);
+    FAIL_IF_NOT(tx->data[1] == 0x0025);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send Modbus Write single register request/response. */
@@ -2869,7 +2623,7 @@ static int ModbusParserTest14(void) {
     Flow f;
     TcpSession ssn;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -2885,51 +2639,31 @@ static int ModbusParserTest14(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 6);
+    FAIL_IF_NOT(tx->write.address == 0x0001);
+    FAIL_IF_NOT(tx->data[0] == 0x0003);
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
-    return result;
+    PASS;
 }
 
 /** \test Send invalid Modbus Mask Write register request. */
@@ -2942,7 +2676,7 @@ static int ModbusParserTest15(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(f));
@@ -2964,8 +2698,7 @@ static int ModbusParserTest15(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -2973,8 +2706,7 @@ static int ModbusParserTest15(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_length; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -2983,64 +2715,41 @@ static int ModbusParserTest15(void) {
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
 
-    if (tx->function != 22) {
-        printf("expected function %d, got %" PRIu8 ": ", 16, tx->function);
-        goto end;
-    }
+    FAIL_IF_NOT(tx->function == 22);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
+    PASS;
 }
 
 /** \test Send invalid Modbus Mask Write register request. */
@@ -3053,7 +2762,7 @@ static int ModbusParserTest16(void) {
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(f));
@@ -3075,8 +2784,7 @@ static int ModbusParserTest16(void) {
     StreamTcpInitConfig(TRUE);
 
     DetectEngineCtx *de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
@@ -3084,8 +2792,7 @@ static int ModbusParserTest16(void) {
                                       "app-layer-event: "
                                       "modbus.invalid_length; "
                                       "sid:1;)");
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -3095,66 +2802,42 @@ static int ModbusParserTest16(void) {
                                 STREAM_TOSERVER,
                                 invalidWriteSingleRegisterReq,
                                 sizeof(invalidWriteSingleRegisterReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     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;
-    }
+    FAIL_IF_NOT(tx->function == 6);
+    FAIL_IF_NOT(tx->write.address == 0x0001);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!PacketAlertCheck(p, 1)) {
-        printf("sid 1 didn't match.  Should have matched: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    if (modbus_state->transaction_max !=1) {
-        printf("expected transaction_max %d, got %" PRIu64 ": ", 1, modbus_state->transaction_max);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus_state->transaction_max == 1);
 
-    result = 1;
-end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
 
     DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
     DetectEngineCtxFree(de_ctx);
 
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePackets(&p, 1);
-    return result;
-}
+    PASS;}
 
 /** \test Checks if stream_depth is correct */
 static int ModbusParserTest17(void) {
@@ -3162,7 +2845,7 @@ static int ModbusParserTest17(void) {
     Flow f;
     TcpSession ssn;
 
-    FAIL_IF(alp_tctx == NULL);
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -3207,7 +2890,7 @@ static int ModbusParserTest18(void) {
     uint32_t    input_len = sizeof(readCoilsReq), part2_len = 3;
     uint8_t     *input = readCoilsReq;
 
-    FAIL_IF(alp_tctx == NULL);
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&f, 0, sizeof(f));
     memset(&ssn, 0, sizeof(ssn));
@@ -3262,6 +2945,80 @@ static int ModbusParserTest18(void) {
     FLOW_DESTROY(&f);
     PASS;
 }
+
+/** \test Send Modbus invalid function. */
+static int ModbusParserTest19(void) {
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+    DetectEngineThreadCtx *det_ctx = NULL;
+    Flow f;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    TcpSession ssn;
+    ThreadVars tv;
+
+    FAIL_IF_NULL(alp_tctx);
+
+    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.alproto   = ALPROTO_MODBUS;
+    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();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                                      "(msg:\"Modbus invalid Function code\"; "
+                                      "app-layer-event: "
+                                      "modbus.invalid_function_code; "
+                                      "sid:1;)");
+    FAIL_IF_NULL(s);
+
+    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,
+                                invalidFunctionCode,
+                                sizeof(invalidFunctionCode));
+    FAIL_IF_NOT(r == 0);
+    FLOWLOCK_UNLOCK(&f);
+
+    ModbusState    *modbus_state = f.alstate;
+    FAIL_IF_NULL(modbus_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+    SigGroupCleanup(de_ctx);
+    SigCleanSignatures(de_ctx);
+
+    DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+    DetectEngineCtxFree(de_ctx);
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(TRUE);
+    FLOW_DESTROY(&f);
+    UTHFreePackets(&p, 1);
+    PASS;
+}
 #endif /* UNITTESTS */
 
 void ModbusParserRegisterTests(void) {
@@ -3302,5 +3059,7 @@ void ModbusParserRegisterTests(void) {
                    ModbusParserTest17);
     UtRegisterTest("ModbusParserTest18 - Modbus stream depth in 2 TCP packets",
                    ModbusParserTest18);
+    UtRegisterTest("ModbusParserTest19 - Modbus invalid Function code",
+                   ModbusParserTest19);
 #endif /* UNITTESTS */
 }
index a8545dfbdc8cc0bd13797ec7793a45337a9939ad..61b66e2a3fd972348f56c3ed42720abd3f8f4358 100644 (file)
@@ -78,6 +78,9 @@ enum {
 #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)
 
+/* Modbus Function Code. */
+#define MODBUS_FUNC_NONE                0x00
+
 /* Modbus Transaction Structure, request/response. */
 typedef struct ModbusTransaction_ {
     struct ModbusState_ *modbus;
@@ -86,6 +89,7 @@ typedef struct ModbusTransaction_ {
     uint32_t    logged;         /**< flags indicating which loggers have logged */
     uint16_t    transactionId;
     uint16_t    length;
+    uint8_t     unit_id;
     uint8_t     function;
     uint8_t     category;
     uint8_t     type;
index 9e1f8537c4dd21d01bf4c39814749af7632ffa7d..dc6dbe9bc08e88704967ef8d6eb4f8505b4e99d2 100644 (file)
@@ -218,16 +218,28 @@ int DetectEngineInspectModbus(ThreadVars            *tv,
         SCReturnInt(0);
     }
 
+    if (modbus->unit_id != NULL) {
+        if (DetectEngineInspectModbusValueMatch(modbus->unit_id, tx->unit_id, 0) == 0) {
+            SCReturnInt(0);
+        } else {
+            ret = 1;
+        }
+    }
+
     if (modbus->type == MODBUS_TYP_NONE) {
         if (modbus->category == MODBUS_CAT_NONE) {
-            if (modbus->function == tx->function) {
-                if (modbus->subfunction != NULL) {
-                    SCLogDebug("looking for Modbus server function %d and subfunction %d",
-                                modbus->function, *(modbus->subfunction));
-                    ret = (*(modbus->subfunction) == (tx->subFunction))? 1 : 0;
+            if (modbus->function != MODBUS_FUNC_NONE) {
+                if (modbus->function == tx->function) {
+                    if (modbus->subfunction != NULL) {
+                        SCLogDebug("looking for Modbus server function %d and subfunction %d",
+                                   modbus->function, *(modbus->subfunction));
+                        ret = (*(modbus->subfunction) == (tx->subFunction))? 1 : 0;
+                    } else {
+                        SCLogDebug("looking for Modbus server function %d", modbus->function);
+                        ret = 1;
+                    }
                 } else {
-                    SCLogDebug("looking for Modbus server function %d", modbus->function);
-                    ret = 1;
+                    ret = 0;
                 }
             }
         } else {
@@ -238,16 +250,20 @@ int DetectEngineInspectModbus(ThreadVars            *tv,
         uint8_t access      = modbus->type & MODBUS_TYP_ACCESS_MASK;
         uint8_t function    = modbus->type & MODBUS_TYP_ACCESS_FUNCTION_MASK;
 
-        if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) {
-            if (modbus->address != NULL) {
-                ret = DetectEngineInspectModbusAddress(tx, modbus->address, access);
+        if (access != MODBUS_TYP_NONE) {
+            if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) {
+                if (modbus->address != NULL) {
+                    ret = DetectEngineInspectModbusAddress(tx, modbus->address, access);
 
-                if (ret && (modbus->data != NULL)) {
-                    ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data);
+                    if (ret && (modbus->data != NULL)) {
+                        ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data);
+                    }
+                } else {
+                    SCLogDebug("looking for Modbus access type %d and function type %d", access, function);
+                    ret = 1;
                 }
             } else {
-                SCLogDebug("looking for Modbus access type %d and function type %d", access, function);
-                ret = 1;
+                ret = 0;
             }
         }
     }
@@ -274,7 +290,7 @@ int DetectEngineInspectModbus(ThreadVars            *tv,
 static uint8_t readCoilsReq[] = {/* Transaction ID */    0x00, 0x00,
                                  /* Protocol ID */       0x00, 0x00,
                                  /* Length */            0x00, 0x06,
-                                 /* Unit ID */           0x00,
+                                 /* Unit ID */           0x0a,
                                  /* Function code */     0x01,
                                  /* Starting Address */  0x78, 0x90,
                                  /* Quantity of coils */ 0x00, 0x13 };
@@ -295,7 +311,7 @@ static uint8_t readInputsRegistersReq[] = {/* Transaction ID */          0x00, 0
 static uint8_t readWriteMultipleRegistersReq[] = {/* Transaction ID */          0x12, 0x34,
                                                   /* Protocol ID */             0x00, 0x00,
                                                   /* Length */                  0x00, 0x11,
-                                                  /* Unit ID */                 0x00,
+                                                  /* Unit ID */                 0x0a,
                                                   /* Function code */           0x17,
                                                   /* Read Starting Address */   0x00, 0x03,
                                                   /* Quantity to Read */        0x00, 0x06,
@@ -345,7 +361,7 @@ static int DetectEngineInspectModbusTest01(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -366,16 +382,13 @@ static int DetectEngineInspectModbusTest01(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                             "(msg:\"Testing modbus code function\"; "
                                             "modbus: function 23; sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -385,43 +398,26 @@ static int DetectEngineInspectModbusTest01(void)
                                 STREAM_TOSERVER,
                                 readWriteMultipleRegistersReq,
                                 sizeof(readWriteMultipleRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test code function and code subfunction. */
@@ -436,7 +432,7 @@ static int DetectEngineInspectModbusTest02(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -457,17 +453,14 @@ static int DetectEngineInspectModbusTest02(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                            "(msg:\"Testing modbus function and subfunction\"; "
                                            "modbus: function 8, subfunction 4;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -476,43 +469,26 @@ static int DetectEngineInspectModbusTest02(void)
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test function category. */
@@ -527,7 +503,7 @@ static int DetectEngineInspectModbusTest03(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -548,17 +524,14 @@ static int DetectEngineInspectModbusTest03(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                            "(msg:\"Testing modbus category function\"; "
                                            "modbus: function reserved;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -568,43 +541,26 @@ static int DetectEngineInspectModbusTest03(void)
                                 STREAM_TOSERVER,
                                 encapsulatedInterfaceTransport,
                                 sizeof(encapsulatedInterfaceTransport));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test negative function category. */
@@ -619,7 +575,7 @@ static int DetectEngineInspectModbusTest04(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -640,17 +596,14 @@ static int DetectEngineInspectModbusTest04(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus category function\"; "
                                        "modbus: function !assigned;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -659,43 +612,26 @@ static int DetectEngineInspectModbusTest04(void)
     int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                                 STREAM_TOSERVER, unassigned,
                                 sizeof(unassigned));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test access type. */
@@ -710,7 +646,7 @@ static int DetectEngineInspectModbusTest05(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -731,17 +667,14 @@ static int DetectEngineInspectModbusTest05(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                            "(msg:\"Testing modbus access type\"; "
                                            "modbus: access read;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -750,43 +683,26 @@ static int DetectEngineInspectModbusTest05(void)
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-    result = 1;
-
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test access function. */
@@ -801,7 +717,7 @@ static int DetectEngineInspectModbusTest06(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -822,17 +738,14 @@ static int DetectEngineInspectModbusTest06(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                            "(msg:\"Testing modbus access type\"; "
                                            "modbus: access read input;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -841,43 +754,26 @@ static int DetectEngineInspectModbusTest06(void)
     int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                                 STREAM_TOSERVER, readInputsRegistersReq,
                                 sizeof(readInputsRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test read access at an address. */
@@ -892,7 +788,7 @@ static int DetectEngineInspectModbusTest07(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -913,17 +809,14 @@ static int DetectEngineInspectModbusTest07(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     s = de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                            "(msg:\"Testing modbus address access\"; "
                                            "modbus: access read, address 30870;  sid:1;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -932,43 +825,26 @@ static int DetectEngineInspectModbusTest07(void)
     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;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (!(PacketAlertCheck(p, 1))) {
-        printf("sid 1 didn't match but should have: ");
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test read access at a range of address. */
@@ -983,7 +859,7 @@ static int DetectEngineInspectModbusTest08(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -1004,8 +880,7 @@ static int DetectEngineInspectModbusTest08(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
@@ -1060,9 +935,7 @@ static int DetectEngineInspectModbusTest08(void)
                                       "(msg:\"Testing modbus access\"; "
                                       "modbus: access read input, "
                                       "address 104<>110;  sid:10;)");
-
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -1071,88 +944,36 @@ static int DetectEngineInspectModbusTest08(void)
     int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                                 STREAM_TOSERVER, readInputsRegistersReq,
                                 sizeof(readInputsRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (PacketAlertCheck(p, 1)) {
-        printf("sid 1 did match but should not have: ");
-        goto end;
-    }
+    FAIL_IF(PacketAlertCheck(p, 1));
+    FAIL_IF(PacketAlertCheck(p, 3));
+    FAIL_IF(PacketAlertCheck(p, 9));
+    FAIL_IF(PacketAlertCheck(p, 10));
 
-    if (!(PacketAlertCheck(p, 2))) {
-        printf("sid 2 didn't match but should have: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 2));
+    FAIL_IF_NOT(PacketAlertCheck(p, 4));
+    FAIL_IF_NOT(PacketAlertCheck(p, 5));
+    FAIL_IF_NOT(PacketAlertCheck(p, 6));
+    FAIL_IF_NOT(PacketAlertCheck(p, 7));
+    FAIL_IF_NOT(PacketAlertCheck(p, 8));
 
-    if (PacketAlertCheck(p, 3)) {
-        printf("sid 3 did match but should not have: ");
-        goto end;
-    }
-
-    if (!(PacketAlertCheck(p, 4))) {
-        printf("sid 4 didn't match but should have: ");
-        goto end;
-    }
-
-    if (!(PacketAlertCheck(p, 5))) {
-        printf("sid 5 didn't match but should have: ");
-        goto end;
-    }
-
-    if (!(PacketAlertCheck(p, 6))) {
-        printf("sid 6 didn't match but should have: ");
-        goto end;
-    }
-
-    if (!(PacketAlertCheck(p, 7))) {
-        printf("sid 7 didn't match but should have: ");
-        goto end;
-    }
-
-    if (!(PacketAlertCheck(p, 8))) {
-        printf("sid 8 didn't match but should have: ");
-        goto end;
-    }
-
-    if (PacketAlertCheck(p, 9)) {
-        printf("sid 9 did match but should not have: ");
-        goto end;
-    }
-
-    if (PacketAlertCheck(p, 10)) {
-        printf("sid 10 did match but should not have: ");
-        goto end;
-    }
-
-    result = 1;
-
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 
 /** \test write access at a address in a range of value. */
@@ -1167,7 +988,7 @@ static int DetectEngineInspectModbusTest09(void)
     TcpSession ssn;
     ThreadVars tv;
 
-    int result = 0;
+    FAIL_IF_NULL(alp_tctx);
 
     memset(&tv, 0, sizeof(ThreadVars));
     memset(&f, 0, sizeof(Flow));
@@ -1188,8 +1009,7 @@ static int DetectEngineInspectModbusTest09(void)
     StreamTcpInitConfig(TRUE);
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
@@ -1246,9 +1066,129 @@ static int DetectEngineInspectModbusTest09(void)
                                       "(msg:\"Testing modbus write access\"; "
                                       "modbus: access write holding, "
                                       "address 15, value >4660;  sid:10;)");
+    FAIL_IF_NULL(s);
+
+    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));
+    FAIL_IF_NOT(r == 0);
+    FLOWLOCK_UNLOCK(&f);
+
+    ModbusState    *modbus_state = f.alstate;
+    FAIL_IF_NULL(modbus_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    FAIL_IF(PacketAlertCheck(p, 1));
+    FAIL_IF(PacketAlertCheck(p, 4));
+    FAIL_IF(PacketAlertCheck(p, 5));
+    FAIL_IF(PacketAlertCheck(p, 8));
+    FAIL_IF(PacketAlertCheck(p, 10));
+
+    FAIL_IF_NOT(PacketAlertCheck(p, 2));
+    FAIL_IF_NOT(PacketAlertCheck(p, 3));
+    FAIL_IF_NOT(PacketAlertCheck(p, 6));
+    FAIL_IF_NOT(PacketAlertCheck(p, 7));
+    FAIL_IF_NOT(PacketAlertCheck(p, 9));
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+
+    StreamTcpFreeConfig(TRUE);
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+    PASS;
+}
+
+/** \test Test code unit_id. */
+static int DetectEngineInspectModbusTest10(void)
+{
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+    DetectEngineThreadCtx *det_ctx = NULL;
+    DetectEngineCtx *de_ctx = NULL;
+    Flow f;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    TcpSession ssn;
+    ThreadVars tv;
 
-    if (s == NULL)
-        goto end;
+    FAIL_IF_NULL(alp_tctx);
+
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+    memset(&ssn, 0, sizeof(TcpSession));
+
+    p = UTHBuildPacket(readWriteMultipleRegistersReq,
+                       sizeof(readWriteMultipleRegistersReq),
+                       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);
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    /* readWriteMultipleRegistersReq, Write Starting Address = 0x0E, Quantity to Write = 0x03 */
+    /* Unit ID                          = 0x0a (10)         */
+    /* Function code                    = 0x17 (23)         */
+    /* Write access register address 15 = 0x1234 (4660)     */
+    /* Write access register address 16 = 0x5678 (22136)    */
+    /* Write access register address 17 = 0x9ABC (39612)    */
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10; sid:1;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 12; sid:2;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 5<>15; sid:3;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 5<>9; sid:4;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 11<>15; sid:5;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit >9; sid:6;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit >11; sid:7;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit <11; sid:8;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit <9; sid:9;)");
+    FAIL_IF_NULL(s);
 
     SigGroupBuild(de_ctx);
     DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
@@ -1258,88 +1198,246 @@ static int DetectEngineInspectModbusTest09(void)
                                 STREAM_TOSERVER,
                                 readWriteMultipleRegistersReq,
                                 sizeof(readWriteMultipleRegistersReq));
-    if (r != 0) {
-        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
-        FLOWLOCK_UNLOCK(&f);
-        goto end;
-    }
+    FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
     ModbusState    *modbus_state = f.alstate;
-    if (modbus_state == NULL) {
-        printf("no modbus state: ");
-        goto end;
-    }
+    FAIL_IF_NULL(modbus_state);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
 
-    if (PacketAlertCheck(p, 1)) {
-        printf("sid 1 did match but should not have: ");
-        goto end;
-    }
+    FAIL_IF(PacketAlertCheck(p, 2));
+    FAIL_IF(PacketAlertCheck(p, 4));
+    FAIL_IF(PacketAlertCheck(p, 5));
+    FAIL_IF(PacketAlertCheck(p, 7));
+    FAIL_IF(PacketAlertCheck(p, 9));
 
-    if (!(PacketAlertCheck(p, 2))) {
-        printf("sid 2 didn't match but should have: ");
-        goto end;
-    }
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
+    FAIL_IF_NOT(PacketAlertCheck(p, 3));
+    FAIL_IF_NOT(PacketAlertCheck(p, 6));
+    FAIL_IF_NOT(PacketAlertCheck(p, 8));
 
-    if (!(PacketAlertCheck(p, 3))) {
-        printf("sid 3 didn't match but should have: ");
-        goto end;
-    }
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
-    if (PacketAlertCheck(p, 4)) {
-        printf("sid 4 did match but should not have: ");
-        goto end;
-    }
+    StreamTcpFreeConfig(TRUE);
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+    PASS;
+}
 
-    if (PacketAlertCheck(p, 5)) {
-        printf("sid 5 did match but should not have: ");
-        goto end;
-    }
+/** \test Test code unit_id and code function. */
+static int DetectEngineInspectModbusTest11(void)
+{
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+    DetectEngineThreadCtx *det_ctx = NULL;
+    DetectEngineCtx *de_ctx = NULL;
+    Flow f;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    TcpSession ssn;
+    ThreadVars tv;
 
-    if (!(PacketAlertCheck(p, 6))) {
-        printf("sid 6 didn't match but should have: ");
-        goto end;
-    }
+    FAIL_IF_NULL(alp_tctx);
 
-    if (!(PacketAlertCheck(p, 7))) {
-        printf("sid 7 didn't match but should have: ");
-        goto end;
-    }
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+    memset(&ssn, 0, sizeof(TcpSession));
 
-    if (PacketAlertCheck(p, 8)) {
-        printf("sid 8 did match but should not have: ");
-        goto end;
-    }
+    p = UTHBuildPacket(readWriteMultipleRegistersReq,
+                       sizeof(readWriteMultipleRegistersReq),
+                       IPPROTO_TCP);
 
-    if (!(PacketAlertCheck(p, 9))) {
-        printf("sid 9 didn't match but should have: ");
-        goto end;
-    }
+    FLOW_INITIALIZE(&f);
+    f.alproto   = ALPROTO_MODBUS;
+    f.protoctx  = (void *)&ssn;
+    f.proto     = IPPROTO_TCP;
+    f.flags     |= FLOW_IPV4;
 
-    if (PacketAlertCheck(p, 10)) {
-        printf("sid 10 did match but should not have: ");
-        goto end;
-    }
+    p->flow         = &f;
+    p->flags        |= PKT_HAS_FLOW | PKT_STREAM_EST;
+    p->flowflags    |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+    StreamTcpInitConfig(TRUE);
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    /* readWriteMultipleRegistersReq, Write Starting Address = 0x0E, Quantity to Write = 0x03 */
+    /* Unit ID                          = 0x0a (10)         */
+    /* Function code                    = 0x17 (23)         */
+    /* Write access register address 15 = 0x1234 (4660)     */
+    /* Write access register address 16 = 0x5678 (22136)    */
+    /* Write access register address 17 = 0x9ABC (39612)    */
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10, function 20; sid:1;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10, function 23; sid:2;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 11, function 20; sid:3;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 11, function 23; sid:4;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10, function public; sid:5;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 11, function public; sid:6;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10, function user; sid:7;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus code unit_id\"; "
+                              "modbus: unit 10, function !user; sid:8;)");
+    FAIL_IF_NULL(s);
+
+    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));
+    FAIL_IF_NOT(r == 0);
+    FLOWLOCK_UNLOCK(&f);
+
+    ModbusState    *modbus_state = f.alstate;
+    FAIL_IF_NULL(modbus_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    FAIL_IF(PacketAlertCheck(p, 1));
+    FAIL_IF(PacketAlertCheck(p, 3));
+    FAIL_IF(PacketAlertCheck(p, 4));
+    FAIL_IF(PacketAlertCheck(p, 6));
+    FAIL_IF(PacketAlertCheck(p, 7));
+
+    FAIL_IF_NOT(PacketAlertCheck(p, 2));
+    FAIL_IF_NOT(PacketAlertCheck(p, 5));
+    FAIL_IF_NOT(PacketAlertCheck(p, 8));
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+
+    StreamTcpFreeConfig(TRUE);
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+    PASS;
+}
+
+/** \test unit_id and read access at an address. */
+static int DetectEngineInspectModbusTest12(void)
+{
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+    DetectEngineThreadCtx *det_ctx = NULL;
+    DetectEngineCtx *de_ctx = NULL;
+    Flow f;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    TcpSession ssn;
+    ThreadVars tv;
+
+    FAIL_IF_NULL(alp_tctx);
+
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+    memset(&ssn, 0, sizeof(TcpSession));
+
+    p = UTHBuildPacket(readCoilsReq, sizeof(readCoilsReq), 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);
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    /* readCoilsReq, Read coils Starting Address = 0x7890 (30864), Quantity of coils = 0x13 (19) */
+    /* Unit ID              = 0x0a (10) */
+    /* Function code        = 0x01 (01) */
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus address access\"; "
+                              "modbus: unit 10, access read, address 30870;  sid:1;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus address access\"; "
+                              "modbus: unit 10, access read, address 30863;  sid:2;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus address access\"; "
+                              "modbus: unit 11, access read, address 30870;  sid:3;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus address access\"; "
+                              "modbus: unit 11, access read, address 30863;  sid:4;)");
+
+    s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+                              "(msg:\"Testing modbus address access\"; "
+                              "modbus: unit 10, access write;  sid:5;)");
+    FAIL_IF_NULL(s);
+
+    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));
+    FAIL_IF_NOT(r == 0);
+    FLOWLOCK_UNLOCK(&f);
+
+    ModbusState    *modbus_state = f.alstate;
+    FAIL_IF_NULL(modbus_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    FAIL_IF(PacketAlertCheck(p, 2));
+    FAIL_IF(PacketAlertCheck(p, 3));
+    FAIL_IF(PacketAlertCheck(p, 4));
+    FAIL_IF(PacketAlertCheck(p, 5));
 
-    result = 1;
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
 
-end:
-    if (alp_tctx != NULL)
-        AppLayerParserThreadCtxFree(alp_tctx);
-    if (det_ctx != NULL)
-        DetectEngineThreadCtxDeinit(&tv, det_ctx);
-    if (de_ctx != NULL)
-        SigGroupCleanup(de_ctx);
-    if (de_ctx != NULL)
-        DetectEngineCtxFree(de_ctx);
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    SigGroupCleanup(de_ctx);
+    DetectEngineCtxFree(de_ctx);
 
     StreamTcpFreeConfig(TRUE);
     FLOW_DESTROY(&f);
     UTHFreePacket(p);
-    return result;
+    PASS;
 }
 #endif /* UNITTESTS */
 
@@ -1364,6 +1462,12 @@ void DetectEngineInspectModbusRegisterTests(void)
                    DetectEngineInspectModbusTest08);
     UtRegisterTest("DetectEngineInspectModbusTest09 - Write access at an address a range of value",
                    DetectEngineInspectModbusTest09);
+    UtRegisterTest("DetectEngineInspectModbusTest10 - Code unit_id",
+                   DetectEngineInspectModbusTest10);
+    UtRegisterTest("DetectEngineInspectModbusTest11 - Code unit_id and code function",
+                   DetectEngineInspectModbusTest11);
+    UtRegisterTest("DetectEngineInspectModbusTest12 - Code unit_id and acces function",
+                   DetectEngineInspectModbusTest12);
 #endif /* UNITTESTS */
     return;
 }
index 972e09543d6d53d5e06d4907608053daf879134b..159dd05dd175e569be5f06c84a3612ae6af9822b 100644 (file)
 
 #include "stream-tcp.h"
 
+/**
+ * \brief Regex for parsing the Modbus unit id string
+ */
+#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$"
+static pcre         *unit_id_parse_regex;
+static pcre_extra   *unit_id_parse_regex_study;
+
 /**
  * \brief Regex for parsing the Modbus function string
  */
@@ -90,6 +97,9 @@ static void DetectModbusFree(void *ptr) {
         if (modbus->subfunction)
             SCFree(modbus->subfunction);
 
+        if (modbus->unit_id)
+            SCFree(modbus->unit_id);
+
         if (modbus->address)
             SCFree(modbus->address);
 
@@ -302,6 +312,14 @@ static DetectModbus *DetectModbusFunctionParse(const char *str)
 
     if (isdigit((unsigned char)ptr[0])) {
         modbus->function = atoi((const char*) ptr);
+        /* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */
+        if (modbus->function == MODBUS_FUNC_NONE) {
+            SCLogError(SC_ERR_INVALID_SIGNATURE,
+                    "Invalid argument \"%d\" supplied to modbus function keyword.",
+                    modbus->function);
+            goto error;
+        }
+
         SCLogDebug("will look for modbus function %d", modbus->function);
 
         if (ret > 2) {
@@ -354,6 +372,96 @@ error:
     SCReturnPtr(NULL, "DetectModbus");
 }
 
+/** \internal
+ *
+ * \brief This function is used to parse Modbus parameters in unit id mode
+ *
+ * \param str Pointer to the user provided id option
+ *
+ * \retval Pointer to DetectModbusUnit on success or NULL on failure
+ */
+static DetectModbus *DetectModbusUnitIdParse(const char *str)
+{
+    SCEnter();
+    DetectModbus *modbus = NULL;
+
+    char    arg[MAX_SUBSTRINGS];
+    int     ov[MAX_SUBSTRINGS], ret, res;
+
+    ret = pcre_exec(unit_id_parse_regex, unit_id_parse_regex_study, str, strlen(str), 0, 0, ov, MAX_SUBSTRINGS);
+
+    if (ret < 1)
+        goto error;
+
+    res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
+        goto error;
+    }
+
+    if (ret > 3) {
+        /* We have more Modbus option */
+        const char *str_ptr;
+
+        res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr);
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
+            goto error;
+        }
+
+        if ((modbus = DetectModbusFunctionParse(str_ptr)) == NULL) {
+            if ((modbus = DetectModbusAccessParse(str_ptr)) == NULL) {
+                SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
+                goto error;
+            }
+        }
+    } else {
+        /* We have only unit id Modbus option */
+        modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
+        if (unlikely(modbus == NULL))
+            goto error;
+    }
+
+    /* We have a correct unit id option */
+    modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
+    if (unlikely(modbus->unit_id == NULL))
+        goto error;
+
+    if (arg[0] == '>') {
+        modbus->unit_id->min   = atoi((const char*) (arg+1));
+        modbus->unit_id->mode  = DETECT_MODBUS_GT;
+    } else if (arg[0] == '<') {
+        modbus->unit_id->min   = atoi((const char*) (arg+1));
+        modbus->unit_id->mode  = DETECT_MODBUS_LT;
+    } else {
+        modbus->unit_id->min   = atoi((const char*) arg);
+    }
+    SCLogDebug("and min/equal unit id %d", modbus->unit_id->min);
+
+    if (ret > 2) {
+        res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
+            goto error;
+        }
+
+        if (*arg != '\0') {
+            modbus->unit_id->max   = atoi((const char*) (arg+2));
+            modbus->unit_id->mode  = DETECT_MODBUS_RA;
+            SCLogDebug("and max unit id %d", modbus->unit_id->max);
+        }
+    }
+
+    SCReturnPtr(modbus, "DetectModbusUnitId");
+
+error:
+    if (modbus != NULL)
+        DetectModbusFree(modbus);
+
+    SCReturnPtr(NULL, "DetectModbus");
+}
+
+
 /** \internal
  *
  * \brief this function is used to add the parsed "id" option into the current signature
@@ -373,10 +481,12 @@ static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *
     if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0)
         return -1;
 
-    if ((modbus = DetectModbusFunctionParse(str)) == NULL) {
-        if ((modbus = DetectModbusAccessParse(str)) == NULL) {
-            SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
-            goto error;
+    if ((modbus = DetectModbusUnitIdParse(str)) == NULL) {
+        if ((modbus = DetectModbusFunctionParse(str)) == NULL) {
+            if ((modbus = DetectModbusAccessParse(str)) == NULL) {
+                SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
+                goto error;
+            }
         }
     }
 
@@ -412,6 +522,8 @@ void DetectModbusRegister(void)
     sigmatch_table[DETECT_AL_MODBUS].Free          = DetectModbusFree;
     sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests;
 
+    DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID,
+            &unit_id_parse_regex, &unit_id_parse_regex_study);
     DetectSetupParseRegexes(PARSE_REGEX_FUNCTION,
             &function_parse_regex, &function_parse_regex_study);
     DetectSetupParseRegexes(PARSE_REGEX_ACCESS,
@@ -433,42 +545,26 @@ static int DetectModbusTest01(void)
     DetectEngineCtx *de_ctx = NULL;
     DetectModbus    *modbus = NULL;
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus function\"; "
                                        "modbus: function 1;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if (modbus->function != 1) {
-        printf("expected function %d, got %" PRIu8 ": ", 1, modbus->function);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->function == 1);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a function and a subfunction. */
@@ -477,43 +573,27 @@ static int DetectModbusTest02(void)
     DetectEngineCtx *de_ctx = NULL;
     DetectModbus    *modbus = NULL;
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus function and subfunction\"; "
                                        "modbus: function 8, subfunction 4;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if ((modbus->function != 8) || (*modbus->subfunction != 4)) {
-        printf("expected function %d, got %" PRIu8 ": ", 1, modbus->function);
-        printf("expected subfunction %d, got %" PRIu16 ": ", 4, *modbus->subfunction);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->function == 8);
+    FAIL_IF_NOT(*modbus->subfunction == 4);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a function category. */
@@ -522,42 +602,26 @@ static int DetectModbusTest03(void)
     DetectEngineCtx *de_ctx = NULL;
     DetectModbus    *modbus = NULL;
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.function\"; "
                                        "modbus: function reserved;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if (modbus->category != MODBUS_CAT_RESERVED) {
-        printf("expected function %d, got %" PRIu8 ": ", MODBUS_CAT_RESERVED, modbus->category);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a negative function category. */
@@ -568,42 +632,26 @@ static int DetectModbusTest04(void)
 
     uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED;
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus function\"; "
                                        "modbus: function !assigned;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if (modbus->category != category) {
-        printf("expected function %u, got %" PRIu8 ": ", ~MODBUS_CAT_PUBLIC_ASSIGNED, modbus->category);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->category == category);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a access type. */
@@ -612,42 +660,26 @@ static int DetectModbusTest05(void)
     DetectEngineCtx *de_ctx = NULL;
     DetectModbus    *modbus = NULL;
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.access\"; "
                                        "modbus: access read;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if (modbus->type != MODBUS_TYP_READ) {
-        printf("expected function %d, got %" PRIu8 ": ", MODBUS_TYP_READ, modbus->type);
-        goto end;
-    }
+    FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ);
 
-    result = 1;
-
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a access function. */
@@ -658,42 +690,26 @@ static int DetectModbusTest06(void)
 
     uint8_t type = (MODBUS_TYP_READ | MODBUS_TYP_DISCRETES);
 
-    int result = 0;
-
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.access\"; "
                                        "modbus: access read discretes;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if (modbus->type != type) {
-        printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->type == type);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a read access at an address. */
@@ -704,46 +720,29 @@ static int DetectModbusTest07(void)
     DetectModbusMode    mode = DETECT_MODBUS_EQ;
 
     uint8_t type = MODBUS_TYP_READ;
-    int result = 0;
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.access\"; "
                                        "modbus: access read, address 1000;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if ((modbus->type != type) ||
-        ((*modbus->address).mode != mode) ||
-        ((*modbus->address).min != 1000)) {
-        printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
-        printf("expected mode %u, got %u: ", mode, (*modbus->address).mode);
-        printf("expected address %d, got %" PRIu16 ": ", 1000, (*modbus->address).min);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->type == type);
+    FAIL_IF_NOT((*modbus->address).mode == mode);
+    FAIL_IF_NOT((*modbus->address).min == 1000);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a write access at a range of address. */
@@ -754,46 +753,29 @@ static int DetectModbusTest08(void)
     DetectModbusMode    mode = DETECT_MODBUS_GT;
 
     uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_COILS);
-    int result = 0;
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.access\"; "
                                        "modbus: access write coils, address >500;  sid:1;)");
-
-    if (de_ctx->sig_list == NULL)
-        goto end;
-
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if ((modbus->type != type) ||
-        ((*modbus->address).mode != mode) ||
-        ((*modbus->address).min != 500)) {
-        printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
-        printf("expected mode %d, got %u: ", mode, (*modbus->address).mode);
-        printf("expected address %u, got %" PRIu16 ": ", 500, (*modbus->address).min);
-        goto end;
-    }
-
-    result = 1;
+    FAIL_IF_NOT(modbus->type == type);
+    FAIL_IF_NOT((*modbus->address).mode == mode);
+    FAIL_IF_NOT((*modbus->address).min == 500);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
-
-    return result;
+    PASS;
 }
 
 /** \test Signature containing a write access at a address a range of value. */
@@ -805,52 +787,160 @@ static int DetectModbusTest09(void)
     DetectModbusMode    valueMode = DETECT_MODBUS_RA;
 
     uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_HOLDING);
-    int result = 0;
 
     de_ctx = DetectEngineCtxInit();
-    if (de_ctx == NULL)
-        goto end;
+    FAIL_IF_NULL(de_ctx);
 
     de_ctx->flags |= DE_QUIET;
 
     de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
                                        "(msg:\"Testing modbus.access\"; "
                                        "modbus: access write holding, address 100, value 500<>1000;  sid:1;)");
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    if (de_ctx->sig_list == NULL)
-        goto end;
+    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
-        (de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) {
-        printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: ");
-        goto end;
-    }
+    FAIL_IF_NOT(modbus->type == type);
+    FAIL_IF_NOT((*modbus->address).mode == addressMode);
+    FAIL_IF_NOT((*modbus->address).min == 100);
+    FAIL_IF_NOT((*modbus->data).mode == valueMode);
+    FAIL_IF_NOT((*modbus->data).min == 500);
+    FAIL_IF_NOT((*modbus->data).max == 1000);
+
+    SigGroupCleanup(de_ctx);
+    SigCleanSignatures(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+/** \test Signature containing a unit_id. */
+static int DetectModbusTest10(void)
+{
+    DetectEngineCtx    *de_ctx = NULL;
+    DetectModbus       *modbus = NULL;
+    DetectModbusMode    mode = DETECT_MODBUS_EQ;
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
+                                       "(msg:\"Testing modbus unit_id\"; "
+                                       "modbus: unit 10;  sid:1;)");
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
 
-    if ((modbus->type != type) ||
-        ((*modbus->address).mode != addressMode) ||
-        ((*modbus->address).min != 100)  ||
-        ((*modbus->data).mode != valueMode) ||
-        ((*modbus->data).min != 500)   ||
-        ((*modbus->data).max != 1000)) {
-        printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
-        printf("expected address mode %u, got %u: ", addressMode, (*modbus->address).mode);
-        printf("expected address %d, got %" PRIu16 ": ", 500, (*modbus->address).min);
-        printf("expected value mode %u, got %u: ", valueMode, (*modbus->data).mode);
-        printf("expected min value %d, got %" PRIu16 ": ", 500, (*modbus->data).min);
-        printf("expected max value %d, got %" PRIu16 ": ", 1000, (*modbus->data).max);
-        goto end;
-    }
+    FAIL_IF_NOT((*modbus->unit_id).min == 10);
+    FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+
+    SigGroupCleanup(de_ctx);
+    SigCleanSignatures(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
 
-    result = 1;
+/** \test Signature containing a unit_id, a function and a subfunction. */
+static int DetectModbusTest11(void)
+{
+    DetectEngineCtx    *de_ctx = NULL;
+    DetectModbus       *modbus = NULL;
+    DetectModbusMode    mode = DETECT_MODBUS_EQ;
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
+                                       "(msg:\"Testing modbus function and subfunction\"; "
+                                       "modbus: unit 10, function 8, subfunction 4;  sid:1;)");
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
+
+    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+
+    FAIL_IF_NOT((*modbus->unit_id).min == 10);
+    FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+    FAIL_IF_NOT(modbus->function == 8);
+    FAIL_IF_NOT((*modbus->subfunction) == 4);
 
- end:
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
+    PASS;
+}
 
-    return result;
+/** \test Signature containing an unit_id and a read access at an address. */
+static int DetectModbusTest12(void)
+{
+    DetectEngineCtx     *de_ctx = NULL;
+    DetectModbus        *modbus = NULL;
+    DetectModbusMode    mode = DETECT_MODBUS_EQ;
+
+    uint8_t type = MODBUS_TYP_READ;
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
+                                       "(msg:\"Testing modbus.access\"; "
+                                       "modbus: unit 10, access read, address 1000;  sid:1;)");
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
+
+    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+
+    FAIL_IF_NOT((*modbus->unit_id).min == 10);
+    FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+    FAIL_IF_NOT(modbus->type == type);
+    FAIL_IF_NOT((*modbus->address).mode == mode);
+    FAIL_IF_NOT((*modbus->address).min == 1000);
+
+    SigGroupCleanup(de_ctx);
+    SigCleanSignatures(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+/** \test Signature containing a range of unit_id. */
+static int DetectModbusTest13(void)
+{
+    DetectEngineCtx     *de_ctx = NULL;
+    DetectModbus        *modbus = NULL;
+    DetectModbusMode    mode = DETECT_MODBUS_RA;
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->flags |= DE_QUIET;
+
+    de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
+                                       "(msg:\"Testing modbus.access\"; "
+                                       "modbus: unit 10<>500;  sid:1;)");
+    FAIL_IF_NULL(de_ctx->sig_list);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
+    FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
+
+    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+
+    FAIL_IF_NOT((*modbus->unit_id).min == 10);
+    FAIL_IF_NOT((*modbus->unit_id).max == 500);
+    FAIL_IF_NOT((*modbus->unit_id).mode == mode);
+
+    SigGroupCleanup(de_ctx);
+    SigCleanSignatures(de_ctx);
+    DetectEngineCtxFree(de_ctx);
+    PASS;
 }
 #endif /* UNITTESTS */
 
@@ -878,5 +968,13 @@ void DetectModbusRegisterTests(void)
                    DetectModbusTest08);
     UtRegisterTest("DetectModbusTest09 - Testing write a range of value",
                    DetectModbusTest09);
+    UtRegisterTest("DetectModbusTest10 - Testing unit_id",
+                   DetectModbusTest10);
+    UtRegisterTest("DetectModbusTest11 - Testing unit_id, function and subfunction",
+                   DetectModbusTest11);
+    UtRegisterTest("DetectModbusTest12 - Testing unit_id and access at address",
+                   DetectModbusTest12);
+    UtRegisterTest("DetectModbusTest13 - Testing a range of unit_id",
+                   DetectModbusTest13);
 #endif /* UNITTESTS */
 }
index 408fb7aff80979e60e4e1de61a3b1db4485dfa1c..8ec02c99cf79e0455c55280b60f3d71732ab7c5d 100644 (file)
@@ -54,6 +54,7 @@ typedef struct DetectModbus_ {
     uint8_t             function;       /** < Modbus function code to match */
     uint16_t            *subfunction;   /** < Modbus subfunction to match */
     uint8_t             type;           /** < Modbus access type to match */
+    DetectModbusValue   *unit_id;       /** < Modbus unit id to match */
     DetectModbusValue   *address;       /** < Modbus address to match */
     DetectModbusValue   *data;          /** < Modbus data to match */
 } DetectModbus;