0. 简介
Suricata是一个高性能的网络入侵检测(IDS:Intrusion Detection System)、入侵防御(IPS:Intrusion Prevention System)和网络安全监控(NSM)的多线程引擎,内置支持IPv6。可加载snort规则和签名,支持barnyard2。使用pcap提供的接口进行抓包,运行前电脑必须安装有pcap才可以使用
1. 编译
Installation
https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Suricata_Installation
Ubuntu
https://redmine.openinfosecfoundation.org/projects/suricata/wiki/Ubuntu_Installation_from_GIT
Other 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; // 线程id
char *name; // 线程name
char *thread_group_name; // 线程group name
SC_ATOMIC_DECLARE(unsigned short, flags); // 原子声明,不知道作用,暂且不管
uint8_t aof; // 线程遇到故障时怎么做
uint8_t type; // 线程类型,例如:TVT_PPT, TVT_MGMT
uint8_t restarted; // 线程重新启动失败的次数
Tmq *inq;
Tmq *outq;
void *outctx;
char *outqh_name
struct 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 ID
int id; // slot ID
struct 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+pcap
RunModeFilePcapRegister(); // File+pcap
RunModeIdsPfringRegister(); // IDS+pfring
RunModeIpsIPFWRegister(); // IPS+ipfw
RunModeIpsNFQRegister(); // IPS+nfq
RunModeErfFileRegister(); // erf+file
RunModeErfDagRegister(); // erf+dag
RunModeNapatechRegister(); // napatech
RunModeIdsAFPRegister(); // IDS+AFP
RunModeUnixSocketRegister(); // 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函数 TmSlotSetFuncAppend
void 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_ts
4. 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_pool
4. 动态分配的内存:
① 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.