From 916ed58adbb2483bb3f74fa7d6cf001a2af15f33 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Tue, 31 Mar 2026 11:35:37 -0300 Subject: [PATCH 1/8] docs/configuration: add firewall mode settings Partly related to Ticket #7699 --- doc/userguide/configuration/suricata-yaml.rst | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 88d266a0dc..8137ce3ab0 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -2805,6 +2805,29 @@ use of. vista: [] windows2k3: [] +Suricata as a Firewall options (experimental) +--------------------------------------------- + +It is possible to run Suricata as a firewall. +Please read :ref:`Firewall Mode Design ` before using this. +The existing yaml configuration options are listed below. If the engine is run +in firewall mode, dedicated stats counters will be added to the stats logs. + +To see the stats counters reported for the firewall, refer to :ref:`firewall mode stats`. + + firewall: + # toggle to enable firewall mode + #enabled: no + # Firewall rule file are in their own path and are not managed + # by Suricata-Update. + #rule-path: /etc/suricata/firewall/ + + # List of files with firewall rules. Order matters, files are loaded + # in order and rules are applied in that order (per state, see docs) + #rule-files: + # - firewall.rules + + Engine analysis and profiling ----------------------------- From 33d0d409c51d223137eab268fd92e28f925195b4 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Wed, 20 May 2026 15:42:52 -0300 Subject: [PATCH 2/8] detect/firewall: add dedicated stats counters Add a `firewall` stats counter aggregator for all firewall-related stats. De-overload "detect.alert_queue_overflow", by adding "firewall.discarded_alerts" to account for discarded drops in Firewall mode. Add Debug statements for tracking corner cases where it can be difficult to know where a drop is coming from. Added counters: - stats.firewall.blocked - stats.firewall.accepted - stats.firewall.rejected - stats.firewall.drop_reason.default_app_policy - stats.firewall.drop_reason.default_packet_policy - stats.firewall.drop_reason.flow_drop - stats.firewall.drop_reason.pre_flow_hook - stats.firewall.drop_reason.pre_stream_hook - stats.firewall.drop_reason.rules - stats.firewall.discarded_alerts Ticket #7699 --- etc/schema.json | 72 +++++++++++++++++-------- src/decode.c | 107 +++++++++++++++++++++++++++++--------- src/decode.h | 21 +++++--- src/detect-engine-alert.c | 20 ++++--- src/detect-engine.c | 8 +++ src/detect.c | 15 ++++-- src/detect.h | 2 + src/flow-worker.c | 4 +- src/flow.h | 3 ++ src/packet.c | 3 +- 10 files changed, 187 insertions(+), 68 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index e73d99bec9..274eb8c099 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -7352,7 +7352,7 @@ }, "alert_queue_overflow": { "type": "integer", - "description": "Count of alerts discarded due to alert queue overflow or a drop in firewall mode" + "description": "Count of alerts discarded due to alert queue overflow" }, "alerts_suppressed": { "type": "integer", @@ -7521,6 +7521,56 @@ } } }, + "firewall": { + "type": "object", + "additionalProperties": false, + "properties": { + "accepted": { + "type": "integer", + "description": "Count of accepted packets due to firewall policies" + }, + "blocked": { + "type": "integer", + "description": "Count of blocked packets due to firewall policies" + }, + "discarded_alerts": { + "type": "integer", + "description": "Count of alerts discarded due to a drop while in firewall mode" + }, + "drop_reason": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_app_policy": { + "type": "integer", + "description": + "Number of packets dropped due to firewall's mode default app policy" + }, + "default_packet_policy": { + "type": "integer", + "description": + "Number of packets dropped due to firewall's mode default packet policy" + }, + "pre_flow_hook": { + "type": "integer", + "description": "Number of packets dropped due to pre-flow hook" + }, + "pre_stream_hook": { + "type": "integer", + "description": "Number of packets dropped due to pre-stream hook" + }, + "rules": { + "type": "integer", + "description": "Number of packets dropped due to firewall rules" + } + } + }, + "rejected": { + "type": "integer", + "description": "Count of packets rejected due to firewall policies" + } + } + }, "flow": { "type": "object", "description": "Stats on flow-related diagnostics", @@ -7938,16 +7988,6 @@ "description": "Number of packets dropped due to decoding errors" }, - "default_app_policy": { - "type": "integer", - "description": - "Number of packets dropped due to default app policy" - }, - "default_packet_policy": { - "type": "integer", - "description": - "Number of packets dropped due to default packet policy" - }, "defrag_error": { "type": "integer", "description": @@ -7971,16 +8011,6 @@ "type": "integer", "description": "Number of packets dropped due to no NFQ verdict" }, - "pre_flow_hook": { - "description": - "Number of packets dropped in the pre_flow hook ", - "type": "integer" - }, - "pre_stream_hook": { - "description": - "Number of packets dropped in the pre_stream hook ", - "type": "integer" - }, "rules": { "type": "integer", "description": "Number of packets dropped due to rule actions" diff --git a/src/decode.c b/src/decode.c index 1a1b76ea89..b3ee90a965 100644 --- a/src/decode.c +++ b/src/decode.c @@ -956,14 +956,16 @@ const char *PacketDropReasonToString(enum PacketDropReason r) return "nfq error"; case PKT_DROP_REASON_INNER_PACKET: return "tunnel packet drop"; - case PKT_DROP_REASON_DEFAULT_PACKET_POLICY: - return "default packet policy"; - case PKT_DROP_REASON_DEFAULT_APP_POLICY: - return "default app policy"; - case PKT_DROP_REASON_STREAM_PRE_HOOK: - return "pre stream hook"; - case PKT_DROP_REASON_FLOW_PRE_HOOK: - return "pre flow hook"; + case PKT_DROP_REASON_FW_RULES: + return "firewall rules"; + case PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY: + return "firewall default packet policy"; + case PKT_DROP_REASON_FW_DEFAULT_APP_POLICY: + return "firewall default app policy"; + case PKT_DROP_REASON_FW_STREAM_PRE_HOOK: + return "firewall pre stream hook"; + case PKT_DROP_REASON_FW_FLOW_PRE_HOOK: + return "firewall pre flow hook"; case PKT_DROP_REASON_NOT_SET: case PKT_DROP_REASON_MAX: return NULL; @@ -1006,14 +1008,16 @@ static const char *PacketDropReasonToJsonString(enum PacketDropReason r) return "ips.drop_reason.nfq_error"; case PKT_DROP_REASON_INNER_PACKET: return "ips.drop_reason.tunnel_packet_drop"; - case PKT_DROP_REASON_DEFAULT_PACKET_POLICY: - return "ips.drop_reason.default_packet_policy"; - case PKT_DROP_REASON_DEFAULT_APP_POLICY: - return "ips.drop_reason.default_app_policy"; - case PKT_DROP_REASON_STREAM_PRE_HOOK: - return "ips.drop_reason.pre_stream_hook"; - case PKT_DROP_REASON_FLOW_PRE_HOOK: - return "ips.drop_reason.pre_flow_hook"; + case PKT_DROP_REASON_FW_RULES: + return "firewall.drop_reason.rules"; + case PKT_DROP_REASON_FW_STREAM_PRE_HOOK: + return "firewall.drop_reason.pre_stream_hook"; + case PKT_DROP_REASON_FW_FLOW_PRE_HOOK: + return "firewall.drop_reason.pre_flow_hook"; + case PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY: + return "firewall.drop_reason.default_packet_policy"; + case PKT_DROP_REASON_FW_DEFAULT_APP_POLICY: + return "firewall.drop_reason.default_app_policy"; case PKT_DROP_REASON_NOT_SET: case PKT_DROP_REASON_MAX: return NULL; @@ -1026,27 +1030,72 @@ typedef struct CaptureStats_ { StatsCounterId counter_ips_blocked; StatsCounterId counter_ips_rejected; StatsCounterId counter_ips_replaced; + StatsCounterId counter_fw_accepted; + StatsCounterId counter_fw_blocked; + StatsCounterId counter_fw_rejected; StatsCounterId counter_drop_reason[PKT_DROP_REASON_MAX]; } CaptureStats; thread_local CaptureStats t_capture_stats; +static bool VerdictByFirewall(const Packet *p) +{ + if (!EngineModeIsFirewall()) { + return false; + } + if (p->drop_reason >= PKT_DROP_REASON_FW_RULES) { + return true; + } + return false; +} + void CaptureStatsUpdate(ThreadVars *tv, const Packet *p) { if (!EngineModeIsIPS() || PKT_IS_PSEUDOPKT(p)) return; CaptureStats *s = &t_capture_stats; - if (unlikely(PacketCheckAction(p, ACTION_REJECT_ANY))) { - StatsCounterIncr(&tv->stats, s->counter_ips_rejected); - } else if (unlikely(PacketCheckAction(p, ACTION_DROP))) { - StatsCounterIncr(&tv->stats, s->counter_ips_blocked); - } else if (unlikely(p->flags & PKT_STREAM_MODIFIED)) { - StatsCounterIncr(&tv->stats, s->counter_ips_replaced); + if (EngineModeIsFirewall()) { + /** The firewall mode and its stats counters should work as when there are two different + * devices for the firewall control and the IPS control. + * As such, if the firewall blocks a packet, it won't reach the IPS level of evaluation, + * so won't be counted in either stats. + * When the firewall accepts a packet, it can still be blocked, rejected or accepted by + * IPS rules and policies. + */ + if (unlikely(PacketCheckAction(p, ACTION_REJECT_ANY))) { + if (VerdictByFirewall(p)) { + StatsCounterIncr(&tv->stats, s->counter_fw_rejected); + } else { + StatsCounterIncr(&tv->stats, s->counter_fw_accepted); + StatsCounterIncr(&tv->stats, s->counter_ips_rejected); + } + } else if (PacketCheckAction(p, ACTION_DROP)) { + if (VerdictByFirewall(p)) { + StatsCounterIncr(&tv->stats, s->counter_fw_blocked); + } else { + /* If a packet was dropped by IPS, it had to first be accepted by the firewall, to + * reach the IPS flow control */ + StatsCounterIncr(&tv->stats, s->counter_fw_accepted); + StatsCounterIncr(&tv->stats, s->counter_ips_blocked); + } + } else if (PacketCheckAction(p, ACTION_ACCEPT)) { + StatsCounterIncr(&tv->stats, s->counter_fw_accepted); + StatsCounterIncr(&tv->stats, s->counter_ips_accepted); + } } else { - StatsCounterIncr(&tv->stats, s->counter_ips_accepted); + if (unlikely(PacketCheckAction(p, ACTION_REJECT_ANY))) { + StatsCounterIncr(&tv->stats, s->counter_ips_rejected); + } else if (unlikely(PacketCheckAction(p, ACTION_DROP))) { + StatsCounterIncr(&tv->stats, s->counter_ips_blocked); + } else if (unlikely(p->flags & PKT_STREAM_MODIFIED)) { + StatsCounterIncr(&tv->stats, s->counter_ips_replaced); + } else { + StatsCounterIncr(&tv->stats, s->counter_ips_accepted); + } } + if (p->drop_reason != PKT_DROP_REASON_NOT_SET) { StatsCounterIncr(&tv->stats, s->counter_drop_reason[p->drop_reason]); } @@ -1060,11 +1109,21 @@ void CaptureStatsSetup(ThreadVars *tv) s->counter_ips_blocked = StatsRegisterCounter("ips.blocked", &tv->stats); s->counter_ips_rejected = StatsRegisterCounter("ips.rejected", &tv->stats); s->counter_ips_replaced = StatsRegisterCounter("ips.replaced", &tv->stats); - for (int i = PKT_DROP_REASON_NOT_SET; i < PKT_DROP_REASON_MAX; i++) { + for (int i = PKT_DROP_REASON_NOT_SET; i <= PKT_DROP_REASON_NON_FW_MAX; i++) { const char *name = PacketDropReasonToJsonString(i); if (name != NULL) s->counter_drop_reason[i] = StatsRegisterCounter(name, &tv->stats); } + if (EngineModeIsFirewall()) { + s->counter_fw_accepted = StatsRegisterCounter("firewall.accepted", &tv->stats); + s->counter_fw_blocked = StatsRegisterCounter("firewall.blocked", &tv->stats); + s->counter_fw_rejected = StatsRegisterCounter("firewall.rejected", &tv->stats); + for (int i = PKT_DROP_REASON_FW_RULES; i < PKT_DROP_REASON_MAX; i++) { + const char *name = PacketDropReasonToJsonString(i); + if (name != NULL) + s->counter_drop_reason[i] = StatsRegisterCounter(name, &tv->stats); + } + } } } diff --git a/src/decode.h b/src/decode.h index 7172cc048b..63f25a617a 100644 --- a/src/decode.h +++ b/src/decode.h @@ -248,7 +248,7 @@ struct PacketContextData { * found in this packet */ typedef struct PacketAlert_ { SigIntId iid; /* Internal ID, used for sorting */ - uint8_t action; /* Internal num, used for thresholding */ + uint8_t action; /* Rule or threshold action to be applied to packet */ uint8_t flags; const struct Signature_ *s; uint64_t tx_id; /* Used for sorting */ @@ -287,6 +287,7 @@ extern uint16_t packet_alert_max; typedef struct PacketAlerts_ { uint16_t cnt; uint16_t discarded; + uint16_t firewall_discarded; /* alerts discarded after a drop, in fw mode*/ uint16_t suppressed; PacketAlert *alerts; /* single pa used when we're dropping, @@ -394,12 +395,18 @@ enum PacketDropReason { PKT_DROP_REASON_STREAM_MIDSTREAM, PKT_DROP_REASON_STREAM_REASSEMBLY, PKT_DROP_REASON_STREAM_URG, - PKT_DROP_REASON_NFQ_ERROR, /**< no nfq verdict, must be error */ - PKT_DROP_REASON_INNER_PACKET, /**< drop issued by inner (tunnel) packet */ - PKT_DROP_REASON_DEFAULT_PACKET_POLICY, /**< drop issued by default packet policy */ - PKT_DROP_REASON_DEFAULT_APP_POLICY, /**< drop issued by default app policy */ - PKT_DROP_REASON_STREAM_PRE_HOOK, /**< drop issued in the pre_stream hook */ - PKT_DROP_REASON_FLOW_PRE_HOOK, /**< drop issued in the pre_flow hook */ + PKT_DROP_REASON_NFQ_ERROR, /**< no nfq verdict, must be error */ + PKT_DROP_REASON_INNER_PACKET, /**< drop issued by inner (tunnel) packet */ +/** If more non-firewall drop reasons are added, make sure not to "break" PKT_DROP_REASON_NON_FW_MAX + */ +/* Limiter for non-firewall drop reasons. */ +#define PKT_DROP_REASON_NON_FW_MAX PKT_DROP_REASON_INNER_PACKET + /** Firewall-related reasons only */ + PKT_DROP_REASON_FW_RULES, + PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY, /**< drop issued by default packet policy */ + PKT_DROP_REASON_FW_DEFAULT_APP_POLICY, /**< drop issued by default app policy */ + PKT_DROP_REASON_FW_STREAM_PRE_HOOK, /**< drop issued in the pre_stream hook */ + PKT_DROP_REASON_FW_FLOW_PRE_HOOK, /**< drop issued in the pre_flow hook */ PKT_DROP_REASON_MAX, }; diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c index bcb5bb2aa1..241c69a696 100644 --- a/src/detect-engine-alert.c +++ b/src/detect-engine-alert.c @@ -199,15 +199,17 @@ static void PacketApplySignatureActions(Packet *p, const Signature *s, const Pac SCLogDebug("packet %" PRIu64 " sid %u action %02x alert_flags %02x", PcapPacketCntGet(p), s->id, pa->action, pa->flags); + const bool is_fw_rule = s->flags & SIG_FLAG_FIREWALL ? true : false; /* REJECT also sets ACTION_DROP, just make it more visible with this check */ if (pa->action & ACTION_DROP_REJECT) { - uint8_t drop_reason = PKT_DROP_REASON_RULES; - if (s->detect_table == DETECT_TABLE_PACKET_PRE_STREAM) { - drop_reason = PKT_DROP_REASON_STREAM_PRE_HOOK; - } else if (s->detect_table == DETECT_TABLE_PACKET_PRE_FLOW) { - drop_reason = PKT_DROP_REASON_FLOW_PRE_HOOK; + uint8_t drop_reason = is_fw_rule ? PKT_DROP_REASON_FW_RULES : PKT_DROP_REASON_RULES; + if (is_fw_rule) { + if (s->detect_table == DETECT_TABLE_PACKET_PRE_STREAM) { + drop_reason = PKT_DROP_REASON_FW_STREAM_PRE_HOOK; + } else if (s->detect_table == DETECT_TABLE_PACKET_PRE_FLOW) { + drop_reason = PKT_DROP_REASON_FW_FLOW_PRE_HOOK; + } } - /* PacketDrop will update the packet action, too */ PacketDrop(p, pa->action, (pa->flags & PACKET_ALERT_FLAG_RATE_FILTER_MODIFIED) @@ -546,9 +548,10 @@ static inline void PacketAlertFinalizeProcessQueue( } } - /* skip firewall sigs following a drop: IDS mode still shows alerts after an alert. */ + /* skip firewall sigs following a drop: IDS mode still shows alerts after a drop. */ if ((s->flags & SIG_FLAG_FIREWALL) && dropped) { - p->alerts.discarded++; + SCLogDebug("Skippig firewall signature after a drop."); + p->alerts.firewall_discarded++; /* Thresholding removes this alert */ } else if (res == 0 || res == 2 || (s->action & (ACTION_ALERT | ACTION_PASS)) == 0) { @@ -576,6 +579,7 @@ static inline void PacketAlertFinalizeProcessQueue( // TODO we can also drop if alert is suppressed, right? if (s->action & ACTION_DROP) { + SCLogDebug("sid:%u led to a drop that will skip any firewall alerts", s->id); dropped = true; } } else { diff --git a/src/detect-engine.c b/src/detect-engine.c index 66b9697627..9319e96633 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -3421,6 +3421,10 @@ TmEcode DetectEngineThreadCtxInit(ThreadVars *tv, void *initdata, void **data) det_ctx->counter_alerts = StatsRegisterCounter("detect.alert", &tv->stats); det_ctx->counter_alerts_overflow = StatsRegisterCounter("detect.alert_queue_overflow", &tv->stats); + if (EngineModeIsFirewall()) { + det_ctx->counter_firewall_discarded_alerts = + StatsRegisterCounter("firewall.discarded_alerts", &tv->stats); + } det_ctx->counter_alerts_suppressed = StatsRegisterCounter("detect.alerts_suppressed", &tv->stats); @@ -3504,6 +3508,10 @@ DetectEngineThreadCtx *DetectEngineThreadCtxInitForReload( det_ctx->counter_alerts = StatsRegisterCounter("detect.alert", &tv->stats); det_ctx->counter_alerts_overflow = StatsRegisterCounter("detect.alert_queue_overflow", &tv->stats); + if (EngineModeIsFirewall()) { + det_ctx->counter_firewall_discarded_alerts = + StatsRegisterCounter("firewall.discarded_alerts", &tv->stats); + } det_ctx->counter_alerts_suppressed = StatsRegisterCounter("detect.alerts_suppressed", &tv->stats); #ifdef PROFILING diff --git a/src/detect.c b/src/detect.c index c507cf623d..af735919dd 100644 --- a/src/detect.c +++ b/src/detect.c @@ -697,9 +697,9 @@ static uint8_t DetectRunApplyPacketPolicy(const DetectEngineCtx *de_ctx, DEBUG_VALIDATE_BUG_ON(de_ctx->fw_policies == NULL); const struct DetectFirewallPolicy *pol = &de_ctx->fw_policies->pkt[policy]; if (pol->action & ACTION_DROP) { - SCLogDebug("packet %" PRIu64 ": drop PKT_DROP_REASON_DEFAULT_PACKET_POLICY", + SCLogDebug("packet %" PRIu64 ": drop PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY", PcapPacketCntGet(p)); - PacketDrop(p, pol->action, PKT_DROP_REASON_DEFAULT_PACKET_POLICY); + PacketDrop(p, pol->action, PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY); } else if (pol->action & ACTION_ACCEPT) { SCLogDebug("packet %" PRIu64 ": accept", PcapPacketCntGet(p)); if (pol->action_scope == ACTION_SCOPE_PACKET) { @@ -1012,6 +1012,7 @@ static DetectRunScratchpad DetectRunSetup(const DetectEngineCtx *de_ctx, det_ctx->alert_queue_size = 0; p->alerts.drop.action = 0; + p->alerts.firewall_discarded = 0; #ifdef DEBUG if (p->flags & PKT_STREAM_ADD) { @@ -1104,6 +1105,10 @@ static inline void DetectRunPostRules(ThreadVars *tv, const DetectEngineCtx *de_ StatsCounterAddI64( &tv->stats, det_ctx->counter_alerts_overflow, (uint64_t)p->alerts.discarded); } + if (p->alerts.firewall_discarded > 0) { + StatsCounterAddI64(&tv->stats, det_ctx->counter_firewall_discarded_alerts, + (uint64_t)p->alerts.firewall_discarded); + } if (p->alerts.suppressed > 0) { StatsCounterAddI64( &tv->stats, det_ctx->counter_alerts_suppressed, (uint64_t)p->alerts.suppressed); @@ -1649,8 +1654,8 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( } if (policy->action & ACTION_DROP) { - SCLogDebug("dropping packet PKT_DROP_REASON_DEFAULT_APP_POLICY"); - PacketDrop(p, policy->action, PKT_DROP_REASON_DEFAULT_APP_POLICY); + SCLogDebug("dropping packet PKT_DROP_REASON_FW_DEFAULT_APP_POLICY"); + PacketDrop(p, policy->action, PKT_DROP_REASON_FW_DEFAULT_APP_POLICY); if (policy->action_scope == ACTION_SCOPE_FLOW) { SCLogDebug("dropping flow"); p->flow->flags |= FLOW_ACTION_DROP; @@ -2332,7 +2337,7 @@ static void DetectRunTx(ThreadVars *tv, } } else if (s->action & ACTION_DROP) { SCLogDebug("drop packet because of rule with drop action"); - PacketDrop(p, s->action, PKT_DROP_REASON_RULES); + PacketDrop(p, s->action, PKT_DROP_REASON_FW_RULES); if (s->action_scope == ACTION_SCOPE_FLOW) { SCLogDebug("drop flow because of rule with drop action"); f->flags |= FLOW_ACTION_DROP; diff --git a/src/detect.h b/src/detect.h index 75cdd016ea..63fc2246d4 100644 --- a/src/detect.h +++ b/src/detect.h @@ -1329,6 +1329,8 @@ typedef struct DetectEngineThreadCtx_ { StatsCounterId counter_alerts; /** id for discarded alerts counter */ StatsCounterId counter_alerts_overflow; + /** id for firewall discarded alerts counter */ + StatsCounterId counter_firewall_discarded_alerts; /** id for suppressed alerts counter */ StatsCounterId counter_alerts_suppressed; #ifdef PROFILING diff --git a/src/flow-worker.c b/src/flow-worker.c index 0d1bb86c3d..9da0ef387e 100644 --- a/src/flow-worker.c +++ b/src/flow-worker.c @@ -369,7 +369,7 @@ static inline void FlowWorkerStreamTCPUpdate(ThreadVars *tv, FlowWorkerThreadDat if (det_ctx != NULL && det_ctx->de_ctx->PreStreamHook != NULL) { const uint8_t action = det_ctx->de_ctx->PreStreamHook(tv, det_ctx, p); if (action & ACTION_DROP) { - PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_STREAM_PRE_HOOK); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FW_STREAM_PRE_HOOK); return; } } @@ -569,7 +569,7 @@ static TmEcode FlowWorker(ThreadVars *tv, Packet *p, void *data) if (det_ctx != NULL && det_ctx->de_ctx->PreFlowHook != NULL) { const uint8_t action = det_ctx->de_ctx->PreFlowHook(tv, det_ctx, p); if (action & ACTION_DROP) { - PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_PRE_HOOK); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FW_FLOW_PRE_HOOK); goto pre_flow_drop; } } diff --git a/src/flow.h b/src/flow.h index 75dce38f68..1e3a77f2bd 100644 --- a/src/flow.h +++ b/src/flow.h @@ -121,6 +121,9 @@ typedef struct AppLayerParserState_ AppLayerParserState; /** next packet in toserver direction will act on updated app-layer state */ #define FLOW_TS_APP_UPDATE_NEXT BIT_U64(31) +/** Flow action issued by firewall */ +#define FLOW_ACTION_BY_FIREWALL BIT_U64(32) + /* File flags */ #define FLOWFILE_INIT 0 diff --git a/src/packet.c b/src/packet.c index 1f0e306a1a..37caada75b 100644 --- a/src/packet.c +++ b/src/packet.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2022 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -136,6 +136,7 @@ void PacketReinit(Packet *p) #define RESET_PKT_LEN(p) ((p)->pktlen = 0) RESET_PKT_LEN(p); p->alerts.discarded = 0; + p->alerts.firewall_discarded = 0; p->alerts.suppressed = 0; p->alerts.drop.action = 0; if (p->alerts.cnt > 0) { From 21a1daa17ed084dd1414fed9edd7c41c400725cd Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Sun, 5 Apr 2026 17:50:19 -0300 Subject: [PATCH 3/8] docs: add firewall stats doc Related to Ticket #7699 --- doc/userguide/configuration/suricata-yaml.rst | 2 ++ doc/userguide/firewall/firewall-stats.rst | 34 +++++++++++++++++++ doc/userguide/firewall/index.rst | 1 + 3 files changed, 37 insertions(+) create mode 100644 doc/userguide/firewall/firewall-stats.rst diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 8137ce3ab0..4307b60317 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -2815,6 +2815,8 @@ in firewall mode, dedicated stats counters will be added to the stats logs. To see the stats counters reported for the firewall, refer to :ref:`firewall mode stats`. +To see the stats reported for the firewall mode, refer to :ref:`firewall mode stats`. + firewall: # toggle to enable firewall mode #enabled: no diff --git a/doc/userguide/firewall/firewall-stats.rst b/doc/userguide/firewall/firewall-stats.rst new file mode 100644 index 0000000000..f681331df2 --- /dev/null +++ b/doc/userguide/firewall/firewall-stats.rst @@ -0,0 +1,34 @@ +.. _firewall mode stats: + +Firewall Mode Stats +******************* + +Statistics counters for the firewall mode cover: + + - drop reasons: ``stats.firewall.drop_reason`` + - discarded alerts: ``stats.firewall.discarded_alerts`` + - blocked packets: ``stats.firewall.blocked`` + - accepted packets: ``stats.firewall.accepted`` + - rejected packets: ``stats.firewall.rejected`` + +These will be present in the stats logs if the engine is run in firewall mode, +only. + +Drop reasons +============ + +If a drop was caused by the firewall, the corresponding counter will be incremented. The existing ones are: + + - ``rules``: a firewall rule triggered the drop + - ``default_packet_policy``: drop caused by the default fail closed firewall behavior, on the packet hook level + - ``default_app_policy``: drop caused by the default fail close firewall behavior, on the app-layer hook level + - ``pre_flow_hook``: drop caused by the pre-flow hook + - ``pre_stream_hook``: drop caused by the pre-stream hook + - ``flow_drop``: the whole flow was dropped after a firewall action. + +Discarded alerts +================ + +In Firewall mode, alerts generated *after* a drop are discarded. +These are reported with the counter ``stats.firewall.discarded_alerts``. +Note that the drop may be caused by non-firewall rules. diff --git a/doc/userguide/firewall/index.rst b/doc/userguide/firewall/index.rst index 96fc081ba2..c1386a371b 100644 --- a/doc/userguide/firewall/index.rst +++ b/doc/userguide/firewall/index.rst @@ -5,3 +5,4 @@ Firewall Mode firewall-design firewall-example + firewall-stats From 083cb88ff231455964e2ad796ff0b2c98bd4181d Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Wed, 13 May 2026 12:30:19 -0300 Subject: [PATCH 4/8] schema: expand stats.ips.replaced explanation As this is a less obvious counter. --- etc/schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/schema.json b/etc/schema.json index 274eb8c099..52c6b6410d 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -8059,7 +8059,7 @@ }, "replaced": { "type": "integer", - "description": "Number of replaced packets" + "description": "Number of packets replaced by the stream engine or based on a match of the 'replaced' keyword." } } }, From ebfc5f402358aeef35e217c4260c890554085484 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Wed, 13 May 2026 12:47:27 -0300 Subject: [PATCH 5/8] detect: add flow drop by firewall as drop reason To track flow drops triggered by the firewall. Add flow drop by firewall as drop reason. As part of Ticket #7699 --- etc/schema.json | 14 +++++++++----- src/decode.c | 5 +++++ src/decode.h | 1 + src/detect-engine-alert.c | 10 +++++++--- src/detect.c | 2 ++ src/flow-worker.c | 6 +++++- src/flow.c | 6 +++++- 7 files changed, 34 insertions(+), 10 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index 52c6b6410d..3b96c2e7a9 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -7544,24 +7544,28 @@ "default_app_policy": { "type": "integer", "description": - "Number of packets dropped due to firewall's mode default app policy" + "Count of packets dropped due to firewall's mode default app policy" }, "default_packet_policy": { "type": "integer", "description": - "Number of packets dropped due to firewall's mode default packet policy" + "Count of packets dropped due to firewall's mode default packet policy" + }, + "flow_drop": { + "type": "integer", + "description": "Count of packets dropped due to a firewall policy that led to flow drop" }, "pre_flow_hook": { "type": "integer", - "description": "Number of packets dropped due to pre-flow hook" + "description": "Count of packets dropped due to pre-flow hook" }, "pre_stream_hook": { "type": "integer", - "description": "Number of packets dropped due to pre-stream hook" + "description": "Count of packets dropped due to pre-stream hook" }, "rules": { "type": "integer", - "description": "Number of packets dropped due to firewall rules" + "description": "Count of packets dropped due to firewall rules" } } }, diff --git a/src/decode.c b/src/decode.c index b3ee90a965..8ca4c3308f 100644 --- a/src/decode.c +++ b/src/decode.c @@ -966,6 +966,8 @@ const char *PacketDropReasonToString(enum PacketDropReason r) return "firewall pre stream hook"; case PKT_DROP_REASON_FW_FLOW_PRE_HOOK: return "firewall pre flow hook"; + case PKT_DROP_REASON_FW_FLOW_DROP: + return "firewall flow drop"; case PKT_DROP_REASON_NOT_SET: case PKT_DROP_REASON_MAX: return NULL; @@ -1014,6 +1016,8 @@ static const char *PacketDropReasonToJsonString(enum PacketDropReason r) return "firewall.drop_reason.pre_stream_hook"; case PKT_DROP_REASON_FW_FLOW_PRE_HOOK: return "firewall.drop_reason.pre_flow_hook"; + case PKT_DROP_REASON_FW_FLOW_DROP: + return "firewall.drop_reason.flow_drop"; case PKT_DROP_REASON_FW_DEFAULT_PACKET_POLICY: return "firewall.drop_reason.default_packet_policy"; case PKT_DROP_REASON_FW_DEFAULT_APP_POLICY: @@ -1056,6 +1060,7 @@ void CaptureStatsUpdate(ThreadVars *tv, const Packet *p) return; CaptureStats *s = &t_capture_stats; + if (EngineModeIsFirewall()) { /** The firewall mode and its stats counters should work as when there are two different * devices for the firewall control and the IPS control. diff --git a/src/decode.h b/src/decode.h index 63f25a617a..5a37bfe0dd 100644 --- a/src/decode.h +++ b/src/decode.h @@ -407,6 +407,7 @@ enum PacketDropReason { PKT_DROP_REASON_FW_DEFAULT_APP_POLICY, /**< drop issued by default app policy */ PKT_DROP_REASON_FW_STREAM_PRE_HOOK, /**< drop issued in the pre_stream hook */ PKT_DROP_REASON_FW_FLOW_PRE_HOOK, /**< drop issued in the pre_flow hook */ + PKT_DROP_REASON_FW_FLOW_DROP, PKT_DROP_REASON_MAX, }; diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c index 241c69a696..ae88eb66f4 100644 --- a/src/detect-engine-alert.c +++ b/src/detect-engine-alert.c @@ -154,7 +154,7 @@ int PacketAlertCheck(Packet *p, uint32_t sid) } #endif -static inline void RuleActionToFlow(const uint8_t action, Flow *f) +static inline void RuleActionToFlow(const uint8_t action, Flow *f, const bool fw_rule) { if (action & ACTION_ACCEPT) { f->flags |= FLOW_ACTION_ACCEPT; @@ -187,6 +187,10 @@ static inline void RuleActionToFlow(const uint8_t action, Flow *f) } } } + + if (fw_rule) { + f->flags |= FLOW_ACTION_BY_FIREWALL; + } } /** \brief Apply action(s) and Set 'drop' sig info, @@ -223,7 +227,7 @@ static void PacketApplySignatureActions(Packet *p, const Signature *s, const Pac p->alerts.drop.s = (Signature *)s; } if ((p->flow != NULL) && (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)) { - RuleActionToFlow(pa->action, p->flow); + RuleActionToFlow(pa->action, p->flow, is_fw_rule); } DEBUG_VALIDATE_BUG_ON(!PacketCheckAction(p, ACTION_DROP)); @@ -248,7 +252,7 @@ static void PacketApplySignatureActions(Packet *p, const Signature *s, const Pac if ((pa->action & (ACTION_PASS | ACTION_ACCEPT)) && (p->flow != NULL) && (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)) { - RuleActionToFlow(pa->action, p->flow); + RuleActionToFlow(pa->action, p->flow, is_fw_rule); } } } diff --git a/src/detect.c b/src/detect.c index af735919dd..277465eeff 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1659,6 +1659,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( if (policy->action_scope == ACTION_SCOPE_FLOW) { SCLogDebug("dropping flow"); p->flow->flags |= FLOW_ACTION_DROP; + p->flow->flags |= FLOW_ACTION_BY_FIREWALL; } } else if (policy->action & ACTION_ACCEPT) { if (policy->action_scope == ACTION_SCOPE_FLOW) { @@ -2341,6 +2342,7 @@ static void DetectRunTx(ThreadVars *tv, if (s->action_scope == ACTION_SCOPE_FLOW) { SCLogDebug("drop flow because of rule with drop action"); f->flags |= FLOW_ACTION_DROP; + f->flags |= FLOW_ACTION_BY_FIREWALL; } } } diff --git a/src/flow-worker.c b/src/flow-worker.c index 9da0ef387e..0ccf6680dc 100644 --- a/src/flow-worker.c +++ b/src/flow-worker.c @@ -427,7 +427,11 @@ static inline void FlowWorkerStreamTCPUpdate(ThreadVars *tv, FlowWorkerThreadDat } if (FlowChangeProto(p->flow) && p->flow->flags & FLOW_ACTION_DROP) { // in case f->flags & FLOW_ACTION_DROP was set by one of the dequeued packets - PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_DROP); + if (p->flow->flags & FLOW_ACTION_BY_FIREWALL) { + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FW_FLOW_DROP); + } else { + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_DROP); + } } } diff --git a/src/flow.c b/src/flow.c index 4881463819..3f0fbb6823 100644 --- a/src/flow.c +++ b/src/flow.c @@ -535,7 +535,11 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p, ThreadVars *tv, DecodeThreadVars } if (f->flags & FLOW_ACTION_DROP) { - PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_DROP); + if (f->flags & FLOW_ACTION_BY_FIREWALL) { + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FW_FLOW_DROP); + } else { + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_DROP); + } } if (f->flags & FLOW_NOPAYLOAD_INSPECTION) { From ce175eaa09655ca6fbec87f1fc57baf0bf93a207 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Thu, 14 May 2026 16:41:53 -0300 Subject: [PATCH 6/8] exceptions: add dedicated flow drop reason To better control stats counters. --- etc/schema.json | 4 ++++ src/decode.c | 4 ++++ src/decode.h | 1 + src/flow.c | 2 ++ src/flow.h | 2 ++ src/util-exception-policy.c | 1 + 6 files changed, 14 insertions(+) diff --git a/etc/schema.json b/etc/schema.json index 3b96c2e7a9..00572e921f 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -8002,6 +8002,10 @@ "description": "Number of packets dropped due to defrag memcap exception policy" }, + "exception_policy_flow_drop": { + "type": "integer", + "description": "Number of packets dropped due to an exception policy flow dropping" + }, "flow_drop": { "type": "integer", "description": "Number of packets dropped due to dropped flows" diff --git a/src/decode.c b/src/decode.c index 8ca4c3308f..ce452930aa 100644 --- a/src/decode.c +++ b/src/decode.c @@ -934,6 +934,8 @@ const char *PacketDropReasonToString(enum PacketDropReason r) return "flow memcap"; case PKT_DROP_REASON_FLOW_DROP: return "flow drop"; + case PKT_DROP_REASON_EP_FLOW_DROP: + return "exception policy flow drop"; case PKT_DROP_REASON_STREAM_ERROR: return "stream error"; case PKT_DROP_REASON_STREAM_MEMCAP: @@ -988,6 +990,8 @@ static const char *PacketDropReasonToJsonString(enum PacketDropReason r) return "ips.drop_reason.flow_memcap"; case PKT_DROP_REASON_FLOW_DROP: return "ips.drop_reason.flow_drop"; + case PKT_DROP_REASON_EP_FLOW_DROP: + return "ips.drop_reason.exception_policy_flow_drop"; case PKT_DROP_REASON_STREAM_ERROR: return "ips.drop_reason.stream_error"; case PKT_DROP_REASON_STREAM_MEMCAP: diff --git a/src/decode.h b/src/decode.h index 5a37bfe0dd..5b6e2a1220 100644 --- a/src/decode.h +++ b/src/decode.h @@ -386,6 +386,7 @@ enum PacketDropReason { PKT_DROP_REASON_DEFRAG_MEMCAP, PKT_DROP_REASON_FLOW_MEMCAP, PKT_DROP_REASON_FLOW_DROP, + PKT_DROP_REASON_EP_FLOW_DROP, PKT_DROP_REASON_APPLAYER_ERROR, PKT_DROP_REASON_APPLAYER_MEMCAP, PKT_DROP_REASON_RULES, diff --git a/src/flow.c b/src/flow.c index 3f0fbb6823..75995cb9c1 100644 --- a/src/flow.c +++ b/src/flow.c @@ -537,6 +537,8 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p, ThreadVars *tv, DecodeThreadVars if (f->flags & FLOW_ACTION_DROP) { if (f->flags & FLOW_ACTION_BY_FIREWALL) { PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FW_FLOW_DROP); + } else if (f->flags & FLOW_ACTION_BY_EXCEPTION_POLICY) { + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_EP_FLOW_DROP); } else { PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_FLOW_DROP); } diff --git a/src/flow.h b/src/flow.h index 1e3a77f2bd..f3e7db8ce5 100644 --- a/src/flow.h +++ b/src/flow.h @@ -123,6 +123,8 @@ typedef struct AppLayerParserState_ AppLayerParserState; /** Flow action issued by firewall */ #define FLOW_ACTION_BY_FIREWALL BIT_U64(32) +/** Flow action issued by exception policy */ +#define FLOW_ACTION_BY_EXCEPTION_POLICY BIT_U64(33) /* File flags */ diff --git a/src/util-exception-policy.c b/src/util-exception-policy.c index 4276636fb2..fd105d139c 100644 --- a/src/util-exception-policy.c +++ b/src/util-exception-policy.c @@ -163,6 +163,7 @@ void ExceptionPolicyApply(Packet *p, enum ExceptionPolicy policy, enum PacketDro SCLogDebug("EXCEPTION_POLICY_DROP_FLOW"); if (p->flow) { p->flow->flags |= FLOW_ACTION_DROP; + p->flow->flags |= FLOW_ACTION_BY_EXCEPTION_POLICY; FlowSetNoPayloadInspectionFlag(p->flow); StreamTcpDisableAppLayer(p->flow); } From 5455c1b9a7268661ff8205f940a775e5278a1fe8 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Thu, 21 May 2026 16:58:14 -0300 Subject: [PATCH 7/8] qa/live: update tests for fw stats counters Part of Ticket #7699 --- qa/live/netns/afp-fw-netns-bridge.sh | 5 +++-- qa/live/netns/nfq-fw-netns-route.sh | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/qa/live/netns/afp-fw-netns-bridge.sh b/qa/live/netns/afp-fw-netns-bridge.sh index 190571828b..99a4e352bf 100755 --- a/qa/live/netns/afp-fw-netns-bridge.sh +++ b/qa/live/netns/afp-fw-netns-bridge.sh @@ -248,8 +248,8 @@ SID201=$(jq -c 'select(.alert.signature_id==201)' ./eve.json | wc -l) SID202=$(jq -c 'select(.alert.signature_id==202)' ./eve.json | wc -l) echo "SID201 $SID201 SID202 $SID202" -ACCEPTED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.ips.accepted') -BLOCKED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.ips.blocked') +ACCEPTED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.firewall.accepted') +BLOCKED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.firewall.blocked') KERNEL_PACKETS=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.capture.kernel_packets') echo "ACCEPTED $ACCEPTED BLOCKED $BLOCKED KERNEL_PACKETS $KERNEL_PACKETS" @@ -303,6 +303,7 @@ fi echo "* dumping some stats..." cat ./eve.json | jq -c 'select(.http)'|tail -n1|jq cat ./eve.json | jq -c 'select(.stats)|.stats.ips'|tail -n1|jq +cat ./eve.json | jq -c 'select(.stats)|.stats.firewall'|tail -n1|jq cat ./eve.json | jq -c 'select(.stats)|.stats.capture'|tail -n1|jq echo "* dumping some stats... done" diff --git a/qa/live/netns/nfq-fw-netns-route.sh b/qa/live/netns/nfq-fw-netns-route.sh index 060211cdfd..31b1a2fc84 100755 --- a/qa/live/netns/nfq-fw-netns-route.sh +++ b/qa/live/netns/nfq-fw-netns-route.sh @@ -268,8 +268,8 @@ SID201=$(jq -c 'select(.alert.signature_id==201)' ./eve.json | wc -l) SID202=$(jq -c 'select(.alert.signature_id==202)' ./eve.json | wc -l) echo "SID201 $SID201 SID202 $SID202" -ACCEPTED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.ips.accepted') -BLOCKED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.ips.blocked') +ACCEPTED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.firewall.accepted') +BLOCKED=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.firewall.blocked') echo "ACCEPTED $ACCEPTED BLOCKED $BLOCKED" if [ $ACCEPTED -eq 0 ]; then @@ -317,6 +317,7 @@ fi echo "* dumping some stats..." cat ./eve.json | jq -c 'select(.http)'|tail -n1|jq +cat ./eve.json | jq -c 'select(.stats)|.stats.firewall'|tail -n1|jq cat ./eve.json | jq -c 'select(.stats)|.stats.ips'|tail -n1|jq echo "* dumping some stats... done" From ad8ec7497804c6658f669908f2c81b8b86028aef Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Mon, 25 May 2026 13:22:34 -0300 Subject: [PATCH 8/8] detect/parse: convert Notice Log into Debug --- src/detect-parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 82822ba8bf..4bf0fdc5e1 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -3737,7 +3737,7 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p int idx = 0; SCConfNode *paction = NULL; TAILQ_FOREACH (paction, &policy_actions->head, next) { - SCLogNotice("fw: %s => %s", policy_name, paction->val); + SCLogDebug("fw: %s => %s", policy_name, paction->val); if (SigParseActionDo(paction->val, idx, true, &action, &action_scope) < 0) return -1; idx++;