初始化suricata实例:程序名设置为程序文件名,其他变量复位。
int InitGlobal(void)
{
//调用rust代码初始化context
rs_init(&suricata_context);
//初始化引擎状态:
SC_ATOMIC_INIT(engine_stage);
/* initialize the logging subsys */
//包括日志级别、日志格式、日志输出接口(默认屏幕)、日志输出过滤
//后面还会用配置重新初始化一遍日志模块
SCLogInitLogModule(NULL);
//设置进程名,及ps看到的名字
SCSetThreadName("Suricata-Main");
/* Ignore SIGUSR2 as early as possible. We redeclare interest
* once we're done launching threads. The goal is to either die
* completely or handle any and all SIGUSR2s correctly.
*/
#ifndef OS_WIN32
UtilSignalHandlerSetup(SIGUSR2, SIG_IGN);
if (UtilSignalBlock(SIGUSR2)) {
SCLogError("SIGUSR2 initialization error");
return EXIT_FAILURE;
}
#endif
//初始化解析size的pcre正则表达式,用于解析xkb、xMB、xGb等
ParseSizeInit();
//注册运行模式
RunModeRegisterRunModes();
/* Initialize the configuration module. */
//只是将配置模块初始化为一个尾队列
ConfInit();
return 0;
}
注册所有的运行模式
运行模式结构体:
/* Run mode */
enum RunModes {
RUNMODE_UNKNOWN = 0,
RUNMODE_PCAP_DEV,
RUNMODE_PCAP_FILE,
RUNMODE_PFRING,
RUNMODE_NFQ,
RUNMODE_NFLOG,
RUNMODE_IPFW,
RUNMODE_ERF_FILE,
RUNMODE_DAG,
RUNMODE_AFP_DEV,
RUNMODE_AFXDP_DEV,
RUNMODE_NETMAP,
RUNMODE_DPDK,
RUNMODE_UNITTEST,
RUNMODE_NAPATECH,
RUNMODE_UNIX_SOCKET,
RUNMODE_WINDIVERT,
RUNMODE_PLUGIN,
RUNMODE_USER_MAX, /* Last standard running mode */
RUNMODE_LIST_KEYWORDS,
RUNMODE_LIST_APP_LAYERS,
RUNMODE_LIST_RUNMODES,
RUNMODE_PRINT_VERSION,
RUNMODE_PRINT_BUILDINFO,
RUNMODE_PRINT_USAGE,
RUNMODE_DUMP_CONFIG,
RUNMODE_CONF_TEST,
RUNMODE_LIST_UNITTEST,
RUNMODE_ENGINE_ANALYSIS,
#ifdef OS_WIN32
RUNMODE_INSTALL_SERVICE,
RUNMODE_REMOVE_SERVICE,
RUNMODE_CHANGE_SERVICE_PARAMS,
#endif
RUNMODE_DUMP_FEATURES,
RUNMODE_MAX,
};
typedef struct RunMode_ {
/* the runmode type */
enum RunModes runmode;
const char *name;//定制模式的名字,主要有single、workers、autofp
const char *description;
/* runmode function */
int (*RunModeFunc)(void);//运行模式的入口函数
void (*RunModeIsIPSEnabled)(void);
} RunMode;
typedef struct RunModes_ {
int cnt;
RunMode *runmodes;
} RunModes;
static RunModes runmodes[RUNMODE_USER_MAX];
解析命令行
检查运行模式设置是否已设置,并确保suricata后台模式(daemon)下不能运行RUNMODE_PCAP_FILE、RUNMODE_UNITTEST两种运行模式。
启动内部运行模式,主要包括RUNMODE_LIST_KEYWORDS、RUNMODE_LIST_APP_LAYERS、RUNMODE_PRINT_VERSION、RUNMODE_PRINT_BUILDINFO、RUNMODE_PRINT_USAGE、RUNMODE_LIST_RUNMODES、RUNMODE_LIST_UNITTEST、RUNMODE_UNITTEST这几个运行模式,运行完响应的处理函数,直接退出程序。
Initializations for global vars, queues, etc (memsets, mutex init…)
void GlobalsInitPreConfig(void)
{
//初始化时间锁和时区
TimeInit();
//Registers the keywords(SMs) that should be given fp support.
//g_fp_support_smlist_list:存储单模匹配链表ID和优先级的对应关系,单模匹配链表ID类型为enum DetectSigmatchListEnum
SupportFastPatternForSigMatchTypes();
//初始化解析阈值规则的相关正则表达式
SCThresholdConfGlobalInit();
//初始化一个哈希表,用于存储协议名和协议号的对应关系
SCProtoNameInit();
//
FrameConfigInit();
}
从suricata.yaml加载配置到内存
这个是设置非system模式的情况下,把日志和数据目录设置到当前目录;suricata有system模式和user模式
Initialize the user and group Suricata is to run as。即初始化suricata运行的用户名和组名
读取logging.outputs配置节点,调用SCLogInitLogModule重新初始化日志模块
打印suricata版本和模式(user模式或system模式)
Print a summary of CPUs detected (configured and online)。
sysconf(_SC_NPROCESSORS_CONF)返回系统可以使用的核数,但是其值会包括系统中禁用的核的数目,因此该值并不代表当前系统中可用的核数。而 sysconf(_SC_NPROCESSORS_ONLN)的返回值真正的代表了系统当前可用的核数.
根据运行模式,从配置读取网卡名或者设置网卡配置:
对于RUNMODE_PCAP_DEV、RUNMODE_DPDK:如果命令行未设置网卡,则从配置读取网卡名,存到设备名链表pre_live_devices。
对于RUNMODE_PFRING:将从命令行读取的网卡名存到配置节点pfring.live-interface
This function is meant to contain code that needs to be run once the configuration has been loaded.即加载完配置后首先运行这个函数。
typedef struct StorageMapping_ {
const char *name;
StorageEnum type; // host, flow, tx, stream, ssn, etc
unsigned int size;
void *(*Alloc)(unsigned int);
void (*Free)(void *);
} StorageMapping;
/** \brief list of StorageMapping used at registration time */
typedef struct StorageList_ {
StorageMapping map;
int id;
struct StorageList_ *next;
} StorageList;
static StorageList *storage_list = NULL;
static int storage_max_id[STORAGE_MAX];
static int storage_registration_closed = 0;
static StorageMapping **storage_map = NULL;
enum ExceptionPolicy {
EXCEPTION_POLICY_NOT_SET = 0,
EXCEPTION_POLICY_PASS_PACKET,
EXCEPTION_POLICY_PASS_FLOW,
EXCEPTION_POLICY_BYPASS_FLOW,
EXCEPTION_POLICY_DROP_PACKET,
EXCEPTION_POLICY_DROP_FLOW,
EXCEPTION_POLICY_REJECT,
};
/** storage for live device names */
typedef struct LiveDevice_ {
char *dev; /**< the device (e.g. "eth0") */
char dev_short[MAX_DEVNAME + 1];
bool tenant_id_set;
int id;
SC_ATOMIC_DECLARE(uint64_t, pkts);
SC_ATOMIC_DECLARE(uint64_t, drop);
SC_ATOMIC_DECLARE(uint64_t, bypassed);
SC_ATOMIC_DECLARE(uint64_t, invalid_checksums);
TAILQ_ENTRY(LiveDevice_) next;
uint32_t tenant_id; /**< tenant id in multi-tenancy */
uint32_t offload_orig; /**< original offload settings to restore @exit */
} LiveDevice;
/***** Setup/General Registration *****/
int AppLayerSetup(void)
{
SCEnter();
//初始化应用层协议检测context(主要是初始化单模匹配算法、多模匹配算法)
//注册名为expectation的IPPairStorage和FlowStorage
AppLayerProtoDetectSetup();
//memset AppLayerParserCtx
AppLayerParserSetup();
//注册应用层处理函数
AppLayerParserRegisterProtocolParsers();
//SSL和IMAP在注册协议处理函数时,会注册一些模式匹配填充到alpd_ctx。这里将注册的模式填充到多模匹配总表mpm_table
AppLayerProtoDetectPrepareState();
//设置每个应用层协议计数器的名字
/*typedef struct AppLayerCounterNames_ {
char name[MAX_COUNTER_SIZE];
char tx_name[MAX_COUNTER_SIZE];
char gap_error[MAX_COUNTER_SIZE];
char parser_error[MAX_COUNTER_SIZE];
char internal_error[MAX_COUNTER_SIZE];
char alloc_error[MAX_COUNTER_SIZE];
} AppLayerCounterNames;*/
AppLayerSetupCounters();
SCReturnInt(0);
}
获取与包捕获相关的一些配置参数,目前包括:max-pending-packets、default-packet-size
从配置文件中载入host os policy(主机OS策略)信息,
radix tree保存所有主机策略信息。网络入侵通常是针对某些特定OS的漏洞,因此如果能够获取部署环境中主机的OS信息,肯定对入侵检测大有裨益。
注册检测引擎所支持的规则格式中的关键字,比如sid、priority、msg、within、distance等等
为指定的检测关键字设置严格匹配。
初始化queue handler(队列处理函数),这个是衔接线程模块和数据包队列之间的桥梁,这里注册了3类handler:simple, packetpool, flow。每类handler内部都有一个InHandler和OutHandler,simple和flow的InHandler都是从inQueue队列中获取数据包,simple的OutHandler将数据包送入outQueue。packetpool的InHandler从packetpool获取数据包,OutHandler将数据包送入packetpool。
注册名为tag的HostStorage和FlowStorage
初始化了tag signature和 tag packet alert structure。
tag signature we use for tag alerts。
tag packet alert structure for tag alerts。
tag signature 的sid为1.
注册名为threshold的HostStorage和IPPairStorage
注册名为bit的HostStorage
注册名为bit的IPPairStorage
检测配置文件里面的address-groups配置是否正确
检测配置文件里面的port-groups配置是否正确
初始化feature_hash_table
注册所有线程模块
Sets a flag that informs the HTP app layer that some module in the engine needs the http request file。即设置htp_config_flags,有4个flag:HTP_REQUIRE_REQUEST_BODY、HTP_REQUIRE_REQUEST_MULTIPART、HTP_REQUIRE_REQUEST_FILE、HTP_REQUIRE_RESPONSE_BODY
前面注册了一些storage,存储在StorageList。这里将其挪到StorageMapping,即注册时采用链表数组形式,为了实现快速查找,将其挪到二维数组里,可通过type:id快速访问对应的storage
前面注册了各个线程模块,这里对每个模块进行初始化
检查是否进入Daemon模式。若需要进入Daemon模式,则会检测pidfile是否已经存在(daemon下只 能有一个实例运行),然后进行Daemonize,最后创建一个pidfile。Daemonize的主要思路是:fork->子进程调用 setsid创建一个新的session,关闭stdin、stdout、stderr,并告诉父进程 –> 父进程等待子进程通知,然后退出 –> 子进程继续执行。
初始化信号handler。首先为SIGINT(ctrl-c触发)和SIGTERM(不带参数kill时触发)这两个常规退出信号分别注册handler,对SIGINT的处理是设置程序的状态标志为STOP,即让程序优雅地退出;而对SIGTERM是设置为KILL,即强杀。接着,程序会忽略SIGPIPE(这个信号通常是在Socket通信时向已关闭的连接另一端发送数据时收到)和SIGSYS(当进程尝试执行一个不存在的系统调用时收到)信号,以加强程序的容错性和健壮性。
获取程序日志目录
检查日志目录是否存在
检查日志目录是否可写
initializing host engine:读取配置文件host节点的hash-size, prealloc, memcap配置,初始化大小为hash-size、元素为HostHashRow的hash,并预分配prealloc个Host,放在host_spare_q里面。
Configures the core dump size
加载解码器decoder配置,设置packet_alert_max(后面会分配packet_alert_max个PacketAlert)
设置Host mode: set if box is sniffing only or is a router
/* initialization code for both the main modes and for
* unix socket mode.
*
* Will be run once per pcap in unix-socket mode */
void PreRunInit(const int runmode)
{
//初始化一个名为byterange的hash,不知道干啥用的
HttpRangeContainersInit();
if (runmode == RUNMODE_UNIX_SOCKET)
return;
//初始化stats_ctx:Holds the output interface context for the counter api
StatsInit();
#ifdef PROFILING
SCProfilingRulesGlobalInit();
SCProfilingKeywordsGlobalInit();
SCProfilingPrefilterGlobalInit();
SCProfilingSghsGlobalInit();
SCProfilingInit();
#endif /* PROFILING */
//初始化IP分片重组模块
DefragInit();
//初始化flow_hash
FlowInitConfig(FLOW_QUIET);
//初始化ippair_hash
IPPairInitConfig(FLOW_QUIET);
//初始化Stream TCP配置。其中调用了StreamTcpReassembleInit函数进行重组模块初始化
StreamTcpInitConfig(STREAM_VERBOSE);
//为流深度设置一个默认值
AppLayerParserPostStreamSetup();
// 注册应用层全局计数器
AppLayerRegisterGlobalCounters();
//Register a file data output module
OutputFilestoreRegisterGlobalCounters();
}
去除主线程的权限。这个是通过libcap-ng实现的,首先调用capng_clear清空所有权限,然后根据运行模式添加一些必要权限(主要是为了抓包),最后调用capng_change_id设置新的uid和gid。主线程的权限应该会被新建的子线程继承,因此只需要在主线程设置即可。
Enable coredumps on systems where coredumps can and need to be enabled.
/* tasks we need to run before packets start flowing,
* but after we dropped privs */
void PreRunPostPrivsDropInit(const int runmode)
{
//Initializes stats context
StatsSetupPostConfigPreOutput();
//Initialize the output modules
//前面注册线程模块时注册了日志模块,这里初始化输出日志模块,并激活
RunModeInitializeOutputs();
DatasetsInit();
if (runmode == RUNMODE_UNIX_SOCKET) {
/* As the above did some necessary startup initialization, it
* also setup some outputs where only one is allowed, so
* deinitialize to the state that unix-mode does after every
* pcap. */
PostRunDeinit(RUNMODE_PCAP_FILE, NULL);
return;
}
StatsSetupPostConfigPostOutput();
}
void PostConfLoadedDetectSetup(SCInstance *suri)
{
DetectEngineCtx *de_ctx = NULL;
if (!suri->disabled_detect) {
//初始化解析规则classtype的正则表达式
SCClassConfInit();
//初始化解析reference的正则表达式
SCReferenceConfInit();
//设置是否延迟检测。若delayed-detect为yes,则系统将在载入规则集之前就开始处理数据包,这样能够在IPS模式下减少系统的down time(宕机时间)
SetupDelayedDetect(suri);
int mt_enabled = 0;
(void)ConfGetBool("multi-detect.enabled", &mt_enabled);
int default_tenant = 0;
if (mt_enabled)
(void)ConfGetBool("multi-detect.default", &default_tenant);
if (DetectEngineMultiTenantSetup(suri->unix_socket_enabled) == -1) {
FatalError("initializing multi-detect "
"detection engine contexts failed.");
}
if (suri->delayed_detect && suri->run_mode != RUNMODE_CONF_TEST) {
de_ctx = DetectEngineCtxInitStubForDD();
} else if (mt_enabled && !default_tenant && suri->run_mode != RUNMODE_CONF_TEST) {
de_ctx = DetectEngineCtxInitStubForMT();
} else {
de_ctx = DetectEngineCtxInit();
}
if (de_ctx == NULL) {
FatalError("initializing detection engine failed.");
}
if (de_ctx->type == DETECT_ENGINE_TYPE_NORMAL) {
if (LoadSignatures(de_ctx, suri) != TM_ECODE_OK)
exit(EXIT_FAILURE);
}
gettimeofday(&de_ctx->last_reload, NULL);
DetectEngineAddToMaster(de_ctx);
DetectEngineBumpVersion();
}
}
设置suricata开始时间
获取suricata运行模式,调用对应的运行模式函数,创建flow管理线程(只有一个FlowManager模块)、flow回收线程(只有一个FlowRecycler模块)、StatsWakeupThread、StatsMgmtThread
等待所有线程初始化完成
设置引擎状态为RUNTIME
Set the max_pending_return_packets value
Un-pause all the paused threads
Waits for all threads to be in a running state
注册信号处理函数,重新加载检测引擎