Suricata6.0流管理代码梳理
目录
包括新建流和老化流两大部分,一个流用一个flow结构体表示,具体工作又包括为packet获取一个flow,更新流的状态,流的老化回收,空闲flow个数维护即flow空闲数量少于预分配数量的90%时则进行补充,如果flow空闲数量多于预分配的flow数量的110%时则进行释放。
释放逻辑:多出来的flow数量x是flow队列(固定100个)数量的10倍及以上则释放x/10个flow,就是多出来的flow数量必须小于单个flow队列flow数量的10倍,比如单个flow队列固定flow数量为100,则由于从内存分配而多出来的flow数量必须小于1000(100*10),如果为1000则释放的数量为1000/10=100,如果为1100,则需要释放1100/10=110。
多出来的flow是因为预分配的flow使用完了,而且当前flow占用的总内存没有超过配置的内存字节数,于是flowalloc分配新flow,这样flow数量就会增多。
(1)根据流的状态设置流的超时时间,时间到了则老化之,回收flow到全局的flow_spare_pool中,哪个flow队列不够100,就放入哪个flow队列。
(2)如果预先分配的flow结构体不够用了,配置的flow内存也用完了,则设置进入紧急模式状态,一个是把流的超时时间设置为紧急超时时间,这个时间远远短于正常老化时间,二是流老化处理线程轮询间隔时间也会缩短,轮询间隔时间正常时667毫秒检查一次老化,紧急模式为250毫秒检查一次;另外,每个线程只老化自己负责的flowbucket上的flow,这个flowbucket的范围在FlowInitConfig初始化时设置,就是按照flowbucket的数量平均分配给每个老化线程即运行FlowManager函数的线程。
(3)进入紧急模式后,会不断检查可用flow的数量,如果这个数量达到预分配flow数量的一定的百分比,则退出紧急模式。
当一个网络数据包到达时,经过收包,解码产生了packet结构体指针p,这时进入下一个模块第一个工作就是为这个p查找其所属流flow,这个工作从函数FlowHandlePacket开始,不同的suricata版本对这个函数的调用位置不一样,但都是在解码后进行调用,从4.0开始在FlowWorker函数中调用,3.0及以前的版本都在解码模块的解码函数中调用比如在DecodeUDP,DecodeTCP等函数中调用。
FlowHandlePacket函数做了大部分流查找分配及状态设置更新的工作,有一部分更新状态的工作放在另外一个函数FlowUpdate,在如果没有找到则分配一个flow,如果找到则更新流状态。
- 流结构体flow的分配
流的flow存放在全局flow哈希数组中:FlowBucket *flow_hash,所以操作bucket时需要对其上锁,每个bucket存放若干哈希值相同的flow结构体,哈希值就是五元组哈希后与数组大小的模,解码后为一个包获取flow时,先从全局变量flow_hash[hash%hash_size]数组里进行查找,存在则进行状态更新操作,不存在则需要从空闲的flow池子里分配一个flow结构体。
分配flow两个方式,一个是从预先分配的全局flow池子里获取,如果没有可用的flow,则在配置的内存空间范围内直接从内存上分配flow,这个池子就称为空闲flow池子,它是个根据配置文件预先分配一定数量flow的内存池,需要的时候从里边取,不需要了则归还进去,取到flow后,设置相关成员数据,然后放入全局变量flow_hash中,该flow后续的packet都可以匹配到该flow结构体。
不同版本的全局flow池子有所区别,比如4.0和之前的版本都是FlowQueue flow_spare_q(带锁的flow链表)这样的全局变量(5.0左右版本没看过),直接用锁子从这个全局变量获取可用的flow结构体。
6.0版本也是用的全局变量,只不过flow不是组织成一个链表,而是先根据配置文件配置的flow个数进行预分配,把这些flow每100个flow组织成一个空闲的flow队列,再把每个包含100个flow的队列组成pool,就是通过static FlowSparePool *flow_spare_pool链接起来形成pool,它是个全局变量,由static SCMutex flow_spare_pool_m保护。
处理包的每个线程都有自己的空闲flow队列,分配flow时,从线程自己的flow队列(变量名字:spare_queue)里获取,如果没有可用flow,则从上边说的全局pool里取一个flow队列赋值给spare_queue,后续有包到来就从spare_queue里取flow使用,所以从全局pool获取的flow是一个flow队列(100个),一次获取一个队列的flow,只有100个flow用完了才需要锁一次全局pool获取一个flow队列,而不像4.0及之前版本一次获取一个flow每次都要锁,从这点看,减小了锁的粒度,如果流的数量比较多的话,在获取flow时可以提高不少性能。
全局flow哈希数组中:FlowBucket *flow_hash,正则使用的流的结构体flow存放到这里
存放空闲Flow队列的pool定义:
typedef struct FlowSparePool {
FlowQueuePrivate queue; //flow队列,就是个链表,由100个flow组成
struct FlowSparePool *next; //池子链表,把多个flow队列组织成链表,线程自己的flow队列用完时,从这个pool里获取一个flow队列
} FlowSparePool;
static uint32_t flow_spare_pool_flow_cnt = 0; //空闲flow总个数,包括所有flow队列的flow
static uint32_t flow_spare_pool_block_size = 100;//每个flow队列里空闲flow的个数
static FlowSparePool *flow_spare_pool = NULL; //全局链表头指针,存储可用的flow
static SCMutex flow_spare_pool_m = SCMUTEX_INITIALIZER;//保护上边那位爷
流初始化函数:
入口函数 |
被直接调用函数 |
FlowInitConfig (flow.c) |
FlowQueueInit |
FlowSparePoolInit | |
FlowInitFlowProto | |
FlowSparePoolInit (flow-spare-pool.c) |
FlowSpareGetPool |
FlowSparePoolUpdateBlock |
流查询建立主要函数:
入口函数 |
被直接调用函数 |
FlowWorker (flow-worker.c) |
FlowHandlePacket |
FlowHandlePacket (flow.c) |
FlowGetFlowFromHash |
FlowGetFlowFromHash (flow-hash.c) |
FlowGetNew |
MoveToWorkQueue | |
FlowInit | |
FlowUpdateState | |
FlowGetNew (flow-hash.c) |
FlowQueuePrivateGetFromTop |
FlowSpareSync | |
FlowTimeoutsEmergency | |
FlowGetUsedFlow | |
FlowAlloc |
流老化检查主要函数:
入口函数 |
被直接调用函数 |
FlowManage (flow-manager.c) |
FlowSpareGetPoolSize |
FlowSparePoolUpdate | |
FlowTimeoutHash | |
FlowTimeoutHash (flow-manager.c) |
FlowManagerHashRowTimeout |
FlowManagerHashRowClearEvictedList | |
ProcessAsideQueue | |
FlowManagerHashRowClearEvictedList (flow-manager.c) |
FlowQueuePrivateAppendFlow |
FlowManagerFlowTimeout (flow-manager.c) |
FlowGetFlowTimeoutDirect |
ProcessAsideQueue (flow-manager.c) |
FlowForceReassemblyForFlow |
FlowQueuePrivateAppendFlow | |
FlowForceReassemblyForFlow (flow-timeout.c) |
TmThreadsInjectFlowById |
TmThreadsInjectFlowById (tm-threads.c) |
FlowEnqueue |
SCCondSignal |
流回收主要函数:
入口函数 |
被直接调用函数 |
FlowRecycler (flow-manager.c) |
FlowQueueExtractPrivate |
FlowQueuePrivateGetFromTop | |
Recycler | |
Recycler (flow-manager.c) |
OutputFlowLog |
FlowClearMemory | |
FlowSparePoolReturnFlow |