suricata 编译成动态库使用

项目中需求使用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控制的)。


 凡是过往,即为序章 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值