【网络入侵检测】基于源码分析Suricata的统计模块

【作者主页】只道当时是寻常

【专栏介绍】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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_只道当时是寻常

打赏不得超过工资的一半呦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值