diff --git a/doc/userguide/rules/flow-keywords.rst b/doc/userguide/rules/flow-keywords.rst index 00801352303e..ad494c0a11f1 100644 --- a/doc/userguide/rules/flow-keywords.rst +++ b/doc/userguide/rules/flow-keywords.rst @@ -318,6 +318,34 @@ Signature example:: In this example, we combine `flow.age` and `flowbits` to get an alert on the first packet after the flow's age is older than one hour. +flow.rate +--------- + +The rate of the flow calculated by dividing number of bytes by the time in seconds. +So, the unit of the flow rate is bytes/second. Note that it is possible to denote +the rate with units like kb, mb, etc. +Currently, it is implemented as a check against the total number of bytes seen by +the flow in both the directions divided by the age of the flow. + +For example, if a flow has seen 5000000 bytes in 10 seconds, a rule for this flow +will be matched against 500000 bytes/s. + +flow.rate uses an :ref:`unsigned 64-bit integer `. + +Syntax:: + + flow.rate: [op] + +The rate can be matched exactly, or compared using the _op_ setting:: + + flow.rate:10000 # exactly 10000 bytes per second + flow.rate:>20000 # greater than 20000 bytes per second + flow.rate:>=30mb # greater than equal to 30mbps + +Signature example:: + + pass tcp any any -> any any (msg:"Flow rate higher than 50kbps"; flow.rate:>50kb; sid:1; rev:1;) + flow.pkts_toclient ------------------ diff --git a/rust/src/detect/tojson/mod.rs b/rust/src/detect/tojson/mod.rs index 1a1f0cee8028..9c7aa4abf738 100644 --- a/rust/src/detect/tojson/mod.rs +++ b/rust/src/detect/tojson/mod.rs @@ -83,4 +83,11 @@ pub unsafe extern "C" fn SCDetectU32ToJson( js: &mut JsonBuilder, du: &DetectUintData, ) -> bool { return detect_uint_to_json(js, du).is_ok(); -} \ No newline at end of file +} + +#[no_mangle] +pub unsafe extern "C" fn SCDetectU64ToJson( + js: &mut JsonBuilder, du: &DetectUintData, +) -> bool { + return detect_uint_to_json(js, du).is_ok(); +} diff --git a/src/Makefile.am b/src/Makefile.am index c2cf2dd93ab5..89ce0fb16223 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -160,6 +160,7 @@ noinst_HEADERS = \ detect-flowbits.h \ detect-flow.h \ detect-flow-age.h \ + detect-flow-rate.h \ detect-flow-pkts.h \ detect-flowint.h \ detect-flowvar.h \ @@ -725,6 +726,7 @@ libsuricata_c_a_SOURCES = \ detect-flowbits.c \ detect-flow.c \ detect-flow-age.c \ + detect-flow-rate.c \ detect-flow-pkts.c \ detect-flowint.c \ detect-flowvar.c \ diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index dae6f9f2f270..82cf2c0a22c2 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -41,6 +41,7 @@ #include "detect-bytetest.h" #include "detect-isdataat.h" #include "detect-flow.h" +#include "detect-flow-rate.h" #include "detect-tcp-flags.h" #include "detect-tcp-ack.h" #include "detect-ipopts.h" @@ -957,6 +958,13 @@ static void DumpMatches(RuleAnalyzer *ctx, JsonBuilder *js, const SigMatchData * jb_close(js); break; } + case DETECT_FLOW_RATE: { + const DetectFlowRate *fr = (const DetectFlowRate *)smd->ctx; + jb_open_object(js, "flow_rate"); + SCDetectU64ToJson(js, fr->rate); + jb_close(js); + break; + } } jb_close(js); diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 9bddf0fd8437..510c29e52e3b 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -123,6 +123,7 @@ #include "detect-rev.h" #include "detect-flow.h" #include "detect-flow-age.h" +#include "detect-flow-rate.h" #include "detect-flow-pkts.h" #include "detect-requires.h" #include "detect-tcp-window.h" @@ -581,6 +582,7 @@ void SigTableSetup(void) DetectReplaceRegister(); DetectFlowRegister(); DetectFlowAgeRegister(); + DetectFlowRateRegister(); DetectFlowPktsToClientRegister(); DetectFlowPktsToServerRegister(); DetectFlowBytesToClientRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index b7a029998555..d18f6385fed8 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -131,6 +131,7 @@ enum DetectKeywordId { DETECT_FLOW_PKTS_TO_SERVER, DETECT_FLOW_BYTES_TO_CLIENT, DETECT_FLOW_BYTES_TO_SERVER, + DETECT_FLOW_RATE, DETECT_REQUIRES, diff --git a/src/detect-flow-rate.c b/src/detect-flow-rate.c new file mode 100644 index 000000000000..36396461aa44 --- /dev/null +++ b/src/detect-flow-rate.c @@ -0,0 +1,84 @@ +/* Copyright (C) 2024 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Shivani Bhardwaj + */ + +#include "suricata-common.h" +#include "rust.h" +#include "detect-flow-rate.h" +#include "detect-engine.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-uint.h" +#include "detect-parse.h" + +static int DetectFlowRateMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + if (p->flow == NULL) { + return 0; + } + + uint64_t age = SCTIME_SECS(p->flow->lastts) - SCTIME_SECS(p->flow->startts); + uint64_t rate = (p->flow->tosrcbytecnt + p->flow->todstbytecnt) / age; + + const DetectFlowRate *expected = (const DetectFlowRate *)ctx; + return DetectU64Match(rate, expected->rate); +} + +static int DetectFlowRateSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + DetectU64Data *du64 = DetectU64Parse(rawstr); + if (du64 == NULL) + return -1; + + DetectFlowRate *fr = SCCalloc(1, sizeof(DetectFlowRate)); + if (fr == NULL) + return -1; + + fr->rate = du64; + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_FLOW_RATE, (SigMatchCtx *)fr, DETECT_SM_LIST_MATCH) == NULL) { + return -1; + } + s->flags |= SIG_FLAG_REQUIRE_PACKET; + + return 0; +} + +static void DetectFlowRateFree(DetectEngineCtx *de_ctx, void *ptr) +{ + DetectFlowRate *fr = (DetectFlowRate *)ptr; + if (fr != NULL) { + rs_detect_u64_free(fr->rate); + SCFree(fr); + } +} + +void DetectFlowRateRegister(void) +{ + sigmatch_table[DETECT_FLOW_RATE].name = "flow.rate"; + sigmatch_table[DETECT_FLOW_RATE].desc = "match flow rate"; + sigmatch_table[DETECT_FLOW_RATE].url = "/rules/flow-keywords.html#flow-rate"; + sigmatch_table[DETECT_FLOW_RATE].Match = DetectFlowRateMatch; + sigmatch_table[DETECT_FLOW_RATE].Setup = DetectFlowRateSetup; + sigmatch_table[DETECT_FLOW_RATE].Free = DetectFlowRateFree; +} diff --git a/src/detect-flow-rate.h b/src/detect-flow-rate.h new file mode 100644 index 000000000000..fd4cea6c2c67 --- /dev/null +++ b/src/detect-flow-rate.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2024 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. + */ + +#ifndef SURICATA_DETECT_FLOW_RATE_H +#define SURICATA_DETECT_FLOW_RATE_H + +typedef struct DetectFlowRate_ { + DetectUintData_u64 *rate; +} DetectFlowRate; + +void DetectFlowRateRegister(void); + +#endif /* SURICATA_DETECT_FLOW_RATE_H */