uint32_t seconds; /**< Event seconds */
uint32_t current_count; /**< Var for count control */
- SCTime_t tv1; /**< Var for time control */
+ union {
+ struct {
+ uint32_t next_value;
+ } backoff;
+ struct {
+ SCTime_t tv1; /**< Var for time control */
+ Address addr; /* used for src/dst/either tracking */
+ Address addr2; /* used for both tracking */
+ };
+ };
- Address addr; /* used for src/dst/either tracking */
- Address addr2; /* used for both tracking */
} ThresholdEntry;
static int ThresholdEntrySet(void *dst, void *src)
}
}
+/** \internal
+ * \brief Apply the multiplier and return the new value.
+ * If it would overflow the uint32_t we return UINT32_MAX.
+ */
+static uint32_t BackoffCalcNextValue(const uint32_t cur, const uint32_t m)
+{
+ /* goal is to see if cur * m would overflow uint32_t */
+ if (unlikely(UINT32_MAX / m < cur)) {
+ return UINT32_MAX;
+ }
+ return cur * m;
+}
+
+/**
+ * \retval 2 silent match (no alert but apply actions)
+ * \retval 1 normal match
+ * \retval 0 no match
+ */
static int ThresholdSetup(const DetectThresholdData *td, ThresholdEntry *te,
const SCTime_t packet_time, const uint32_t sid, const uint32_t gid, const uint32_t rev,
const uint32_t tenant_id)
te->key[REV] = rev;
te->key[TRACK] = td->track;
te->key[TENANT] = tenant_id;
- te->seconds = td->seconds;
+ te->seconds = td->seconds;
te->current_count = 1;
- te->tv1 = packet_time;
- te->tv_timeout = 0;
+
+ switch (td->type) {
+ case TYPE_BACKOFF:
+ te->backoff.next_value = td->count;
+ break;
+ default:
+ te->tv1 = packet_time;
+ te->tv_timeout = 0;
+ break;
+ }
switch (td->type) {
case TYPE_LIMIT:
if (td->count == 1)
return 1;
return 0;
+ case TYPE_BACKOFF:
+ if (td->count == 1) {
+ te->backoff.next_value =
+ BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
+ return 1;
+ }
+ return 0;
case TYPE_DETECTION:
return 0;
}
}
}
break;
+ case TYPE_BACKOFF:
+ SCLogDebug("backoff");
+
+ if (te->current_count < UINT32_MAX) {
+ te->current_count++;
+ if (te->backoff.next_value == te->current_count) {
+ te->backoff.next_value =
+ BackoffCalcNextValue(te->backoff.next_value, td->multiplier);
+ SCLogDebug("te->backoff.next_value %u", te->backoff.next_value);
+ ret = 1;
+ } else {
+ ret = 2;
+ }
+ } else {
+ /* if count reaches UINT32_MAX, we just silent match on the rest of the flow */
+ ret = 2;
+ }
+ break;
}
return ret;
}
#include "util-cpu.h"
#endif
-#define PARSE_REGEX_NAME "(track|type|count|seconds)"
-#define PARSE_REGEX_VALUE "(limit|both|threshold|by_dst|by_src|by_both|by_rule|by_flow|\\d+)"
+#define PARSE_REGEX_NAME "(track|type|count|seconds|multiplier)"
+#define PARSE_REGEX_VALUE \
+ "(limit|both|threshold|backoff|by_dst|by_src|by_both|by_rule|by_flow|\\d+)"
#define PARSE_REGEX \
"^\\s*" PARSE_REGEX_NAME "\\s+" PARSE_REGEX_VALUE "\\s*,\\s*" PARSE_REGEX_NAME \
char *copy_str = NULL, *threshold_opt = NULL;
int second_found = 0, count_found = 0;
int type_found = 0, track_found = 0;
- int second_pos = 0, count_pos = 0;
+ int multiplier_found = 0;
+ int second_pos = 0, count_pos = 0, multiplier_pos = 0;
size_t pos = 0;
int i = 0;
pcre2_match_data *match = NULL;
type_found++;
if (strstr(threshold_opt, "track"))
track_found++;
+ if (strstr(threshold_opt, "multiplier"))
+ multiplier_found++;
}
SCFree(copy_str);
copy_str = NULL;
- if (count_found != 1 || second_found != 1 || type_found != 1 || track_found != 1)
+ if (!(count_found == 1 && (second_found == 1 || multiplier_found == 1) && track_found == 1 &&
+ type_found == 1)) {
goto error;
+ }
ret = DetectParsePcreExec(&parse_regex, &match, rawstr, 0, 0);
- if (ret < 5) {
+ if (ret < 5 || ret > 9) {
SCLogError("pcre_exec parse error, ret %" PRId32 ", string %s", ret, rawstr);
goto error;
}
de->type = TYPE_BOTH;
if (strncasecmp(args[i], "threshold", strlen("threshold")) == 0)
de->type = TYPE_THRESHOLD;
+ if (strcasecmp(args[i], "backoff") == 0)
+ de->type = TYPE_BACKOFF;
if (strncasecmp(args[i], "by_dst", strlen("by_dst")) == 0)
de->track = TRACK_DST;
if (strncasecmp(args[i], "by_src", strlen("by_src")) == 0)
count_pos = i + 1;
if (strncasecmp(args[i], "seconds", strlen("seconds")) == 0)
second_pos = i + 1;
+ if (strcasecmp(args[i], "multiplier") == 0)
+ multiplier_pos = i + 1;
}
- if (args[count_pos] == NULL || args[second_pos] == NULL) {
- goto error;
- }
+ if (de->type != TYPE_BACKOFF) {
+ if (args[count_pos] == NULL || args[second_pos] == NULL) {
+ goto error;
+ }
- if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
- goto error;
- }
+ if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
+ goto error;
+ }
+ if (StringParseUint32(&de->seconds, 10, strlen(args[second_pos]), args[second_pos]) <= 0) {
+ goto error;
+ }
+ } else {
+ if (args[count_pos] == NULL || args[multiplier_pos] == NULL) {
+ goto error;
+ }
- if (StringParseUint32(&de->seconds, 10, strlen(args[second_pos]), args[second_pos]) <= 0) {
- goto error;
+ if (second_found) {
+ goto error;
+ }
+
+ if (StringParseUint32(&de->count, 10, strlen(args[count_pos]), args[count_pos]) <= 0) {
+ goto error;
+ }
+ if (StringParseUint32(
+ &de->multiplier, 10, strlen(args[multiplier_pos]), args[multiplier_pos]) <= 0) {
+ goto error;
+ }
+
+ /* impose some sanity limits on the count and multiplier values. Upper bounds are a bit
+ * artificial. */
+ if (!(de->count > 0 && de->count < 65536)) {
+ SCLogError("invalid count value '%u': must be in the range 1-65535", de->count);
+ goto error;
+ }
+ if (!(de->multiplier > 1 && de->multiplier < 65536)) {
+ SCLogError(
+ "invalid multiplier value '%u': must be in the range 2-65535", de->multiplier);
+ goto error;
+ }
+
+ if (de->track != TRACK_FLOW) {
+ SCLogError("invalid track value: type backoff only supported for track by_flow");
+ goto error;
+ }
+
+ SCLogDebug("TYPE_BACKOFF count %u multiplier %u", de->count, de->multiplier);
}
for (i = 0; i < (ret - 1); i++) {
PASS;
}
+/** \test backoff by_flow */
+static int ThresholdTestParse08(void)
+{
+ DetectThresholdData *de =
+ DetectThresholdParse("count 10, track by_flow, multiplier 2, type backoff");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(de->type == TYPE_BACKOFF);
+ FAIL_IF_NOT(de->track == TRACK_FLOW);
+ FAIL_IF_NOT(de->count == 10);
+ FAIL_IF_NOT(de->multiplier == 2);
+ DetectThresholdFree(NULL, de);
+ PASS;
+}
+
/**
* \test DetectThresholdTestSig1 is a test for checking the working of limit keyword
* by setting up the signature and later testing its working by matching
UtRegisterTest("ThresholdTestParse05", ThresholdTestParse05);
UtRegisterTest("ThresholdTestParse06", ThresholdTestParse06);
UtRegisterTest("ThresholdTestParse07", ThresholdTestParse07);
+ UtRegisterTest("ThresholdTestParse08", ThresholdTestParse08);
UtRegisterTest("DetectThresholdTestSig1", DetectThresholdTestSig1);
UtRegisterTest("DetectThresholdTestSig2", DetectThresholdTestSig2);
UtRegisterTest("DetectThresholdTestSig3", DetectThresholdTestSig3);