【网络入侵检测】基于Suricata源码分析运行模式(Runmode)
【作者主页】只道当时是寻常
【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。
1. 概要
👋 在 Suricata 中抽象出线程、线程模块和队列三个概念:线程类似进程,可多线程并行执行操作;监听、解码、检测等网络操作被抽象为模块,由线程执行,而不同的线程可以执行一个或多个不同的模块;队列实现线程间数据包流转,三者的不同组合构成 Suricata 的运行模式。
2. 运行模式
Suricata的运行模式可通过参数 --list-runmodes 显示,执行后结果汇总成表格如下所示,RunMode Type 和 Custom Mode 配置同时指定一种运行模式。
RunMode Type | Custom Mode | Description |
PCAP_DEV | single | 单线程pcap实时模式 |
autofp | 多线程pcap实时模式。来自每个流的数据包被分配到一个一致的检测线程 | |
workers | 工作者pcap实时模式,每个线程执行从采集到日志记录的所有任务 | |
PCAP_FILE | single | 单线程pcap文件模式 |
autofp | 多线程pcap文件模式。来自每个流的数据包被分配到一个一致的检测线程 | |
PFRING | autofp | 多线程pfring模式。来自每个流的数据包被分配到一个检测线程,不同于"pfring_auto"模式,在"pfring_auto"模式中,同一流的数据包可以由任何检测线程处理 |
single | 单线程pfring模式 | |
workers | 工作者pfring模式,每个线程执行从采集到日志记录的所有任务 | |
NFQ | autofp | 考虑流的多线程NFQ IPS模式 |
workers | 每队列一个线程的多队列NFQ IPS模式 | |
NFLOG | autofp | 多线程nflog模式 |
single | 单线程nflog模式 | |
workers | 工作者nflog模式 | |
IPFW | autofp | 考虑流的多线程IPFW IPS模式 |
workers | 每队列一个线程的多队列IPFW IPS模式 | |
ERF_FILE | single | 单线程ERF文件模式 |
autofp | 多线程ERF文件模式。来自每个流的数据包被分配到一个检测线程 | |
ERF_DAG | autofp | 多线程DAG模式。来自每个流的数据包被分配到一个检测线程,不同于"dag_auto"模式,在"dag_auto"模式中,同一流的数据包可以由任何检测线程处理 |
single | 单线程DAG模式 | |
workers | 工作者DAG模式,每个线程执行从采集到日志记录的所有任务 | |
AF_PACKET_DEV | single | 单线程af-packet模式 |
workers | 工作者af-packet模式,每个线程执行从采集到日志记录的所有任务 | |
autofp | 多套接字AF_PACKET模式。来自每个流的数据包被分配到一个检测线程。 | |
AF_XDP_DEV | single | 单线程af-xdp模式 |
workers | 工作者af-xdp模式,每个线程执行从采集到日志记录的所有任务 | |
NETMAP(DISABLED) | single | 单线程netmap模式 |
workers | 工作者netmap模式,每个线程执行从采集到日志记录的所有任务 | |
autofp | 多线程netmap模式。来自每个流的数据包被分配到一个检测线程。 | |
DPDK(DISABLED) | workers | 工作者DPDK模式,每个线程执行从采集到日志记录的所有任务 |
UNIX_SOCKET | single | Unix套接字模式 |
autofp | Unix套接字模式 | |
WINDIVERT | autofp | 按流负载均衡的多线程WinDivert IPS模式 |
2.1 single模式
在 Suricata 里,single 模式被限制为只能监听单个网卡设备,并且仅仅创建一个线程来执行数据包嗅探、解码以及 flow 解析等操作。由于这种单线程的处理方式,开发者在调试和测试过程中能够更方便地进行操作,所以该模式在开发过程中十分实用。
2.2 autofp模式
在 Suricata 的 autofp 模式中,捕获解码线程负责数据包的捕获与解码,并通过队列将其传递至 "flow worker" 线程进行处理和响应。此模式支持一个或者多个网卡的同时监听,每个网卡设备可通过配置文件设定数据包嗅探线程的数量(例如pcap-interface:ens33-threads配置),而"flow worker"线程处理数量取决于配置文件中threading-cpu-affinity-worker-cpu-set-threads数量。
2.3 workers模式
通常情况下,workers 运行模式的性能表现最佳。在此模式中,每个数据包处理线程都包含完整的数据包处理流程(即完整的数据包管道)。此模式支持同时监听多个网卡设备,且可通过配置文件配置指定每个网卡设备线程数量。
3. 源码分析
3.1 注册运行模式
在 Suricata 中,RunModeRegisterRunModes 函数是各种运行模式注册函数,该函数会将章节2中通过--list-runmodes 显示的所有运行模式注册到一个全局变量 runmodes 中,该变量的结构和定义如下所示:
typedef struct RunModes_ {int cnt;RunMode *runmodes;
} RunModes;static RunModes runmodes[RUNMODE_USER_MAX];
下面是 RunModeRegisterRunModes 函数示意图:
3.2 注册线程模块
Suricata 中将不同的功能封装成模块的形式,通过将这些模块分配给不同的线程从而显示对线程所实现功能的控制。RegisterAllModules 函数是注册模块的入口函数,将所有的模块功能存储在一个名为 tmm_modules 的全局变量中,该变量的声明与定义如下所示:
typedef struct TmModule_ {const char *name;/** thread handling */TmEcode (*ThreadInit)(ThreadVars *, const void *, void **);void (*ThreadExitPrintStats)(ThreadVars *, void *);TmEcode (*ThreadDeinit)(ThreadVars *, void *);/** the packet processing function */TmEcode (*Func)(ThreadVars *, Packet *, void *);TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);/** terminates the capture loop in PktAcqLoop */TmEcode (*PktAcqBreakLoop)(ThreadVars *, void *);/** does a thread still have tasks to complete before it can be killed?* \retval bool* \param tv threadvars* \param thread_data thread module thread data (e.g. FlowWorkerThreadData for FlowWorker) */bool (*ThreadBusy)(ThreadVars *tv, void *thread_data);TmEcode (*Management)(ThreadVars *, void *);/** global Init/DeInit */TmEcode (*Init)(void);TmEcode (*DeInit)(void);
#ifdef UNITTESTSvoid (*RegisterTests)(void);
#endifuint8_t cap_flags; /**< Flags to indicate the capability requirement ofthe given TmModule *//* Other flags used by the module */uint8_t flags;
} TmModule;TmModule tmm_modules[TMM_SIZE];
RegisterAllModules 函数实现如下所示:
以 TmModuleReceivePcapRegister 为例,Suricata 会将网络报文的嗅探功能封装成一个模块,模块中包含初始化函数、销毁函数、主函数等信息,并通过注册函数将其添加到tmm_modules数组中。 TmModuleReceivePcapRegister 函数实现如下所示:
/*** \brief Registration Function for ReceivePcap.*/
void TmModuleReceivePcapRegister (void)
{tmm_modules[TMM_RECEIVEPCAP].name = "ReceivePcap";tmm_modules[TMM_RECEIVEPCAP].ThreadInit = ReceivePcapThreadInit;tmm_modules[TMM_RECEIVEPCAP].ThreadDeinit = ReceivePcapThreadDeinit;tmm_modules[TMM_RECEIVEPCAP].PktAcqLoop = ReceivePcapLoop;tmm_modules[TMM_RECEIVEPCAP].PktAcqBreakLoop = ReceivePcapBreakLoop;tmm_modules[TMM_RECEIVEPCAP].ThreadExitPrintStats = ReceivePcapThreadExitStats;tmm_modules[TMM_RECEIVEPCAP].cap_flags = SC_CAP_NET_RAW;tmm_modules[TMM_RECEIVEPCAP].flags = TM_FLAG_RECEIVE_TM;
#ifdef UNITTESTStmm_modules[TMM_RECEIVEPCAP].RegisterTests = SourcePcapRegisterTests;
#endif
}