diff options
Diffstat (limited to 'src/util-threshold-config.c')
-rw-r--r-- | src/util-threshold-config.c | 2656 |
1 files changed, 2656 insertions, 0 deletions
diff --git a/src/util-threshold-config.c b/src/util-threshold-config.c new file mode 100644 index 0000000..5d762a8 --- /dev/null +++ b/src/util-threshold-config.c @@ -0,0 +1,2656 @@ +/* Copyright (C) 2007-2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \ingroup threshold + * @{ + */ + +/** + * \file + * + * \author Breno Silva Pinto <breno.silva@gmail.com> + * + * Implements Threshold support + */ + +#include "suricata-common.h" + +#include "host.h" +#include "ippair.h" + +#include "detect.h" +#include "detect-engine.h" +#include "detect-engine-address.h" +#include "detect-engine-threshold.h" +#include "detect-threshold.h" +#include "detect-parse.h" +#include "detect-engine-build.h" + +#include "conf.h" +#include "util-threshold-config.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-byte.h" +#include "util-time.h" +#include "util-error.h" +#include "util-debug.h" +#include "util-fmemopen.h" + +typedef enum ThresholdRuleType { + THRESHOLD_TYPE_EVENT_FILTER, + THRESHOLD_TYPE_THRESHOLD, + THRESHOLD_TYPE_RATE, + THRESHOLD_TYPE_SUPPRESS, +} ThresholdRuleType; + +#ifdef UNITTESTS +/* File descriptor for unittests */ +static FILE *g_ut_threshold_fp = NULL; +#endif + +/* common base for all options */ +#define DETECT_BASE_REGEX "^\\s*(event_filter|threshold|rate_filter|suppress)\\s*gen_id\\s*(\\d+)\\s*,\\s*sig_id\\s*(\\d+)\\s*(.*)\\s*$" + +#define DETECT_THRESHOLD_REGEX \ + "^,\\s*type\\s*(limit|both|threshold)\\s*,\\s*track\\s*(by_dst|by_src|by_both|by_rule)\\s*," \ + "\\s*count\\s*(\\d+)\\s*,\\s*seconds\\s*(\\d+)\\s*$" + +/* TODO: "apply_to" */ +#define DETECT_RATE_REGEX "^,\\s*track\\s*(by_dst|by_src|by_both|by_rule)\\s*,\\s*count\\s*(\\d+)\\s*,\\s*seconds\\s*(\\d+)\\s*,\\s*new_action\\s*(alert|drop|pass|log|sdrop|reject)\\s*,\\s*timeout\\s*(\\d+)\\s*$" + +/* + * suppress has two form: + * suppress gen_id 0, sig_id 0, track by_dst, ip 10.88.0.14 + * suppress gen_id 1, sig_id 2000328 + * suppress gen_id 1, sig_id 2000328, track by_src, ip fe80::/10 +*/ +#define DETECT_SUPPRESS_REGEX "^,\\s*track\\s*(by_dst|by_src|by_either)\\s*,\\s*ip\\s*([\\[\\],\\$\\s\\da-zA-Z.:/_]+)*\\s*$" + +/* Default path for the threshold.config file */ +#if defined OS_WIN32 || defined __CYGWIN__ +#define THRESHOLD_CONF_DEF_CONF_FILEPATH CONFIG_DIR "\\\\threshold.config" +#else +#define THRESHOLD_CONF_DEF_CONF_FILEPATH CONFIG_DIR "/threshold.config" +#endif + +static DetectParseRegex *regex_base = NULL; +static DetectParseRegex *regex_threshold = NULL; +static DetectParseRegex *regex_rate = NULL; +static DetectParseRegex *regex_suppress = NULL; + +static void SCThresholdConfDeInitContext(DetectEngineCtx *de_ctx, FILE *fd); + +void SCThresholdConfGlobalInit(void) +{ + regex_base = DetectSetupPCRE2(DETECT_BASE_REGEX, 0); + if (regex_base == NULL) { + FatalError("classification base regex setup failed"); + } + regex_threshold = DetectSetupPCRE2(DETECT_THRESHOLD_REGEX, 0); + if (regex_threshold == NULL) { + FatalError("classification threshold regex setup failed"); + } + regex_rate = DetectSetupPCRE2(DETECT_RATE_REGEX, 0); + if (regex_rate == NULL) { + FatalError("classification rate_filter regex setup failed"); + } + regex_suppress = DetectSetupPCRE2(DETECT_SUPPRESS_REGEX, 0); + if (regex_suppress == NULL) { + FatalError("classification suppress regex setup failed"); + } +} + +/** + * \brief Returns the path for the Threshold Config file. We check if we + * can retrieve the path from the yaml conf file. If it is not present, + * return the default path for the threshold file which is + * "./threshold.config". + * + * \retval log_filename Pointer to a string containing the path for the + * Threshold Config file. + */ +static const char *SCThresholdConfGetConfFilename(const DetectEngineCtx *de_ctx) +{ + const char *log_filename = NULL; + + if (de_ctx != NULL && strlen(de_ctx->config_prefix) > 0) { + char config_value[256]; + snprintf(config_value, sizeof(config_value), + "%s.threshold-file", de_ctx->config_prefix); + + /* try loading prefix setting, fall back to global if that + * fails. */ + if (ConfGet(config_value, &log_filename) != 1) { + if (ConfGet("threshold-file", &log_filename) != 1) { + log_filename = (char *)THRESHOLD_CONF_DEF_CONF_FILEPATH; + } + } + } else { + if (ConfGet("threshold-file", &log_filename) != 1) { + log_filename = (char *)THRESHOLD_CONF_DEF_CONF_FILEPATH; + } + } + return log_filename; +} + +/** + * \brief Inits the context to be used by the Threshold Config parsing API. + * + * This function initializes the hash table to be used by the Detection + * Engine Context to hold the data from the threshold.config file, + * obtains the file desc to parse the threshold.config file, and + * inits the regex used to parse the lines from threshold.config + * file. + * + * \param de_ctx Pointer to the Detection Engine Context. + * + * \retval 0 On success. + * \retval -1 On failure. + */ +int SCThresholdConfInitContext(DetectEngineCtx *de_ctx) +{ + const char *filename = NULL; + int ret = 0; +#ifndef UNITTESTS + FILE *fd = NULL; +#else + FILE *fd = g_ut_threshold_fp; + if (fd == NULL) { +#endif + filename = SCThresholdConfGetConfFilename(de_ctx); + if ( (fd = fopen(filename, "r")) == NULL) { + SCLogWarning("Error opening file: \"%s\": %s", filename, strerror(errno)); + SCThresholdConfDeInitContext(de_ctx, fd); + return 0; + } +#ifdef UNITTESTS + } +#endif + + if (SCThresholdConfParseFile(de_ctx, fd) < 0) { + SCLogWarning("Error loading threshold configuration from %s", filename); + SCThresholdConfDeInitContext(de_ctx, fd); + /* maintain legacy behavior so no errors unless config testing */ + if (RunmodeGetCurrent() == RUNMODE_CONF_TEST) { + ret = -1; + } + return ret; + } + SCThresholdConfDeInitContext(de_ctx, fd); + +#ifdef UNITTESTS + g_ut_threshold_fp = NULL; +#endif + SCLogDebug("Global thresholding options defined"); + return 0; +} + +/** + * \brief Releases resources used by the Threshold Config API. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param fd Pointer to file descriptor. + */ +static void SCThresholdConfDeInitContext(DetectEngineCtx *de_ctx, FILE *fd) +{ + if (fd != NULL) + fclose(fd); + return; +} + +/** \internal + * \brief setup suppress rules + * \retval 0 ok + * \retval -1 error + */ +static int SetupSuppressRule(DetectEngineCtx *de_ctx, uint32_t id, uint32_t gid, + uint8_t parsed_type, uint8_t parsed_track, uint32_t parsed_count, + uint32_t parsed_seconds, uint32_t parsed_timeout, uint8_t parsed_new_action, + const char *th_ip) +{ + Signature *s = NULL; + SigMatch *sm = NULL; + DetectThresholdData *de = NULL; + + BUG_ON(parsed_type != TYPE_SUPPRESS); + + DetectThresholdData *orig_de = NULL; + if (parsed_track != TRACK_RULE) { + orig_de = SCCalloc(1, sizeof(DetectThresholdData)); + if (unlikely(orig_de == NULL)) + goto error; + + orig_de->type = TYPE_SUPPRESS; + orig_de->track = parsed_track; + orig_de->count = parsed_count; + orig_de->seconds = parsed_seconds; + orig_de->new_action = parsed_new_action; + orig_de->timeout = parsed_timeout; + if (DetectAddressParse((const DetectEngineCtx *)de_ctx, &orig_de->addrs, (char *)th_ip) < + 0) { + SCLogError("failed to parse %s", th_ip); + goto error; + } + } + + /* Install it */ + if (id == 0 && gid == 0) { + if (parsed_track == TRACK_RULE) { + SCLogWarning("suppressing all rules"); + } + + /* update each sig with our suppress info */ + for (s = de_ctx->sig_list; s != NULL; s = s->next) { + /* tag the rule as noalert */ + if (parsed_track == TRACK_RULE) { + s->flags |= SIG_FLAG_NOALERT; + continue; + } + + de = DetectThresholdDataCopy(orig_de); + if (unlikely(de == NULL)) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_SUPPRESS); + } + } else if (id == 0 && gid > 0) { + if (parsed_track == TRACK_RULE) { + SCLogWarning("suppressing all rules with gid %" PRIu32, gid); + } + /* set up suppression for each signature with a matching gid */ + for (s = de_ctx->sig_list; s != NULL; s = s->next) { + if (s->gid != gid) + continue; + + /* tag the rule as noalert */ + if (parsed_track == TRACK_RULE) { + s->flags |= SIG_FLAG_NOALERT; + continue; + } + + de = DetectThresholdDataCopy(orig_de); + if (unlikely(de == NULL)) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_SUPPRESS); + } + } else if (id > 0 && gid == 0) { + SCLogError("Can't use a event config that has " + "sid > 0 and gid == 0. Please fix this " + "in your threshold.config file"); + goto error; + } else { + s = SigFindSignatureBySidGid(de_ctx, id, gid); + if (s == NULL) { + SCLogWarning("can't suppress sid " + "%" PRIu32 ", gid %" PRIu32 ": unknown rule", + id, gid); + } else { + if (parsed_track == TRACK_RULE) { + s->flags |= SIG_FLAG_NOALERT; + goto end; + } + + de = DetectThresholdDataCopy(orig_de); + if (unlikely(de == NULL)) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_SUPPRESS); + } + } + +end: + if (orig_de != NULL) { + DetectAddressHeadCleanup(&orig_de->addrs); + SCFree(orig_de); + } + return 0; +error: + if (orig_de != NULL) { + DetectAddressHeadCleanup(&orig_de->addrs); + SCFree(orig_de); + } + if (de != NULL) { + DetectAddressHeadCleanup(&de->addrs); + SCFree(de); + } + return -1; +} + +/** \internal + * \brief setup suppress rules + * \retval 0 ok + * \retval -1 error + */ +static int SetupThresholdRule(DetectEngineCtx *de_ctx, uint32_t id, uint32_t gid, + uint8_t parsed_type, uint8_t parsed_track, uint32_t parsed_count, + uint32_t parsed_seconds, uint32_t parsed_timeout, uint8_t parsed_new_action, + const char *th_ip) +{ + Signature *s = NULL; + SigMatch *sm = NULL; + DetectThresholdData *de = NULL; + + BUG_ON(parsed_type == TYPE_SUPPRESS); + + /* Install it */ + if (id == 0 && gid == 0) { + for (s = de_ctx->sig_list; s != NULL; s = s->next) { + sm = DetectGetLastSMByListId(s, + DETECT_SM_LIST_THRESHOLD, DETECT_THRESHOLD, -1); + if (sm != NULL) { + SCLogWarning("signature sid:%" PRIu32 " has " + "an event var set. The signature event var is " + "given precedence over the threshold.conf one. " + "We'll change this in the future though.", + s->id); + continue; + } + + sm = DetectGetLastSMByListId(s, + DETECT_SM_LIST_THRESHOLD, DETECT_DETECTION_FILTER, -1); + if (sm != NULL) { + SCLogWarning("signature sid:%" PRIu32 " has " + "an event var set. The signature event var is " + "given precedence over the threshold.conf one. " + "We'll change this in the future though.", + s->id); + continue; + } + + de = SCMalloc(sizeof(DetectThresholdData)); + if (unlikely(de == NULL)) + goto error; + memset(de,0,sizeof(DetectThresholdData)); + + de->type = parsed_type; + de->track = parsed_track; + de->count = parsed_count; + de->seconds = parsed_seconds; + de->new_action = parsed_new_action; + de->timeout = parsed_timeout; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + if (parsed_type == TYPE_RATE) + sm->type = DETECT_DETECTION_FILTER; + else + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_THRESHOLD); + } + + } else if (id == 0 && gid > 0) { + for (s = de_ctx->sig_list; s != NULL; s = s->next) { + if (s->gid == gid) { + sm = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, DETECT_DETECTION_FILTER, -1); + if (sm != NULL) { + SCLogWarning("signature sid:%" PRIu32 " has " + "an event var set. The signature event var is " + "given precedence over the threshold.conf one. " + "We'll change this in the future though.", + id); + continue; + } + + de = SCMalloc(sizeof(DetectThresholdData)); + if (unlikely(de == NULL)) + goto error; + memset(de,0,sizeof(DetectThresholdData)); + + de->type = parsed_type; + de->track = parsed_track; + de->count = parsed_count; + de->seconds = parsed_seconds; + de->new_action = parsed_new_action; + de->timeout = parsed_timeout; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + if (parsed_type == TYPE_RATE) + sm->type = DETECT_DETECTION_FILTER; + else + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_THRESHOLD); + } + } + } else if (id > 0 && gid == 0) { + SCLogError("Can't use a event config that has " + "sid > 0 and gid == 0. Please fix this " + "in your threshold.conf file"); + } else { + s = SigFindSignatureBySidGid(de_ctx, id, gid); + if (s == NULL) { + SCLogWarning("can't suppress sid " + "%" PRIu32 ", gid %" PRIu32 ": unknown rule", + id, gid); + } else { + if (parsed_type != TYPE_SUPPRESS && parsed_type != TYPE_THRESHOLD && + parsed_type != TYPE_BOTH && parsed_type != TYPE_LIMIT) + { + sm = DetectGetLastSMByListId(s, + DETECT_SM_LIST_THRESHOLD, DETECT_THRESHOLD, -1); + if (sm != NULL) { + SCLogWarning("signature sid:%" PRIu32 " has " + "a threshold set. The signature event var is " + "given precedence over the threshold.conf one. " + "Bug #425.", + s->id); + goto end; + } + + sm = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_DETECTION_FILTER, -1); + if (sm != NULL) { + SCLogWarning("signature sid:%" PRIu32 " has " + "a detection_filter set. The signature event var is " + "given precedence over the threshold.conf one. " + "Bug #425.", + s->id); + goto end; + } + + /* replace threshold on sig if we have a global override for it */ + } else if (parsed_type == TYPE_THRESHOLD || parsed_type == TYPE_BOTH || parsed_type == TYPE_LIMIT) { + sm = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, DETECT_DETECTION_FILTER, -1); + if (sm != NULL) { + SigMatchRemoveSMFromList(s, sm, DETECT_SM_LIST_THRESHOLD); + SigMatchFree(de_ctx, sm); + } + } + + de = SCMalloc(sizeof(DetectThresholdData)); + if (unlikely(de == NULL)) + goto error; + memset(de,0,sizeof(DetectThresholdData)); + + de->type = parsed_type; + de->track = parsed_track; + de->count = parsed_count; + de->seconds = parsed_seconds; + de->new_action = parsed_new_action; + de->timeout = parsed_timeout; + + sm = SigMatchAlloc(); + if (sm == NULL) { + SCLogError("Error allocating SigMatch"); + goto error; + } + + if (parsed_type == TYPE_RATE) + sm->type = DETECT_DETECTION_FILTER; + else + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_THRESHOLD); + } + } +end: + return 0; +error: + if (de != NULL) { + DetectAddressHeadCleanup(&de->addrs); + SCFree(de); + } + return -1; +} + +static int ParseThresholdRule(const DetectEngineCtx *de_ctx, char *rawstr, uint32_t *ret_id, + uint32_t *ret_gid, uint8_t *ret_parsed_type, uint8_t *ret_parsed_track, + uint32_t *ret_parsed_count, uint32_t *ret_parsed_seconds, uint32_t *ret_parsed_timeout, + uint8_t *ret_parsed_new_action, char **ret_th_ip) +{ + char th_rule_type[32]; + char th_gid[16]; + char th_sid[16]; + const char *rule_extend = NULL; + char th_type[16] = ""; + char th_track[16] = ""; + char th_count[16] = ""; + char th_seconds[16] = ""; + char th_new_action[16] = ""; + char th_timeout[16] = ""; + const char *th_ip = NULL; + + uint8_t parsed_type = 0; + uint8_t parsed_track = 0; + uint8_t parsed_new_action = 0; + uint32_t parsed_count = 0; + uint32_t parsed_seconds = 0; + uint32_t parsed_timeout = 0; + + int ret = 0; + uint32_t id = 0, gid = 0; + ThresholdRuleType rule_type; + + if (de_ctx == NULL) + return -1; + + pcre2_match_data *regex_base_match = NULL; + ret = DetectParsePcreExec(regex_base, ®ex_base_match, rawstr, 0, 0); + if (ret < 4) { + SCLogError("pcre2_match parse error, ret %" PRId32 ", string %s", ret, rawstr); + pcre2_match_data_free(regex_base_match); + goto error; + } + + /* retrieve the classtype name */ + size_t copylen = sizeof(th_rule_type); + ret = pcre2_substring_copy_bynumber( + regex_base_match, 1, (PCRE2_UCHAR8 *)th_rule_type, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(regex_base_match); + goto error; + } + + /* retrieve the classtype name */ + copylen = sizeof(th_gid); + ret = pcre2_substring_copy_bynumber(regex_base_match, 2, (PCRE2_UCHAR8 *)th_gid, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(regex_base_match); + goto error; + } + + copylen = sizeof(th_sid); + ret = pcre2_substring_copy_bynumber(regex_base_match, 3, (PCRE2_UCHAR8 *)th_sid, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(regex_base_match); + goto error; + } + + /* Use "get" for heap allocation */ + ret = pcre2_substring_get_bynumber( + regex_base_match, 4, (PCRE2_UCHAR8 **)&rule_extend, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_get_bynumber failed"); + pcre2_match_data_free(regex_base_match); + goto error; + } + pcre2_match_data_free(regex_base_match); + regex_base_match = NULL; + + /* get type of rule */ + if (strcasecmp(th_rule_type,"event_filter") == 0) { + rule_type = THRESHOLD_TYPE_EVENT_FILTER; + } else if (strcasecmp(th_rule_type,"threshold") == 0) { + rule_type = THRESHOLD_TYPE_THRESHOLD; + } else if (strcasecmp(th_rule_type,"rate_filter") == 0) { + rule_type = THRESHOLD_TYPE_RATE; + } else if (strcasecmp(th_rule_type,"suppress") == 0) { + rule_type = THRESHOLD_TYPE_SUPPRESS; + } else { + SCLogError("rule type %s is unknown", th_rule_type); + goto error; + } + + /* get end of rule */ + switch(rule_type) { + case THRESHOLD_TYPE_EVENT_FILTER: + case THRESHOLD_TYPE_THRESHOLD: + if (strlen(rule_extend) > 0) { + pcre2_match_data *match = NULL; + + ret = DetectParsePcreExec(regex_threshold, &match, rule_extend, 0, 0); + if (ret < 4) { + SCLogError("pcre2_match parse error, ret %" PRId32 ", string %s", ret, + rule_extend); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_type); + ret = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)th_type, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_track); + ret = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)th_track, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_count); + ret = pcre2_substring_copy_bynumber(match, 3, (PCRE2_UCHAR8 *)th_count, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_seconds); + ret = pcre2_substring_copy_bynumber(match, 4, (PCRE2_UCHAR8 *)th_seconds, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + pcre2_match_data_free(match); + + if (strcasecmp(th_type,"limit") == 0) + parsed_type = TYPE_LIMIT; + else if (strcasecmp(th_type,"both") == 0) + parsed_type = TYPE_BOTH; + else if (strcasecmp(th_type,"threshold") == 0) + parsed_type = TYPE_THRESHOLD; + else { + SCLogError("limit type not supported: %s", th_type); + goto error; + } + } else { + SCLogError("rule invalid: %s", rawstr); + goto error; + } + break; + case THRESHOLD_TYPE_SUPPRESS: + if (strlen(rule_extend) > 0) { + pcre2_match_data *match = NULL; + ret = DetectParsePcreExec(regex_suppress, &match, rule_extend, 0, 0); + if (ret < 2) { + SCLogError("pcre2_match parse error, ret %" PRId32 ", string %s", ret, + rule_extend); + pcre2_match_data_free(match); + goto error; + } + /* retrieve the track mode */ + copylen = sizeof(th_seconds); + ret = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)th_track, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + /* retrieve the IP; use "get" for heap allocation */ + ret = pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&th_ip, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_get_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + pcre2_match_data_free(match); + } else { + parsed_track = TRACK_RULE; + } + parsed_type = TYPE_SUPPRESS; + break; + case THRESHOLD_TYPE_RATE: + if (strlen(rule_extend) > 0) { + pcre2_match_data *match = NULL; + ret = DetectParsePcreExec(regex_rate, &match, rule_extend, 0, 0); + if (ret < 5) { + SCLogError("pcre2_match parse error, ret %" PRId32 ", string %s", ret, + rule_extend); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_track); + ret = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)th_track, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_count); + ret = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)th_count, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_seconds); + ret = pcre2_substring_copy_bynumber(match, 3, (PCRE2_UCHAR8 *)th_seconds, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_new_action); + ret = pcre2_substring_copy_bynumber( + match, 4, (PCRE2_UCHAR8 *)th_new_action, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + + copylen = sizeof(th_timeout); + ret = pcre2_substring_copy_bynumber(match, 5, (PCRE2_UCHAR8 *)th_timeout, ©len); + if (ret < 0) { + SCLogError("pcre2_substring_copy_bynumber failed"); + pcre2_match_data_free(match); + goto error; + } + pcre2_match_data_free(match); + + /* TODO: implement option "apply_to" */ + + if (StringParseUint32(&parsed_timeout, 10, sizeof(th_timeout), th_timeout) <= 0) { + goto error; + } + + /* Get the new action to take */ + if (strcasecmp(th_new_action, "alert") == 0) + parsed_new_action = TH_ACTION_ALERT; + if (strcasecmp(th_new_action, "drop") == 0) + parsed_new_action = TH_ACTION_DROP; + if (strcasecmp(th_new_action, "pass") == 0) + parsed_new_action = TH_ACTION_PASS; + if (strcasecmp(th_new_action, "reject") == 0) + parsed_new_action = TH_ACTION_REJECT; + if (strcasecmp(th_new_action, "log") == 0) { + SCLogInfo("log action for rate_filter not supported yet"); + parsed_new_action = TH_ACTION_LOG; + } + if (strcasecmp(th_new_action, "sdrop") == 0) { + SCLogInfo("sdrop action for rate_filter not supported yet"); + parsed_new_action = TH_ACTION_SDROP; + } + parsed_type = TYPE_RATE; + } else { + SCLogError("rule invalid: %s", rawstr); + goto error; + } + break; + } + + switch (rule_type) { + /* This part is common to threshold/event_filter/rate_filter */ + case THRESHOLD_TYPE_EVENT_FILTER: + case THRESHOLD_TYPE_THRESHOLD: + case THRESHOLD_TYPE_RATE: + if (strcasecmp(th_track,"by_dst") == 0) + parsed_track = TRACK_DST; + else if (strcasecmp(th_track,"by_src") == 0) + parsed_track = TRACK_SRC; + else if (strcasecmp(th_track, "by_both") == 0) { + parsed_track = TRACK_BOTH; + } + else if (strcasecmp(th_track,"by_rule") == 0) + parsed_track = TRACK_RULE; + else { + SCLogError("Invalid track parameter %s in %s", th_track, rawstr); + goto error; + } + + if (StringParseUint32(&parsed_count, 10, sizeof(th_count), th_count) <= 0) { + goto error; + } + if (parsed_count == 0) { + SCLogError("rate filter count should be > 0"); + goto error; + } + + if (StringParseUint32(&parsed_seconds, 10, sizeof(th_seconds), th_seconds) <= 0) { + goto error; + } + + break; + case THRESHOLD_TYPE_SUPPRESS: + /* need to get IP if extension is provided */ + if (strcmp("", th_track) != 0) { + if (strcasecmp(th_track,"by_dst") == 0) + parsed_track = TRACK_DST; + else if (strcasecmp(th_track,"by_src") == 0) + parsed_track = TRACK_SRC; + else if (strcasecmp(th_track,"by_either") == 0) { + parsed_track = TRACK_EITHER; + } + else { + SCLogError("Invalid track parameter %s in %s", th_track, rule_extend); + goto error; + } + } + break; + } + + if (StringParseUint32(&id, 10, sizeof(th_sid), th_sid) <= 0) { + goto error; + } + + if (StringParseUint32(&gid, 10, sizeof(th_gid), th_gid) <= 0) { + goto error; + } + + *ret_id = id; + *ret_gid = gid; + *ret_parsed_type = parsed_type; + *ret_parsed_track = parsed_track; + *ret_parsed_new_action = parsed_new_action; + *ret_parsed_count = parsed_count; + *ret_parsed_seconds = parsed_seconds; + *ret_parsed_timeout = parsed_timeout; + *ret_th_ip = NULL; + if (th_ip != NULL) { + *ret_th_ip = (char *)th_ip; + } + pcre2_substring_free((PCRE2_UCHAR8 *)rule_extend); + return 0; + +error: + if (rule_extend != NULL) { + pcre2_substring_free((PCRE2_UCHAR8 *)rule_extend); + } + if (th_ip != NULL) { + pcre2_substring_free((PCRE2_UCHAR8 *)th_ip); + } + return -1; +} + +/** + * \brief Parses a line from the threshold file and applies it to the + * detection engine + * + * \param rawstr Pointer to the string to be parsed. + * \param de_ctx Pointer to the Detection Engine Context. + * + * \retval 0 On success. + * \retval -1 On failure. + */ +static int SCThresholdConfAddThresholdtype(char *rawstr, DetectEngineCtx *de_ctx) +{ + uint8_t parsed_type = 0; + uint8_t parsed_track = 0; + uint8_t parsed_new_action = 0; + uint32_t parsed_count = 0; + uint32_t parsed_seconds = 0; + uint32_t parsed_timeout = 0; + char *th_ip = NULL; + uint32_t id = 0, gid = 0; + + int r = ParseThresholdRule(de_ctx, rawstr, &id, &gid, &parsed_type, &parsed_track, + &parsed_count, &parsed_seconds, &parsed_timeout, &parsed_new_action, &th_ip); + if (r < 0) + goto error; + + if (parsed_type == TYPE_SUPPRESS) { + r = SetupSuppressRule(de_ctx, id, gid, parsed_type, parsed_track, + parsed_count, parsed_seconds, parsed_timeout, parsed_new_action, + th_ip); + } else { + r = SetupThresholdRule(de_ctx, id, gid, parsed_type, parsed_track, + parsed_count, parsed_seconds, parsed_timeout, parsed_new_action, + th_ip); + } + if (r < 0) { + goto error; + } + + pcre2_substring_free((PCRE2_UCHAR8 *)th_ip); + return 0; +error: + if (th_ip != NULL) + pcre2_substring_free((PCRE2_UCHAR8 *)th_ip); + return -1; +} + +/** + * \brief Checks if a string is a comment or a blank line. + * + * Comments lines are lines of the following format - + * "# This is a comment string" or + * " # This is a comment string". + * + * \param line String that has to be checked + * + * \retval 1 On the argument string being a comment or blank line + * \retval 0 Otherwise + */ +static int SCThresholdConfIsLineBlankOrComment(char *line) +{ + while (*line != '\0') { + /* we have a comment */ + if (*line == '#') + return 1; + + /* this line is neither a comment line, nor a blank line */ + if (!isspace((unsigned char)*line)) + return 0; + + line++; + } + + /* we have a blank line */ + return 1; +} + +/** + * \brief Checks if the rule is multiline, by searching an ending slash + * + * \param line String that has to be checked + * + * \retval the position of the slash making it multiline + * \retval 0 Otherwise + */ +static int SCThresholdConfLineIsMultiline(char *line) +{ + int flag = 0; + char *rline = line; + int len = strlen(line); + + while (line < rline + len && *line != '\n') { + /* we have a comment */ + if (*line == '\\') + flag = line - rline; + else + if (!isspace((unsigned char)*line)) + flag = 0; + + line++; + } + + /* we have a blank line */ + return flag; +} + +/** + * \brief Parses the Threshold Config file + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param fd Pointer to file descriptor. + */ +int SCThresholdConfParseFile(DetectEngineCtx *de_ctx, FILE *fp) +{ + char line[8192] = ""; + int rule_num = 0; + + /* position of "\", on multiline rules */ + int esc_pos = 0; + + if (fp == NULL) + return -1; + + while (fgets(line + esc_pos, (int)sizeof(line) - esc_pos, fp) != NULL) { + if (SCThresholdConfIsLineBlankOrComment(line)) { + continue; + } + + esc_pos = SCThresholdConfLineIsMultiline(line); + if (esc_pos == 0) { + if (SCThresholdConfAddThresholdtype(line, de_ctx) < 0) { + if (RunmodeGetCurrent() == RUNMODE_CONF_TEST) + return -1; + } else { + SCLogDebug("Adding threshold.config rule num %" PRIu32 "( %s )", rule_num, line); + rule_num++; + } + } + } + + SCLogInfo("Threshold config parsed: %d rule(s) found", rule_num); + return 0; +} + +#ifdef UNITTESTS +#include "detect-engine-alert.h" +#include "packet.h" +#include "action-globals.h" + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD01(void) +{ + FILE *fd = NULL; + const char *buffer = + "event_filter gen_id 1, sig_id 10, type limit, track by_src, count 1, seconds 60\n" + "threshold gen_id 1, sig_id 100, type both, track by_dst, count 10, seconds 60\n" + "event_filter gen_id 1, sig_id 1000, type threshold, track by_src, count 100, seconds 60\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with some valid options and a couple of invalid options. + * For testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateInvalidDummyFD02(void) +{ + FILE *fd; + const char *buffer = + "event_filter gen_id 1, sig_id 1000, type invalid, track by_src, count 100, seconds 60\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD03(void) +{ + FILE *fd; + const char *buffer = + "event_filter gen_id 0, sig_id 0, type threshold, track by_src, count 100, seconds 60\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, but + * with split rules (multiline), for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD04(void) +{ + FILE *fd = NULL; + const char *buffer = + "event_filter gen_id 1 \\\n, sig_id 10, type limit, track by_src, \\\ncount 1, seconds 60\n" + "threshold gen_id 1, \\\nsig_id 100, type both\\\n, track by_dst, count 10, \\\n seconds 60\n" + "event_filter gen_id 1, sig_id 1000, \\\ntype threshold, track \\\nby_src, count 100, seconds 60\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD05(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter gen_id 1, sig_id 10, track by_src, count 1, seconds 60, new_action drop, timeout 10\n" + "rate_filter gen_id 1, sig_id 100, track by_dst, count 10, seconds 60, new_action pass, timeout 5\n" + "rate_filter gen_id 1, sig_id 1000, track by_rule, count 100, seconds 60, new_action alert, timeout 30\n" + "rate_filter gen_id 1, sig_id 10000, track by_both, count 1000, seconds 60, new_action reject, timeout 21\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, but + * with split rules (multiline), for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD06(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter \\\ngen_id 1, sig_id 10, track by_src, count 1, seconds 60\\\n, new_action drop, timeout 10\n" + "rate_filter gen_id 1, \\\nsig_id 100, track by_dst, \\\ncount 10, seconds 60, new_action pass, timeout 5\n" + "rate_filter gen_id 1, sig_id 1000, \\\ntrack by_rule, count 100, seconds 60, new_action alert, timeout 30\n" + "rate_filter gen_id 1, sig_id 10000, track by_both, count 1000, \\\nseconds 60, new_action reject, timeout 21\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, but + * with split rules (multiline), for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD07(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter gen_id 1, sig_id 10, track by_src, count 3, seconds 3, new_action drop, timeout 10\n" + "rate_filter gen_id 1, sig_id 11, track by_src, count 3, seconds 1, new_action drop, timeout 5\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, for testing rate_filter, track by_rule + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD08(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter gen_id 1, sig_id 10, track by_rule, count 3, seconds 3, new_action drop, timeout 10\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, but + * with split rules (multiline), for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD09(void) +{ + FILE *fd = NULL; + const char *buffer = + "event_filter gen_id 1 \\\n, sig_id 10, type limit, track by_src, \\\ncount 2, seconds 60\n" + "threshold gen_id 1, \\\nsig_id 11, type threshold\\\n, track by_dst, count 3, \\\n seconds 60\n" + "event_filter gen_id 1, sig_id 12, \\\ntype both, track \\\nby_src, count 2, seconds 60\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, but + * with split rules (multiline), for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD10(void) +{ + FILE *fd = NULL; + const char *buffer = + "event_filter gen_id 1 \\\n, sig_id 10, type limit, track by_src, \\\ncount 5, seconds 2\n" + "threshold gen_id 1, \\\nsig_id 11, type threshold\\\n, track by_dst, count 5, \\\n seconds 2\n" + "event_filter gen_id 1, sig_id 12, \\\ntype both, track \\\nby_src, count 5, seconds 2\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD11(void) +{ + FILE *fd = NULL; + const char *buffer = + "suppress gen_id 1, sig_id 10000\n" + "suppress gen_id 1, sig_id 1000, track by_src, ip 192.168.1.1\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest01(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD01(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + + FAIL_IF_NOT(de->type == TYPE_LIMIT && de->track == TRACK_SRC && de->count == 1 && de->seconds == 60); + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest02(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:100;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD01(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + + FAIL_IF_NOT(de->type == TYPE_BOTH && de->track == TRACK_DST && de->count == 10 && de->seconds == 60); + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest03(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:1000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD01(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + + FAIL_IF_NOT(de->type == TYPE_THRESHOLD && de->track == TRACK_SRC && de->count == 100 && de->seconds == 60); + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest04(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:1000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateInvalidDummyFD02(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NOT_NULL(m); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest05(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:1;)"); + FAIL_IF_NULL(sig); + sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any 80 (msg:\"Threshold limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any 80 (msg:\"Threshold limit\"; gid:1; sid:100;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD03(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + Signature *s = de_ctx->sig_list; + SigMatch *m = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + FAIL_IF_NULL(m->ctx); + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NOT(de->type == TYPE_THRESHOLD && de->track == TRACK_SRC && de->count == 100 && de->seconds == 60); + + s = de_ctx->sig_list->next; + m = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + FAIL_IF_NULL(m->ctx); + de = (DetectThresholdData *)m->ctx; + FAIL_IF_NOT(de->type == TYPE_THRESHOLD && de->track == TRACK_SRC && de->count == 100 && de->seconds == 60); + + s = de_ctx->sig_list->next->next; + m = DetectGetLastSMByListId(s, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + FAIL_IF_NULL(m->ctx); + de = (DetectThresholdData *)m->ctx; + FAIL_IF_NOT(de->type == TYPE_THRESHOLD && de->track == TRACK_SRC && de->count == 100 && de->seconds == 60); + + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest06(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD04(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_LIMIT && de->track == TRACK_SRC && de->count == 1 && de->seconds == 60); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the rate_filter rules are loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest07(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD05(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_DETECTION_FILTER, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_RATE && de->track == TRACK_SRC && de->count == 1 && de->seconds == 60); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the rate_filter rules are loaded and well parsed + * with multilines + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest08(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD06(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, DETECT_SM_LIST_THRESHOLD, + DETECT_DETECTION_FILTER, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_RATE && de->track == TRACK_SRC && de->count == 1 && de->seconds == 60); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the rate_filter rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest09(void) +{ + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacket((uint8_t*)"lalala", 6, IPPROTO_TCP); + FAIL_IF_NULL(p); + + DetectEngineThreadCtx *det_ctx = NULL; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"ratefilter test\"; gid:1; sid:10;)"); + FAIL_IF_NULL(s); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD07(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + p->ts = TimeGet(); + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || PacketTestAction(p, ACTION_DROP)); + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || PacketTestAction(p, ACTION_DROP)); + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || PacketTestAction(p, ACTION_DROP)); + + TimeSetIncrementTime(2); + p->ts = TimeGet(); + + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || !(PacketTestAction(p, ACTION_DROP))); + + TimeSetIncrementTime(3); + p->ts = TimeGet(); + + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || !(PacketTestAction(p, ACTION_DROP))); + + TimeSetIncrementTime(10); + p->ts = TimeGet(); + + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || PacketTestAction(p, ACTION_DROP)); + + p->alerts.cnt = 0; + p->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + FAIL_IF(p->alerts.cnt != 1 || PacketTestAction(p, ACTION_DROP)); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the rate_filter rules work with track by_rule + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest10(void) +{ + HostInitConfig(HOST_QUIET); + + /* Create two different packets falling to the same rule, and + * because count:3, we should drop on match #4. + */ + Packet *p1 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, + "172.26.0.2", "172.26.0.11"); + FAIL_IF_NULL(p1); + Packet *p2 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, + "172.26.0.1", "172.26.0.10"); + FAIL_IF_NULL(p2); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + DetectEngineThreadCtx *det_ctx = NULL; + + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"ratefilter test\"; gid:1; sid:10;)"); + FAIL_IF_NULL(s); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD08(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + p1->ts = TimeGet(); + p2->ts = p1->ts; + + /* All should be alerted, none dropped */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + p1->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + FAIL_IF(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + p2->action = 0; + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + p1->action = 0; + + /* Match #4 should be dropped*/ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + FAIL_IF_NOT(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + p2->action = 0; + + TimeSetIncrementTime(2); + p1->ts = TimeGet(); + + /* Still dropped because timeout not expired */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF_NOT(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + p1->action = 0; + + TimeSetIncrementTime(10); + p1->ts = TimeGet(); + + /* Not dropped because timeout expired */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + /* Ensure that a Threshold entry was installed at the sig */ + FAIL_IF_NULL(de_ctx->ths_ctx.th_entry[s->num]); + + UTHFreePacket(p1); + UTHFreePacket(p2); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the rate_filter rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest11(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacket((uint8_t*)"lalala", 6, IPPROTO_TCP); + FAIL_IF_NULL(p); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + DetectEngineThreadCtx *det_ctx = NULL; + + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(s); + s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test threshold\"; gid:1; sid:11;)"); + FAIL_IF_NULL(s); + s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test both\"; gid:1; sid:12;)"); + FAIL_IF_NULL(s); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD09(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + p->ts = TimeGet(); + + int alerts10 = 0; + int alerts11 = 0; + int alerts12 = 0; + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + + TimeSetIncrementTime(100); + p->ts = TimeGet(); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + + TimeSetIncrementTime(10); + p->ts = TimeGet(); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + + FAIL_IF_NOT(alerts10 == 4); + /* One on the first interval, another on the second */ + FAIL_IF_NOT(alerts11 == 2); + FAIL_IF_NOT(alerts12 == 2); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the rate_filter rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest12(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacket((uint8_t*)"lalala", 6, IPPROTO_TCP); + FAIL_IF_NULL(p); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + DetectEngineThreadCtx *det_ctx = NULL; + + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test limit\"; gid:1; sid:10;)"); + FAIL_IF_NULL(s); + s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test threshold\"; gid:1; sid:11;)"); + FAIL_IF_NULL(s); + s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"event_filter test both\"; gid:1; sid:12;)"); + FAIL_IF_NULL(s); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD10(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + p->ts = TimeGet(); + + int alerts10 = 0; + int alerts11 = 0; + int alerts12 = 0; + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + + TimeSetIncrementTime(100); + p->ts = TimeGet(); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + + TimeSetIncrementTime(10); + p->ts = TimeGet(); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + alerts10 += PacketAlertCheck(p, 10); + alerts11 += PacketAlertCheck(p, 11); + alerts12 += PacketAlertCheck(p, 12); + + FAIL_IF_NOT(alerts10 == 10); + /* One on the first interval, another on the second */ + FAIL_IF_NOT(alerts11 == 1); + FAIL_IF_NOT(alerts12 == 1); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest13(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; gid:1; sid:1000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD11(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigMatch *m = DetectGetLastSMByListId(sig, + DETECT_SM_LIST_SUPPRESS, DETECT_THRESHOLD, -1); + FAIL_IF_NULL(m); + + DetectThresholdData *de = (DetectThresholdData *)m->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Check if the suppress rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest14(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p1 = UTHBuildPacketReal((uint8_t*)"lalala", 6, IPPROTO_TCP, "192.168.0.10", + "192.168.0.100", 1234, 24); + FAIL_IF_NULL(p1); + Packet *p2 = UTHBuildPacketReal((uint8_t*)"lalala", 6, IPPROTO_TCP, "192.168.1.1", + "192.168.0.100", 1234, 24); + FAIL_IF_NULL(p2); + + DetectEngineThreadCtx *det_ctx = NULL; + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"suppress test\"; gid:1; sid:10000;)"); + FAIL_IF_NULL(sig); + sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"suppress test 2\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"suppress test 3\"; gid:1; sid:1000;)"); + FAIL_IF_NULL(sig); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD11(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + + FAIL_IF_NOT(PacketAlertCheck(p1, 10000) == 0); + FAIL_IF_NOT(PacketAlertCheck(p1, 10) == 1); + FAIL_IF_NOT(PacketAlertCheck(p1, 1000) == 1); + FAIL_IF_NOT(PacketAlertCheck(p2, 1000) == 0); + + UTHFreePacket(p1); + UTHFreePacket(p2); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + HostShutdown(); + PASS; +} + +/** + * \test Check if the suppress rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest15(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacketReal((uint8_t*)"lalala", 6, IPPROTO_TCP, "192.168.0.10", + "192.168.0.100", 1234, 24); + FAIL_IF_NULL(p); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineThreadCtx *det_ctx = NULL; + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "drop tcp any any -> any any (msg:\"suppress test\"; content:\"lalala\"; gid:1; sid:10000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD11(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + /* 10000 shouldn't match */ + FAIL_IF(PacketAlertCheck(p, 10000) != 0); + /* however, it should have set the drop flag */ + FAIL_IF(!(PacketTestAction(p, ACTION_DROP))); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the suppress rules work + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest16(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacketReal((uint8_t*)"lalala", 6, IPPROTO_TCP, "192.168.1.1", + "192.168.0.100", 1234, 24); + FAIL_IF_NULL(p); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineThreadCtx *det_ctx = NULL; + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "drop tcp any any -> any any (msg:\"suppress test\"; gid:1; sid:1000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD11(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + FAIL_IF(PacketAlertCheck(p, 1000) != 0); + /* however, it should have set the drop flag */ + FAIL_IF(!(PacketTestAction(p, ACTION_DROP))); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the suppress rules work - ip only rule + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest17(void) +{ + HostInitConfig(HOST_QUIET); + + Packet *p = UTHBuildPacketReal((uint8_t*)"lalala", 6, IPPROTO_TCP, "192.168.0.10", + "192.168.0.100", 1234, 24); + FAIL_IF_NULL(p); + + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineThreadCtx *det_ctx = NULL; + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "drop tcp 192.168.0.10 any -> 192.168.0.100 any (msg:\"suppress test\"; gid:1; sid:10000;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD11(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + /* 10000 shouldn't match */ + FAIL_IF(PacketAlertCheck(p, 10000) != 0); + /* however, it should have set the drop flag */ + FAIL_IF(!(PacketTestAction(p, ACTION_DROP))); + + UTHFreePacket(p); + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateInvalidDummyFD12(void) +{ + FILE *fd = NULL; + const char *buffer = + "suppress gen_id 1, sig_id 2200029, track by_dst, ip fe80::/16\n" + "suppress gen_id 1, sig_id 2200029, track by_stc, ip fe80::/16\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the suppress rule parsing handles errors correctly + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest18(void) +{ + HostInitConfig(HOST_QUIET); + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp 192.168.0.10 any -> 192.168.0.100 any (msg:\"suppress test\"; gid:1; sid:2200029;)"); + FAIL_IF_NULL(s); + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateInvalidDummyFD12(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + SigGroupBuild(de_ctx); + + FAIL_IF_NULL(s->sm_arrays[DETECT_SM_LIST_SUPPRESS]); + SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_SUPPRESS]; + DetectThresholdData *de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_DST); + + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateInvalidDummyFD13(void) +{ + FILE *fd = NULL; + const char *buffer = + "suppress gen_id 1, sig_id 2200029, track by_stc, ip fe80::/16\n" + "suppress gen_id 1, sig_id 2200029, track by_dst, ip fe80::/16\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the suppress rule parsing handles errors correctly + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest19(void) +{ + HostInitConfig(HOST_QUIET); + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp 192.168.0.10 any -> 192.168.0.100 any (msg:\"suppress test\"; gid:1; sid:2200029;)"); + FAIL_IF_NULL(s); + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateInvalidDummyFD13(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + SigGroupBuild(de_ctx); + FAIL_IF_NULL(s->sm_arrays[DETECT_SM_LIST_SUPPRESS]); + SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_SUPPRESS]; + DetectThresholdData *de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_DST); + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \brief Creates a dummy threshold file, with all valid options, for testing purposes. + * + * \retval fd Pointer to file descriptor. + */ +static FILE *SCThresholdConfGenerateValidDummyFD20(void) +{ + FILE *fd = NULL; + const char *buffer = + "suppress gen_id 1, sig_id 1000, track by_src, ip 2.2.3.4\n" + "suppress gen_id 1, sig_id 1000, track by_src, ip 1.2.3.4\n" + "suppress gen_id 1, sig_id 1000, track by_src, ip 192.168.1.1\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the threshold file is loaded and well parsed + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest20(void) +{ + HostInitConfig(HOST_QUIET); + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; content:\"abc\"; sid:1000;)"); + FAIL_IF_NULL(s); + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD20(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + SigGroupBuild(de_ctx); + FAIL_IF_NULL(s->sm_arrays[DETECT_SM_LIST_SUPPRESS]); + + SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_SUPPRESS]; + DetectThresholdData *de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF(smd->is_last); + + smd++; + de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF(smd->is_last); + + smd++; + de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF_NOT(smd->is_last); + + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** + * \test Check if the threshold file is loaded and well parsed, and applied + * correctly to a rule with thresholding + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest21(void) +{ + HostInitConfig(HOST_QUIET); + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"Threshold limit\"; content:\"abc\"; threshold: type limit, track by_dst, count 5, seconds 60; sid:1000;)"); + FAIL_IF_NULL(s); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD20(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + SigGroupBuild(de_ctx); + FAIL_IF_NULL(s->sm_arrays[DETECT_SM_LIST_SUPPRESS]); + + SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_SUPPRESS]; + DetectThresholdData *de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF(smd->is_last); + + smd++; + de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF(smd->is_last); + + smd++; + de = (DetectThresholdData *)smd->ctx; + FAIL_IF_NULL(de); + FAIL_IF_NOT(de->type == TYPE_SUPPRESS && de->track == TRACK_SRC); + FAIL_IF_NOT(smd->is_last); + + DetectEngineCtxFree(de_ctx); + HostShutdown(); + PASS; +} + +/** +* \brief Creates a dummy rate_filter file, for testing rate filtering by_both source and destination +* +* \retval fd Pointer to file descriptor. +*/ +static FILE *SCThresholdConfGenerateValidDummyFD22(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter gen_id 1, sig_id 10, track by_both, count 2, seconds 5, new_action drop, timeout 6\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the rate_filter rules work with track by_both + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest22(void) +{ + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + IPPairInitConfig(IPPAIR_QUIET); + + /* This packet will cause rate_filter */ + Packet *p1 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.10"); + FAIL_IF_NULL(p1); + + /* Should not be filtered for different destination */ + Packet *p2 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.2"); + FAIL_IF_NULL(p2); + + /* Should not be filtered when both src and dst the same */ + Packet *p3 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.1"); + FAIL_IF_NULL(p3); + + DetectEngineThreadCtx *det_ctx = NULL; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"ratefilter by_both test\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD22(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + p1->ts = TimeGet(); + p2->ts = p3->ts = p1->ts; + + /* All should be alerted, none dropped */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + FAIL_IF(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p3); + FAIL_IF(PacketTestAction(p3, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p3, 10) != 1); + + p1->action = p2->action = p3->action = 0; + + TimeSetIncrementTime(2); + p1->ts = TimeGet(); + p2->ts = p3->ts = p1->ts; + + /* p1 still shouldn't be dropped after 2nd alert */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + p1->action = 0; + + TimeSetIncrementTime(2); + p1->ts = TimeGet(); + p2->ts = p3->ts = p1->ts; + + /* All should be alerted, only p1 must be dropped due to rate_filter*/ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF_NOT(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + FAIL_IF(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p3); + FAIL_IF(PacketTestAction(p3, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p3, 10) != 1); + + p1->action = p2->action = p3->action = 0; + + TimeSetIncrementTime(7); + p1->ts = TimeGet(); + p2->ts = p3->ts = p1->ts; + + /* All should be alerted, none dropped (because timeout expired) */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + FAIL_IF(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, p3); + FAIL_IF(PacketTestAction(p3, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p3, 10) != 1); + + UTHFreePacket(p3); + UTHFreePacket(p2); + UTHFreePacket(p1); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + IPPairShutdown(); + PASS; +} + +/** +* \brief Creates a dummy rate_filter file, for testing rate filtering by_both source and destination +* +* \retval fd Pointer to file descriptor. +*/ +static FILE *SCThresholdConfGenerateValidDummyFD23(void) +{ + FILE *fd = NULL; + const char *buffer = + "rate_filter gen_id 1, sig_id 10, track by_both, count 1, seconds 5, new_action drop, timeout 6\n"; + + fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); + if (fd == NULL) + SCLogDebug("Error with SCFmemopen() called by Threshold Config test code"); + + return fd; +} + +/** + * \test Check if the rate_filter by_both work when similar packets + * going in opposite direction + * + * \retval 1 on success + * \retval 0 on failure + */ +static int SCThresholdConfTest23(void) +{ + ThreadVars th_v; + memset(&th_v, 0, sizeof(th_v)); + + IPPairInitConfig(IPPAIR_QUIET); + + /* Create two packets between same addresses in opposite direction */ + Packet *p1 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.1", "172.26.0.10"); + FAIL_IF_NULL(p1); + + Packet *p2 = UTHBuildPacketSrcDst((uint8_t*)"lalala", 6, IPPROTO_TCP, "172.26.0.10", "172.26.0.1"); + FAIL_IF_NULL(p2); + + DetectEngineThreadCtx *det_ctx = NULL; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any (msg:\"ratefilter by_both test\"; gid:1; sid:10;)"); + FAIL_IF_NULL(sig); + + FAIL_IF_NOT_NULL(g_ut_threshold_fp); + g_ut_threshold_fp = SCThresholdConfGenerateValidDummyFD23(); + FAIL_IF_NULL(g_ut_threshold_fp); + FAIL_IF(-1 == SCThresholdConfInitContext(de_ctx)); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + p1->ts = TimeGet(); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p1); + /* First packet should be alerted, not dropped */ + FAIL_IF(PacketTestAction(p1, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p1, 10) != 1); + + TimeSetIncrementTime(2); + p2->ts = TimeGet(); + SigMatchSignatures(&th_v, de_ctx, det_ctx, p2); + + /* Second packet should be dropped because it considered as "the same pair" + and rate_filter count reached*/ + FAIL_IF_NOT(PacketTestAction(p2, ACTION_DROP)); + FAIL_IF(PacketAlertCheck(p2, 10) != 1); + + UTHFreePacket(p2); + UTHFreePacket(p1); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + IPPairShutdown(); + PASS; +} +#endif /* UNITTESTS */ + +/** + * \brief This function registers unit tests for Classification Config API. + */ +void SCThresholdConfRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("SCThresholdConfTest01", SCThresholdConfTest01); + UtRegisterTest("SCThresholdConfTest02", SCThresholdConfTest02); + UtRegisterTest("SCThresholdConfTest03", SCThresholdConfTest03); + UtRegisterTest("SCThresholdConfTest04", SCThresholdConfTest04); + UtRegisterTest("SCThresholdConfTest05", SCThresholdConfTest05); + UtRegisterTest("SCThresholdConfTest06", SCThresholdConfTest06); + UtRegisterTest("SCThresholdConfTest07", SCThresholdConfTest07); + UtRegisterTest("SCThresholdConfTest08", SCThresholdConfTest08); + UtRegisterTest("SCThresholdConfTest09 - rate_filter", + SCThresholdConfTest09); + UtRegisterTest("SCThresholdConfTest10 - rate_filter", + SCThresholdConfTest10); + UtRegisterTest("SCThresholdConfTest11 - event_filter", + SCThresholdConfTest11); + UtRegisterTest("SCThresholdConfTest12 - event_filter", + SCThresholdConfTest12); + UtRegisterTest("SCThresholdConfTest13", SCThresholdConfTest13); + UtRegisterTest("SCThresholdConfTest14 - suppress", SCThresholdConfTest14); + UtRegisterTest("SCThresholdConfTest15 - suppress drop", + SCThresholdConfTest15); + UtRegisterTest("SCThresholdConfTest16 - suppress drop", + SCThresholdConfTest16); + UtRegisterTest("SCThresholdConfTest17 - suppress drop", + SCThresholdConfTest17); + + UtRegisterTest("SCThresholdConfTest18 - suppress parsing", + SCThresholdConfTest18); + UtRegisterTest("SCThresholdConfTest19 - suppress parsing", + SCThresholdConfTest19); + UtRegisterTest("SCThresholdConfTest20 - suppress parsing", + SCThresholdConfTest20); + UtRegisterTest("SCThresholdConfTest21 - suppress parsing", + SCThresholdConfTest21); + UtRegisterTest("SCThresholdConfTest22 - rate_filter by_both", + SCThresholdConfTest22); + UtRegisterTest("SCThresholdConfTest23 - rate_filter by_both opposite", + SCThresholdConfTest23); + +#endif /* UNITTESTS */ +} + +/** + * @} + */ |