1. 参考资料:
(1)suricata架构——数据结构和代码流程图解
(这⾥⾯⼏张图很不错,⽅便结构理解,另外解释了 ⾏锁,全局的nf_conntrack_lock)
⾏锁:⾏级锁哈希表则每⼀⾏都有⼀把锁,内存开销⼤,实现复杂,但是在⼤并发,⾼效率的后台服务程序中使⽤⾮常⼴泛。suricata针对snort单线程处理数据包,⽆法很好利⽤多核cpu的劣势,开发了多线程架构⽅式并发处理数据包,⽽很多数据是线程间共享,所以在很多地⽅使⽤⾏级锁哈希表等其他⾼效数据结构。全局的nf_conntrack_lock:⽤于保护全局会话表
(2)suricata 源码分析之⾏锁
连接管理的哈希表:FlowBucket *flow_hash。其为链式哈希表(数组每个元素存放⼀个链表⾸地址,⼀个链表为⼀个桶。输⼊⼀个数据,函数h将其转化为整型数据k,根据k处理得到数组元素指向的桶,在桶(链表)中执⾏插⼊或删除操作。)。
flow_hash 是⼀个数组,每个FlowBucket 元素由 head 、tail和flow的hnext、hprev 构成⼀个双向链表,也就是所谓的⾏。该哈希的⾏数由配置⽂件或者默认值决定(flow_config.hash_size)
flow_hash = SCCalloc(flow_config.hash_size, sizeof(FlowBucket));
使⽤接⼝如下,以下两个函数的返回成功的话,会对该节点加锁保护,也是⽤来并发访问;并且都会将当前的这个节点放在该⾏的第⼀个节点,作为缓存假定其为最活跃的、近期最可能被访问的内容。
FlowGetFlowFromHash:通过数据包的信息获取连接,如果哈希表中没有,则创建flow;
作⽤:先计算出该数据包对应的flow的哈希值,然后通过哈希值去定位到某⼀⾏,再对该⾏加锁,此时,flow_hash的其他⾏可以访问的,如果这⼀⾏不为空,再去遍历者这个链表(⾏),如果已经存在,对该链表节点加锁、解锁⾏锁返回,如果没有创建节点插⼊链表,对该链表节点加锁、解锁⾏锁返回,如果这⼀⾏为空,创建节点插⼊链表,对该链表节点加锁、解锁⾏锁返回。
FlowLookupFlowFromHash:通过数据包的信息查找连接,不会创建;先计算出该数据包对应的flow的哈希值,然后通过哈希值去定位到某⼀⾏,再对该⾏加锁,此时,flow_hash的其他⾏可以访问的, (我下载的版本的surcicata⾥⾯没有找到这个函数)(3)⼀个⽐较全⾯的suricata 的介绍博客:属乌鸦的 (3)⼀个⽐较全⾯的suricata 的介绍博客:属乌鸦的
缩略:
2019-11-162018-06-202018-05-282018-05-282018-04-162018-03-222018-03-202018-03-072018-02-11
(4)博客园博主:⼤数据和⼈⼯智能躺过的坑
* Suricata的规则解读(默认和⾃定义)
该⽂章主要讲解suricata的规则定义⽅式,涉及到具体规则细节,没有细看,后⾯在真正做的时候可以参考 * 使⽤ Suricata 进⾏⼊侵监控(⼀个简单⼩例⼦访问百度)
*基于CentOS6.5下Suricata(⼀款⾼性能的⽹络IDS、IPS和⽹络安全监控引擎)的搭建(图⽂详解)(博主推荐)
(5)Suricata配置⽂件说明:
该⽂章详细解读了 suricata.yaml 的各配置以及字段的作⽤和范围。
(6)Suricata 开源平台wiki: (7)Suricata 博客专栏:
收录了11篇相关⽂章。作者: tiny⼂ *Suricata源码阅读笔记:main()
这个⽂章详细的写了SuricataMain函数中,每个步骤的作⽤。⽐较有⽤ * Suricata 3.2.1 源码阅读笔记:数据包队列:
这篇⽂章解读了 缓存线程模块内部新产⽣数据包的线程内队列,以及线程之间⽤来传递数据包的线程间队列。下⾯相关的结构体和函数的笔记有部分来⾃该⽂章的借鉴。但是可能suricata版本不同,原⽂提到的⼀些函数和字段在当前版本的代码中已不存在。
(8)浅谈开源⼊侵检测引擎 Suricata
收费内容,暂时没学习到。后⾯有机会再看。
2. ⼏⼤结构体的定义和作⽤
(Flow_ / FlowBucket_ /PacketQueue_ /ThreadVars_ ):
(1)Flow_
(流。 ⽤于处理包含多个包的消息流,流是为流的新数据包创建的全局数据结构,然后查找流的其他数据包)
流“头”(地址、端⼝、协议、递归级别)在初始化后是静态的,并且在流的整个活动期间保持只读。这就是为什么我们可以在没有锁保护的情况下访问它们。
流的锁:流由多个数据包同时更新/使⽤。这就是为什么会有流互斥。它是互斥锁,⽽不是⾃旋锁,因为流上的⼀些操作可能代价⽐较⾼,因此⾃旋锁代价更加⾼。
(2)FlowBucket_
(顾名思义:流的桶或者流的链表,每个存储桶包含⼀个或多个流,桶内的流具有相同的哈希键值(哈希是链式哈希)。进⾏修改时,整个桶都被锁定。也就是⾏锁)
全局变量 FlowBucket *flow_hash ⽤来存储流桶的哈希表,哈希表的⼤⼩通过配置设定。数组每个元素存放⼀个链表⾸地址,⼀个链表为⼀个桶。输⼊⼀个数据,函数h将其转化为整型数据k,根据k处理得到数组元素指向的桶,在桶(链表)中执⾏插⼊或删除操作。
flow_hash = SCMallocAligned(flow_config.hash_size * sizeof(FlowBucket), CLS);
/* 每个存储桶包含⼀个或多个流,桶内的流具有相同的哈希键值(哈希是链式哈希)。进⾏修改时,整个桶都被锁定。也就是⾏锁 */typedef struct FlowBucket_ {
/** head of the list of active flows for this row. */ Flow *head; /*可以形容为链表头*/
/** head of the list of evicted flows for this row. Waiting to be * collected by the Flow Manager. */
Flow *evicted; /*百度翻译为驱逐,结合现在的代码,可以理解为暂存在本桶中,但是不使⽤的第⼀个流的指针*//*⾏锁*/
#ifdef FBLOCK_MUTEX SCMutex m;
#elif defined FBLOCK_SPIN SCSpinlock s;#else
#error Enable FBLOCK_SPIN or FBLOCK_MUTEX#endif
/** timestamp in seconds of the earliest possible moment a flow * will time out in this row. Set by the flow manager. Cleared * to 0 by workers, either when new flows are added or when a * flow state changes. The flow manager sets this to INT_MAX for * empty buckets. */
SC_ATOMIC_DECLARE(int32_t, next_ts);} __attribute__((aligned(CLS))) FlowBucket;
(3)数据包队列相关
* 数据包队列 PacketQueue_ * 线程内队列 TmSlot_
* 线程间队列: PacketQueue trans_q[256]; Suricata中使⽤了⼀个全局数组作为所有的线程间队列的存储,但是从我当前的代码来看,⽤的是动态⽣成的,⽽不是全局变量。 Tmq_ ⽤于管理线程间队列。
typedef struct Tmq_ {
char *name;/* 队列名字 */
bool is_packet_pool; /*队列名为packetpool时为真*//*从数据包池中获取数据包,将数据包放回数据包池。(从代码实现看,此种情况不会动态分配队列)*/ uint16_t id; /* 对应的队列存储在trans_q中的索引 */ uint16_t reader_cnt; /* 向这个队列读数据的线程数 */ uint16_t writer_cnt; /* 向这个队列写数据的线程数 */
PacketQueue *pq; /*相⽐于之前的⽂档链接,多了⼀个这个指针,不再指向全局数组,⽽是动态分配*/ TAILQ_ENTRY(Tmq_) next;} Tmq;
#define TAILQ_ENTRY(type) \\struct { \\
struct type *tqe_next; /* next element */ \\
struct type **tqe_prev; /* address of previous next element */ \\}
系统初始化时会调⽤TmqhSetup注册所有队列handler,类似包括:
类型simplepacketpool*flow
说明
简单地从inq获取数据包,线程处理完后将包送往唯⼀的outq。
从数据包池中获取数据包,将数据包放回数据包池。见下⾯注释。(从代码实现看,此种情况不会动态分配队列)
⽤于autofp模式的handler,实现流的绑定和负载均衡。
(4)线程变量 ThreadVars_
在代码中变量名称⼀般定义为 tv.
3. ⼀些函数:
(1)流⽐较:FlowCompare
static inline int FlowCompare(Flow *f, const Packet *p){
if (p->proto == IPPROTO_ICMP) { return FlowCompareICMPv4(f, p); } else if (p->proto == IPPROTO_ESP) { return FlowCompareESP(f, p); } else {
return CmpFlowPacket(f, p); }}
(2)TcpSessionPacketSsnReuse 判断包是否可以重⽤?啥场景??
从代码看,FlowGetFlowFromHash 执⾏时,如果已经存在,则考虑复⽤该流,复⽤后替换原有的流。
(3)MoveToWorkQueue 从hash桶中删除或者放到 evicted 指针后
static inline void MoveToWorkQueue(ThreadVars *tv, FlowLookupStruct *fls, FlowBucket *fb, Flow *f, Flow *prev_f){
f->flow_end_flags |= FLOW_END_FLAG_TIMEOUT; /* remove from hash... */ if (prev_f) {
prev_f->next = f->next; }
if (f == fb->head) { fb->head = f->next; }
if (f->proto != IPPROTO_TCP || FlowBelongsToUs(tv, f)) { // TODO thread_id[] direction f->fb = NULL; f->next = NULL;
FlowQueuePrivateAppendFlow(&fls->work_queue, f); FLOWLOCK_UNLOCK(f); } else {
/*evicted 可以理解为暂存在本桶中,但是不使⽤的第⼀个流的指针,f 插⼊作为 evicted的第⼀个指针,原有的向后排列*//* implied: TCP but our thread does not own it. So set it * aside for the Flow Manager to pick it up. */ f->next = fb->evicted; fb->evicted = f;
if (SC_ATOMIC_GET(f->fb->next_ts) != 0) { SC_ATOMIC_SET(f->fb->next_ts, 0); }
FLOWLOCK_UNLOCK(f); }}
(4)TmThreadSetSlots 设置线程功能,在创建线程的时候使⽤:
static TmEcode TmThreadSetSlots(ThreadVars *tv, const char *name, void *(*fn_p)(void *)){
if (name == NULL) { if (fn_p == NULL) {
printf(\"Both slot name and function pointer can't be NULL inside \" \"TmThreadSetSlots\\n\"); goto error; } else {
name = \"custom\"; } }
if (strcmp(name, \"varslot\") == 0) { tv->tm_func = TmThreadsSlotVar;
} else if (strcmp(name, \"pktacqloop\") == 0) { tv->tm_func = TmThreadsSlotPktAcqLoop; } else if (strcmp(name, \"management\") == 0) { tv->tm_func = TmThreadsManagement; } else if (strcmp(name, \"command\") == 0) { tv->tm_func = TmThreadsManagement; } else if (strcmp(name, \"custom\") == 0) { if (fn_p == NULL) goto error;
tv->tm_func = fn_p; } else {
printf(\"Error: Slot \\\"%s\\\" not supported\\n\", name); goto error; }
return TM_ECODE_OK;error:
return TM_ECODE_FAILED;}
数组每个元素存放⼀个链表⾸地址,⼀个链表为⼀个桶。输⼊⼀个数据,函数h将其转化为整型数据k,根据k处理得到数组元素指向的桶,在桶(链表)中执⾏插⼊或删除操作。
Suricata 3.2.1 源码阅读笔记:数据包队列
因篇幅问题不能全部显示,请点此查看更多更全内容