当前位置: 首页 > news >正文

【网络入侵检测】基于源码分析Suricata的引擎日志配置解析

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

【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。

1. 概要

👋 Suricata 的引擎日志记录系统主要记录该引擎在启动、运行以及关闭期间应用程序的相关信息,如错误信息和其他诊断信息,但不包含 Suricata 自身生成的警报和事件。该系统设有多个日志级别,包括错误、警告、通知、信息、性能、配置和调试。

2. 配置格式

下面是Suricata配置文件中对于引擎日志默认配置:

logging:default-log-level: notice#default-log-format: "[%i] %t [%S] - (%f:%l) <%d> (%n) -- "default-output-filter:#stacktrace-on-signal: onoutputs:- console:enabled: yes# type: json- file:enabled: yeslevel: infofilename: suricata.log# format: "[%i - %m] %z %d: %S: %M"# type: json- syslog:enabled: nofacility: local5format: "[%i] <%d> -- "# type: json
  • default-log-level:此选项可以设置 Suricata 引擎日志输出级别。

    • 日志等级主要分为errowarningnoticeinfoperfconfig debug。注意,只有当 Suricata 使用 --enable-debug 配置选项进行编译时,才会发出调试级别的日志记录。

    • 注意,默认日志级别的值会被环境变量 “SC_LOG_LEVEL” 所覆盖。

  • default-log-format:自定义引擎日志输出格式。注意,默认日志级别的值会被环境变量 “SC_LOG_FORMAT” 所覆盖。日志格式详细介绍见3.4章节内容。

  • default-output-filter:自定义正则表达式,用于过滤引擎日志输出。

  • outputs:自定义Suricata 引擎日志输出类型。

    • console,输出到终端;file,输出到文件;syslog,输出到syslog。

    • enabled:(yes/no)开关选项,用于控制输出方式的开关。

    • level:用于控制日志输出等级,支持的等级为errowarningnoticeinfoperfconfig debug

    • type:(regular/json)控制引擎日志输出格式。

    • format:引擎日志格式。

    • filename:引擎日志输出类型为 file 时有效,用于指定引擎日志文件名。

    • facility:引擎日志输出类型为 syslog 时有效,此字段能够对日志消息的来源进行分类,也就是说这里定义 Suricata 通过 syslog 发送的日志类型。Suricata 支持此字段配置值分别为 authauthprivcrondaemonftpkernlprmailnewssecuritysysloguseruucplocal0local1local2local3local4local5local6local7。

3. 源码解析

3.1 配置解析

Suricata 启动后,通过调用 SCLogLoadConfig 函数解析配置文件中引擎日志模块的相关信息。在该函数执行过程中,会依次读取、解析引擎日志的各类参数,并完成初始化操作,为后续引擎日志的输出提供配置支持。

引擎日志的配置信息存储在下面数据结构中:

typedef struct SCLogInitData_ {/* startup message */const char *startup_message;/* the log level */SCLogLevel global_log_level;/* the log format */const char *global_log_format;/* output filter */const char *op_filter;/* list of output interfaces to be used */SCLogOPIfaceCtx *op_ifaces;/* no of op ifaces */uint8_t op_ifaces_cnt;
} SCLogInitData;

SCLogLoadConfig 函数实现代码如下所示:

void SCLogLoadConfig(int daemon, int verbose, uint32_t userid, uint32_t groupid)
{ConfNode *outputs;SCLogInitData *sc_lid;int have_logging = 0;int max_level = 0;SCLogLevel min_level = 0;/* If verbose logging was requested, set the minimum as* SC_LOG_NOTICE plus the extra verbosity. */if (verbose) {min_level = SC_LOG_NOTICE + verbose;}outputs = ConfGetNode("logging.outputs");if (outputs == NULL) {SCLogDebug("No logging.output configuration section found.");return;}sc_lid = SCLogAllocLogInitData();if (sc_lid == NULL) {SCLogDebug("Could not allocate memory for log init data");return;}/* Get default log level and format. */const char *default_log_level_s = NULL;if (ConfGet("logging.default-log-level", &default_log_level_s) == 1) {SCLogLevel default_log_level =SCMapEnumNameToValue(default_log_level_s, sc_log_level_map);if (default_log_level == -1) {SCLogError("Invalid default log level: %s", default_log_level_s);exit(EXIT_FAILURE);}sc_lid->global_log_level = MAX(min_level, default_log_level);}else {sc_lid->global_log_level = MAX(min_level, SC_LOG_NOTICE);}if (ConfGet("logging.default-log-format", &sc_lid->global_log_format) != 1)sc_lid->global_log_format = SCLogGetDefaultLogFormat(sc_lid->global_log_level);(void)ConfGet("logging.default-output-filter", &sc_lid->op_filter);ConfNode *seq_node, *output;TAILQ_FOREACH(seq_node, &outputs->head, next) {SCLogLevel level = sc_lid->global_log_level;SCLogOPIfaceCtx *op_iface_ctx = NULL;const char *format;const char *level_s;output = ConfNodeLookupChild(seq_node, seq_node->val);if (output == NULL)continue;/* By default an output is enabled. */const char *enabled = ConfNodeLookupChildValue(output, "enabled");if (enabled != NULL && ConfValIsFalse(enabled))continue;SCLogOPType type = SC_LOG_OP_TYPE_REGULAR;const char *type_s = ConfNodeLookupChildValue(output, "type");if (type_s != NULL) {if (strcmp(type_s, "regular") == 0)type = SC_LOG_OP_TYPE_REGULAR;else if (strcmp(type_s, "json") == 0) {type = SC_LOG_OP_TYPE_JSON;}}format = ConfNodeLookupChildValue(output, "format");level_s = ConfNodeLookupChildValue(output, "level");if (level_s != NULL) {level = SCMapEnumNameToValue(level_s, sc_log_level_map);if (level == -1) {SCLogError("Invalid log level: %s", level_s);exit(EXIT_FAILURE);}max_level = MAX(max_level, level);}/* Increase the level of extra verbosity was requested. */level = MAX(min_level, level);if (strcmp(output->name, "console") == 0) {op_iface_ctx = SCLogInitConsoleOPIface(format, level, type);}else if (strcmp(output->name, "file") == 0) {if (format == NULL) {format = SC_LOG_DEF_FILE_FORMAT;}const char *filename = ConfNodeLookupChildValue(output, "filename");if (filename == NULL) {FatalError("Logging to file requires a filename");}char *path = NULL;if (!(PathIsAbsolute(filename))) {path = SCLogGetLogFilename(filename);} else {path = SCStrdup(filename);}if (path == NULL)FatalError("failed to setup output to file");have_logging = 1;op_iface_ctx = SCLogInitFileOPIface(path, userid, groupid, format, level, type);SCFree(path);}else if (strcmp(output->name, "syslog") == 0) {int facility = SC_LOG_DEF_SYSLOG_FACILITY;const char *facility_s = ConfNodeLookupChildValue(output,"facility");if (facility_s != NULL) {facility = SCMapEnumNameToValue(facility_s, SCSyslogGetFacilityMap());if (facility == -1) {SCLogWarning("Invalid syslog ""facility: \"%s\", now using \"%s\" as syslog ""facility",facility_s, SC_LOG_DEF_SYSLOG_FACILITY_STR);facility = SC_LOG_DEF_SYSLOG_FACILITY;}}SCLogDebug("Initializing syslog logging with format \"%s\"", format);have_logging = 1;op_iface_ctx = SCLogInitSyslogOPIface(facility, format, level, type);}else {SCLogWarning("invalid logging method: %s, ignoring", output->name);}if (op_iface_ctx != NULL) {SCLogAppendOPIfaceCtx(op_iface_ctx, sc_lid);}}if (daemon && (have_logging == 0)) {SCLogWarning("no logging compatible with daemon mode selected,"" suricata won't be able to log. Please update "" 'logging.outputs' in the YAML.");}/* Set the global log level to that of the max level used. */sc_lid->global_log_level = MAX(sc_lid->global_log_level, max_level);SCLogInitLogModule(sc_lid);SCLogDebug("sc_log_global_log_level: %d", sc_log_global_log_level);SCLogDebug("sc_lc->log_format: %s", sc_log_config->log_format);SCLogDebug("SCLogSetOPFilter: filter: %s", sc_log_config->op_filter);if (sc_lid != NULL)SCFree(sc_lid);
}

3.2 日志等级

在 Suricata 中,对于引擎告警日志默认日志等级为 notice。其支持的日志等级定义在 default_log_level_s 这个结构体中,其格式如下所示:

typedef struct SCEnumCharMap_ {const char *enum_name;int enum_value;
} SCEnumCharMap;SCEnumCharMap sc_log_level_map[] = {{ "Not set",        SC_LOG_NOTSET },{ "None",           SC_LOG_NONE },{ "Error",          SC_LOG_ERROR },{ "Warning",        SC_LOG_WARNING },{ "Notice",         SC_LOG_NOTICE },{ "Info",           SC_LOG_INFO },{ "Perf",           SC_LOG_PERF },{ "Config",         SC_LOG_CONFIG },{ "Debug",          SC_LOG_DEBUG },{ NULL,             -1 }
};

3.3 日志过滤

在 Suricata 中,引擎日志具备正则表达式过滤功能。SCLogMessageGetBuffer 函数承担着依据指定的日志格式和内容,生成最终日志消息字符串的任务。当该函数完成字符串的组装工作后,若系统配置了日志过滤规则,便会调用 pcre2_match 函数,将生成的日志消息字符串与过滤规则进行精确匹配。只有当字符串命中过滤规则时,相应的日志才会被输出;若未命中,则不会进行输出操作。(注意,如果字符串是JSON格式则过滤条件不生效)

SCLogMessageGetBuffer 函数进行正则匹配部分的实现如下所示:

/*** \brief Adds the global log_format to the outgoing buffer** \param log_level log_level of the message that has to be logged* \param msg       Buffer containing the outgoing message* \param file      File_name from where the message originated* \param function  Function_name from where the message originated* \param line      Line_no from where the messaged originated** \retval 0 on success; else a negative value on error*/
static SCError SCLogMessageGetBuffer(SCTime_t tval, int color, SCLogOPType type, char *buffer,size_t buffer_size, const char *log_format, const SCLogLevel log_level, const char *file,const unsigned int line, const char *function, const char *module, const char *message)
{if (type == SC_LOG_OP_TYPE_JSON)return SCLogMessageJSON(tval, buffer, buffer_size, log_level, file, line, function, module, message);... ... // 省略部分代码实现if (sc_log_config->op_filter_regex != NULL) {if (pcre2_match(sc_log_config->op_filter_regex, (PCRE2_SPTR8)buffer, strlen(buffer), 0, 0,sc_log_config->op_filter_regex_match, NULL) < 0) {return -1; // bit hacky, but just return !0}}return 0;
}

3.4 日志格式

在 Suricata 中,默认状态下,不同告警等级对应着差异化的告警格式。若未配置 default-log-format 日志格式,系统将通过 SCLogGetDefaultLogFormat 函数,依据具体的日志等级,从预定义的日志格式集合中选取适配的格式进行应用。SCLogGetDefaultLogFormat 函数实现如下所示:

/* The default log_format, if it is not supplied by the user */
#define SC_LOG_DEF_FILE_FORMAT           "[%i - %m] %z %d: %S: %M"
#define SC_LOG_DEF_LOG_FORMAT_REL_NOTICE "%D: %S: %M"
#define SC_LOG_DEF_LOG_FORMAT_REL_INFO   "%d: %S: %M"
#define SC_LOG_DEF_LOG_FORMAT_REL_CONFIG "[%i] %d: %S: %M"
#define SC_LOG_DEF_LOG_FORMAT_DEBUG      "%d: %S: %M [%n:%f:%l]"static inline const char *SCLogGetDefaultLogFormat(const SCLogLevel lvl)
{const char *prog_ver = GetProgramVersion();if (strstr(prog_ver, "RELEASE") != NULL) {if (lvl <= SC_LOG_NOTICE)return SC_LOG_DEF_LOG_FORMAT_REL_NOTICE;else if (lvl <= SC_LOG_INFO)return SC_LOG_DEF_LOG_FORMAT_REL_INFO;else if (lvl <= SC_LOG_CONFIG)return SC_LOG_DEF_LOG_FORMAT_REL_CONFIG;}return SC_LOG_DEF_LOG_FORMAT_DEBUG;
}

日志格式定义中包含诸如 % D、% S、% M 等特殊字符,其具体含义如下:

/* The different log format specifiers supported by the API */
#define SC_LOG_FMT_TIME             'z' /* Timestamp in RFC3339 like format */
#define SC_LOG_FMT_TIME_LEGACY      't' /* Timestamp in legacy format */
#define SC_LOG_FMT_PID              'p' /* PID */
#define SC_LOG_FMT_TID              'i' /* Thread ID */
#define SC_LOG_FMT_TM               'm' /* Thread module name */
#define SC_LOG_FMT_LOG_LEVEL        'd' /* Log level */
#define SC_LOG_FMT_LOG_SLEVEL       'D' /* Log level */
#define SC_LOG_FMT_FILE_NAME        'f' /* File name */
#define SC_LOG_FMT_LINE             'l' /* Line number */
#define SC_LOG_FMT_FUNCTION         'n' /* Function */
#define SC_LOG_FMT_SUBSYSTEM        'S' /* Subsystem name */
#define SC_LOG_FMT_THREAD_NAME      'T' /* thread name */
#define SC_LOG_FMT_MESSAGE          'M' /* log message body *//* The log format prefix for the format specifiers */
#define SC_LOG_FMT_PREFIX           '%'

%z

符合 RFC3339 格式的时间戳

%t

旧格式的时间戳

%p

进程 ID(PID)

%i

线程 ID(TID)

%m

线程模块名称

%d

日志级别

%D

日志级别

%f

文件名

%l

行号

%n

函数名

%S

子系统名称

%T

线程名称

%M

日志消息主体

3.5 syslog-facility

在 Suricata 中,SCLogLoadConfig 函数负责解析日志输出类型。当检测到开启 console 输出时,会调用 SCLogInitConsoleOPIface 函数进行初始化;若开启 file 输出,则调用 SCLogInitFileOPIface 函数;而当 syslog 输出开启时,会调用 SCLogInitSyslogOPIface 函数。其中,console 和 file 输出类型的初始化函数,其功能正如我们所预期,主要是完成文件描述符的打开操作。接下来,我们着重探讨 syslog 输出模式。

Suricata在使用syslog作为输出时支持facility类型的配置,其所支持的类型存储在

sc_syslog_facility_map结构体中,其定义如下所示:

SCEnumCharMap sc_syslog_facility_map[] = {{ "auth",           LOG_AUTH },{ "authpriv",       LOG_AUTHPRIV },{ "cron",           LOG_CRON },{ "daemon",         LOG_DAEMON },{ "ftp",            LOG_FTP },{ "kern",           LOG_KERN },{ "lpr",            LOG_LPR },{ "mail",           LOG_MAIL },{ "news",           LOG_NEWS },{ "security",       LOG_AUTH },{ "syslog",         LOG_SYSLOG },{ "user",           LOG_USER },{ "uucp",           LOG_UUCP },{ "local0",         LOG_LOCAL0 },{ "local1",         LOG_LOCAL1 },{ "local2",         LOG_LOCAL2 },{ "local3",         LOG_LOCAL3 },{ "local4",         LOG_LOCAL4 },{ "local5",         LOG_LOCAL5 },{ "local6",         LOG_LOCAL6 },{ "local7",         LOG_LOCAL7 },{ NULL,             -1         }
};

SCLogInitSyslogOPIface 函数中通过调用openlog函数打开 syslog,并将facility 属性赋值。该函数实现如下所示:

/*** \brief Initializes the syslog output interface** \param facility   The facility code for syslog* \param log_format Pointer to the log_format for this op interface, that*                   overrides the global_log_format* \param log_level  Override of the global_log_level by this interface** \retval iface_ctx Pointer to the syslog output interface context created*/
static inline SCLogOPIfaceCtx *SCLogInitSyslogOPIface(int facility,const char *log_format,SCLogLevel log_level,SCLogOPType type)
{SCLogOPIfaceCtx *iface_ctx = SCLogAllocLogOPIfaceCtx();if ( iface_ctx == NULL) {FatalError("Fatal error encountered in SCLogInitSyslogOPIface. Exiting...");}iface_ctx->iface = SC_LOG_OP_IFACE_SYSLOG;iface_ctx->type = type;if (facility == -1)facility = SC_LOG_DEF_SYSLOG_FACILITY;iface_ctx->facility = facility;if (log_format != NULL &&(iface_ctx->log_format = SCStrdup(log_format)) == NULL) {printf("Error allocating memory\n");exit(EXIT_FAILURE);}iface_ctx->log_level = log_level;openlog(NULL, LOG_NDELAY, iface_ctx->facility);return iface_ctx;
}

相关文章:

  • 弹窗探索鸿蒙之旅:揭秘弹窗的本质与奥秘
  • WHAT - Tailwind CSS + Antd = MetisUI组件库
  • C++后端服务器开发:侵入式与非侵入式程序结构解析
  • Unity编辑器扩展之导出项目中所有预制体中文本组件文字内容
  • 4月30日星期三今日早报简报微语报早读
  • 第十六届蓝桥杯 2025 C/C++组 数列差分
  • 墨西哥游戏出海推广本土网盟cpi广告策略
  • 【AI提示词】SWOT分析师
  • K8S - 命名空间实战 - 从资源隔离到多环境管理
  • 【CF】Day47——Educational Codeforces Round 178 (Rated for Div. 2) E
  • C++ Lambda表达式中 与 =的作用
  • DeepSeek驱动的金市情绪量化:NLP解析贸易政策文本的情绪传导路径
  • 第十节:文本编辑
  • ES搜索知识
  • Qt窗口关闭特效:自底而上逐渐消失
  • 【补题】Codeforces Round 664 (Div. 1) A. Boboniu Chats with Du
  • 蓝桥杯赛后总结
  • AI时代生产工厂制造业数字化转型培训师培训讲师唐兴通教授专家顾问清华大学讲授AI库存降本增效智能制造供应链生产调度智能管理设备健康
  • 微波功率器件的发展
  • 部署若依项目到服务器遇到的问题
  • 上海科创的三种品格
  • 哈莉·贝瑞、洪常秀等出任戛纳主竞赛单元评委
  • 比熬夜更伤肝的事,你可能每天都在做
  • 黄永年:说狄仁杰的奏毁淫祠
  • 加总理:目前没有针对加拿大人的“活跃威胁”
  • 人民日报:光荣属于每一个挺膺担当的奋斗者