Suricata源码分析-配置模块加载
初始化加载配置
ConfYamlLoadFile(主函数)
模块主函数是通过SuricataMain -> LoadYamlConfig -> ConfYamlLoadFile 进入的,Suricata中解析YAML文件是通过c语言libyaml库实现的,libyaml库有三种方式来解析YAML文件,本程序中使用了基于事件(event-based)的方式。
tips:以下说明默认了解libyaml库的基本使用
/* 从YAML文件加载配置。
* 这个函数将加载一个配置文件。失败返回-1并退出,加载配置文件时的任何错误都将被记录下来
* 参数:要加载的配置文件名字
*/
int ConfYamlLoadFile(const char *filename)
{
FILE *infile; // 文件指针
yaml_parser_t parser; // yaml解析器
int ret;
ConfNode *root = ConfGetRootNode(); // 配置节点树的根节点
// 初始化yaml解析器
if (yaml_parser_initialize(&parser) != 1) {
SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
return -1;
}
// 检查文件名,是否是目录
struct stat stat_buf;
if (stat(filename, &stat_buf) == 0) {
if (stat_buf.st_mode & S_IFDIR) {
SCLogError(SC_ERR_FATAL,
"yaml argument is not a file but a directory: %s. "
"Please specify the yaml file in your -c option.",
filename);
yaml_parser_delete(&parser);
return -1;
}
}
// 打开文件并检查文件指针
// coverity[toctou : FALSE]
infile = fopen(filename, "r");
if (infile == NULL) {
SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename, strerror(errno));
yaml_parser_delete(&parser);
return -1;
}
// 设置配置文件的目录名
if (conf_dirname == NULL) {
ConfYamlSetConfDirname(filename);
}
// 打开YAML文件,开始递归解析
yaml_parser_set_input_file(&parser, infile);
ret = ConfYamlParse(&parser, root, 0, 0);
yaml_parser_delete(&parser);
fclose(infile);
return ret;
}
ConfYamlParse(递归解析函数)
ConfYamlParse是一个递归的函数,按层级解析yaml文件(当递归次数达到128时,解析会失败)。这个函数主要是按照事件类型(详见yaml.h)判断,进行对应的操作。以下截取几个重要的事件类型进行说明
1. Document Start事件
suricata.yaml文件最开始写有字符串%YAML 1.1 ---,是用来注明YAML文件版本的。当该事件触发时,程序会验证YAML文件版本,来判断此配置文件是否有效。
// 验证YAML版本,如果版本正确,它就更有可能是一个有效的Suricata配置文件
yaml_version_directive_t *ver = event.data.document_start.version_directive;
// 版本信息为空,输出提示信息跳转失败
if (ver == NULL) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid configuration file.");
SCLogError(SC_ERR_CONF_YAML_ERROR, "The configuration file must begin with the "
"following two lines: %%YAML 1.1 and ---");
goto fail;
}
// 验证版本信息
int major = ver->major;
int minor = ver->minor;
// 验证失败,输出提示信息跳转失败
if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid YAML version. Must be 1.1");
goto fail;
}
2. Scalar事件
scalar是最主要的事件,用来获取具体的配置内容。
首先根据参数inseq判断是否是列表,如果是列表就开始添加节点
// 拼接节点名
char sequence_node_name[DEFAULT_NAME_LEN];
snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
// 通过名称查找子节点
ConfNode *seq_node = ConfNodeLookupChild(parent, sequence_node_name);
// 如果节点不为空,就删除它
if (seq_node != NULL) {
/* 这个序列节点已经被设置,可能是从命令行中。删除它,
* 以便按照迭代的期望序列重新添加。
*/
TAILQ_REMOVE(&parent->head, seq_node, next);
} else { // 如果节点为空,则分配一个新节点
seq_node = ConfNodeNew();
if (unlikely(seq_node == NULL)) {
goto fail;
}
// 拷贝节点名称
seq_node->name = SCStrdup(sequence_node_name);
if (unlikely(seq_node->name == NULL)) {
SCFree(seq_node);
goto fail;
}
// 如果节点value不为空,则拷贝
if (value != NULL) {
seq_node->val = SCStrdup(value);
if (unlikely(seq_node->val == NULL)) {
SCFree(seq_node->name);
goto fail;
}
} else { // 节点值为空
seq_node->val = NULL;
}
}
// 插入节点到队伍末尾
TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
如果不是列表,再根据配置处理状态分为三种情况:CONF_INCLUDE,CONF_KEY,CONF_VALUE
CONF_INCLUDE:include关键字用来包含另一个YAML文件,要调用ConfYamlHandleInclude函数解析另外的YAML文件
// 用来解析配置中包含的YAML文件
static int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
{
yaml_parser_t parser;
char include_filename[PATH_MAX];
FILE *file = NULL;
int ret = -1;
// 初始化yaml对象
if (yaml_parser_initialize(&parser) != 1) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "Failed to initialize YAML parser");
return -1;
}
// 验证路径是否是绝对路径
if (PathIsAbsolute(filename)) {
strlcpy(include_filename, filename, sizeof(include_filename));
} else {
// 拼接出绝对路径
snprintf(include_filename, sizeof(include_filename), "%s/%s", conf_dirname, filename);
}
// 打开文件并检测
file = fopen(include_filename, "r");
if (file == NULL) {
SCLogError(SC_ERR_FOPEN, "Failed to open configuration include file %s: %s",
include_filename, strerror(errno));
goto done;
}
// 关联文件句柄和yaml对象
yaml_parser_set_input_file(&parser, file);
// 递归解析
if (ConfYamlParse(&parser, parent, 0, 0) != 0) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "Failed to include configuration file %s", filename);
goto done;
}
ret = 0;
// 清理环境,退出
done:
yaml_parser_delete(&parser);
if (file != NULL) {
fclose(file);
}
return ret;
}
CONF_KEY:获取到键,添加新节点,检测是否有非法字符'_'并输出警告
// include关键字用来包含另一个yaml文件,需要另外解析
if (strcmp(value, "include") == 0) {
state = CONF_INCLUDE;
goto next;
}
/* 判断父节点如果是序列,并且为空。则将
* value 拷贝过去,并替换不支持的字符
*/
if (parent->is_seq) {
if (parent->val == NULL) {
parent->val = SCStrdup(value);
if (parent->val && strchr(parent->val, '_'))
// 损坏不支持的字符
Mangle(parent->val);
}
}
// 通过名称查找子节点
ConfNode *existing = ConfNodeLookupChild(parent, value);
// 如果查找结果不为空,需要重定义节点
if (existing != NULL) {
if (!existing->final) {
SCLogInfo("Configuration node '%s' redefined.", existing->name);
// 修整配置节点(类似释放,但留下final参数)
ConfNodePrune(existing);
}
node = existing;
} else {
// 查找结果为空,则分配一个新的节点
node = ConfNodeNew();
// 拷贝数据
node->name = SCStrdup(value);
// 遇到不支持的字符'_'且不属于特殊的两组的配置,则输出警告:不建议使用……
if (node->name && strchr(node->name, '_')) {
if (!(parent->name &&
((strcmp(parent->name, "address-groups") == 0) ||
(strcmp(parent->name, "port-groups") == 0)))) {
// 损坏不支持的字符
Mangle(node->name);
if (mangle_errors < MANGLE_ERRORS_MAX) {
SCLogWarning(SC_WARN_DEPRECATED,
"%s is deprecated. Please use %s on line %" PRIuMAX ".",
value, node->name, (uintmax_t)parser->mark.line + 1);
mangle_errors++;
// 警告提示10次以上则不再提示
if (mangle_errors >= MANGLE_ERRORS_MAX)
SCLogWarning(SC_WARN_DEPRECATED,
"not showing more "
"parameter name warnings.");
}
}
}
// 插入节点到队伍末尾
TAILQ_INSERT_TAIL(&parent->head, node, next);
}
state = CONF_VAL; // 配置处理状态赋值为:值
CONF_VALUE:获取到值,添加到对应的键下
if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
SCLogInfo("Including configuration file %s at "
"parent node %s.",
value, node->name);
// 解析配置中包含的YAML文件
if (ConfYamlHandleInclude(node, value) != 0)
goto fail;
} else if (!node->final && value != NULL) {
// 拷贝数据
if (node->val != NULL)
SCFree(node->val);
node->val = SCStrdup(value);
}
state = CONF_KEY;
3. Sequence Start事件
sequence事件说明此时是列表,需要继续递归解析下一层信息
4. Mapping Start事件
mapping事件触发时,根据参数inseq决定是否开始添加节点,或是继续下一层
SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
if (inseq) { // 如果是序列,开始添加节点
char sequence_node_name[DEFAULT_NAME_LEN];
snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
// 根据名称查找子配置节点
ConfNode *seq_node = ConfNodeLookupChild(node, sequence_node_name);
if (seq_node != NULL) {
// 序列节点已经被设置,可能是从命令行。删除它,以便按照迭代的预期序列重新添加
TAILQ_REMOVE(&node->head, seq_node, next);
} else {
// 分配一个新的配置节点
seq_node = ConfNodeNew();
// 检查结果
if (unlikely(seq_node == NULL)) {
goto fail;
}
// 拷贝节点名称
seq_node->name = SCStrdup(sequence_node_name);
if (unlikely(seq_node->name == NULL)) {
SCFree(seq_node);
goto fail;
}
}
seq_node->is_seq = 1;
// 插入到队列末尾
TAILQ_INSERT_TAIL(&node->head, seq_node, next);
// 继续递归
if (ConfYamlParse(parser, seq_node, 0, rlevel) != 0)
goto fail;
} else { // 如果不是序列,继续递归解析
if (ConfYamlParse(parser, node, inseq, rlevel) != 0)
goto fail;
}
state = CONF_KEY; // 配置处理状态:键
重载配置
配置的重载是在SuricataMainLoop主线程循环中,当程序接收到信号,会调用DetectEngineReload函数重新加载规则、配置等相关信息。关于配置的重载主要是通过ConfYamlLoadFileWithPrefix函数实现
/* 重载时调用的加载配置函数:从YAML文件中加载配置,插入到树的‘prefix’处
*
* 这个函数将加载一个配置文件并插入到配置树的‘prefix’节点。这意味着,如果以前缀“abc”
* 调用这个函数,并且文件包含参数“def”,他将作为“abc.def”被加载。
*
* 参数1:文件名
* 参数2:要使用的前缀名
* 返回值:成功返回0,失败返回-1
*/
int ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
{
FILE *infile;
yaml_parser_t parser;
int ret;
ConfNode *root = ConfGetNode(prefix);
// 初始化yaml对象
if (yaml_parser_initialize(&parser) != 1) {
SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
return -1;
}
// 检查 配置文件是否是目录
struct stat stat_buf;
/* coverity[toctou] */
if (stat(filename, &stat_buf) == 0) {
if (stat_buf.st_mode & S_IFDIR) {
SCLogError(SC_ERR_FATAL,
"yaml argument is not a file but a directory: %s. "
"Please specify the yaml file in your -c option.",
filename);
return -1;
}
}
// 打开配置文件
/* coverity[toctou] */
infile = fopen(filename, "r");
if (infile == NULL) {
SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename, strerror(errno));
yaml_parser_delete(&parser);
return -1;
}
// 设置配置文件的目录名
if (conf_dirname == NULL) {
ConfYamlSetConfDirname(filename);
}
// 如果作为前缀的节点不存在,添加一个占位符
if (root == NULL) {
ConfSet(prefix, "<prefix root node>");
// 通过名字查找节点
root = ConfGetNode(prefix);
// 如果不存在,退出函数
if (root == NULL) {
fclose(infile);
yaml_parser_delete(&parser);
return -1;
}
}
// 开始递归解析
yaml_parser_set_input_file(&parser, infile);
ret = ConfYamlParse(&parser, root, 0, 0);
yaml_parser_delete(&parser);
fclose(infile);
return ret;
}

浙公网安备 33010602011771号