在《suricata中的线程管理分析》一文中,我们看到suricata中有FlowWorker和FlowManager两个线程来处理流表,说明流表的实现应该不简单,果然,看了流相关的这块代码后,发现确实有点复杂,代码估计得慢慢坑,今天我们就来个对流表的初步分析,细节上咱们先放一放。
1、流表数据结构的初始化:
流表的初始化在FlowInitConfig函数中,函数调用栈如下:
main
-->SuricataMain
-->PostConfLoadedSetup
-->PreRunInit
-->FlowInitConfig(流相关数据结构初始化)
-->FlowQueueInit(flow_recycle_q流回收队列初始化)
-->FlowSparePoolInit(flow_spare_pool流节点预分配链表初始化)
-->FlowInitFlowProto(设置不同协议不同状态的超时时间)
FlowInitConfig主要是读取配置,对流表的配置结构体FlowConfig flow_config进行初始化、flow_recycle_q流回收队列初始化、flow_spare_pool流节点预分配链表初始化以及给不同协议不同状态设置不同的超时时间。
flow_spare_pool流节点预分配链表会根据配置事先分配一定数量的flow节点,这些flow节点按比例挂在flow_spare_pool链表的每个节点上。创建流表时,先优先从这些节点上获取flow。
2、收包线程FlowWorker模块对流表的处理:
收包线程FlowWorker模块对流表进行处理的函数调用栈如下:
FlowWorker
-->FlowHandlePacket
-->FlowGetFlowFromHash
-->FlowGetNew(新建流)
-->FlowQueuePrivateGetFromTop(从flow_spare_pool中申请流)
-->FlowAlloc(flow_spare_pool不够,直接alloc申请)
-->MoveToWorkQueue(已有流超时的,从哈希桶中删除,并放入work_queue或者evicted链表)
-->FlowWorkerProcessInjectedFlows(取出flow_queue中的flow放入到work_queue)
-->FlowWorkerProcessLocalFlows
-->CheckWorkQueue
-->FlowClearMemory(清除流信息)
-->FlowSparePoolReturnFlow(将流归还到flow_spare_pool中)
流表的哈希表本身并不复杂,所有的流节点都是通过全局的FlowBucket *flow_hash哈希桶管理起来的,哈希桶的大小就是FlowInitConfig的hash_size,收包线程收到报文后,根据报文的流的哈希值(以五元组+vlan为key),先查流表,如果没有就创建流表项,如果有就找到对应的流节点。
这里面复杂的几个地方如下:
1)如果包对应的哈希链没有节点,需要获取流节点:先从上面讲到的flow_spare_pool链表中获取,获取不到就看能不能重用,不能重用就直接调用alloc自己创建一个节点。
2)如果哈希链有节点,则遍历链,看有没有超时的,超时就从哈希链中删除,放入work_queue或者evicted链表
3)从flow_queue中取出flow放入到work_queue,flow_queue是FlowManager线程处理时,发现的超时流需要重组时放入的
4)检查work_queue,清楚流节点信息,将流归还到flow_spare_pool链表中。
关键代码加注释如下:
Flow *FlowGetFlowFromHash(ThreadVars *tv, FlowLookupStruct *fls, Packet *p, Flow **dest)
{
Flow *f = NULL;
/* get our hash bucket and lock it */
const uint32_t hash = p->flow_hash; //Decode协议解码时已经计算好的哈希值
FlowBucket *fb = &flow_hash[hash % flow_config.hash_size]; //查找到对应的哈希链
FBLOCK_LOCK(fb); //每个哈希链一个锁
SCLogDebug("fb %p fb->head %p", fb, fb->head);
/* see if the bucket already has a flow */
if (fb->head == NULL) { //哈希链为空
f = FlowGetNew(tv, fls, p); //新建流
if (f == NULL) {
FBLOCK_UNLOCK(fb);
return NULL;
}
/* flow is locked */
fb->head = f;
/* got one, now lock, initialize and return */
FlowInit(f, p);
f->flow_hash = hash;
f->fb = fb;
FlowUpdateState(f, FLOW_STATE_NEW);
FlowReference(dest, f); //给包的流指针赋值
FBLOCK_UNLOCK(fb);
return f;
}
const bool emerg = (SC_ATOMIC_GET(flow_flags) & FLOW_EMERGENCY) != 0;
const uint32_t fb_nextts = !emerg ? SC_ATOMIC_GET(fb->next_ts) : 0;
/* ok, we have a flow in the bucket. Let's find out if it is our flow */
Flow *prev_f = NULL; /* previous flow */
f = fb->head;
do { //遍历哈希桶fb
Flow *next_f = NULL; //先判断每个flow节点是否超时
const bool timedout = (fb_nextts < (uint32_t)SCTIME_SECS(p->ts) &&
FlowIsTimedOut(f, (uint32_t)S