suricata源码之Storage

在suricata中经常会发现storage这样一个结构,很多人可能不是特别理解其表达的含义,本节就对此结构进行说明。

suricata中有一个Storage结构,从字面意思来看是存储的含义。存储的内容是什么呢?suricata中storage的定义如下:

typedef void* Storage;

可以看到storage是一个void类型的指针,而void类型的指针可以根据需要转换成为各种具体类型的指针。从这个地方就可以看出storage表示的是一个比较抽象或者一般化的概念。他表示的是一块内存空间,这块内存空间可以存储各种具体的结构体内存,可以根据具体的业务需求进行指针的转换。而storage所要作的事情就是定义对这块内存空间的各种管理操作,包括申请,查找,释放等等, src文件夹下面的util-storage.c文件中是所有关于storage的处理函数,如下:
在这里插入图片描述
从函数名猜测来看register表示的是注册,即对于storage内存管理的注册,get表示的是内存的查找,alloc表示内存的分配。其实直接看函数源码,可能还不是特别清楚storage的真正如何去使用?在suricata的学习过程中,UT的用例是很重要,UT用例往往表达了设计者的用途,因为通常来说UT都是开发者来编写的。

StorageTest01可以得到如下的信息:

  • 1,suricata中storage的类型包括STORAGE_HOST,STORAGE_FLOW,STORAGE_IPPAIR,STORAGE_DEVICE。以STORAGE_FLOW为例说明,在suricata系统中,除了flow这样一个流结构体存储流的信息之外,有的时候还需要根据规则存储一些可选的流信息,如tag关键字,由于这类信息依赖于规则,同时出现的频率不高,因此不是流结构体必须的。如果把这些信息放在流结构体中势必浪费,因此可以使用STORAGE来存储这类信息。
  • 2,STORAGE最终将是一块内存空间,为了管理这块内存,需要进行不同类型和名称的STORAGE,注册函数为StorageRegister,注册的内容包括storage的名称,类型,内存申请函数,内存大小以及内存释放函数。当然所有的storage都是有链表storage_list记录。
  • 3,你可能发现一个问题就是storage_list中存储了不同类型的storage,同一种类型的storage也可能有多个,用链表来管理存在一个问题就是查询效率不够高。如果想要查询某个类型,指定name的storage则需要遍历整个链表。怎么样来提高这种查找效率呢,计算机里面往往是空间换时间。即StorageFinalize函数的主要功能,引入了storage_map用于快速的查找。可以看到storage_map和storage_list存储的内容是一样的,但是storage_map的查找是数组查找,O(1)效率。这也是很多的高级语言中map的基本实现。但是事实上storage_map在suricata汇总基本没有使用上,这可能是一种过度设计吧,或者写这个代码的程序员写这类代码写习惯了,顺势而为。不仅如此,整个util-storage.c文件中很多的函数都没有在实际的代码中进行使用。

StorageTest02可得如下信息:

  • 1,StorageAllocById给定参数type和id,通过map索引到注册阶段注册的storage信息,按照注册阶段的注册信息,分配指定内存大小的空间。这个地方体现出map的作用了,不然的话需要遍历storage_list链表。
  • 2,StorageFree函数释放申请的Storage内存空间。

通过UT对于storage的概念有了初步的了解之后,看看实际是怎么进行使用的。

在前面的文章中分析流表的时候,FlowAlloc函数中,可以看到申请的内存空间为size_t size = sizeof(Flow) + FlowStorageSize()FlowStorageSize表示的是几个指针的内存空间大小。即紧挨着流结构体预留了几个指针的空间,而这几个指针最终会指向的是和流有关的一些信息。具体多少个指针的内存空间呢,是由storage_max_id[STORAGE_FLOW]决定的。而storage_max_id[STORAGE_FLOW]的值是在注册的时候确定的。FlowStorageRegister函数被调用了三次,可知storage_max_id[STORAGE_FLOW]为3,是三种不同类型和流有关的数据。 其中在TagInitCtx函数中,不仅注册了STORAGE_FLOW的类型,还注册了STORAGE_HOST类型,关于tag,参考这篇文章

那么这些指针指向的空间在什么地方被赋值呢?同样的我们可以通过FlowStorage的测试用例来找寻一些蛛丝马迹。在FlowStorage的测试用例FlowStorageTest02中的主要代码如下:

	Flow *f = NULL;

    StorageInit();

    int id1 = FlowStorageRegister("test", sizeof(void *), NULL, StorageTestFree);
    if (id1 < 0)
        goto error;

    if (StorageFinalize() < 0)
        goto error;

    FlowInitConfig(FLOW_QUIET);
    f = FlowAlloc();
    if (f == NULL) {
        goto error;
    }

    void *ptr = FlowGetStorageById(f, id1);
    if (ptr != NULL) {
        goto error;
    }

    void *ptr1a = SCMalloc(128);
    if (unlikely(ptr1a == NULL)) {
        goto error;
    }
    FlowSetStorageById(f, id1, ptr1a);

    void *ptr1b = FlowGetStorageById(f, id1);
    if (ptr1a != ptr1b) {
        goto error;
    }

上述用例中可以看到,使用FlowSetStorageById函数将flow结构体之后的内存指针指向了申请的内存ptr1a。其实我在上一篇已经讲述tag关键字是如何在流之后加上tag的一些数据,从而实现tag的功能的,可以结合看一下。就能明白设计者设计storage的初衷,其实就是为了灵活管理一些可选的内存结构,统一抽象为一个存储单元,进行统一的管理以及内存的回收。

例如在流老化的时候,就会通过FlowFreeStorage,HostFreeStorage等函数去释放这些storage内存空间。

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

猜你喜欢

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