项目中需求使用suricata 检测功能,只需要获取检测得到的 alert 结果, 需要将suricata的检测功能集成到我们的项目中,并提供接口动态加载规则。 源代码版本 6.0.4
源码 将suricata编译成动态库,使用suricata检测功能,只需要获取检测得到的alert。
目录
TmModuleDecodeLibraryRegister 注册DecodeLibrary 的解析函数
1、将 suricata 编译成动态库(以下简称为 libsuri_603.so),供我们的项目调用。
2、我们的项目已自己采集网卡数据,程序将调用 libsuri_603.so,将采集到的数据传入。
3、项目中调用的 libsuri_603.so 的也是一个动态库,并且只能在子线程(子线程名字为 Process 线程,和suricata中的worker线程一一对应)中,所有 libsuri_603.so 中的接口全部都在子线程中调用。
4、至少提供四个接口,分别为初始化,检测数据传入,动态规则加载接口,Destroy。
5、增加一个run_mode 为 RUNMODE_LIBRARY,在初始化中直接复制suricata.run_mode = RUNMODE_LIBRARY;
6、增加了三个文件分别为 libsuricata.h(接口头文件), source-library.c(主要负责调用工作线程),runmode-library.c(主要负责worker线程的处理化)
/* libsuricata.h */
//动态加载规则结束的回调
typedef void (*ReloadRuleRet)(int);
/** \internal
* \brief 初始化,每个 Process 线程都会调用这个 函数,但是只有其中一个线程取初始化suricata
* 全局使用的数据,比如诸多回调函数的注册,管理线程的创建,规则文件的加载,检测引擎的加载。
* 待全局数据初始完成后才会在每个 Process 线程中初始 worker线程独有的数据,
* 注册工作流程的回调函数,初始化工作线程变量和存放数据包的内存池。
* \param yamlFilePath suricata.yaml配置文件的路径
* \param ips_ids 0:ips or 1:ids, 没有用到阻断功能,ips只是用以逐包检测
* \param retFunc 动态加载规则结果的回调函数
*/
int suricataLibInit(const char* yamlFilePath, uint8_t ips_ids, ReloadRuleRet retFunc);
/** \internal
* \brief worker 我们项目会处理含有gtp层的码流,suricata不能解析这个,
* 故这里会把含有gtp层的数据直接从内层ip开始,ipType=4 or 6;
* 不含gtp的从Ethernet开始,ipType=0
* \param pkt 一条码流包
* \param caplen 长度
* \param ipType 详见brief
* \param lcapTime 包时间
* \param outAlerts 传入alert,同步数据结构,事实上ids是逐流检测的,出结果可能会晚一些
* 我们程序希望使用ips逐包检测,这样,出的检测结果即是当前传入的包触发的
*/
int suricataLibProcessPacket(const uint8_t* pkt, int caplen, uint8_t ipType
, const struct timeval* lcapTime, PacketOutAlerts* outAlerts);
/** \internal
* \brief 动态加载规则 加载规则比较耗时,在此只是创建一个子线程,在子线程中加载规则
*/
int suricataLibDynamicUpdateRules(void);
/** \internal
* \brief 释放资源
*/
void suricataLibGlobalsDestroy(void);
修改 src/Makefile.am 文件 ,将suricata编译成名字为 libsuri_603.so 的动态库
int suricataLibInit(const char *yamlFilePath, uint8_t ips_ids, ReloadRuleRet retFunc)
{
//先初始化公共部分,保证只有一个线程初始化
int retCode = suricataLibraryPublicInit(yamlFilePath, ips_ids, retFunc);
if (0 != retCode)
{
return retCode;
}
// 公共部分初始完成后再初始化自有线程的数据
return suricataLibrarayThreadInit();
}
//初始化suricata 公共部分的部分代码
int suricataLibraryPublicInit(const char* yamlFilePath, uint8_t ips_ids, ReloadRuleRet retFunc)
{
pthread_mutex_lock(&sg_initMutex);
static int retCode = 1;
do
{
if (0 == retCode)
{
break;
}
//保存动态加载规则结果的回调函数
reloadRuleRetFunc = retFunc;
SC_ATOMIC_INIT(isReloading);
SCInstanceInit(&suricata, "suricata_603_lib");
if (InitGlobal() != 0) {
retCode = -1;
break;
}
//不读命令参数 直接设定为 RUNMODE_LIBRARY
suricata.run_mode = RUNMODE_LIBRARY;
//保存suricata.yaml文件路径
memcpy(yamlFilePathChar, yamlFilePath, pathLen);
suricata.conf_filename = yamlFilePathChar;
/*
* ... 此处省略1w行
*/
retCode = 0;
} while (false);
pthread_mutex_unlock(&sg_initMutex);
return retCode;
}
suricataLibraryPublicInit 函数初始化的内容与 int SuricataMain(int argc, char **argv) 中初始的内容大致相同,不同点主要有三:
1、suricataLibraryPublicInit不初始worker线程的数据,这部分放在Process 线程调用suricataLibrarayThreadInit 函数实现。
2、suricataLibraryPublicInit 去掉了 SuricataMainLoop(&suricata)调用,因为libsuri_603.so不需要这个了,其动态加载规则放在另外的接口中处理 int suricataLibDynamicUpdateRules(void);
3、Destroy部分放在suricataLibGlobalsDestroy 接口中。
RunModeRegisterRunModes 函数中增加 RunModeLibraryRegister() 用以注册RUNMODE_LIBRARY 的回调函数 RunModeLibraryWorkers ,这个函数在各个线程在初始化自有线程的数据时调用。
void RunModeRegisterRunModes(void)
{
/*
* ...
*/
RunModeLibraryRegister();
return;
}
void RunModeLibraryRegister(void)
{
SCEnter();
RunModeRegisterNewRunMode(RUNMODE_LIBRARY, "workers",
"Workers library mode, each thread does all"
" tasks from acquisition to logging",
RunModeLibraryWorkers);
library_default_mode = "workers";
return;
}
void RegisterAllModules(void)
{
/*
*
*/
/* library */
TmModuleDecodeLibraryRegister();
}
void TmModuleDecodeLibraryRegister (void)
{
SCEnter();
SCLogDebug(" libraray support");
tmm_modules[TMM_DECODELIBRARY].name = "DecodeLibrary";
tmm_modules[TMM_DECODELIBRARY].ThreadInit = DecodeLibraryThreadInit;
tmm_modules[TMM_DECODELIBRARY].Func = DecodeLibrary;
tmm_modules[TMM_DECODELIBRARY].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODELIBRARY].ThreadDeinit = DecodeLibraryThreadDeinit;
tmm_modules[TMM_DECODELIBRARY].cap_flags = 0;
tmm_modules[TMM_DECODELIBRARY].flags = TM_FLAG_DECODE_TM;
SCReturn;
}
int suricataLibrarayThreadInit(void)
{
/*
* ... 此处省略1w行
*/
//主要是这个函数 调用 RUNMODE_LIBRARY 模式的回调函数
//这里调用下面RunModeLibraryWorkers这个函数
mode->RunModeFunc();
/*
* ... 此处省略1w行
*/
return 0;
}
static int RunModeLibraryWorkers(void)
{
int ret = -1;
char tname[50] = { "" };
ThreadVars* tv_worker = NULL;
TmModule* tm_module = NULL;
pthread_t pthreadId = pthread_self();
snprintf(tname, sizeof(tname), "LIBW-%lu", pthreadId);
tv_worker = TmThreadCreatePacketHandler(tname,
"packetpool", "packetpool",
"packetpool", "packetpool",
"pktacqloop");
if (tv_worker == NULL) {
SCLogError(SC_ERR_LIBRARY_CONFIG, " TmThreadsCreate failed for (%s)", tname);
//exit(EXIT_FAILURE);
return -1;
}
tv_worker->t = pthreadId;
//注册解析流程 DecodeLibrary为新增的函数
tm_module = TmModuleGetByName("DecodeLibrary");
if (tm_module == NULL) {
SCLogError(SC_ERR_LIBRARY_CONFIG, " TmModuleGetByName failed for DecodeLibrary");
return -1;
}
TmSlotSetFuncAppend(tv_worker, tm_module, NULL);
//注册检测流程 FlowWorker为原有的函数
tm_module = TmModuleGetByName("FlowWorker");
if (tm_module == NULL) {
SCLogError(SC_ERR_RUNMODE, "TmModuleGetByName for FlowWorker failed");
return -1;
}
TmSlotSetFuncAppend(tv_worker, tm_module, NULL);
//初始化worker线程的内存池(PacketPoolInit) 对流等
TmThreadsSlotPktAcqInit(tv_worker);
libraryThreadVarsPtr = tv_worker; //线程变量, 保存每个线的ThreadVars
//不创建线程,只将这个线程变量加入 ThreadVars *tv_root[TVT_MAX] 中保存
TmThreadAppend(tv_worker, tv_worker->type);
return ret;
}
这个就直接调用接口,然后顺着注册的回调函数依次运行即可 DecodeLibrary -> FlowWorker
int suricataLibProcessPacket(const uint8_t* pkt, int caplen, uint8_t ipType, const struct timeval* lcapTime, PacketOutAlerts* outAlerts)
{
TmEcode code = suricataLibraryProcessPacket(pkt, caplen, ipType, lcapTime, outAlerts);
SCReturnInt(code);
}
创建子线程加载 加载函数为suricataLibraryAsyncUpdateRules
int suricataLibraryDynamicUpdateRules(void)
{
if (unlikely(SC_ATOMIC_GET(isReloading) == true))
{
return -1;
}
SC_ATOMIC_SET(isReloading, true);
pthread_t thread_id;
pthread_attr_t attr;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int rc = pthread_create(&thread_id, &attr, suricataLibraryAsyncUpdateRules, NULL);
if (rc) {
printf("ERROR; return code from pthread_create() is %" PRId32 "\n", rc);
SCLogError(SC_ERR_THREAD_DEINIT, "ERROR; return code from suricataLibraryDynamicUpdateRules pthread_create() is %" PRId32 "\n", rc);
SC_ATOMIC_SET(isReloading, false);
return -2;
}
return 0;
}
使用的同步接口检测并获取alter结果
1、ids模式下,因为是逐流检测,所以检测的结果相对于实际有alter的包有延后,这样对于pcap留存功能有影响;流超时检测会导致同步接口获取不到检测结果。
2、改成ips检测,这样有逐包检测,这样的话能够解决上面的问题,但是性能会下降,还会出现多的结果(比如存在这样一个http流,有两个事务,第一个事务只能被规则1命中,第二个事务只能被规则2命中。那么在ips模式下,第一个事务会检测出规则1,第二个事务会出规则2的同时多出一个规则1的结果)
3、同一个事务如果有多次多个结果,目前只取了第一个结果。这种在上面的ips模式下,可能取不到正确的结果(这个问题是dpi控制的)。
凡是过往,即为序章