suricata 学习笔记

1. 参考资料:

(1)suricata架构——数据结构和代码流程图解  https://blog.csdn.net/gengzhikui1992/article/details/103031874

(这里面几张图很不错,方便结构理解,另外解释了 行锁,全局的nf_conntrack_lock)

行锁:行级锁哈希表则每一行都有一把锁,内存开销大,实现复杂,但是在大并发,高效率的后台服务程序中使用非常广泛。suricata针对snort单线程处理数据包,无法很好利用多核cpu的劣势,开发了多线程架构方式并发处理数据包,而很多数据是线程间共享,所以在很多地方使用行级锁哈希表等其他高效数据结构。

全局的nf_conntrack_lock:用于保护全局会话表

(2)suricata 源码分析之行锁     https://blog.csdn.net/guoguangwu/article/details/63318277

连接管理的哈希表: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 的介绍博客:属乌鸦的  http://www.hyuuhit.com/categories/suricata/

缩略:

 (4)博客园博主:大数据和人工智能躺过的坑 

 

         *  Suricata的规则解读(默认和自定义)  https://www.cnblogs.com/zlslch/p/7382190.html  

          该文章主要讲解suricata的规则定义方式,涉及到具体规则细节,没有细看,后面在真正做的时候可以参考

         * 使用 Suricata 进行入侵监控(一个简单小例子访问百度) https://www.cnblogs.com/zlslch/p/7327795.html

          *基于CentOS6.5下Suricata(一款高性能的网络IDS、IPS和网络安全监控引擎)的搭建(图文详解)(博主推荐)https://www.cnblogs.com/zlslch/p/7326291.html

 (5)Suricata配置文件说明: https://www.cnblogs.com/zlslch/p/7326291.html

         该文章详细解读了 suricata.yaml 的各配置以及字段的作用和范围。

 (6)Suricata 开源平台wiki:  https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Debugging

 (7)Suricata 博客专栏: https://blog.csdn.net/vevenlcf/category_6085067.html

收录了11篇相关文章。作者: tiny丶

            *Suricata源码阅读笔记:main() https://blog.csdn.net/vevenlcf/article/details/50600324

          这个文章详细的写了SuricataMain函数中,每个步骤的作用。比较有用

            * Suricata 3.2.1 源码阅读笔记:数据包队列:  https://blog.csdn.net/vevenlcf/article/details/73123770

          这篇文章解读了  缓存线程模块内部新产生数据包的线程内队列,以及线程之间用来传递数据包的线程间队列。下面相关的结构体和函数的笔记有部分来自该文章的借鉴。但是可能suricata版本不同,原文提到的一些函数和字段在当前版本的代码中已不存在。

(8)浅谈开源入侵检测引擎 Suricata https://gitbook.cn/gitchat/activity/5b14b66fdcaebb0977c7f94e?utm_source=csdn_blog

         收费内容,暂时没学习到。后面有机会再看。

 

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,类似包括:

类型 说明
simple 简单地从inq获取数据包,线程处理完后将包送往唯一的outq。
packetpool* 从数据包池中获取数据包,将数据包放回数据包池。见下面注释。(从代码实现看,此种情况不会动态分配队列)
flow 用于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 源码阅读笔记:数据包队列

posted @ 2022-01-30 12:17  JeasonLiu先生  阅读(735)  评论(0编辑  收藏  举报