suricata 中的UT单元测试及其源码介绍

suricata本身自带了一些测试用例。这些测试用例一方面可以作为学习suricata源码的重要指导,符合当下流行的TDD开发。测试用例往往是系统关注的重点方面,因此用例可以指导学习suricata的重点。另一方面在改造引擎的时候,可能也需要一些UT用例的守护,这个时候就可以来改造这些用例。因此本文针对suricata UT的用例做一下简单的介绍。

通常情况下,如果你使用suricata --list-unittests,可能会出现如下的提示:

ERROR: Unit tests not enabled. Make sure to pass --enable-unittests to configure when building.

这是因为你在源码安装的时候没有在configure时候需要加入–enable-unittests参数,这个其实是一个编译宏的控制开关,对应的是RunUnittests函数中#ifdef UNITTESTS。也就是说只有在configure阶段加入–enable-unittest参数,相当于定义了UNITTESTS,测试用例的函数才会进行编译,否则是不会编译的。没有–enable-unittest参数,也就导致suricata -u无法执行。可以发现多数的测试用例都是使用UNITTESTS编译宏的。当然有的测试用例并没有,并不推荐这么做,因为没有该开关会导致测试用例的代码编入正常的发布代码中。关于源码编译添加–enable-unittest参数,参考我这篇文章

正常编译用例之后,可以通过suricata -h可以得知用例的运行方法:

  • 其中suricata -u是运行所有的用例,当前V5.0版本的源码共有用例3654条
  • 如果想要单独执行某一条或者某几条用例,可以使用suricata -U --unittest-filter=REGEX
  • suricata -u --unittests-coverage,生成覆盖率文件。

下面总体介绍一下用例的源码部分,首先用例的执行入口函数为RunUnittests,当使用suricata -u的时候,会调用RunUnittests函数。

整个RunUnittests函数功能分为如下几个部分:

1,全局变量的初始化
对于UT来说并不需要去建立一个完整的引擎环境,但是是suricata中很多的功能模块都是全局变量进行管理的,在UT中测试某一个功能函数不可避免的会使用到这些全局变量。因此可以看到RunUnittests调用的函数基本都是在做这些全局变量的初始化。例如MpmTableSetup以及SpmTableSetup中的全局变量管mpm_table和spm_table理匹配算法,SigTableSetup中的sigmatch_table管理关键字的处理。RegisterAllModules中使用tmm_modules管理一些功能模块。例如RegisterUnittests->SigTableRegisterTests对于关键字测试用例的注册中就会使用到sigmatch_table这样一个全局变量。

2,测试用例的定义
你可能会发现一个问题,就是suricata的UT和源码是混在一起的。通常来说一个项目源码src以及测试用例ut是两个不同的文件夹,但是suricata却是将ut和src混在了一起,通过编译宏UNITTESTS控制。当然这样写也是有一定的道理,可以看到SigTableSetup函数是对于规则关键字的处理函数,其中DetectSidRegister函数处理的是sid关键字,包括关键字的解析Setup,关键字的匹配Match(sid不需要匹配,所以是NULL),关键字内存的释放free以及对于该关键字解析功能的测试函数RegisterTests。这样有一个好处就是UT和对应的功能绑定在一起,当功能发生更改的时候,可以很快的修改UT,毕竟UT一般那也是开发人员编写的。这种做法一个不利的地方就是在代码组织上看测试和功能没有分离开,但是从实际开发来看这样组织还是有一定的优点。DetectSidRegisterTests定义了三个简单的测试用例,从中可以看到规则解析的入口函数是DetectEngineAppendSig,通过这些测试函数,就能够快速的了解各个功能模块。如果main函数入口看的话,对于整个系统来说,各个模块的功能混合在一起,并不利于某一个功能的分析。例如SidTestParse01这个用例中,通过函数名就可以知道DetectEngineAppendSig完成的是规则字符串的解析并追加到引擎中,通过函数注释也可以得知这一点。当然其他的用例都会看到类似的功能。

3,用例的注册
虽然第二步对于用例进行了定义,但是实际运行的时候可以在代码层面控制是否需要执行。DetectSidRegister函数中定义了sid的测试函数DetectSidRegisterTests,但是该函数只是当作指针被赋值给RegisterTests变量,并没有实际的运行,只有实际运行DetectSidRegisterTests函数,具体的用例SidTestParse01才会被添加到ut_list全局变量中。因此所有的定义的用例会在RegisterUnittests函数中进行集中的注册。例如RegisterUnittests->SigTableRegisterTests就是去执行已经定义好的关键字用例注册函数,将用例加入ut_list中,MpmRegisterTests也是类似的操作。可以看到整个系统的处理:

  • 定义用例的处理函数
  • 通过编译宏控制是否编译用例代码
  • 决定是否注册定义的用例
  • 最终还可以在运行时候通过过滤器决定是否运行测试用例,即 --unittest-filter=REGEX参数

4,UT上下文环境的管理
suricata中并没有是用第三方的UT框架来编写UT,而是自带了一套简单的UT框架,通过学习这套UT框架,对于构建自己项目的UT也是非常的有用。关于suricata的ut框架代码都集中在util-unittest.c以及util-unitest.h两个文件中,主要包括如下几个函数:

UtInitialize初始化ut_list为空,因为每次重新运行ut的时候,需要重置该变量。ut_list变量存储的是所有的UT用例列表,在RunUnittests函数中UtInitialize位于RegisterAllModules之前。

UtRegisterTest用例的注册,首先执行UtAllocTest为用例申请内部的结构空间,如下:

typedef struct UtTest_
{
    const char *name;
    int(*TestFn)(void);

    struct UtTest_ *next;

} UtTest;

然后对于用例名称,用例函数进行赋值。最后将新的用例使用UtAppendTest函数加入到ut_list中,各种模块中都会通过该模块进行用例的注册。

UtRunTests执行通过UtRegisterTest注册的用例,就是不断遍历ut_list链表中的用例并执行。有的时候并不是想执行所有的用例,需要加入一些过滤条件,即 --unittest-filter=REGEX参数。UtRegex函数作用在于对于过滤条件的通配符编译成正则,在通过pcre_exec去匹配特定的用例并执行。对于执行正确的用例使用good变量进行统计,错误用例使用bad进行统计。

UtCleanup在UT执行完毕的时候,清楚一在用例注册时候申请的内存空间,即ut_list。

对于UtRunSelftes,UtSelftestTrue,UtSelftestFalse是一些UT 的示例函数,是UT框架开发人员用来指导开发者编写UT用例的。

当然你也可以像gtest框架那样定义EXPECT_EQ这样一些宏,给用户更加快捷的使用方式。可以看到一个简易 的UT框架并不复杂,结合上一张关于suricata用例的编写,相信你也是能够给自己的项目快速搭建UT框架的。

以上就是对于suricata用例的简单介绍,后续将通过这些用例一一的介绍suricata各个模块的功能以及实现源码,包括流管理,报文解码,规则解析,规则加载,规则检测等方面的内容。

本文为CSDN村中少年原创文章,转载记得加上原创出处,博主链接这里

猜你喜欢

转载自blog.csdn.net/javajiawei/article/details/104588906