0. 简介

Suricata是一个高性能的网络入侵检测(IDS:Intrusion Detection System)、入侵防御(IPS:Intrusion Prevention System)和网络安全监控(NSM)的多线程引擎,内置支持IPv6。可加载snort规则和签名,支持barnyard2。使用pcap提供的接口进行抓包,运行前电脑必须安装有pcap才可以使用

1. 编译

  1. Installation
  2. https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Suricata_Installation
  3. Ubuntu
  4. https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Ubuntu_Installation_from_GIT
  5. Other point
  6. # cd suricata-update && curl -L https://github.com/OISF/suricata-update/archive/master.tar.gz | tar zxvf - --strip-components=1
  7. $ sudo apt install cbindgen

2. 重要结构体

suricata中tv、slot和tm的关系必须要搞清楚,汇总如下:

  • tv:ThreadVars类型,线程
  • slot:TmSlot类型,槽
  • tm:TmModule类型,模块

  1. 线程的定义:
  2. typedef struct ThreadVars_ {
  3. pthread_t t; // 线程id
  4. char *name; // 线程name
  5. char *thread_group_name; // 线程group name
  6. SC_ATOMIC_DECLARE(unsigned short, flags); // 原子声明,不知道作用,暂且不管
  7. uint8_t aof; // 线程遇到故障时怎么做
  8. uint8_t type; // 线程类型,例如:TVT_PPT, TVT_MGMT
  9. uint8_t restarted; // 线程重新启动失败的次数
  10. Tmq *inq;
  11. Tmq *outq;
  12. void *outctx;
  13. char *outqh_name
  14. struct Packet_ * (*tmqh_in)(struct ThreadVars_ *);
  15. void (*InShutdownHandler)(struct ThreadVars_ *);
  16. void (*tmqh_out)(struct ThreadVars_ *, struct Packet_ *);
  17. void *(*tm_func)(void *);
  18. struct TmSlot_ *tm_slots;
  19. uint8_t thread_setup_flags;
  20. uint16_t cpu_affinity;
  21. int thread_priority; // 线程优先级
  22. SCPerfContext sc_perf_pctx;
  23. SCPerfCounterArray *sc_perf_pca;
  24. SCMutex *m;
  25. SCCondT *cond;
  26. uint8_t cap_flags;
  27. struct ThreadVars_ *next;
  28. struct ThreadVars_ *prev;
  29. } ThreadVars;

  1. 槽的定义:
  2. typedef struct TmSlot_ {
  3. ThreadVars *tv; // 拥有该slot的线程
  4. SC_ATOMIC_DECLARE(TmSlotFunc, SlotFunc);// 函数指针
  5. TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *); // 模块数据包获取函数
  6. TmEcode (*SlotThreadInit)(ThreadVars *, void *, void **); // 模块初始化执行函数
  7. void (*SlotThreadExitPrintStats)(ThreadVars *, void *); // 模块退出打印函数
  8. TmEcode (*SlotThreadDeinit)(ThreadVars *, void *); // 模块清理执行函数
  9. void *slot_initdata; // 数据存储
  10. SC_ATOMIC_DECLARE(void *, slot_data);
  11. PacketQueue slot_pre_pq;
  12. PacketQueue slot_post_pq;
  13. int tm_id; // tm ID
  14. int id; // slot ID
  15. struct TmSlot_ *slot_next;
  16. } TmSlot;

  1. 模块定义:
  2. typedef struct TmModule_ {
  3. char *name; // 模块名称
  4. TmEcode (*ThreadInit)(ThreadVars *, void *, void **);
  5. void (*ThreadExitPrintStats)(ThreadVars *, void *);
  6. TmEcode (*ThreadDeinit)(ThreadVars *, void *);
  7. TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
  8. TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);
  9. TmEcode (*Init)(void);
  10. TmEcode (*DeInit)(void);
  11. void (*RegisterTests)(void);
  12. uint8_t cap_flags;
  13. uint8_t flags;
  14. } TmModule;

三者之间的关系,每一个线程都包含一个slot的链表,每个slot结点都悬挂着不同的模块,程序执行的时候会遍历slot链表,按照加入链表的顺序执行模块。

3. main函数

3.1 注册各种运行模式

  • RunModeRegisterRunModes()函数
  1. RunModeIdsPcapRegister(); // IDS+pcap
  2. RunModeFilePcapRegister(); // File+pcap
  3. RunModeIdsPfringRegister(); // IDS+pfring
  4. RunModeIpsIPFWRegister(); // IPS+ipfw
  5. RunModeIpsNFQRegister(); // IPS+nfq
  6. RunModeErfFileRegister(); // erf+file
  7. RunModeErfDagRegister(); // erf+dag
  8. RunModeNapatechRegister(); // napatech
  9. RunModeIdsAFPRegister(); // IDS+AFP
  10. RunModeUnixSocketRegister(); // UnixSocket
  11. 其中每一种运行模式调用RunModeRegisterNewRunMode注册各自的Custom mode(暂且翻译为“自定义模式”)
  12. RunModeRegisterNewRunMode设置各种运行模式的执行函数
  13. 例如: RunModeRegisterNewRunMode(RUNMODE_PCAP_FILE, "single",
  14. "Single threaded pcap file mode",
  15. RunModeFilePcapSingle);
  16. 参数:运行模式,single名称,描述,执行函数
  17. 将执行函数添加到runmodes全局数组中。
  18. mode->RunModeFunc = RunModeFunc; // RunModeFilePcapSingle

suricata源码记录 - 图1

3.2 注册模块

  • 注册suricata所支持的所有线程模块
  1. TmModuleReceiveNFQRegister();
  2. TmModuleVerdictNFQRegister();
  3. TmModuleDecodeNFQRegister();
  4. TmModuleReceiveIPFWRegister();
  5. TmModuleVerdictIPFWRegister();
  6. TmModuleDecodeIPFWRegister();
  7. TmModuleReceivePcapRegister();
  8. TmModuleDecodePcapRegister();
  9. TmModuleReceivePcapFileRegister();
  10. TmModuleDecodePcapFileRegister();
  11. ……
  12. ……
  13. 函数内部实现:
  14. void TmModuleReceivePcapRegister (void)
  15. {
  16. tmm_modules[TMM_RECEIVEPCAP].name = "ReceivePcap";
  17. tmm_modules[TMM_RECEIVEPCAP].ThreadInit = ReceivePcapThreadInit;
  18. tmm_modules[TMM_RECEIVEPCAP].Func = NULL;
  19. tmm_modules[TMM_RECEIVEPCAP].PktAcqLoop = ReceivePcapLoop;
  20. tmm_modules[TMM_RECEIVEPCAP].ThreadExitPrintStats = ReceivePcapThreadExitStats;
  21. tmm_modules[TMM_RECEIVEPCAP].ThreadDeinit = NULL;
  22. tmm_modules[TMM_RECEIVEPCAP].RegisterTests = NULL;
  23. tmm_modules[TMM_RECEIVEPCAP].cap_flags = SC_CAP_NET_RAW;
  24. tmm_modules[TMM_RECEIVEPCAP].flags = TM_FLAG_RECEIVE_TM;
  25. }
  26. 保存在全局TmModule tmm_modules[TMM_SIZE]数组中。
  27. typedef struct TmModule_
  28. {
  29. char *name;
  30. TmEcode (*ThreadInit)(ThreadVars *, void *, void **); // 线程初始化函数
  31. void (*ThreadExitPrintStats)(ThreadVars *, void *); // 线程退出打印函数
  32. TmEcode (*ThreadDeinit)(ThreadVars *, void *); // 线程关闭函数
  33. TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
  34. TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);
  35. TmEcode (*Init)(void);// 全局初始化模块函数
  36. TmEcode (*DeInit)(void);// 全局关闭模块函数
  37. void (*RegisterTests)(void);
  38. uint8_t cap_flags;
  39. uint8_t flags;
  40. } TmModule;

suricata源码记录 - 图2

3.3 模块初始化

  • TmModuleRunInit()函数
  1. 调用tmm_modules[TMM_SIZE]数组中模块各个模块初始化函数。
  2. for (i = 0; i < TMM_SIZE; i++)
  3. {
  4. t = &tmm_modules[i];
  5. t->Init(); // 注意这里执行的是模块全局初始化函数
  6. }

3.4 运行模式调度

  • RunModeDispatch()函数
    • 从配置中读取运行模式。
    • 获得该运行模式中默认的Custom mode(如:single、auto等)。
    • 执行Custom mode中设置的执行函数,如RunModeFilePcapSingle函数。

3.5 运行模式执行函数

  • 例如:RunModeFilePcapSingle()
    • 通用模块初始化RunModeInitialize
    • 创建tv实例TmThreadCreatePacketHandler
    • 从tmm_modules中获得模块TmModuleGetByName
    • 插入槽slot
    • TmThreadSpawn真正创建线程函数(tm->Func),查找调用者可看出开启线程个数
  1. 插入槽slot函数 TmSlotSetFuncAppend
  2. void TmSlotSetFuncAppend(ThreadVars *tv, TmModule *tm, const void *data)
  3. {
  4. TmSlot *slot = SCMalloc(sizeof(TmSlot)); // 创建TmSlot
  5. ......
  6. slot->tv = tv; // TmSlot与ThreadVars关联
  7. ......
  8. SC_ATOMIC_INIT(slot->SlotFunc);
  9. (void)SC_ATOMIC_SET(slot->SlotFunc, tm->Func); // TmSlot与TmModule关联
  10. ......
  11. if (tv->tm_slots == NULL) {
  12. tv->tm_slots = slot; // ThreadVars与TmSlot关联
  13. ......
  14. } else {
  15. ......
  16. // 建立TmSlot链表(ReceivePcapFile->DecodePcapFile->FlowWorker)
  17. b->slot_next = slot;
  18. ......
  19. }
  20. return;
  21. }

3.6 整理下执行顺序

  • 运行模式注册,设置执行函数
  • 所有模块注册,设置模块相关函数
  • 所有模块初始化
  • 从配置获取运行模式类型,执行函数
  • 创建线程
  • 根据模块名称从全局数组tmm_modules中得到模块指针
  • 插入线程槽slot

3.7 架构图

3.7.1 所有模式

suricata源码记录 - 图3

3.7.2 线程_队列

suricata源码记录 - 图4

3.7.3 autofp模式

suricata源码记录 - 图5

3.7.4 整体架构图

suricata源码记录 - 图6

3.7.5 整体数据结构

suricata源码记录 - 图7

4. 部分模块

  • flow-hash.c
  1. 参考点
  2. 1. 锁:设置读写锁、尝试读写锁。读锁、尝试读锁没有调用
  3. 2. 哈希算法:uint32_t* 替代 char*
  4. 3. atomic变量:
  5. - 1flow_state 2use_cnt
  6. - 1next_ts
  7. 4. FlowInitConfig:申请内存(桶、流)时字节对齐,定义时字节对齐。初始化时使用
  8. 5. FlowGetUsedFlow:先尝试锁桶,再尝试锁流,从桶的最底端开始。内存不足需要释放已存在的流时使用
  9. 6. FlowGetFlowFromHash:找到的流,移动到list的顶端。每次查找元素时使用
  10. 7. FlowTimeoutHash:循环每个桶,先尝试锁桶,从桶的最底端开始。新线程超时时使用
  • defrag.c
  1. 1. ip重组使用大流表,以ipvlan_idip_frag_id作为key
  2. 2. 每个流表中的元素是一个tracker,每个tracker有一组红黑树指针,用于对多个ip分组进行排序,保证乱序情况下也可以重组
  3. 3. 提前分配的内存:
  4. defrag bucketdefrag tracker(放入队列,hash_table的实际元素)
  5. Frag Pool,申请多个Frag放入Pool,即 defrag_context->frag_pool
  6. 4. 动态分配的内存:
  7. Fragpkt指针,实际申请长度ip len
  8. Frag超时或重组结束时,申请一个新的packet,填写重组信息和数据
  • stream-tcp.c
  1. 初始化
  2. 1. tcp会话是从 PoolThreadGetById 函数中获取(即 pool->alloc_stack->data),
  3. Pool StreamTcpInitConfig--->PoolThreadInit--->PoolInit 初始化中提前分配好内存,每个thread有一个Pool
  4. Pool的申请方式(util-pool-thread.cutil-pool.c
  5. size=0unlimited):分散申请小块内存,元素内存不连续
  6. 申请多个PoolBucket(内存不连续),每个PoolBucket下挂一个data(内存不连续),每个data是一个element大小,data为动态内存申请
  7. size>0limited):集中申请大块内存,元素内存连续
  8. 申请多个PoolBucket(内存连续),申请一大块(num_element*element_size)内存(内存连续),每个data通过偏移量指向大块内存的不同地址
  9. 2. tcp会话中的每个tcp packet信息(即TcpStateQueue)是动态创建的,没有内存池,由StreamTcp3whsQueueSynAck 函数调用申请
  10. 重组
  11. 1. 如下图
  12. 2.

suricata源码记录 - 图8

suricata源码记录 - 图9

5. 参考文献