suricata 各个线程干的事情 -- WorkerThread

目录

线程初始化

packet pool线程池初始化

主要模块的初始化:

线程进入循环

捕获循环

每个模块的数据处理

DecodeAFP

FlowWorker

RespondReject

拓展内容


WorkerThread 线程的创建与注册的slot 和 TmModule 对应的关系参见我的之前的一遍文章中创建工作线程部分

suricata 各个线程干的事情 -- 主线程_xuwaiwai的博客-CSDN博客suricata 各个线程的作用 -- 主线程https://blog.csdn.net/xuwaiwai/article/details/120086508?spm=1001.2014.3001.5501

线程函数: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;
}
  1. 调用TmThreadsSlotVarRun 函数,将数据包依次传入后续的各个slot进行处理。
  2. 若返回失败,则调用TmThreadsSlotProcessPktFail 将数据包( tv->decode_pq 数据包队列 和 tv->stream_pq_local 数据包对流中的数据包)进行回收或释放。然后设置线程标志为THV_FAILED,等待主线程处理。
  3. 若返回成功,则调用tmqh_out(线程创建时设置为与该线程绑定的outqh的处理函数OutHandler,在这里{ RUNMODE_AFP_DEV + "workers" } 模式下,此函数为:TmqhOutputPacketpool(ThreadVars *t, Packet *p)),将数据包送到后续队列中去。
  4. 此外,由于各模块在处理过程中可能会新生成数据包(如隧道数据包、重组数据包),这些数据包存储在 tv->decode_pqtv->stream_pq_local 队列中,因此还需要类似上述流程,对这些数据包进行处理。这里调用 TmThreadsHandleInjectedPackets 函数只集中处理了 tv->stream_pq_local,将里面的数据包直接从 FlowWorker 开始处理。

对数据包进行进一步处理的TmThreadsSlotVarRun函数原型如下:

        TmEcode TmThreadsSlotVarRun(ThreadVars *tv, Packet *p, TmSlot *slot)

按照函数头的注释说明,这个函数被从母函数中拉出来独立存在的原因是,为了能够对其进行递归调用。函数主流程是一个遍历所有slot的for循环,其执行过程如下:

  1. 调用slot的处理函数SlotFunc
  2. 若返回失败,则调用TmThreadsSlotProcessPktFail 将数据包(以及 tv->decode_pq 数据包队列 和 tv->stream_pq_local 数据包对流中的数据包)进行回收或释放。然后设置线程标志为THV_FAILED,等待主线程处理。
  3. 若返回成功,则继续处理 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 函数在后续处理,或是管理线程处理,具体在后续的流管理博文中体现 {\color{Red} ^{[ 1 ]}},在此不表。) 

        如果流没有找到或者桶是空的,一个新的流将从备用池中取出。 只要我们保持在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),

             具体在后续的检测流程博文中体现 {\color{Red} ^{[2]}},在此不表。

        5、输出,TmEcode OutputLoggerLog(ThreadVars *tv, Packet *p, void *thread_data)

             总的日志输出模块,函数中会循环调用packet日志,tx日志,文件类多种日志,

             所有的日志内容都会通过这个模块调用,后续的日志输出博文中体现 {\color{Red} ^{[3]}},在此不表。

        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] 日志输出系统  -- 待写


凡是过往,皆为序章

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值