0. 简介
Suricata是一个高性能的网络入侵检测(IDS:Intrusion Detection System)、入侵防御(IPS:Intrusion Prevention System)和网络安全监控(NSM)的多线程引擎,内置支持IPv6。可加载snort规则和签名,支持barnyard2。使用pcap提供的接口进行抓包,运行前电脑必须安装有pcap才可以使用
1. 编译
Installationhttps://redmine.openinfosecfoundation.org/projects/suricata/wiki/Suricata_InstallationUbuntuhttps://redmine.openinfosecfoundation.org/projects/suricata/wiki/Ubuntu_Installation_from_GITOther point# cd suricata-update && curl -L https://github.com/OISF/suricata-update/archive/master.tar.gz | tar zxvf - --strip-components=1$ sudo apt install cbindgen
2. 重要结构体
suricata中tv、slot和tm的关系必须要搞清楚,汇总如下:
- tv:ThreadVars类型,线程
- slot:TmSlot类型,槽
- tm:TmModule类型,模块
线程的定义:typedef struct ThreadVars_ {pthread_t t; // 线程idchar *name; // 线程namechar *thread_group_name; // 线程group nameSC_ATOMIC_DECLARE(unsigned short, flags); // 原子声明,不知道作用,暂且不管uint8_t aof; // 线程遇到故障时怎么做uint8_t type; // 线程类型,例如:TVT_PPT, TVT_MGMTuint8_t restarted; // 线程重新启动失败的次数Tmq *inq;Tmq *outq;void *outctx;char *outqh_namestruct Packet_ * (*tmqh_in)(struct ThreadVars_ *);void (*InShutdownHandler)(struct ThreadVars_ *);void (*tmqh_out)(struct ThreadVars_ *, struct Packet_ *);void *(*tm_func)(void *);struct TmSlot_ *tm_slots;uint8_t thread_setup_flags;uint16_t cpu_affinity;int thread_priority; // 线程优先级SCPerfContext sc_perf_pctx;SCPerfCounterArray *sc_perf_pca;SCMutex *m;SCCondT *cond;uint8_t cap_flags;struct ThreadVars_ *next;struct ThreadVars_ *prev;} ThreadVars;
槽的定义:typedef struct TmSlot_ {ThreadVars *tv; // 拥有该slot的线程SC_ATOMIC_DECLARE(TmSlotFunc, SlotFunc);// 函数指针TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *); // 模块数据包获取函数TmEcode (*SlotThreadInit)(ThreadVars *, void *, void **); // 模块初始化执行函数void (*SlotThreadExitPrintStats)(ThreadVars *, void *); // 模块退出打印函数TmEcode (*SlotThreadDeinit)(ThreadVars *, void *); // 模块清理执行函数void *slot_initdata; // 数据存储SC_ATOMIC_DECLARE(void *, slot_data);PacketQueue slot_pre_pq;PacketQueue slot_post_pq;int tm_id; // tm IDint id; // slot IDstruct TmSlot_ *slot_next;} TmSlot;
模块定义:typedef struct TmModule_ {char *name; // 模块名称TmEcode (*ThreadInit)(ThreadVars *, void *, void **);void (*ThreadExitPrintStats)(ThreadVars *, void *);TmEcode (*ThreadDeinit)(ThreadVars *, void *);TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);TmEcode (*Init)(void);TmEcode (*DeInit)(void);void (*RegisterTests)(void);uint8_t cap_flags;uint8_t flags;} TmModule;
三者之间的关系,每一个线程都包含一个slot的链表,每个slot结点都悬挂着不同的模块,程序执行的时候会遍历slot链表,按照加入链表的顺序执行模块。
3. main函数
3.1 注册各种运行模式
- RunModeRegisterRunModes()函数
RunModeIdsPcapRegister(); // IDS+pcapRunModeFilePcapRegister(); // File+pcapRunModeIdsPfringRegister(); // IDS+pfringRunModeIpsIPFWRegister(); // IPS+ipfwRunModeIpsNFQRegister(); // IPS+nfqRunModeErfFileRegister(); // erf+fileRunModeErfDagRegister(); // erf+dagRunModeNapatechRegister(); // napatechRunModeIdsAFPRegister(); // IDS+AFPRunModeUnixSocketRegister(); // UnixSocket其中每一种运行模式调用RunModeRegisterNewRunMode注册各自的Custom mode(暂且翻译为“自定义模式”)RunModeRegisterNewRunMode设置各种运行模式的执行函数例如: RunModeRegisterNewRunMode(RUNMODE_PCAP_FILE, "single","Single threaded pcap file mode",RunModeFilePcapSingle);参数:运行模式,single名称,描述,执行函数将执行函数添加到runmodes全局数组中。mode->RunModeFunc = RunModeFunc; // RunModeFilePcapSingle

3.2 注册模块
- 注册suricata所支持的所有线程模块
TmModuleReceiveNFQRegister();TmModuleVerdictNFQRegister();TmModuleDecodeNFQRegister();TmModuleReceiveIPFWRegister();TmModuleVerdictIPFWRegister();TmModuleDecodeIPFWRegister();TmModuleReceivePcapRegister();TmModuleDecodePcapRegister();TmModuleReceivePcapFileRegister();TmModuleDecodePcapFileRegister();…………函数内部实现:void TmModuleReceivePcapRegister (void){tmm_modules[TMM_RECEIVEPCAP].name = "ReceivePcap";tmm_modules[TMM_RECEIVEPCAP].ThreadInit = ReceivePcapThreadInit;tmm_modules[TMM_RECEIVEPCAP].Func = NULL;tmm_modules[TMM_RECEIVEPCAP].PktAcqLoop = ReceivePcapLoop;tmm_modules[TMM_RECEIVEPCAP].ThreadExitPrintStats = ReceivePcapThreadExitStats;tmm_modules[TMM_RECEIVEPCAP].ThreadDeinit = NULL;tmm_modules[TMM_RECEIVEPCAP].RegisterTests = NULL;tmm_modules[TMM_RECEIVEPCAP].cap_flags = SC_CAP_NET_RAW;tmm_modules[TMM_RECEIVEPCAP].flags = TM_FLAG_RECEIVE_TM;}保存在全局TmModule tmm_modules[TMM_SIZE]数组中。typedef struct TmModule_{char *name;TmEcode (*ThreadInit)(ThreadVars *, void *, void **); // 线程初始化函数void (*ThreadExitPrintStats)(ThreadVars *, void *); // 线程退出打印函数TmEcode (*ThreadDeinit)(ThreadVars *, void *); // 线程关闭函数TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);TmEcode (*Init)(void);// 全局初始化模块函数TmEcode (*DeInit)(void);// 全局关闭模块函数void (*RegisterTests)(void);uint8_t cap_flags;uint8_t flags;} TmModule;

3.3 模块初始化
- TmModuleRunInit()函数
调用tmm_modules[TMM_SIZE]数组中模块各个模块初始化函数。for (i = 0; i < TMM_SIZE; i++){t = &tmm_modules[i];t->Init(); // 注意这里执行的是模块全局初始化函数}
3.4 运行模式调度
- RunModeDispatch()函数
- 从配置中读取运行模式。
- 获得该运行模式中默认的Custom mode(如:single、auto等)。
- 执行Custom mode中设置的执行函数,如RunModeFilePcapSingle函数。
3.5 运行模式执行函数
- 例如:RunModeFilePcapSingle()
- 通用模块初始化RunModeInitialize
- 创建tv实例TmThreadCreatePacketHandler
- 从tmm_modules中获得模块TmModuleGetByName
- 插入槽slot
- TmThreadSpawn真正创建线程函数(tm->Func),查找调用者可看出开启线程个数
插入槽slot函数 TmSlotSetFuncAppendvoid TmSlotSetFuncAppend(ThreadVars *tv, TmModule *tm, const void *data){TmSlot *slot = SCMalloc(sizeof(TmSlot)); // 创建TmSlot......slot->tv = tv; // TmSlot与ThreadVars关联......SC_ATOMIC_INIT(slot->SlotFunc);(void)SC_ATOMIC_SET(slot->SlotFunc, tm->Func); // TmSlot与TmModule关联......if (tv->tm_slots == NULL) {tv->tm_slots = slot; // ThreadVars与TmSlot关联......} else {......// 建立TmSlot链表(ReceivePcapFile->DecodePcapFile->FlowWorker)b->slot_next = slot;......}return;}
3.6 整理下执行顺序
- 运行模式注册,设置执行函数
- 所有模块注册,设置模块相关函数
- 所有模块初始化
- 从配置获取运行模式类型,执行函数
- 创建线程
- 根据模块名称从全局数组tmm_modules中得到模块指针
- 插入线程槽slot
3.7 架构图
3.7.1 所有模式

3.7.2 线程_队列

3.7.3 autofp模式

3.7.4 整体架构图

3.7.5 整体数据结构

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


