浅谈开源入侵检测引擎Suricata


引言

从上一篇 浅谈开源入侵检测引擎 Suricata 我们可以了解 Suricata 的安装部署大致框架、以及从配置方面谈及的性能优化,这场 Chat 则从代码角度带您剖析 Suricata。 通过本次 Chat 您将了解以下知识点:

  • suricata运行模式
  • suricata线程模块
  • suricata数据流转
  • suricata抓包&解包
  • suricata数据流管理引擎
  • suricata应用协议解析引擎
  • 总结陈述

Suricata

提到Suricata源码分析,我们首先需要了解suricata整体框架设计,只有你了解了整体框架,他才能对阅读源码有更好的促进作用。而提到框架则需要了解它的运行模式,在上一篇文章中说,suricata相对于传统snort引擎,它是由很多单独的线程模块所组成,使用者可以自行配置组合所需要的线程模块既可进行优化、自定义需求。默认的运行模式主要分为workers模式、autofp模式以及单线程模式single。workers模式主要是采用串行流水线的模式,针对单独数据流采取唯一线程 从头至尾处理,而autofp则是将收包和解包放在一个单独线程,而剩下的工作线程则通过队列传递数据包进行处理。 显然workers模式更适用于高效率处理数据包。

其次suricata支持多种抓包引擎,如:pcap、netmap、pfring、socket等。本文就pfring抓包引擎来谈一下数据包的流转以及部分解析引擎。 ##运行模式 涉及文件:runmodes.c 、util-runmodes.c、tm-thread.c、tm-queue ……

1. 注册运行模式

void RunModeIdsPfringRegister(void)
{
    default_mode_autofp = "autofp";
    RunModeRegisterNewRunMode(RUNMODE_PFRING, "autofp",
                              "Multi threaded pfring mode.  Packets from "
                              "each flow are assigned to a single detect "
                              "thread, unlike \"pfring_auto\" where packets "
                              "from the same flow can be processed by any "
                              "detect thread",
                              RunModeIdsPfringAutoFp);
    RunModeRegisterNewRunMode(RUNMODE_PFRING, "single",
                              "Single threaded pfring mode",
                              RunModeIdsPfringSingle);
    RunModeRegisterNewRunMode(RUNMODE_PFRING, "workers",
                              "Workers pfring mode, each thread does all"
                              " tasks from acquisition to logging",
                              RunModeIdsPfringWorkers);
    return;
} ### 2.  主要数据结构

模块结构

typedef struct TmModule_ {
    const char *name;
    TmEcode (*ThreadInit)(ThreadVars *, const void *, void **);
    ......
} TmModule;

TmModule tmm_modules[TMM_SIZE]; 模块注册结构体,主要用于注册各个线程模块,提供全局变量**tmm_modules**, 便于组装回调。 如pfring模块注册:

void TmModuleReceivePfringRegister (void)
{
    tmm_modules[TMM_RECEIVEPFRING].name = "ReceivePfring";
    tmm_modules[TMM_RECEIVEPFRING].ThreadInit = ReceivePfringThreadInit;
    tmm_modules[TMM_RECEIVEPFRING].Func = NULL;
    tmm_modules[TMM_RECEIVEPFRING].PktAcqLoop = ReceivePfringLoop;
    tmm_modules[TMM_RECEIVEPFRING].PktAcqBreakLoop = PfringBreakLoop;
    tmm_modules[TMM_RECEIVEPFRING].RegisterTests = NULL;
    tmm_modules[TMM_RECEIVEPFRING].flags = TM_FLAG_RECEIVE_TM;
} #### 线程结构

typedef struct ThreadVars_ {
    pthread_t t;
    char name[16];
    char *printable_name;
    char *thread_group_name;
    SC_ATOMIC_DECLARE(unsigned int, flags);
    uint8_t tmm_flags;
    Tmq *inq;
    Tmq *outq;
    void *outctx;
    const char *outqh_name;
    struct Packet_ * (*tmqh_in)(struct ThreadVars_ *);
    void (*InShutdownHandler)(struct ThreadVars_ *);
    void (*tmqh_out)(struct ThreadVars_ *, struct Packet_ *);
    void *(*tm_func)(void *);
    struct TmSlot_ *tm_slots;
	......
    struct ThreadVars_ *next;
    struct ThreadVars_ *prev;
} ThreadVars; 线程结构,主要用于自由组装模块之后,提供线程队列绑定、线程句柄等。

队列结构

typedef struct Tmqh_ {
    const char *name;
    Packet *(*InHandler)(ThreadVars *); 
    void (*InShutdownHandler)(ThreadVars *);
    void (*OutHandler)(ThreadVars *, Packet *); 
    void *(*OutHandlerCtxSetup)(const char *);
    void (*OutHandlerCtxFree)(void *);
    void (*RegisterTests)(void);
} Tmqh;
Tmqh tmqh_table[TMQH_SIZE]; 队列数据结构,提供队列的注册回调、并提供全局句柄**tmqh_table**供线程绑定队列用。而队列主要包含 这几种:  TMQH_SIMPLE,  TMQH_NFQ,  TMQH_PACKETPOOL,  TMQH_FLOW,  这边我们主要关心**TMQH_PACKETPOOL、TMQH_FLOW**一个数据包队列、一个是流控队列。如:

//数据包尺队列注册
 void TmqhPacketpoolRegister (void)
{
    tmqh_table[TMQH_PACKETPOOL].name = "packetpool";
    tmqh_table[TMQH_PACKETPOOL].InHandler = TmqhInputPacketpool;
    tmqh_table[TMQH_PACKETPOOL].OutHandler = TmqhOutputPacketpool;
}
数据包输出队列trans_q句柄
PacketQueue *q = &trans_q[tv->inq->id]; 

//数据流队列注册
void TmqhFlowRegister(void)
{
    tmqh_table[TMQH_FLOW].name = "flow";
    tmqh_table[TMQH_FLOW].InHandler = TmqhInputFlow;
    tmqh_table[TMQH_FLOW].OutHandlerCtxSetup = TmqhOutputFlowSetupCtx;
    tmqh_table[TMQH_FLOW].OutHandlerCtxFree = TmqhOutputFlowFreeCtx;
    tmqh_table[TMQH_FLOW].RegisterTests = TmqhFlowRegisterTests;

    const char *scheduler = NULL;
    //队列的负载均衡模式调度
    if (ConfGet("autofp-scheduler", &scheduler) == 1) {
        if (strcasecmp(scheduler, "round-robin") == 0) {
            SCLogNotice("using flow hash instead of round robin");
            tmqh_table[TMQH_FLOW].OutHandler = TmqhOutputFlowHash;
        } else if (strcasecmp(scheduler, "active-packets") == 0) {
            SCLogNotice("using flow hash instead of active packets");
            tmqh_table[TMQH_FLOW].OutHandler = TmqhOutputFlowHash;
        } else if (strcasecmp(scheduler, "hash") == 0) {
            tmqh_table[TMQH_FLOW].OutHandler = TmqhOutputFlowHash;
        } else if (strcasecmp(scheduler, "ippair") == 0) {
            tmqh_table[TMQH_FLOW].OutHandler = TmqhOutputFlowIPPair;
        } else {
            SCLogError(SC_ERR_INVALID_YAML_CONF_ENTRY, "Invalid entry \"%s\" "
                       "for autofp-scheduler in conf.  Killing engine.",
                       scheduler);
            exit(EXIT_FAILURE);
        }
    } else {
        tmqh_table[TMQH_FLOW].OutHandler = TmqhOutputFlowHash;
    }

    return;
}	 #### Slot链结构

typedef struct TmSlot_ {
    ThreadVars *tv;
    SC_ATOMIC_DECLARE(TmSlotFunc, SlotFunc);
    TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);
    TmEcode (*SlotThreadInit)(ThreadVars *, const void *, void **);
    void (*SlotThreadExitPrintStats)(ThreadVars *, void *);
    TmEcode (*SlotThreadDeinit)(ThreadVars *, void *);
	......	
    struct TmSlot_ *slot_next;
    TmEcode (*Management)(ThreadVars *, void *);
} TmSlot; suricata使用Slot链来讲线程模块中注册条件进行填充,并利用slot将线程模块串联起来。

3. autofp模式注册

这边我们就利用autofp来讲解一下,suricata如何利用这些数据结构进行串联起来的。

  • RunModeSetLiveCaptureAutoFp 函数入口
  • UtilCpuGetNumProcessorsOnline 计算cpu核心数
  • LiveGetDeviceCount 指定网卡数量
  • TmThreadGetNbThreads 指定最大线程数量,默认线程核心*threading_detect_ratio,最大1024,当然此处线程并非最终数量,如果配置文件配置了,那么还是取二者小的一个来创建进行抓包、解包线程,而flow-worker则使用之前的。
  • RunmodeAutoFpCreatePickupQueuesString 根据线程数,初始化队列名字如:”pickup1,pickup2,pickup3\0”
  • TmThreadCreatePacketHandler(tname,”packetpool”, “packetpool”, queues, “flow”, “pktacqloop”); 创建收包线程返回句柄ThreadVars *tv_receive
  • tname –>线程名字 RX#01, RX#02…….
  • 输入队列: tv->inq =NULL
  • 输出队列: tv->outq =pickup1,pickup2…..
  • packetpool–>第一个 输入队列名字,第二个 队列句柄的名字
  • queues –> 输出句柄名字 “pickup1,pickup2,pickup3\0”
  • flow –> 输出句柄名称
  • 这边 收包线程队列 : 输入 === TmqhInputPacketpool 输出 === TmqhOutputFlowHash
  • TmSlotSetFuncAppend 注册抓包 & decode解包线程 .
  • TmThreadSpawn 接受线程启动

下面再来看一下流处理线程模块

    for (int thread = 0; thread < thread_max; thread++) {
        snprintf(tname, sizeof(tname), "%s#%02u", thread_name_workers, thread+1);
        snprintf(qname, sizeof(qname), "pickup%u", thread+1);
        SCLogDebug("tname %s, qname %s", tname, qname);

        ThreadVars *tv_detect_ncpu =
            TmThreadCreatePacketHandler(tname,
                                        qname, "flow",
                                        "packetpool", "packetpool",
                                        "varslot");
        if (tv_detect_ncpu == NULL) {
            SCLogError(SC_ERR_RUNMODE, "TmThreadsCreate failed");
            exit(EXIT_FAILURE);
        }
        TmModule *tm_module = TmModuleGetByName("FlowWorker");
        if (tm_module == NULL) {
            SCLogError(SC_ERR_RUNMODE, "TmModuleGetByName for FlowWorker failed");
            exit(EXIT_FAILURE);
        }
        TmSlotSetFuncAppend(tv_detect_ncpu, tm_module, NULL);
        TmThreadSetCPU(tv_detect_ncpu, WORKER_CPU_SET);
        TmThreadSetGroupName(tv_detect_ncpu, "Detect");
        tm_module = TmModuleGetByName("RespondReject");
        if (tm_module == NULL) {
            SCLogError(SC_ERR_RUNMODE, "TmModuleGetByName RespondReject failed");
            exit(EXIT_FAILURE);
        }
        TmSlotSetFuncAppend(tv_detect_ncpu, tm_module, NULL);
            if (TmThreadSpawn(tv_detect_ncpu) != TM_ECODE_OK) {
            SCLogError(SC_ERR_RUNMODE, "TmThreadSpawn failed");
            exit(EXIT_FAILURE);
        }
    }
  • tname 线程名字:W#01 ,W#02…….
  • qname 队列名字:pickup01,pickup02……
  • TmThreadCreatePacketHandler(tname,qname, “flow”,”packetpool”, “packetpool”,”varslot”);
    • 类似收包线程处理,创建线程并绑定队列
    • 输入队列: tv->inq = pickup01……
    • 输入处理句柄: tv->tmqh_in: TmqhInputFlow
    • 输出处理句柄: tv->tmqh_out :TmqhOutputPacketpool
    • 即 使用完的数据包扔回原始包池的意思。
  • TmModule *tm_module = TmModuleGetByName(“FlowWorker”) 获取模块句柄
  • TmSlotSetFuncAppend(tv_detect_ncpu, tm_module, NULL) 将模块注册进入slot链,
    • 模式顺序: FlowWorker、Detect、RespondReject…
  • TmThreadSetCPU 设置cpu亲和性绑定
  • TmThreadSpawn worker线程启动

数据流转

** 让我们再回到 这个函数**:

 ThreadVars *TmThreadCreate( const char *name, const char *inq_name, 
						     const char *inqh_name,const char *outq_name, 
							 const char *outqh_name, const char *slots,
							 void * (*fn_p)(void *), int mucond)
  • 参数说明
  • name 线程模块的名字
  • inq_name 输入队列名称
  • inqh_name 输入队列句柄名称,由TmqhSetup创建的
  • outq_name 输出队列名称
  • outqh_name 输出队列句柄名称,由TmqhSetup创建的
  • slots 槽名称,后续使用
  • fn_p 回调函数 Pointer to function when "slots" is of type "custom"
  • mucond Flag to indicate whether to initialize the condition and the mutex variables for this newly created TV
  • 线程变量初始化,返回句柄 ThreadVars *tv
  • 设置线程名称、线程标志 THV_PAUSE&&THV_USE
  • tv->inq,如果进来的inq_name不是“packetpool”,则是pickup0x,则创建新的输入队列。
  • tv->tmqh_in 线程 输入队列句柄处理函数初始化
  • tv->tmqh_out 线程 输出队列句柄处理函数初始化
  • tv->outq 取决于outq_name 是否为”packetpool”,主要用于接受线程与work线程数据包传递包用
  • tmqh->OutHandlerCtxSetup 调用TmqhOutputFlowSetupCtx 分离 outq_name “pickup1,pick2……”

接下来我们再来看一下 slot函数初始化过程

 static TmEcode TmThreadSetSlots(ThreadVars *tv, const char *name, void *(*fn_p)(void *))
  • 参数说明
    • tv 线程句柄
    • name 即为上一个slots字符串名称
    • fn_p 回调函数 Pointer to a custom slot function. Used only if slot variant “name” is “custom”
  • tv->tm_func 线程函数注册 《这边是重点哦》
    • varslot 对应 TmThreadsSlotVar , worker模式
    • pktacqloop 对应 TmThreadsSlotPktAcqLoop, 接受线程模式
    • management 对应 TmThreadsManagement ,管理线程模式
    • command 对应 TmThreadsManagement
    • custom 模式 如果 fn_p 不为NULL,则对应 fn_p函数

注册全部ok,那么让我们看一下 创建线程到底做了啥

TmEcode TmThreadSpawn(ThreadVars *tv)
  • 线程启动,tv线程句柄
  • pthread_attr_init 线程属性初始化
  • pthread_attr_setdetachstate 线程分离状态属性
  • pthread_create(&tv->t, &attr, tv->tm_func, (void *)tv) 创建线程,调用tm_fun运行该线程主函数
  • TmThreadWaitForFlag(tv, THV_INIT_DONE THV_RUNNING_DONE) 等待线程结束标志,结束该线程
  • TmThreadAppend 把线程类型 以及线程句柄扔回给 tv_root

下面到好戏了,让我们看看 主线程到底做什么

static void *TmThreadsSlotPktAcqLoop(void *td)    
  • 函数说明: 接受线程主函数
  • UtilSignalBlock(SIGUSR2) 对USER2信号进行屏蔽,仅主线程有效
  • SCSetThreadName 设置线程名称
  • TmThreadSetupOptions 设置线程亲和性,进行线程绑定情况
  • PacketPoolInit 数据池进行初始化 ,全局变量thread_pkt_pool ,根据yaml文件设定的max_pending_packets数量,对数据包池进行初始化
  • 遍历slot链,进行数据包处理
    • slot->SlotThreadInit 模块初始化
    • StatsSetupPrivate 计数初始化
    • TmThreadsSetFlag(tv, THV_INIT_DONE); 设定线程初始化完毕flag
    • s->PktAcqLoop(tv, SC_ATOMIC_GET(s->slot_data), s); 启动线程循环,并调用主函数进行处理。抓包开始咯!

收包&解包

涉及文件:source-pfring.c 从上面 数据流转,我们看到接受线程,最终调用的是s->PktAcqLoop 即初始化:
tmm_modules[TMM_RECEIVEPFRING].PktAcqLoop = ReceivePfringLoop; 下面我们看一下 pfring关于 接受数据循环,到底做了哪些事情

TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot)
  • 参数说明
    • tv 线程变量
    • data 即为 SC_ATOMIC_GET(s->slot_data),即包含pfring 初始化过程中的一些信息
    • slot 即为slot链句柄
  • pfring_enable_ring 开启pfring
  • suricata_ctl_flags & SURICATA_STOP 检查pfring运行状态
  • ptv->slot = s->slot_next; 将slot函数转移到下一个可用节点函数。
  • PacketPoolWait 数据包池是否为NULL等待。
  • PacketGetFromQueueOrAlloc 获取数据包从包池
  • PKT_SET_SRC(p, PKT_SRC_WIRE); 设置数据包状态
  • pfring_recv pfring抓包,并填充进pkt_buffer,hdr
  • PacketSetData && PfringProcessPacket 零拷贝将抓包信息,填充进预分配数据包中,而pfring包,会随着pfring环越来越大,最终释放。否则,拷贝数据包。
  • TmThreadsSlotProcessPkt 将数据包做接下来处理,传递给decode线程
  • PfringDumpCounters 抓包计数

    static inline TmEcode TmThreadsSlotProcessPkt(ThreadVars *tv, TmSlot *s, Packet *p)

  • 检查s是否为NULL,若接下来的处理函数为NULL,则将数据包投递到下一个处理线程
  • 否则调用 TmThreadsSlotVarRun
  • SC_ATOMIC_GET(s->SlotFunc);
  • SlotFunc(tv, p, SC_ATOMIC_GET(s->slot_data), &s->slot_pre_pq, &s->slot_post_pq);
  • 即将数据包 投递给decode线程进行数据包 处理。
  • tv->tmqh_out(tv, p); 将数据包投递给flow进行分发。

文章写道这边,相信读者已经清楚suricata,关于数据包流转的思想,主要是预先注册,然后使用slot链,将队列和线程串联起来。至于工作线程流转这边就略了,毕竟思想是类似的。数据流转清楚了,则梳理出了suricata的命脉,难道你还不能对suricata进行裁剪吗?

##流管理 数据流管理,讲到这边,首先我们需要先明确一下,数据流到底是什么?所谓数据流特指:具有相同特性五元组(源IP、源端口、目的IP、目的端口、传输协议)的一组集合。 下面我们通过结构体来认识一下suricata强大的数据流管理功能。

typedef struct Flow_
{
    FlowAddress src, dst;
    union {
        Port sp;        /**< tcp/udp source port */
        uint8_t type;   /**< icmp type */
    };
    union {
        Port dp;        /**< tcp/udp destination port */
        uint8_t code;   /**< icmp code */
    };
    uint8_t proto;
    uint16_t vlan_id[2];
    uint32_t flow_hash;
    struct timeval lastts;
    SC_ATOMIC_DECLARE(FlowStateType, flow_state);
    SC_ATOMIC_DECLARE(FlowRefCount, use_cnt);
    uint32_t tenant_id;
    uint32_t probing_parser_toserver_alproto_masks;
    uint32_t probing_parser_toclient_alproto_masks;
    uint32_t flags;         /**< generic flags */
    uint16_t file_flags;    /**< file tracking/extraction flags */
    uint16_t protodetect_dp; /**< 0 if not used */
	......
} Flow;

很显然,suricata是通过flow前面几个字段来确定五元组的。而其他一些字段则表明这一条数据流的特性,如flow_hash,则是表明这条数据流的五元组hash数值。

  • FlowGetFlowFromHash(tv, dtv, p, &p->flow); 数据包寻找属于自身的数据流
  • p->flags = PKT_HAS_FLOW; 将数据包打上标志,表示已经找到归属

接下来我们会发现 所有应用层api、output输出层基本都会携带flow这个指针,flow作为一个线程全局数据结构,不仅携带了数据包基础属性,还携带了应用层数据解析结构指针。那么flow什么时候会释放呢?不然内存不是会不停上涨?那么我们必须依赖下面的线程来做这件事情。

void TmModuleFlowManagerRegister (void)
{
    tmm_modules[TMM_FLOWMANAGER].name = "FlowManager";
    tmm_modules[TMM_FLOWMANAGER].ThreadInit = FlowManagerThreadInit;
    tmm_modules[TMM_FLOWMANAGER].ThreadDeinit = FlowManagerThreadDeinit;
    tmm_modules[TMM_FLOWMANAGER].Management = FlowManager;
    tmm_modules[TMM_FLOWMANAGER].cap_flags = 0;
    tmm_modules[TMM_FLOWMANAGER].flags = TM_FLAG_MANAGEMENT_TM;
    SC_ATOMIC_INIT(flowmgr_cnt);
    SC_ATOMIC_INIT(flow_timeouts);
}

void TmModuleFlowRecyclerRegister (void)
{
    tmm_modules[TMM_FLOWRECYCLER].name = "FlowRecycler";
    tmm_modules[TMM_FLOWRECYCLER].ThreadInit = FlowRecyclerThreadInit;
    tmm_modules[TMM_FLOWRECYCLER].ThreadDeinit = FlowRecyclerThreadDeinit;
    tmm_modules[TMM_FLOWRECYCLER].Management = FlowRecycler;
    tmm_modules[TMM_FLOWRECYCLER].cap_flags = 0;
    tmm_modules[TMM_FLOWRECYCLER].flags = TM_FLAG_MANAGEMENT_TM;
    SC_ATOMIC_INIT(flowrec_cnt);
} TmModuleFlowManagerRegister  ----  数据流管理线程主要是配合yaml关于数据流相关配置一起使用,在合适的时间、合适的内存点,进行数据流的老化管理。将老化的数据节点加到FlowEnqueue(&**flow_recycle_q**, f); TmModuleFlowRecyclerRegister  ----- 取出老化后的数据节点FlowDequeue(&**flow_recycle_q**),进行重新清除数据,仍回到flow流表池**flow_spare_q**里面去。

应用层解析引擎

数据结构

typedef struct AppLayerParserProtoCtx_
{
    /* 0 - to_server, 1 - to_client. */
    int (*Parser[2])(Flow *f, void *protocol_state,
                     AppLayerParserState *pstate,
                     uint8_t *input, uint32_t input_len,
                     void *local_storage);
    void *(*StateAlloc)(void);
    void (*StateFree)(void *);
    void (*StateTransactionFree)(void *, uint64_t);
    void *(*LocalStorageAlloc)(void);
    void (*LocalStorageFree)(void *);
	......
} AppLayerParserProtoCtx;

typedef struct AppLayerParserCtx_ {
    AppLayerParserProtoCtx ctxs[FLOW_PROTO_MAX][ALPROTO_MAX];
} AppLayerParserCtx;

/* Static global version of the parser context.
 * Post 2.0 let's look at changing this to move it out to app-layer.c. */
static AppLayerParserCtx alp_ctx;  设定全局回调句柄用于应用层回调之用

应用协议注册

以SMTP为例:

void RegisterSMTPParsers(void)
  • AppLayerProtoDetectConfProtoDetectionEnabled 匹配yaml关于协议的开关是否开启
  • AppLayerProtoDetectRegisterProtocol 注册smtp协议号,交给全局变量alpd_ctx;
  • SMTPRegisterPatternsForProtocolDetection 注册smtp协议识别特征 “EHLO”,”HELO”,”QUIT” 主要使用AppLayerProtoDetectPMRegisterPatternCI,确定传输协议 IPPROTO_TCP、协议号、协议特征、特征长度、特征相对偏移位置、以及特征所在报文的传输方向
  • AppLayerParserRegisterStateFuncs 注册smtp协议相对当前线程的预分配 和 释放数据结构的句柄
  • AppLayerParserRegisterParser 注册smtp 不同方向的数据包 进入不同的解析函数

###应用层数据回调

1、TCP数据报文应用层接口
int AppLayerHandleTCPData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx,
                              Packet *p, Flow *f,
                              TcpSession *ssn, TcpStream *stream,
                              uint8_t *data, uint32_t data_len,
                              uint8_t flags)
  

2、UDP 数据报文应用层接口                             
    int AppLayerHandleUdp(ThreadVars *tv, AppLayerThreadCtx *tctx, Packet *p, Flow *f)

以UDP接口为例:

  • p->flowflags 数据包的传输方向
  • f->alproto 为ALPROTO_FAILED则返回
  • f->alproto为ALPROTO_UNKNOWN 则进入协议识别引擎
  • AppLayerProtoDetectGetProto 识别接口
    • AppLayerProtoDetectPMGetProto 模式匹配算法进行payload识别,mpm_table中的算法根据配置文件确定,默认使用ac算法
    • AppLayerProtoDetectPPGetProto 若PM未识别出来则进行端口识别
  • AppLayerParserParse 识别出了协议进行数据申请回调解析
    • AppLayerParserProtoCtx *p = &alp_ctx.ctxs[f->protomap][alproto]; 获取对应协议注册句柄
    • p->StateAlloc 看有没有注册这个协议,没有则返回
    • flags & STREAM_GAP 如果有gap则终止流程
    • f->alparser = pstate = AppLayerParserStateAlloc(); 设置协议内部解析状态
    • f->alstate = alstate = p->StateAlloc(); 数据结构申请内存,并将句柄指针挂载flow内部
    • p->Parser(flags & STREAM_TOSERVER) ? 0 : 1 将数据传递给协议解析函数进行解析

#总结陈述 本文从suricata的运行模式,讲到数据包的流转,流管理、应用解析,从一个大的源码架构上,让读者先有一个整体认知,然后细化各个解析引擎。不过 细心的读者应该发现,本文并没有讲detect模块,因为detect引擎是suricata的核心规则检测模块,整体相对复杂,我们必须讲解snort规则之后,才能进行该模块的解析,这个之后会单独拎出来讲解。相信经过本篇学习,您已经学习到了如何利用suricata进行ids的一个处理过程,当然suricata的用途绝不仅仅于此,你可以替换suricata抓包引擎,可以裁剪suricata的流程等等方式,让suricata变得更加的高效。由于时间紧促,个人见解难免有所偏差,还希望读者提出来 一起参与讨论。文中若有理解错误之处,也请赐教。参与讨论方式:1、可以通过gitchat读者圈 2、可以加笔者QQ群524557245。让我们一起进步!