当前位置: 首页 > 工具软件 > Suricata > 使用案例 >

suricata 编译成动态库使用

戚峻
2023-12-01

项目中需求使用suricata 检测功能,只需要获取检测得到的 alert 结果, 需要将suricata的检测功能集成到我们的项目中,并提供接口动态加载规则。 源代码版本 6.0.4

源码 将suricata编译成动态库,使用suricata检测功能,只需要获取检测得到的alert。

目录

前提

接口

编译成动态库

初始化部分 suricataLibInit

初始化公共部分

RunModeLibraryRegister

 TmModuleDecodeLibraryRegister  注册DecodeLibrary 的解析函数

初始化自有线程部分

检测接口 suricataLibProcessPacket

动态加载规则


前提

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 的动态库

初始化部分 suricataLibInit


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 接口中。

RunModeLibraryRegister

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;
}

 TmModuleDecodeLibraryRegister  注册DecodeLibrary 的解析函数

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;
}

检测接口 suricataLibProcessPacket

这个就直接调用接口,然后顺着注册的回调函数依次运行即可  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控制的)。


 凡是过往,即为序章 

 类似资料: