suricata日志模块

一 说明

suricata的日志先按照packet日志/事务日志/文件日志/流日志进行分类,这个在output.c的OutputRegisterRootLoggers函数中有注册,内容如下:

void OutputRegisterRootLoggers(void)
{
    OutputPacketLoggerRegister();
    OutputTxLoggerRegister();
    OutputFiledataLoggerRegister();
    OutputFileLoggerRegister();
    OutputStreamingLoggerRegister();
}

以tx的日志为例,看下注册的代码:

void OutputTxLoggerRegister (void)
{
    OutputRegisterRootLogger(OutputTxLogThreadInit, OutputTxLogThreadDeinit,
        OutputTxLogExitPrintStats, OutputTxLog);
}

将这种大类的日志注册到rootLogger上面如下:

void OutputRegisterRootLogger(ThreadInitFunc ThreadInit,
    ThreadDeinitFunc ThreadDeinit,
    ThreadExitPrintStatsFunc ThreadExitPrintStats,
    OutputLogFunc LogFunc)
{
    RootLogger *logger = SCCalloc(1, sizeof(*logger));
    if (logger == NULL) {
        return;
    }
    logger->ThreadInit = ThreadInit;
    logger->ThreadDeinit = ThreadDeinit;
    logger->ThreadExitPrintStats = ThreadExitPrintStats;
    logger->LogFunc = LogFunc;
    TAILQ_INSERT_TAIL(&RootLoggers, logger, entries);
}

RootLoggers就是全局的大类日志,循环调用每个大类的日志输出内容如下:

TmEcode OutputLoggerLog(ThreadVars *tv, Packet *p, void *thread_data)
{
    LoggerThreadStore *thread_store = (LoggerThreadStore *)thread_data;
    RootLogger *logger = TAILQ_FIRST(&RootLoggers);
    LoggerThreadStoreNode *thread_store_node = TAILQ_FIRST(thread_store);
    while (logger && thread_store_node) {
        if (logger->LogFunc != NULL) {
            logger->LogFunc(tv, p, thread_store_node->thread_data);
        }
        logger = TAILQ_NEXT(logger, entries);
        thread_store_node = TAILQ_NEXT(thread_store_node, entries);
    }

    return TM_ECODE_OK;
}

那看下PacketLog(这个日志更简单)这个大类的日志输出吧:

static TmEcode OutputPacketLog(ThreadVars *tv, Packet *p, void *thread_data)
{
    BUG_ON(thread_data == NULL);

    if (list == NULL) {
        /* No child loggers. */
        return TM_ECODE_OK;
    }

    OutputLoggerThreadData *op_thread_data = (OutputLoggerThreadData *)thread_data;
    OutputPacketLogger *logger = list;
    OutputLoggerThreadStore *store = op_thread_data->store;

    BUG_ON(logger == NULL && store != NULL);
    BUG_ON(logger != NULL && store == NULL);
    BUG_ON(logger == NULL && store == NULL);

    while (logger && store) {
        BUG_ON(logger->LogFunc == NULL || logger->ConditionFunc == NULL);

        if ((logger->ConditionFunc(tv, (const Packet *)p)) == TRUE) {
            PACKET_PROFILING_LOGGER_START(p, logger->logger_id);
            logger->LogFunc(tv, store->thread_data, (const Packet *)p);
            PACKET_PROFILING_LOGGER_END(p, logger->logger_id);
        }

        logger = logger->next;
        store = store->next;

        BUG_ON(logger == NULL && store != NULL);
        BUG_ON(logger != NULL && store == NULL);
    }

    return TM_ECODE_OK;
}

关键是从list这个静态链表中取日志,然后输出,那么这个静态的链表的值在哪里赋值的那?
我们看下非root类的日志注册:

void OutputRegisterLoggers(void) {
 /* custom format log*/
    LogCustomFormatRegister();

    LuaLogRegister();
    /* fast log */
    AlertFastLogRegister();
    /* debug log */
    AlertDebugLogRegister();
    /* prelue log */
    AlertPreludeRegister();
    /* syslog log */
    AlertSyslogRegister();
    /* unified2 log */
    Unified2AlertRegister();
    /* drop log */
    LogDropLogRegister();
    JsonDropLogRegister();
....
}

很多,看下具体一个注册到底干了什么事情?

void JsonDropLogRegister (void)
{
    OutputRegisterPacketModule(LOGGER_JSON_DROP, MODULE_NAME, "drop-json-log",
        JsonDropLogInitCtx, JsonDropLogger, JsonDropLogCondition,
        JsonDropLogThreadInit, JsonDropLogThreadDeinit, NULL);
    OutputRegisterPacketSubModule(LOGGER_JSON_DROP, "eve-log", MODULE_NAME,
        "eve-log.drop", JsonDropLogInitCtxSub, JsonDropLogger,
        JsonDropLogCondition, JsonDropLogThreadInit, JsonDropLogThreadDeinit,
        NULL);
}

在进一步看下怎么注册的:

void OutputRegisterPacketModule(LoggerId id, const char *name,
    const char *conf_name, OutputInitFunc InitFunc,
    PacketLogger PacketLogFunc, PacketLogCondition PacketConditionFunc,
    ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit,
    ThreadExitPrintStatsFunc ThreadExitPrintStats)
{
    if (unlikely(PacketLogFunc == NULL || PacketConditionFunc == NULL)) {
        goto error;
    }

    OutputModule *module = SCCalloc(1, sizeof(*module));
    if (unlikely(module == NULL)) {
        goto error;
    }

    module->logger_id = id;
    module->name = name;
    module->conf_name = conf_name;
    module->InitFunc = InitFunc;
    module->PacketLogFunc = PacketLogFunc;
    module->PacketConditionFunc = PacketConditionFunc;
    module->ThreadInit = ThreadInit;
    module->ThreadDeinit = ThreadDeinit;
    module->ThreadExitPrintStats = ThreadExitPrintStats;
    TAILQ_INSERT_TAIL(&output_modules, module, entries);

    SCLogDebug("Packet logger \"%s\" registered.", name);
    return;
error:
    SCLogError(SC_ERR_FATAL, "Fatal error encountered. Exiting...");
    exit(EXIT_FAILURE);
}

申请一个日志模块,然后加入到全局变量:output_modules 里面。
这个地方有点蒙了,为什么循环用的是list,这里面是日志模块,这两者是怎么关联的。
通过循环获取output_modules内容填充到list中去。runmodes.c里面的函数如下:

static void RunModeInitializeEveOutput(ConfNode *conf, OutputCtx *parent_ctx)
{
    ConfNode *types = ConfNodeLookupChild(conf, "types");
    SCLogDebug("types %p", types);
    if (types == NULL) {
        return;
    }

    ConfNode *type = NULL;
    TAILQ_FOREACH(type, &types->head, next) {
        SCLogConfig("enabling 'eve-log' module '%s'", type->val);

        int sub_count = 0;
        char subname[256];
        snprintf(subname, sizeof(subname), "eve-log.%s", type->val);

        /* Now setup all registers logger of this name. */
        OutputModule *sub_module;
        TAILQ_FOREACH(sub_module, &output_modules, entries) {
            if (strcmp(subname, sub_module->conf_name) == 0) {
                sub_count++;

                if (sub_module->parent_name == NULL ||
                        strcmp(sub_module->parent_name, "eve-log") != 0) {
                    FatalError(SC_ERR_INVALID_ARGUMENT,
                            "bad parent for %s", subname);
                }
                if (sub_module->InitSubFunc == NULL) {
                    FatalError(SC_ERR_INVALID_ARGUMENT,
                            "bad sub-module for %s", subname);
                }
                ConfNode *sub_output_config =
                    ConfNodeLookupChild(type, type->val);
                // sub_output_config may be NULL if no config

                /* pass on parent output_ctx */
                OutputInitResult result =
                    sub_module->InitSubFunc(sub_output_config, parent_ctx);
                if (!result.ok || result.ctx == NULL) {
                    continue;
                }

                AddOutputToFreeList(sub_module, result.ctx);
                SetupOutput(sub_module->name, sub_module,
                        result.ctx);
            }
        }

        /* Error is no registered loggers with this name
         * were found .*/
        if (!sub_count) {
            FatalErrorOnInit(SC_ERR_INVALID_ARGUMENT,
                    "No output module named %s", subname);
            continue;
        }
    }
}

runmodes.c中有个SetupOut函数:

static void SetupOutput(const char *name, OutputModule *module, OutputCtx *output_ctx)
{
    /* flow logger doesn't run in the packet path */
    if (module->FlowLogFunc) {
        OutputRegisterFlowLogger(module->name, module->FlowLogFunc,
            output_ctx, module->ThreadInit, module->ThreadDeinit,
            module->ThreadExitPrintStats);
        return;
    }
    /* stats logger doesn't run in the packet path */
    if (module->StatsLogFunc) {
        OutputRegisterStatsLogger(module->name, module->StatsLogFunc,
            output_ctx,module->ThreadInit, module->ThreadDeinit,
            module->ThreadExitPrintStats);
        return;
    }

    if (module->logger_id == LOGGER_ALERT_DEBUG) {
        debuglog_enabled = 1;
    }

    if (module->PacketLogFunc) {
        SCLogDebug("%s is a packet logger", module->name);
        OutputRegisterPacketLogger(module->logger_id, module->name,
            module->PacketLogFunc, module->PacketConditionFunc, output_ctx,
            module->ThreadInit, module->ThreadDeinit,
            module->ThreadExitPrintStats);
    } else if (module->TxLogFunc) {
        SCLogDebug("%s is a tx logger", module->name);
        OutputRegisterTxLogger(module->logger_id, module->name, module->alproto,
                module->TxLogFunc, output_ctx, module->tc_log_progress,
                module->ts_log_progress, module->TxLogCondition,
                module->ThreadInit, module->ThreadDeinit,
                module->ThreadExitPrintStats);
        logger_bits[module->alproto] |= (1<<module->logger_id);
    } else if (module->FiledataLogFunc) {
        SCLogDebug("%s is a filedata logger", module->name);
        OutputRegisterFiledataLogger(module->logger_id, module->name,
            module->FiledataLogFunc, output_ctx, module->ThreadInit,
            module->ThreadDeinit, module->ThreadExitPrintStats);
        filedata_logger_count++;
    } else if (module->FileLogFunc) {
        SCLogDebug("%s is a file logger", module->name);
        OutputRegisterFileLogger(module->logger_id, module->name,
            module->FileLogFunc, output_ctx, module->ThreadInit,
            module->ThreadDeinit, module->ThreadExitPrintStats);
        file_logger_count++;
    } else if (module->StreamingLogFunc) {
        SCLogDebug("%s is a streaming logger", module->name);
        OutputRegisterStreamingLogger(module->logger_id, module->name,
            module->StreamingLogFunc, output_ctx, module->stream_type,
            module->ThreadInit, module->ThreadDeinit,
            module->ThreadExitPrintStats);
    } else {
        SCLogError(SC_ERR_INVALID_ARGUMENT, "Unknown logger type: name=%s",
            module->name);
    }
}

二 整理下逻辑

2.1 初始化思路:

1)首先通过rootlogger的注册,注册几个基本的大日志,注册到RootLoggers里面。
注册的循环函数是对一个大类里面的list做循环调用打印日志。
2)通过非rootLogger的日志注册,注册具体的日志到output_modules模块中。
3)在初始化的时候会将output_modules 变成串到相关大类的list里面去。

2.3 调用思路

1)通过flow-worker.c中的OutputLoggerLog 函数调用。
2)上面函数遍历:RootLoggers 调用相关大类的日志打印函数。
3)日志打印函数遍历内部的list循环打印。
4)list的循环其实是注册具体日志的具体日志模块转变得到的大类日志。
5)实际输出日志内容。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355