suricata5.0.2模块加载及运行笔记

0x00 前言

诶好久没记点儿东西了,这段时间整的杂活也比较多,最近看了一些suricata的部分,这里简单的记一下,suricata的模块是如何注册,生效以及运行的。

整个模块化的情况可以用一张图概括,出处 https://blog.csdn.net/shenwansangz/article/details/37900875

简单来说,就是在一个线程中保存了一个slot链表,每一个slot结构体中,保存了一个模块,在后续运行的时候,会对slot进行遍历,将里面的模块取出并运行。

主要分为以下几个步骤

  1. 模块注册
  2. 模块初始化
  3. 模块关联运行模式
  4. 模块运行

0x01 模块注册

主要的注册逻辑在函数RegisterAllModules中,它的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void RegisterAllModules(void)
{
// zero all module storage
memset(tmm_modules, 0, TMM_SIZE * sizeof(TmModule));

/* commanders */
TmModuleUnixManagerRegister();
........
TmModuleDebugList();
/* nflog */
TmModuleReceiveNFLOGRegister();
TmModuleDecodeNFLOGRegister();

/* windivert */
TmModuleReceiveWinDivertRegister();
TmModuleVerdictWinDivertRegister();
TmModuleDecodeWinDivertRegister();
}

所有的模块都是存储在tmm_modules数组中,它是一个TmModule结构体数组,每一个索引对于一个模块,在代码中是写死的,索引对应一个枚举类型,定义在src/tm-threads-common.h中

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
37
38
39
40
41
42
typedef enum {
TMM_FLOWWORKER,
TMM_DECODENFQ,
TMM_VERDICTNFQ,
TMM_RECEIVENFQ,
TMM_RECEIVEPCAP,
TMM_RECEIVEPCAPFILE,
TMM_DECODEPCAP,
TMM_DECODEPCAPFILE,
TMM_RECEIVEPFRING,
TMM_DECODEPFRING,
TMM_RESPONDREJECT,
TMM_DECODEIPFW,
TMM_VERDICTIPFW,
TMM_RECEIVEIPFW,
TMM_RECEIVEERFFILE,
TMM_DECODEERFFILE,
TMM_RECEIVEERFDAG,
TMM_DECODEERFDAG,
TMM_RECEIVEAFP,
TMM_DECODEAFP,
TMM_RECEIVENETMAP,
TMM_DECODENETMAP,
TMM_ALERTPCAPINFO,
TMM_RECEIVENAPATECH,
TMM_DECODENAPATECH,
TMM_STATSLOGGER,
TMM_RECEIVENFLOG,
TMM_DECODENFLOG,
TMM_RECEIVEWINDIVERT,
TMM_VERDICTWINDIVERT,
TMM_DECODEWINDIVERT,

TMM_FLOWMANAGER,
TMM_FLOWRECYCLER,
TMM_BYPASSEDFLOWMANAGER,
TMM_DETECTLOADER,

TMM_UNIXMANAGER,

TMM_SIZE,
} TmmId;

模块注册本身也比较简单,这里随便找一个注册函数看看

1
2
3
4
5
6
7
8
9
10
void TmModuleFlowWorkerRegister (void)
{
tmm_modules[TMM_FLOWWORKER].name = "FlowWorker";
tmm_modules[TMM_FLOWWORKER].ThreadInit = FlowWorkerThreadInit;
tmm_modules[TMM_FLOWWORKER].Func = FlowWorker;
tmm_modules[TMM_FLOWWORKER].ThreadDeinit = FlowWorkerThreadDeinit;
tmm_modules[TMM_FLOWWORKER].ThreadExitPrintStats = FlowWorkerExitPrintStats;
tmm_modules[TMM_FLOWWORKER].cap_flags = 0;
tmm_modules[TMM_FLOWWORKER].flags = TM_FLAG_STREAM_TM|TM_FLAG_DETECT_TM;
}

注册函数仅仅是将tmm_modules中对应的索引对应的TmModule对象

TmModule本身是一个结构体,成员变量大都是函数指针,定义如下

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
typedef struct TmModule_ {
const char *name;

/** thread handling */
TmEcode (*ThreadInit)(ThreadVars *, const void *, void **);
void (*ThreadExitPrintStats)(ThreadVars *, void *);
TmEcode (*ThreadDeinit)(ThreadVars *, void *);

/** the packet processing function */
TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);

TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);

/** terminates the capture loop in PktAcqLoop */
TmEcode (*PktAcqBreakLoop)(ThreadVars *, void *);

TmEcode (*Management)(ThreadVars *, void *);

/** global Init/DeInit */
TmEcode (*Init)(void);
TmEcode (*DeInit)(void);

void (*RegisterTests)(void);

uint8_t cap_flags; /**< Flags to indicate the capability requierment of
the given TmModule */
/* Other flags used by the module */
uint8_t flags;
} TmModule;

0x02 模块初始化

初始化主要分两个部分,一个是调用TmModule中的Init函数进行初始化,这部分只要有注册过的模块都会进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void TmModuleRunInit(void)
{
TmModule *t;
uint16_t i;

for (i = 0; i < TMM_SIZE; i++) {
t = &tmm_modules[i];

if (t->name == NULL)
continue;

if (t->Init == NULL)
continue;

t->Init();
}
}

还有一部分初始化功能是调用ThreadInit,这个只有和运行模式进行关联的模块才会调用。

0x03 模块关联运行模式

主要代码都在src/util-runmodes.c中,涉及到不同运行模式,使用的模块似乎是不一样的,这里仅仅以其中一个来举例

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
/* create the threads */
for (int thread = 0; thread < threads_count; thread++) {
.........
tm_module = TmModuleGetByName(recv_mod_name);
if (tm_module == NULL) {
SCLogError(SC_ERR_INVALID_VALUE, "TmModuleGetByName failed for %s", recv_mod_name);
exit(EXIT_FAILURE);
}
TmSlotSetFuncAppend(tv, tm_module, aconf);

tm_module = TmModuleGetByName(decode_mod_name);
if (tm_module == NULL) {
SCLogError(SC_ERR_INVALID_VALUE, "TmModuleGetByName %s failed", decode_mod_name);
exit(EXIT_FAILURE);
}
TmSlotSetFuncAppend(tv, tm_module, NULL);

tm_module = TmModuleGetByName("FlowWorker");
if (tm_module == NULL) {
SCLogError(SC_ERR_RUNMODE, "TmModuleGetByName for FlowWorker failed");
exit(EXIT_FAILURE);
}
TmSlotSetFuncAppend(tv, tm_module, NULL);

tm_module = TmModuleGetByName("RespondReject");
if (tm_module == NULL) {
SCLogError(SC_ERR_RUNMODE, "TmModuleGetByName RespondReject failed");
exit(EXIT_FAILURE);
}
TmSlotSetFuncAppend(tv, tm_module, NULL);
........
}

这里省去大部分无关代码,关联运行模式的函数为TmSlotSetFuncAppend,本身的实现这里就不展开了,简单来说,就是将tm module存入thread的slots中,在后续模块触发的时候进行操作。需要注意的是,suricata的数据包捕获模块也是在这里注册的,就是在这个循环中,第一个被关联上的模块,就是用于数据捕获的模块,在启动之后,会循环调用PktAcqLoop来进行数据包的捕获。

0x04 模块运行

这部分的代码就不详细展开了,具体的情况其实和抓包模块的实现有关系,之前简单的对其中一个抓包模块进行了调研,发现它就是在PktAcqLoop中捕获到数据包之后,又对slots中保存的所有模块进行了遍历并调用Func函数,这里找了几个实现了PktAcqLoop接口的模块,发现他们都在里面获取数据包之后,调用了TmThreadsSlotProcessPkt函数。

0x05 总结

总的来讲我觉着suricata的代码,比snort清晰很多,至少没有之前那种看得一头雾水的情况了(主要是菜),各个模块的注册关联运行之类的都挺清楚的,其他部分也看了看,目前感觉还都挺清晰的。