目录
WorkerThread 线程的创建与注册的slot 和 TmModule 对应的关系参见我的之前的一遍文章中创建工作线程部分
线程函数:tv->tm_func()对应的函数,TmThreadsSlotPktAcqLoop
线程初始化
packet pool线程池初始化
在TmThreadsSlotPktAcqLoop中完成线程的初始化,线程绑定cpu核心,调用void PacketPoolInit(void)韩式初始化当前线程的Packet的内存池,并根据max_pending_packets的大小,预先实例化对应数量的Packet *p 并放入packet pool中
void PacketPoolInit(void)
{
extern intmax_t max_pending_packets;
PktPool *my_pool = GetThreadPacketPool();
#ifdef DEBUG_VALIDATION
BUG_ON(my_pool->initialized);
my_pool->initialized = 1;
my_pool->destroyed = 0;
#endif /* DEBUG_VALIDATION */
SCMutexInit(&my_pool->return_stack.mutex, NULL);
SCCondInit(&my_pool->return_stack.cond, NULL);
SC_ATOMIC_INIT(my_pool->return_stack.sync_now);
/* pre allocate packets */
SCLogDebug("preallocating packets... packet size %" PRIuMAX "",
(uintmax_t)SIZE_OF_PACKET);
int i = 0;
for (i = 0; i < max_pending_packets; i++) {
Packet *p = PacketGetFromAlloc();
if (unlikely(p == NULL)) {
FatalError(SC_ERR_FATAL,
"Fatal error encountered while allocating a packet. Exiting...");
}
PacketPoolStorePacket(p);
}
//SCLogInfo("preallocated %"PRIiMAX" packets. Total memory %"PRIuMAX"",
// max_pending_packets, (uintmax_t)(max_pending_packets*SIZE_OF_PACKET));
}
检查初始设置是否设置妥当
遍历tv->tm_slots 中的slot队列,执行各个slot的初始化slot->SlotThreadInit(...),由于slot 与相应模块链(采集模块->解析模块->检测模块)对应,所以其实执行的是各个模块tm->ThreadInit(...)对应的函数,这里以{ RUNMODE_AFP_DEV + "workers" } 为例,在此工作模式下注册了ReceiveAFP ,DecodeAFP,FlowWorker,RespondReject四个模块。
在此只展开将ReceiveAFP ,DecodeAFP,FlowWorker三个模块,一下是在线程开始前已经注册好的三个模块。
/**
* \brief Registration Function for RecieveAFP.
* \todo Unit tests are needed for this module.
*/
void TmModuleReceiveAFPRegister (void)
{
tmm_modules[TMM_RECEIVEAFP].name = "ReceiveAFP";
tmm_modules[TMM_RECEIVEAFP].ThreadInit = ReceiveAFPThreadInit;
tmm_modules[TMM_RECEIVEAFP].Func = NULL;
tmm_modules[TMM_RECEIVEAFP].PktAcqLoop = ReceiveAFPLoop;
tmm_modules[TMM_RECEIVEAFP].PktAcqBreakLoop = NULL;
tmm_modules[TMM_RECEIVEAFP].ThreadExitPrintStats = ReceiveAFPThreadExitStats;
tmm_modules[TMM_RECEIVEAFP].ThreadDeinit = ReceiveAFPThreadDeinit;
tmm_modules[TMM_RECEIVEAFP].cap_flags = SC_CAP_NET_RAW;
tmm_modules[TMM_RECEIVEAFP].flags = TM_FLAG_RECEIVE_TM;
}
/**
* \brief Registration Function for DecodeAFP.
* \todo Unit tests are needed for this module.
*/
void TmModuleDecodeAFPRegister (void)
{
tmm_modules[TMM_DECODEAFP].name = "DecodeAFP";
tmm_modules[TMM_DECODEAFP].ThreadInit = DecodeAFPThreadInit;
tmm_modules[TMM_DECODEAFP].Func = DecodeAFP;
tmm_modules[TMM_DECODEAFP].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODEAFP].ThreadDeinit = DecodeAFPThreadDeinit;
tmm_modules[TMM_DECODEAFP].cap_flags = 0;
tmm_modules[TMM_DECODEAFP].flags = TM_FLAG_DECODE_TM;
}
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;
}
主要模块的初始化:
ReceiveAFP:采集,TmEcode ReceiveAFPThreadInit(ThreadVars *tv, const void *initdata, void **data),tv是附加到捕获将遇到的线程的ThreadVars。initdata是一个在运行模式代码中声明和填充的配置结构。因此,代码的主要部分是从配置中获取元素,并采取相应的操作。最后将将大部分设置存储到init函数的data输出中。
DecodeAFP:解析,TmEcode DecodeAFPThreadInit(ThreadVars *tv, const void *initdata, void **data),tv是附加到捕获将遇到的线程的ThreadVars。initdata是一个在运行模式代码中声明和填充的配置结构。因此,代码的主要部分是从配置中获取元素,并采取相应的操作。最后将将大部分设置存储到init函数的data输出中。主要是注册计数器,在解析时计数使用。
FlowWorker:检测, static TmEcode FlowWorkerThreadInit(ThreadVars *tv, const void *initdata, void **data),设置各个线程tcp的初始化,包括IP分片重组内存池和tcpSession 内存池,各线程的检测引擎加载。
将线程状态设置为 tv->flags |= THV_INIT_DONE;
线程进入循环
线程进入循环,等待外部将 此子线程的暂停状态(THV_PAUSE)取消,线程正式进入工作中,执行 tv->tm_slots 的PktAcqLoop函数:
ThreadVars *tv = (ThreadVars *)td;
TmSlot *s = tv->tm_slots;
...
while(run) {
if (TmThreadsCheckFlag(tv, THV_PAUSE)) {
TmThreadsSetFlag(tv, THV_PAUSED);
TmThreadTestThreadUnPaused(tv);
TmThreadsUnsetFlag(tv, THV_PAUSED);
}
r = s->PktAcqLoop(tv, SC_ATOMIC_GET(s->slot_data), s);
if (r == TM_ECODE_FAILED) {
TmThreadsSetFlag(tv, THV_FAILED);
run = 0;
}
if (TmThreadsCheckFlag(tv, THV_KILL_PKTACQ) || suricata_ctl_flags) {
run = 0;
}
if (r == TM_ECODE_DONE) {
run = 0;
}
}
s->PktAcqLoop(tv, SC_ATOMIC_GET(s->slot_data), s); 根据上面ReceiveAFP 模块的注册来看,此回调函数实际为TmEcode ReceiveAFPLoop(ThreadVars *tv, void *data, void *slot);此函数中主要工作是执行捕获循环。
捕获循环
在进入循环前创建socket,然后进入捕获循环,此循环有两个阶段:
Capture start:打开捕获套接字。在这里这样做很好,因为它将限制打开捕获和开始处理之间的延迟
数据包处理循环:在这个循环中,数据包被获取并发送到处理流程。
采集到的数据包处理是在TmThreadsSlotProcessPkt中完成,其原型为:
static inline TmEcode TmThreadsSlotProcessPkt(ThreadVars *tv, TmSlot *s, Packet *p),s就是前面一路传下来的slot,而p为当前要处理的Packet。处理流程如下:
static inline TmEcode TmThreadsSlotProcessPkt(ThreadVars *tv, TmSlot *s, Packet *p)
{
if (s == NULL) {
tv->tmqh_out(tv, p);
return TM_ECODE_OK;
}
TmEcode r = TmThreadsSlotVarRun(tv, p, s);
if (unlikely(r == TM_ECODE_FAILED)) {
TmThreadsSlotProcessPktFail(tv, s, p);
return TM_ECODE_FAILED;
}
tv->tmqh_out(tv, p);
TmThreadsHandleInjectedPackets(tv);
return TM_ECODE_OK;
}
- 调用TmThreadsSlotVarRun 函数,将数据包依次传入后续的各个slot进行处理。
- 若返回失败,则调用TmThreadsSlotProcessPktFail 将数据包( tv->decode_pq 数据包队列 和 tv->stream_pq_local 数据包对流中的数据包)进行回收或释放。然后设置线程标志为THV_FAILED,等待主线程处理。
- 若返回成功,则调用tmqh_out(线程创建时设置为与该线程绑定的outqh的处理函数OutHandler,在这里{ RUNMODE_AFP_DEV + "workers" } 模式下,此函数为:TmqhOutputPacketpool(ThreadVars *t, Packet *p)),将数据包送到后续队列中去。
- 此外,由于各模块在处理过程中可能会新生成数据包(如隧道数据包、重组数据包),这些数据包存储在 tv->decode_pq 或 tv->stream_pq_local 队列中,因此还需要类似上述流程,对这些数据包进行处理。这里调用 TmThreadsHandleInjectedPackets 函数只集中处理了 tv->stream_pq_local,将里面的数据包直接从 FlowWorker 开始处理。
对数据包进行进一步处理的TmThreadsSlotVarRun函数原型如下:
TmEcode TmThreadsSlotVarRun(ThreadVars *tv, Packet *p, TmSlot *slot)
按照函数头的注释说明,这个函数被从母函数中拉出来独立存在的原因是,为了能够对其进行递归调用。函数主流程是一个遍历所有slot的for循环,其执行过程如下:
- 调用slot的处理函数SlotFunc。
- 若返回失败,则调用TmThreadsSlotProcessPktFail 将数据包(以及 tv->decode_pq 数据包队列 和 tv->stream_pq_local 数据包对流中的数据包)进行回收或释放。然后设置线程标志为THV_FAILED,等待主线程处理。
- 若返回成功,则继续处理 tv->decode_pq:对其中每个数据包,都递归调用TmThreadsSlotVarRun,将其送入下一个slot进行处理。
注: tv->decode_pq 和 tv->stream_pq_local:
某个slot在处理某个母数据包时新产生的子数据包,若放入 tv->decode_pq 中,则这个数据包将在本个slot处理完母数据包后,在后续slot处理母数据包之前,先将这些子数据包放到后续的slot去处理,对应上面的流程这个对流中的数据在TmThreadsSlotVarRun函数中递归处理处理
而如果是放如 tv->stream_pq_local,则需要等到母数据包被所有slot都处理完后,在下一个数据包处理之前,再去集中处理,如上面所述。对应上面的流程这个对流中的数据在TmThreadsSlotProcessPkt 函数中最后处理。
每个模块的数据处理
在TmThreadsSlotVarRun函数中轮询每个slot,并执行其的SlotFunc 函数,按照之前的关联ReceiveAFP ,DecodeAFP,FlowWorker,RespondReject四个模块。ReceiveAFP 是采集的处理,那么在此函数做处理数据包的功能,即为后面三个模块(DecodeAFP,FlowWorker,RespondReject),有RespondReject是阻断的处理,所以此文只展开看与检测有关的两个模块,分别是 DecodeAFP 和 FlowWorker。
DecodeAFP
SlotFunc 对应的函数为TmEcode DecodeAFP(ThreadVars *tv, Packet *p, void *data);
TmEcode DecodeAFP(ThreadVars *tv, Packet *p, void *data)
{
SCEnter();
DecodeThreadVars *dtv = (DecodeThreadVars *)data;
BUG_ON(PKT_IS_PSEUDOPKT(p));
/* update counters */
DecodeUpdatePacketCounters(tv, dtv, p);
/* If suri has set vlan during reading, we increase vlan counter */
if (p->vlan_idx) {
StatsIncr(tv, dtv->counter_vlan);
}
/* call the decoder */
DecodeLinkLayer(tv, dtv, p->datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
PacketDecodeFinalize(tv, dtv, p);
SCReturnInt(TM_ECODE_OK);
}
此模块的主要工作有:
1、其函数的作用是从底向上解析数据包每一层协议并将数据填入到Packet *p 中。
2、同时如果有IP分片重组包或者隧道数据包则新建创建一个字数据包放入到 tv->decode_pq 队列中。
3、通过 FlowSetupPacket 函数 获取每个包的hash值
FlowWorker
SlotFunc 对应的函数为static TmEcode FlowWorker(ThreadVars *tv, Packet *p, void *data);
此模块的主要工作有:
1、根据前一个decode模块计算的hash值 通过 Flow *FlowGetFlowFromHash(ThreadVars *tv, FlowLookupStruct *fls, const Packet *p, Flow **dest) 函数 获得一个流(Flow *f,找到之前的或创建一个新的), 并将此流锁定独占直到此模块执行完毕。
根据流的哈希检索功能。 查找包含流指针的散列桶。 然后将包与发现的流进行比较,看它是否是我们需要的流。 如果不是,就遍历这个列表 fb->head,直到找到正确的流即退出函数。这里还做一件事情,在遍历这个列表fb->head找到正确的流之前,检测遍历到的每一个流是否超时,如果超时,将超时的流 通过static inline void MoveToWorkQueue(ThreadVars *tv, FlowLookupStruct *fls, FlowBucket *fb, Flow *f, Flow *prev_f)函数处理(从fb->head 中删除此流,并根据条件放入对应的对流中,供FlowWorker 函数在后续处理,或是管理线程处理,具体在后续的流管理博文中体现 ,在此不表。)
如果流没有找到或者桶是空的,一个新的流将从备用池中取出。 只要我们保持在memcap限制内,池就会分配新的流。
找到的流加锁,p->flow 流指针更新为指向上面流程找到的流。
2、根据新的packet 和找到的 flow,相互更新数据。
3、根据传输层不同分别处理
tcp: 处理tcp 和应用层,,在此函数中 static inline void FlowWorkerStreamTCPUpdate(ThreadVars *tv, FlowWorkerThreadData *fw, Packet *p, void *detect_thread) 函数中处理,包括报文重组,协议解析等。
udp:处理UDP数据包负载的应用层部分,在此函数中 int AppLayerHandleUdp(ThreadVars *tv, AppLayerThreadCtx *tctx, Packet *p, Flow *f) 函数中处理。
4、开始检测,在此函数进行,TmEcode Detect(ThreadVars *tv, Packet *p, void *data),
具体在后续的检测流程博文中体现 ,在此不表。
5、输出,TmEcode OutputLoggerLog(ThreadVars *tv, Packet *p, void *thread_data)
总的日志输出模块,函数中会循环调用packet日志,tx日志,文件类多种日志,
所有的日志内容都会通过这个模块调用,后续的日志输出博文中体现 ,在此不表。
6、清除一些缓存数据
删除任何存储的文件
流不为NULL的情况下, 释放tcp段;从TcpSession中删除空闲的TcpSegments;应用层解析器的事务清理;解锁流。
7、获取被其他线程注入的流并处理它们(认为是闲置的流,在此回收),将其附加到我们的工作队列中,如果有必要会创建伪数据包并入此流中,并调用 static void FlowWorkerFlowTimeout(ThreadVars *tv, Packet *p, FlowWorkerThreadData *fw, void *detect_thread) 函数处理此流(检测,输出等)。
8、处理本线程闲置的流(认为是闲置的流,在此回收),流来源是 MoveToWorkQueue 函数中放入fls->work_queue中的流, 其流程同上述第 7 条。
RespondReject
本文只关注检测的功能,阻断的内容在此不表。
至此,worker thread的主要工作流程梳理完毕,但是还有一些主要的逻辑没有表述,待后续有更深入的理解后再归纳。
拓展内容
[1] 流管理系统 -- suricata -- 流管理系统
[2] 检测流程 -- 待写
[3] 日志输出系统 -- 待写
凡是过往,皆为序章