流查找/分配
通过FlowWorker线程函数中调用FlowHandlePacket来找到流。
下面将按照FlowHandlePacket的流程,分析flow engine对于新送入的解码后的数据包是如何处理的。
对于一个Packet,首先在流表中查找其所属的流是否已经存在,若存在,则直接返回该流的引用即可,否则就需要分配一个新流。
该过程的实现由FlowGetFlowFromHash完成,函数会返回一个Flow指针,表示找到的或新分配的流。
/** \brief Get Flow for packet
*
* Hash retrieval function for flows. Looks up the hash bucket containing the
* flow pointer. Then compares the packet with the found flow to see if it is
* the flow we need. If it isn't, walk the list until the right flow is found.
*
* If the flow is not found or the bucket was emtpy, a new flow is taken from
* the queue. FlowDequeue() will alloc new flows as long as we stay within our
* memcap limit.
*
* The p->flow pointer is updated to point to the flow.
*
* \param tv thread vars
* \param dtv decode thread vars (for flow log api thread data)
*
* \retval f *LOCKED* flow or NULL
*/
Flow *FlowGetFlowFromHash(ThreadVars *tv, DecodeThreadVars *dtv, const Packet *p, Flow **dest)
{
Flow *f = NULL;
/* get our hash bucket and lock it */
const uint32_t hash = p->flow_hash;
//获取包的hash,这里的hash是由p带来的。p的flow_hash是通过在FlowSetupPacket函数调用FlowGetHash,又通过FlowGetHash中的hashword生成的,对于 hash_key的获取方法以后另行分析。
FlowBucket *fb = &flow_hash[hash % flow_config.hash_size];
//以获取到的key为索引,在流表flow_hash中获取到一个FlowBucket的指针
FBLOCK_LOCK(fb);
//使用FBLOCK_LOCK对该bucket上锁。实际使用的锁可能为spin lock或mutext,取决于FBLOCK_SPIN是否定义。
SCLogDebug("fb %p fb->head %p", fb, fb->head);
/* see if the bucket already has a flow */
if (fb->head == NULL) {
//说明这个bucket中还没有流
f = FlowGetNew(tv, dtv, p);
//调用FlowGetNew新分配一个流
if (f == NULL) {
FBLOCK_UNLOCK(fb);
return NULL;
}
/* flow is locked */
fb->head = f;
fb->tail = f;
/* got one, now lock, initialize and return */
FlowInit(f, p);
//使用Packet的信息初始化这个新流
f->flow_hash = hash;
f->fb = fb;
//设置流的flow_hash和fb
/* update the last seen timestamp of this flow */
COPY_TIMESTAMP(&p->ts,&f->lastts);
//记录最后一次更新流的时间
FlowReference(dest, f);
//使用FlowReference将p->flow指向刚获取流,该函数内部会使用FlowIncrUsecnt增加该流的使用计数。注意,该机制的目的与通常的引用计数不同,不是为了在没有引用时回收资源,而是为了避免出现误删除等问题
FBLOCK_UNLOCK(fb);
//FBLOCK_UNLOCK解锁并返回这个流。由于FlowGetNew中会调用FLOWLOCK_WRLOCK对flow进行上锁(因为后面需要使用),因此这里就不需要再锁了。
return f;
}
//以下代码为fb->head不为NULL,即bucket中有流时,尝试从其中的Flow链表中查找该packet所属的Flow
/* ok, we have a flow in the bucket. Let's find out if it is our flow */
f = fb->head;
/* see if this is the flow we are looking for */
if (FlowCompare(f, p) == 0) {
//代表f和p不匹配,为1匹配,为0不匹配
Flow *pf = NULL; /* previous flow */
while (f) {
pf = f;
f = f->hnext;
if (f == NULL) {
f = pf->hnext = FlowGetNew(tv, dtv, p);
if (f == NULL) {
FBLOCK_UNLOCK(fb);
return NULL;
}
fb->tail = f;
/* flow is locked */
f->hprev = pf;
/* initialize and return */
FlowInit(f, p);
f->flow_hash = hash;
f->fb = fb;
/* update the last seen timestamp of this flow */
COPY_TIMESTAMP(&p->ts,&f->lastts);
FlowReference(dest, f);
FBLOCK_UNLOCK(fb);
return f;
}
//若未找到,则与4类似,获取一个新流并初始化,然后挂到链表尾部再返回。注意,这里并没有移到头部,因为新流不代表就是活跃流。
if (FlowCompare(f, p) != 0) {
/* we found our flow, lets put it on top of the
* hash list -- this rewards active flows */
if (f->hnext) {
f->hnext->hprev = f->hprev;
}
if (f->hprev) {
f->hprev->hnext = f->hnext;
}
if (f == fb->tail) {
fb->tail = f->hprev;
}
f->hnext = fb->head;
f->hprev = NULL;
fb->head->hprev = f;
fb->head = f;
/* found our flow, lock & return */
FLOWLOCK_WRLOCK(f);
if (unlikely(TcpSessionPacketSsnReuse(p, f, f->protoctx) == 1)) {
f = TcpReuseReplace(tv, dtv, fb, f, hash, p);
if (f == NULL) {
FBLOCK_UNLOCK(fb);
return NULL;
}
}
/* update the last seen timestamp of this flow */
COPY_TIMESTAMP(&p->ts,&f->lastts);
FlowReference(dest, f);
FBLOCK_UNLOCK(fb);
return f;
}
//使用FlowCompare比较head flow与packet,若相匹配,则说明已经找到了,且这个流已经在头部不需要再调整,则先锁上该flow再解锁bucket,然后返回。
}
}
/* lock & return */
FLOWLOCK_WRLOCK(f);
if (unlikely(TcpSessionPacketSsnReuse(p, f, f->protoctx) == 1)) {
f = TcpReuseReplace(tv, dtv, fb, f, hash, p);
if (f == NULL) {
FBLOCK_UNLOCK(fb);
return NULL;
}
}
/* update the last seen timestamp of this flow */
COPY_TIMESTAMP(&p->ts,&f->lastts);
//记录最后一次更新流的时间
FlowReference(dest, f);
//将dest指向f,此处dest为p->flow,由函数传入。
FBLOCK_UNLOCK(fb);
return f;
}