【作者主页】只道当时是寻常
【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。
1. 概要
👋 在 Suricata 的配置文件中,stats 节点用于配置统计信息相关的参数,它的主要作用是控制 Suricata 如何收集和输出统计数据,帮助用户了解 Suricata 的运行状态和网络活动情况。
2. 全局配置
下面是Suricata对于统计模块默认的全局配置信息。
# Global stats configuration
stats:
enabled: yes
# The interval field (in seconds) controls the interval at
# which stats are updated in the log.
interval: 8
# Add decode events to stats.
#decoder-events: true
# Decoder event prefix in stats. Has been 'decoder' before, but that leads
# to missing events in the eve.stats records. See issue #2225.
#decoder-events-prefix: "decoder.event"
# Add stream events as stats.
#stream-events: false
-
enabled:[yes/no] 用于开启或关闭统计信息收集功能。
-
interval: 表示统计信息输出的时间间隔。单位通常是秒。注意,由于内部线程同步的原因,如果时间设置小于3~4s时会出现异常。
-
decoder-events:在统计信息中添加解码异常事件统计(例如解析过程中畸形报文)。默认是开启状态。如果关闭则设置为false。
-
decoder-events-prefix:自定义decode-events类型的事件在统计信息中的前缀,默认是"decoder.event"。
-
stream-events:在统计信息中添加流异常事件统计。默认是开启状态。如果关闭则设置为false。
2.1 配置解析
Suricata中,统计模块全局配置解析函数为 StatsInitCtxPreOutput,代码实现如下,下面实现了对配置文件中enabled、interval、decoder-events等键的配置信息的解析与设置。
/**
* \brief Initializes stats context
*/
static void StatsInitCtxPreOutput(void)
{
SCEnter();
ConfNode *stats = GetConfig();
if (stats != NULL) {
const char *enabled = ConfNodeLookupChildValue(stats, "enabled");
if (enabled != NULL && ConfValIsFalse(enabled)) {
stats_enabled = false;
SCLogDebug("Stats module has been disabled");
SCReturn;
}
/* warn if we are using legacy config to enable stats */
ConfNode *gstats = ConfGetNode("stats");
if (gstats == NULL) {
SCLogWarning("global stats config is missing. "
"Stats enabled through legacy stats.log. "
"See %s/configuration/suricata-yaml.html#stats",
GetDocURL());
}
const char *interval = ConfNodeLookupChildValue(stats, "interval");
if (interval != NULL)
if (StringParseUint32(&stats_tts, 10, 0, interval) < 0) {
SCLogWarning("Invalid value for "
"interval: \"%s\". Resetting to %d.",
interval, STATS_MGMTT_TTS);
stats_tts = STATS_MGMTT_TTS;
}
int b;
int ret = ConfGetChildValueBool(stats, "decoder-events", &b);
if (ret) {
stats_decoder_events = (b == 1);
}
ret = ConfGetChildValueBool(stats, "stream-events", &b);
if (ret) {
stats_stream_events = (b == 1);
}
const char *prefix = NULL;
if (ConfGet("stats.decoder-events-prefix", &prefix) != 1) {
prefix = "decoder.event";
}
stats_decoder_events_prefix = prefix;
}
SCReturn;
}
2.2 解码事件(decode-events)
在全局统计配置中,decoder-events 键用于控制解码事件计数器的开关,其默认处于开启状态。关于解码事件的定义,可参考 2.1 配置解析章节。当 decoder-events 键设置为 false 时,会同步将 stats_decoder_events 变量也设置为 false。在 Suricata 中,PacketUpdateEngineEventCounters 函数负责更新引擎事件的计数器,具体代码如下:
void PacketUpdateEngineEventCounters(ThreadVars *tv,
DecodeThreadVars *dtv, Packet *p)
{
for (uint8_t i = 0; i < p->events.cnt; i++) {
const uint8_t e = p->events.events[i];
printf("PacketUpdateEngineEventCounters: e %d\n", e);
if (e <= DECODE_EVENT_PACKET_MAX && !stats_decoder_events)
continue;
else if (e > DECODE_EVENT_PACKET_MAX && !stats_stream_events)
continue;
StatsIncr(tv, dtv->counter_engine_events[e]);
}
}
从代码可知,仅当 e <= DECODE_EVENT_PACKET_MAX 条件满足时,才会判断 stats_decoder_events 标志位。若该标志位设为 false,则不会执行事件计数器的增加操作(StatsIncr)。可见,DECODE_EVENT_PACKET_MAX 这个宏是关键,值小于它的事件类型属于解码事件,这些事件大多是解码时报文格式异常产生的,种类繁多。也就是说,decoder-events 用于控制统计信息中是否包含解码异常包的事件统计。下面是这些事件的宏,由于数量太多,这里不全部展示,更多宏定义查看decode-events.h文件中对于事件类型的枚举结构。
/* packet decoder events */
enum {
AFP_TRUNC_PKT = 0, /**< packet truncated by af-packet */
/* IPV4 EVENTS */
IPV4_PKT_TOO_SMALL, /**< ipv4 pkt smaller than minimum header size */
IPV4_HLEN_TOO_SMALL, /**< ipv4 header smaller than minimum size */
IPV4_IPLEN_SMALLER_THAN_HLEN, /**< ipv4 pkt len smaller than ip header size */
IPV4_TRUNC_PKT, /**< truncated ipv4 packet */
/* IPV4 OPTIONS */
IPV4_OPT_INVALID, /**< invalid ip options */
IPV4_OPT_INVALID_LEN, /**< ip options with invalid len */
IPV4_OPT_MALFORMED, /**< malformed ip options */
IPV4_OPT_PAD_REQUIRED, /**< pad bytes are needed in ip options */
IPV4_OPT_EOL_REQUIRED, /**< "end of list" needed in ip options */
IPV4_OPT_DUPLICATE, /**< duplicated ip option */
IPV4_OPT_UNKNOWN, /**< unknown ip option */
IPV4_WRONG_IP_VER, /**< wrong ip version in ip options */
IPV4_WITH_ICMPV6, /**< IPv4 packet with ICMPv6 header */
/* ICMP EVENTS */
ICMPV4_PKT_TOO_SMALL, /**< icmpv4 packet smaller than minimum size */
ICMPV4_UNKNOWN_TYPE, /**< icmpv4 unknown type */
ICMPV4_UNKNOWN_CODE, /**< icmpv4 unknown code */
ICMPV4_IPV4_TRUNC_PKT, /**< truncated icmpv4 packet */
ICMPV4_IPV4_UNKNOWN_VER, /**< unknown version in icmpv4 packet*/
...
}
2.3 流事件(stream-events)
在全局统计配置中,stream-events 键用于控制流事件开关,默认开启。与 decoder-events 键配置类似(详见 2.1、2.2 章节),根据 StatsInitCtxPreOutput 函数实现,当 stream-events 键设为 false 时,stats_stream_events 变量也会同步置为 false。在 PacketUpdateEngineEventCounters 函数中,仅当 e > DECODE_EVENT_PACKET_MAX 时才判断 stats_stream_events 标志位,若该标志位为 false,则不执行事件计数器增加操作(StatsIncr)。相关事件宏众多,具体定义可查看 decode-events.h 文件中的事件类型枚举结构。
/* packet decoder events */
enum {
...
/* END OF DECODE EVENTS ON SINGLE PACKET */
DECODE_EVENT_PACKET_MAX = GENERIC_TOO_MANY_LAYERS,
/* STREAM EVENTS */
STREAM_3WHS_ACK_IN_WRONG_DIR,
STREAM_3WHS_ASYNC_WRONG_SEQ,
STREAM_3WHS_RIGHT_SEQ_WRONG_ACK_EVASION,
STREAM_3WHS_SYNACK_IN_WRONG_DIRECTION,
STREAM_3WHS_SYNACK_RESEND_WITH_DIFFERENT_ACK,
STREAM_3WHS_SYNACK_RESEND_WITH_DIFF_SEQ,
STREAM_3WHS_SYNACK_TOSERVER_ON_SYN_RECV,
STREAM_3WHS_SYNACK_WITH_WRONG_ACK,
STREAM_3WHS_SYNACK_FLOOD,
STREAM_3WHS_SYNACK_TFO_DATA_IGNORED,
STREAM_3WHS_SYN_RESEND_DIFF_SEQ_ON_SYN_RECV,
STREAM_3WHS_SYN_TOCLIENT_ON_SYN_RECV,
STREAM_3WHS_SYN_FLOOD,
STREAM_3WHS_WRONG_SEQ_WRONG_ACK,
STREAM_3WHS_ACK_DATA_INJECT,
STREAM_4WHS_SYNACK_WITH_WRONG_ACK,
STREAM_4WHS_SYNACK_WITH_WRONG_SYN,
STREAM_4WHS_WRONG_SEQ,
STREAM_4WHS_INVALID_ACK,
STREAM_CLOSEWAIT_ACK_OUT_OF_WINDOW,
STREAM_CLOSEWAIT_FIN_OUT_OF_WINDOW,
STREAM_CLOSEWAIT_PKT_BEFORE_LAST_ACK,
STREAM_CLOSEWAIT_INVALID_ACK,
...
}
3. outputs配置
在Suricata中,统计信息除了全局配置外,在outputs节点中也包含了对统计模块的配置,格式如下:
# Configure the type of alert (and other) logging you would like.
outputs:
# Stats.log contains data from various counters of the Suricata engine.
- stats:
enabled: yes
filename: stats.log
append: no # append to file (yes) or overwrite it (no)
totals: yes # stats for all threads merged together
threads: no # per thread stats
null-values: yes # print counters that have value 0. Default: no
-
enabled:统计日志输出具备独立的开关。同时,全局配置里存在stats关键字,其中enabled字段用于控制整个统计模块的开启与关闭。而在当前语境下,enabled字段的作用是决定是否生成统计文件并写入统计信息。
-
filename:统计信息文件名。结合default-log-dir关键字决定该文件的路径。
-
append:(yes/no)yes表示以追加的形式写入文件,no表示以覆盖的形式写入文件。
-
totals:(yes/no)yes表示所有线程的统计数据合并在一起了。
-
threads:(yes/no)yes表示打印每一个线程的他统计。
-
null-values:(yes/no)yes表示在打印统计信息时,即使计数器值为0也要打印出。默认是不打印的。
3.1 配置解析
在Suricata中,outputs节点的stats关键字配置信息的解析函数为LogStatsLogInitCtx,代码实现如下:
static OutputInitResult LogStatsLogInitCtx(ConfNode *conf)
{
OutputInitResult result = { NULL, false };
LogFileCtx *file_ctx = LogFileNewCtx();
if (file_ctx == NULL) {
SCLogError("couldn't create new file_ctx");
return result;
}
if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) {
LogFileFreeCtx(file_ctx);
return result;
}
LogStatsFileCtx *statslog_ctx = SCMalloc(sizeof(LogStatsFileCtx));
if (unlikely(statslog_ctx == NULL)) {
LogFileFreeCtx(file_ctx);
return result;
}
memset(statslog_ctx, 0x00, sizeof(LogStatsFileCtx));
statslog_ctx->flags = LOG_STATS_TOTALS;
if (conf != NULL) {
const char *totals = ConfNodeLookupChildValue(conf, "totals");
const char *threads = ConfNodeLookupChildValue(conf, "threads");
const char *nulls = ConfNodeLookupChildValue(conf, "null-values");
SCLogDebug("totals %s threads %s", totals, threads);
if ((totals != NULL && ConfValIsFalse(totals)) &&
(threads != NULL && ConfValIsFalse(threads))) {
LogFileFreeCtx(file_ctx);
SCFree(statslog_ctx);
SCLogError("Cannot disable both totals and threads in stats logging");
return result;
}
if (totals != NULL && ConfValIsFalse(totals)) {
statslog_ctx->flags &= ~LOG_STATS_TOTALS;
}
if (threads != NULL && ConfValIsTrue(threads)) {
statslog_ctx->flags |= LOG_STATS_THREADS;
}
if (nulls != NULL && ConfValIsTrue(nulls)) {
statslog_ctx->flags |= LOG_STATS_NULLS;
}
SCLogDebug("statslog_ctx->flags %08x", statslog_ctx->flags);
}
statslog_ctx->file_ctx = file_ctx;
OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
if (unlikely(output_ctx == NULL)) {
LogFileFreeCtx(file_ctx);
SCFree(statslog_ctx);
return result;
}
output_ctx->data = statslog_ctx;
output_ctx->DeInit = LogStatsLogDeInitCtx;
SCLogDebug("STATS log output initialized");
result.ctx = output_ctx;
result.ok = true;
return result;
}
4. 源码实现
上面是Suricata中统计模块的启动与运行流程,SuricataMain函数是Suricata的主入口函数,其中在主函数中,关于统计模块函数调用依次是PostConfLoadedSetup、PreRunPostPrivsDropInit和RunModeDispatch。