Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

suricata 状态监控模块详解 #2

Open
scarletttt opened this issue Sep 9, 2020 · 0 comments
Open

suricata 状态监控模块详解 #2

scarletttt opened this issue Sep 9, 2020 · 0 comments

Comments

@scarletttt
Copy link
Owner

scarletttt commented Sep 9, 2020

suricata 状态监控模块详解

在输出日志stats.log中,suricata会定时输出某些统计信息供分析。这些信息大部分是从多个flow worker线程统计出来的,比如抓包数,解码的包数,tcp flow 或者 udp flow的个数。
image

下面就来分析一下suricata是如何统计各个线程的状态并输出到日志的。

线程创建模式

执行命令 pidstat -p pidof suricata -t 2
这些线程分为三种:管理线程,工作线程,控制线程
image

四个工作线程:
flow worker:W#01,W#02,W#03,W#04线程监听在网卡enp5s0f0。

五个管理线程:

  1. FM#01:flow manager线程
  2. FR#01,FR#02 flow recycle线程
  3. CW:唤醒线程
  4. CS:统计线程

一个控制线程:US (暂时不清楚该线程功能)

CS线程就是负责统计读取各个线程的状态并进行统计输出的。

统计功能模块分析

每个线程以ThreadVar(tv)的实例存在,每个tv内维护一个StatsPublicThreadContext(pctx)和一个StatsPrivateThreadContext(pca)。

/** \brief Per thread variable structure */
typedef struct ThreadVars_ {
   ...

    /* counters */

    /** public counter store: counter syncs update this */
    StatsPublicThreadContext perf_public_ctx;

    /** private counter store: counter updates modify this */
    StatsPrivateThreadContext perf_private_ctx;
	...
} ThreadVars;

线程通过counters模块提供的api: StatsAddUI64, StatsSetUI64, StatsIncr 来对 tv->pca 中的statsLocalCounter进行状态更改。

那么tv中为什么还要维护一个 StatsPublicThreadContext 呢?来看一下 pca 和 pctx 之间的行为。

static int StatsGetAllCountersArray(StatsPublicThreadContext *pctx, StatsPrivateThreadContext *private)
{
    if (pctx == NULL || private == NULL)
        return -1;

    return StatsGetCounterArrayRange(1, pctx->curr_id, pctx, private);
}

static int StatsGetCounterArrayRange(uint16_t s_id, uint16_t e_id,
                                      StatsPublicThreadContext *pctx,
                                      StatsPrivateThreadContext *pca)
{
    StatsCounter *pc = NULL;
    uint32_t i = 0;

    if (pctx == NULL || pca == NULL) {
        SCLogDebug("pctx/pca is NULL");
        return -1;
    }

    if (s_id < 1 || e_id < 1 || s_id > e_id) {
        SCLogDebug("error with the counter ids");
        return -1;
    }

    if (e_id > pctx->curr_id) {
        SCLogDebug("end id is greater than the max id for this tv");
        return -1;
    }

    if ( (pca->head = SCMalloc(sizeof(StatsLocalCounter) * (e_id - s_id  + 2))) == NULL) {
        return -1;
    }
    memset(pca->head, 0, sizeof(StatsLocalCounter) * (e_id - s_id  + 2));

    pc = pctx->head;
    while (pc->id != s_id)
        pc = pc->next;

    i = 1;
    while ((pc != NULL) && (pc->id <= e_id)) {
        pca->head[i].pc = pc;
        pca->head[i].id = pc->id;
        pc = pc->next;
        i++;
    }
    pca->size = i - 1;

    pca->initialized = 1;
    return 0;
}

原来 pca->statsLocalCounter->ctx 就指向着 pctx->statsCounter。tv在修改自身状态时,是去修改 pca->statsLocalCounter 里的内容,并没有去修改 pctx->statsCounter。那如何将修改同步到pctx->statsCounter 呢?

在下面这个函数找到了答案。

int StatsUpdateCounterArray(StatsPrivateThreadContext *pca, StatsPublicThreadContext *pctx)
{

    if (pca == NULL || pctx == NULL) {
        SCLogDebug("pca or pctx is NULL inside StatsUpdateCounterArray");
        return -1;
    }

    SCMutexLock(&pctx->m);
    StatsLocalCounter *pcae = pca->head;
    for (uint32_t i = 1; i <= pca->size; i++) {
        StatsCopyCounterValue(&pcae[i]);
    }
    SCMutexUnlock(&pctx->m);

    pctx->perf_flag = 0;
    return 1;
}

所以说,这个 pctx->statsCounter 就是一个单消费者单生产者下的一个读写缓冲区,tv 充当生产者的角色,随时根据自己的状态对 pca 里的 statsLocalCounter 进行 update/set。CW 唤醒线程设置定时闹钟,引发各线程调用 StatsUpdateCounterArray 将状态同步到 pctx->statsCounter。

接下来需要研究 CS 线程如何读取 pctx->statsCounter 并进行统计计算。

typedef struct StatsGlobalContext_ {
    /** list of thread stores: one per thread plus one global */
    StatsThreadStore *sts;
    SCMutex sts_lock;
    int sts_cnt;

    HashTable *counters_id_hash;

    StatsPublicThreadContext global_counter_ctx;
} StatsGlobalContext;

看了一眼这个结构体,大致明白了 sts 是用来存储所有 tv 的 statsCounter 。countsers_id_hash 是 将 tv 索引 到 sts 这个表的下标。但里面这个单独的 global_counter_ctx 就很疑惑了? 继续往下看。。。

static int StatsThreadRegister(const char *thread_name, StatsPublicThreadContext *pctx)
{
    if (stats_ctx == NULL) {
        SCLogDebug("Counter module has been disabled");
        return 0;
    }

    if (thread_name == NULL || pctx == NULL) {
        SCLogDebug("supplied argument(s) to StatsThreadRegister NULL");
        return 0;
    }

    SCMutexLock(&stats_ctx->sts_lock);
    if (stats_ctx->counters_id_hash == NULL) {
        stats_ctx->counters_id_hash = HashTableInit(256, CountersIdHashFunc,
                                                              CountersIdHashCompareFunc,
                                                              CountersIdHashFreeFunc);
        BUG_ON(stats_ctx->counters_id_hash == NULL);
    }
	//将stats_ctx->glocal_counter_ctx 加入到 sts 中
    StatsCounter *pc = pctx->head;
    while (pc != NULL) {
        CountersIdType t = { 0, pc->name }, *id = NULL;
        id = HashTableLookup(stats_ctx->counters_id_hash, &t, sizeof(t));
        if (id == NULL) {
            id = SCCalloc(1, sizeof(*id));
            BUG_ON(id == NULL);
            id->id = counters_global_id++;
            id->string = pc->name;
            BUG_ON(HashTableAdd(stats_ctx->counters_id_hash, id, sizeof(*id)) < 0);
        }
        pc->gid = id->id;
        pc = pc->next;
    }


    StatsThreadStore *temp = NULL;
    if ( (temp = SCMalloc(sizeof(StatsThreadStore))) == NULL) {
        SCMutexUnlock(&stats_ctx->sts_lock);
        return 0;
    }
    memset(temp, 0, sizeof(StatsThreadStore));

    temp->ctx = pctx;
    temp->name = thread_name;

    temp->next = stats_ctx->sts;
    stats_ctx->sts = temp;
    stats_ctx->sts_cnt++;
    SCLogDebug("stats_ctx->sts %p", stats_ctx->sts);

    SCMutexUnlock(&stats_ctx->sts_lock);
    return 1;
}

所以

/** list of thread stores: one per thread plus one global */
    StatsThreadStore *sts; 

说的是其中既包括 tv 中的 statsCounter 也包括global_counter_ctx 的 statsCounter。

同理,tv->pctx 也是通过 StatsThreadRegister 这个方法注册到 stats_ctx->sts 中的~

结构都关联起来了,接下来就是研究一下counter模块怎么进行统计计算啦。

counters模块就一个统计输出接口 StatsOutput。看看函数源码还是很容易懂的,不赘述啦~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant