您的当前位置:首页suricata学习笔记

suricata学习笔记

来源:小侦探旅游网
suricata学习笔记

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 源码阅读笔记:数据包队列

因篇幅问题不能全部显示,请点此查看更多更全内容