配置部分主要涉及两个变量:

  • main函数中的局部变量SCInstance suri
  • conf.c中的静态全局变量static ConfNode *root

SCInstance suri

suricata.h 204行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct SCInstance_ {
enum RunModes run_mode;

char pcap_dev[128];
char *sig_file;
int sig_file_exclusive;
const char *pid_filename;
char *regex_arg;

char *keyword_info;
char *runmode_custom_mode;
#ifndef OS_WIN32
const char *user_name;
const char *group_name;
uint8_t do_setuid;
uint8_t do_setgid;
uint32_t userid;
uint32_t groupid;
#endif /* OS_WIN32 */
int delayed_detect;
int disabled_detect;
int daemon;
int offline;
int verbose;
int checksum_validation;

struct timeval start_time;

const char *log_dir;
const char *progname; /**< pointer to argv[0] */
const char *conf_filename;
} SCInstance;
runmodes.h 113行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* Run mode */
enum RunModes {
RUNMODE_UNKNOWN = 0,
RUNMODE_PCAP_DEV,
RUNMODE_PCAP_FILE,
RUNMODE_PFRING,
RUNMODE_NFQ,
RUNMODE_NFLOG,
RUNMODE_IPFW,
RUNMODE_ERF_FILE,
RUNMODE_DAG,
RUNMODE_AFP_DEV,
RUNMODE_NETMAP,
RUNMODE_TILERA_MPIPE,
RUNMODE_UNITTEST,
RUNMODE_NAPATECH,
RUNMODE_UNIX_SOCKET,
RUNMODE_USER_MAX, /* Last standard running mode */
RUNMODE_LIST_KEYWORDS,
RUNMODE_LIST_APP_LAYERS,
RUNMODE_LIST_CUDA_CARDS,
RUNMODE_LIST_RUNMODES,
RUNMODE_PRINT_VERSION,
RUNMODE_PRINT_BUILDINFO,
RUNMODE_PRINT_USAGE,
RUNMODE_DUMP_CONFIG,
RUNMODE_CONF_TEST,
RUNMODE_LIST_UNITTEST,
RUNMODE_ENGINE_ANALYSIS,
#ifdef OS_WIN32
RUNMODE_INSTALL_SERVICE,
RUNMODE_REMOVE_SERVICE,
RUNMODE_CHANGE_SERVICE_PARAMS,
#endif
RUNMODE_MAX,
};

ConfInit()初始化root变量后,开始解析命令行参数,会填充suri变量和root变量所链接的表结构。

main函数中定义了SCInstance suri;,其中成员如下:

  • run_mode
    • 枚举类型,依据命令行参数中指定的抓包引擎而设定,比如--pcap参数会设定run_mode为RUNMODE_PCAP_DEV。命令行参数解析完成后会写入全局变量run_mode
  • pcap_dev
    • 命令行参数提供的抓包设备的名字,超长会截断至128字节限制。比如--pcap参数如果有值,则会存储该值或该IP所对应的设备名,同时会被传入函数int LiveRegisterDevice(const char *dev),用以将需要抓包的设备连接到一个链表中。命令行参数多次传入设备名时,该字段每次会被清空再使用,这个字段看起来更像是一个缓冲区的作用。在后续的配置文件加载完成后,会再次检查该字段,用以确定是否需要使用配置文件中的设备列表,如果使用配置文件中的列表同样需要注册到前文提到的链表中。
  • sig_file
    • 填充为命令行参数-s/-S的值,用于记录需要包含或排除的特征文件。
  • sig_file_exclusive
    • 用于标识sig_file是需要包含的特征文件还是需要排除的特征文件。
  • pid_filename
    • 填充为命令行参数--pidfile的值。
  • regex_arg
    • 填充为命令行参数-U的值,unittest使用的过滤器。
  • keyword_info
    • 填充为命令行参数--list-keywords的值(如果提供的话),这里的keyword是检测引擎使用的特征关键字。
  • runmode_custom_mode
    • 填充为命令行参数中的--runmode值。
  • delayed_detect
    • 由配置文件中的detect.delayed-detect项设定。设定为真的话,将在抓包启动开始加载特征文件,这将有助于降低IPS模式下的宕机时间。
  • disabled_detect
    • 由编译参数决定初始值,同时命令行参数--disable-detection也会将该值设定为真。
  • daemon
    • 命令行参数出现-D则置1,用于标识是否以守护进程运行。
  • offline
    • 命令行参数解析完成后,以runmode项值确定,以下值将使offline为真。
      • RUNMODE_CONF_TEST
      • RUNMODE_PCAP_FILE
      • RUNMODE_ERF_FILE
      • RUNMODE_ENGINE_ANALYSIS
      • RUNMODE_UNIX_SOCKET
  • verbose
    • 由命令行参数-v设定,影响日志模块,使日志等级提升一个值。
  • checksum_validation
    • 默认值为unknown。若命令行提供了-k参数,则设置为all(1)或none(0)。若加载配置文件后该值依然为unknown,则会读取配置文件项capture.checksum-validation,若为all或none则设置该值。进一步影响配置文件项stream.checksum-validation,也就是说命令行参数的有效值是会覆盖配置文件项的。配置文件项stream.checksum-validation在使用时值为”1”为检查checksum,其他值皆不检查。
  • start_time
    • 记录启动时间,进程结束后会计算运行时间。
  • log_dir
    • 记录日志文件所在目录。该值的配置优先级为:
      1. 命令行参数-l
      2. 配置文件项default-log-dir
      3. 宏定义#define DEFAULT_LOG_DIR "/var/log/suricata"
  • progname
    • 顾名思义,存储argv[0]
  • conf_filename
    • 配置文件路径。该值的配置优先级为:
      1. 命令行参数-c
      2. 宏定义#define DEFAULT_CONF_FILE CONFIG_DIR "/suricata.yaml"

设定的抓包设备会注册在一个链表中

util-device.c 68行
1
int LiveRegisterDevice(const char *dev)

链表结构为

util-device.c 40行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define MAX_DEVNAME 10

/** storage for live device names */
typedef struct LiveDevice_ {
char *dev; /**< the device (e.g. "eth0") */
char dev_short[MAX_DEVNAME + 1];
int ignore_checksum;
SC_ATOMIC_DECLARE(uint64_t, pkts);
SC_ATOMIC_DECLARE(uint64_t, drop);
SC_ATOMIC_DECLARE(uint64_t, invalid_checksums);
TAILQ_ENTRY(LiveDevice_) next;

uint32_t offload_orig; /**< original offload settings to restore @exit */
} LiveDevice;

static ConfNode *root

加载配置文件晚于命令行参数解析,配置文件路径取suri->conf_filename。若命令行参数未提供路径,则填充为默认路径DEFAULT_CONF_FILE

配置文件内存节点结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Structure of a configuration parameter.
*/
typedef struct ConfNode_ {
char *name;
char *val;

int is_seq;

/**< Flag that sets this nodes value as final. */
int final;

struct ConfNode_ *parent;
TAILQ_HEAD(, ConfNode_) head;
TAILQ_ENTRY(ConfNode_) next;
} ConfNode;
假设一个YAML配置段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vars:
address-groups:
HOME_NET: "any"
EXTERNAL_NET: "!$HOME_NET"
port-groups:
HTTP_PORTS: "80"
ORACLE_PORTS: 1521

rule-files:
- botcc.rules
- ciarmy.rules

outputs:
- fast:
enabled: yes
filename: fast.log
- eve-log:
enabled: yes
filetype: regular

这个假设的配置段包含了几种结构,足够体现suricata配置文件需要的几种结构,更深的结构可以多次组合

  • mapping内嵌套mapping
  • mapping内嵌套sequence,sequence内包含scalar
  • mapping内嵌套sequence,sequence内嵌套mapping

下图显示了几种结构在内存中的关系,其中的链表结构参考Tail queue。ConfNode结构体成员部分理解如下:

  • is_seq,两种情况下被置为1
    1. 图2的rule-files节点和图3的outputs节点,用以标明这个mapping节点的值是一个sequence,但是这个标记并没有什么实际用处,只在获取rule-files节点用以加载特征文件时检查了一下。
    2. 图3的0节点和1节点,当sequence内嵌套mapping时,在outputs和fast/eve-log节点间生成了一个中间节点,这个中间节点的is_seq被置为1,这里的标记更没有用处。
  • val,值存在几种情况
    1. sequence内scalar节点的值,这时其name为sequence内索引序号。
    2. mapping节点value是scalar类型时,val为其value的值,这时其name为key的值。
    3. 上文提到的中间节点后所连接的mapping结构的第一个key,配置文件中sequence内mapping结构全部是只有一对key-value的,且value是又一个mapping结构。在这种情况下,这个val的作用是用于查找中间节点后连接的节点。这种情况下val并没有什么作用,完全可以顺序获取。
  • final,命令行参数所生成的节点的final值被置1。当加载配置文件遇到配置需要覆盖时,final为1的节点不被覆盖,只是会被移除并重新加入链表用以保证链表顺序与配置文件写作顺序一致。
  • parent, 看起来也没什么用。

mapping内嵌套mapping
mapping内嵌套sequence,sequence内包含scalar
mapping内嵌套sequence,sequence内嵌套mapping

下面以配置节点查找操作举例使用方式

仅查找子节点一层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* \brief Lookup a child configuration node by name.
*
* Given a ConfNode this function will lookup an immediate child
* ConfNode by name and return the child ConfNode.
*
* \param node The parent configuration node.
* \param name The name of the child node to lookup.
*
* \retval A pointer the child ConfNode if found otherwise NULL.
*/
ConfNode *ConfNodeLookupChild(const ConfNode *node, const char *name)
{
ConfNode *child;

if (node == NULL || name == NULL) {
return NULL;
}

TAILQ_FOREACH(child, &node->head, next) {
if (child->name != NULL && strcmp(child->name, name) == 0)
return child;
}

return NULL;
}
直接查找定位到深层节点(调用用了单层子节点查找)。参数name可以是用英文句号分割的多层mapping结构,比如"logging.outputs"。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* \brief Get a ConfNode by name.
*
* \param name The full name of the configuration node to lookup.
*
* \retval A pointer to ConfNode is found or NULL if the configuration
* node does not exist.
*/
ConfNode *ConfGetNode(const char *name)
{
ConfNode *node = root;
char node_name[NODE_NAME_MAX];
char *key;
char *next;

if (strlcpy(node_name, name, sizeof(node_name)) >= sizeof(node_name)) {
SCLogError(SC_ERR_CONF_NAME_TOO_LONG,
"Configuration name too long: %s", name);
return NULL;
}

key = node_name;
do {
if ((next = strchr(key, '.')) != NULL)
*next++ = '\0';
node = ConfNodeLookupChild(node, key);
key = next;
} while (next != NULL && node != NULL);

return node;
}

配置部分的代码没有对YAML的所有结构完成有效的支持,比如多层sequence嵌套在suricata配置解析中就会出现问题。但由于suricata配置文件中没有出现这种结构,所以配置部分的代码对此特定应用的配置文件解析足够用了。