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
This commit is contained in:
Juliana Fajardini 2026-05-20 15:42:52 -03:00 committed by Victor Julien
parent 6ba610a5d3
commit 8028b85efe
10 changed files with 187 additions and 68 deletions

View file

@ -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"

View file

@ -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);
}
}
}
}

View file

@ -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,
};

View file

@ -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 {

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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) {