一个数据包如何穿过 Suricata
一个数据包从网卡钻进 Suricata 的那一刻开始,会经过哪些地方,又是怎么一步步变成一条 EVE JSON 告警的?
它会被谁接住,在哪一步变成 Packet,什么时候和别的包组成 Flow,TCP 重组又是谁负责的?规则看起来只是一行文本,为什么加载之后却能参与几万次匹配?这些问题,光看配置手册很难得到答案——手册告诉你怎么用这台机器,不告诉你机器里齿轮怎么咬合。真正的答案藏在 Suricata 的线程、队列、状态机和那些名字并不总是直观的 C 结构体里。
把 Suricata 想象成一条工厂流水线:包从网卡或 pcap 文件进来,依次经过捕获、解码、Flow、Stream、应用层解析、规则检测这几道工位,才走到终点。先给这条产线画一张图,后面每一篇都是在往图里的某一格添细节:
1 | flowchart LR |
这条流水线本身,靠 TmModule 串在线程上跑(第2篇);而线程、模块在跑起来之前先要被搭出来,这就是第1篇的内容。
这是一份源码阅读记录,也是一张逐步画出来的地图。我会沿着一个包的生命周期往前走,从进程启动开始,经过捕获、解码、流跟踪、协议解析和规则检测,最后走到输出模块。每一篇尽量落到具体的函数、数据结构和代码位置,不把源码阅读写成概念名词的目录。
这里不讨论怎么写规则,也不展开部署调优。更关心的是:一个成熟的开源检测引擎,是怎样把网络世界里持续到来的字节,组织成可以处理、可以匹配、可以解释的事件。
如果你有 C 基础,对网络协议栈不算陌生,又想知道一个真实的安全引擎内部是怎么运转的——这份地图适合你。不用一次记住所有细节。先在源码里认出路,再慢慢看懂每个路口为什么这样设计,就够了。
我打算怎么读
Suricata 的 src/ 目录有上千个文件、几十万行代码,一开始就想”通读”基本等于劝退自己——像是站在一座陌生工厂门口,想靠一份零件清单搞懂整条产线怎么运转。所以这个系列不按目录结构走,而是跟着一个包实际走过的顺序走——先问”这一步在流水线的哪个工位”,再去看对应目录下的代码,而不是反过来从文件名倒推它是干什么的。
每一篇只挖一条主线,遇到旁支细节(比如某个协议的边界情况、某个参数的调优选项)会先记下来,不在当篇展开,避免一篇文章塞进两三个话题。同时尽量落到具体的 文件:行号,方便你对照源码验证我说得对不对——源码阅读最怕的就是”感觉理解了”却经不起追问。
系列篇目
- 先别急着看规则:Suricata 启动时做了什么 —— 从
main()到第一个包被处理之前,进程先把哪些东西搭起来?先把全局地图画出来。[已发布] - 一条流水线是怎么跑起来的:线程模型与 TmModule ——
TmModule和ThreadVars如何把各个处理阶段串起来?single、autofp、workers 三种模式,到底差在哪里? - 包是怎么进来的:捕获模块与 Packet 结构体 —— AF_PACKET、PCAP、NFQ 抓到的都只是原始字节,它们如何把一个包交给引擎?
Packet又为什么不只是一个普通结构体? - 把字节剥成协议:解码层 —— 从以太网到 IP,再到 TCP,一层层拆开的过程并不只是”取几个字段”。畸形包、隧道和边界情况,会在这里露出真面目。
- 引擎开始记住事情:Flow —— 单个包很快就过去了,
Flow才让 Suricata 记住一段通信。流表怎么查找、什么时候过期、谁来回收,这里是理解后续模块的关键。 - TCP 重组为什么这么难:Stream 引擎 —— 乱序、重传、gap,还有专门用来绕过检测的 evasion。看似只是把 TCP 段拼起来,实际上是一场持续处理不确定性的状态机游戏。
- 从端口到协议:应用层解析框架 —— Suricata 怎么判断眼前是 HTTP、TLS 还是别的协议?以 probing parser 和 HTTP 为例,走一遍解析器被发现、注册和调用的完整路径。
- 规则还没开始匹配:检测引擎如何把文本变成结构 —— 一行规则加载之后,会被拆成什么?
Signature、SigMatch这些结构,如何把规则描述转换成可执行的检测条件? - 几万条规则一起跑:多模式匹配与规则分组 —— 规则数量上来之后,逐条检查显然行不通。MPM 和签名分组(SGH)如何把搜索空间压下来,让检测引擎仍然有机会跑到线速?
- 命中之后发生什么:从规则匹配到一行 JSON —— 一条告警不是凭空写进日志的。
output模块如何注册,事件如何一路传到 EVE,最后又怎样变成我们看到的 JSON?
顺序可能会随着源码阅读的进展微调,但这条主线不会变:先跟着一个包走一遍,再回头拆开它经过的每个模块。
下一篇,从线程模型开始。因为在 Suricata 里,很多”它为什么这样处理”的答案,最后都会落到一个问题上:这段工作究竟在哪个线程里发生?