Skip to content

协议解析

目的与范围

协议解析子系统用于解释从 SSL/TLS 连接中捕获的载荷数据,为 HTTP/1.x 和 HTTP/2 协议提供自动检测和结构化输出。解析器处理 HPACK 头部解压缩、gzip 内容解压缩和特定协议的格式化。它们在事件处理流程中运行(参见事件处理流程),将原始二进制载荷转换为可读的协议消息。

相关主题:输入事件结构参见事件结构与类型,格式化输出处理参见输出格式


解析器架构

IParser 接口

所有协议解析器的核心抽象是在 pkg/event_processor/iparser.go:49-60 中定义的 IParser 接口。每个解析器实现都必须满足此接口:

方法用途
detect(b []byte) error识别载荷是否匹配此协议
Write(b []byte) (int, error)累积载荷数据用于解析
ParserType() ParserType返回解析器类型标识符
PacketType() PacketType返回检测到的编码/压缩类型
Name() string返回人类可读的解析器名称
IsDone() bool指示解析是否完成
Init()初始化解析器状态
Display() []byte返回格式化输出
Reset()清除状态以便重用

来源: pkg/event_processor/iparser.go:49-60

解析器与数据包类型

系统定义了用于解析器标识和内容编码的枚举:

go
// ParserType 标识协议解析器
const (
    ParserTypeNull ParserType = iota
    ParserTypeHttpRequest
    ParserTypeHttp2Request
    ParserTypeHttpResponse
    ParserTypeHttp2Response
    ParserTypeWebSocket
)

// PacketType 标识内容编码
const (
    PacketTypeNull PacketType = iota
    PacketTypeUnknow
    PacketTypeGzip
    PacketTypeWebSocket
)

ParserType 被事件处理系统用于路由事件,而 PacketType 指示内容是否被压缩以及在解析期间是否已被解压缩。

来源: pkg/event_processor/iparser.go:23-47

解析器注册系统

解析器使用 init() 函数模式进行自注册。注册系统维护一个可用解析器的全局映射表:

Register() 函数 pkg/event_processor/iparser.go:64-73 将解析器添加到全局映射表中,确保没有重复的名称。当新的载荷数据到达时,NewParser() pkg/event_processor/iparser.go:85-115 遍历已注册的解析器,调用每个 detect() 方法直到找到匹配,或回退到 DefaultParser

来源: pkg/event_processor/iparser.go:62-115


解析器检测与选择

解析器选择过程使用尝试所有解析器的方法,在第一次匹配时提前终止:

每个解析器的 detect() 方法检查协议特定的标记:

来源: pkg/event_processor/iparser.go:85-115, pkg/event_processor/http_request.go:83-91, pkg/event_processor/http_response.go:94-102, pkg/event_processor/http2_request.go:42-58, pkg/event_processor/http2_response.go:56-88


HTTP/1.x 解析

请求解析

HTTPRequest 解析器使用 Go 的标准 net/http 包来解析 HTTP/1.x 请求:

解析器在缓冲区中累积数据 pkg/event_processor/http_request.go:54-80,然后使用 http.ReadRequest() 解析头部和主体。在第一次写入时,它初始化请求结构;后续写入累积主体数据。

Gzip 检测与解压缩: 解析器检查 Content-Encoding 头部 pkg/event_processor/http_request.go:123-142。如果设置为 "gzip",它会:

  1. 从主体字节创建 gzip.NewReader()
  2. 通过 io.ReadAll() 解压缩
  3. PacketType 设置为 PacketTypeGzip
  4. 在输出中返回解压缩的内容

来源: pkg/event_processor/http_request.go:28-163

响应解析

HTTPResponse 解析器遵循类似的结构,但使用 http.ReadResponse()

字段用途
response *http.Response解析的 HTTP 响应结构
reader *bytes.Buffer累积原始字节
bufReader *bufio.Reader为 http.ReadResponse 提供缓冲
packerType PacketType跟踪 gzip 压缩
isInit bool指示是否已解析响应头部

解析器通过 Go 的 HTTP 库自动处理分块传输编码。当调用 Display()pkg/event_processor/http_response.go:115-175,它会:

  1. 通过 io.ReadAll() 读取完整的响应主体
  2. 通过 Content-Encoding 头部检测 gzip
  3. 如有必要进行解压缩
  4. 使用 httputil.DumpResponse() 进行格式化输出

截断主体处理: 解析器优雅地处理 io.ErrUnexpectedEOF pkg/event_processor/http_response.go:121-128,当 SSL/TLS 数据增量捕获且声明的 Content-Length 超过接收的数据时会发生此情况。

来源: pkg/event_processor/http_response.go:28-182


HTTP/2 解析

由于二进制帧和 HPACK 头部压缩,HTTP/2 解析明显更复杂。

HTTP/2 帧结构

所有 HTTP/2 通信使用带有 9 字节头部的帧 pkg/event_processor/http2_response.go:56-88

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+

帧类型包括:DATAHEADERSPRIORITYRST_STREAMSETTINGSPUSH_PROMISEPINGGOAWAYWINDOW_UPDATECONTINUATION

HTTP/2 请求解析

ClientPreface 检测: HTTP/2 请求以魔术字符串 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" 开始 pkg/event_processor/http2_request.go:42-58detect() 方法验证这个 24 字节序列。在 Display() 期间,解析器在处理帧之前丢弃此前言 pkg/event_processor/http2_request.go:103-112

帧处理: 解析器使用来自 golang.org/x/net/http2http2.Framer pkg/event_processor/http2_request.go:61-71 顺序读取帧。对于每个帧:

来源: pkg/event_processor/http2_request.go:32-206

HTTP/2 响应解析

HTTP2Response 解析器共享类似的结构但处理响应:

HPACK 动态表: HTTP/2 规范要求在整个连接上维护一个共享的动态表 pkg/event_processor/http2_response.go:94-100。解析器为每个连接生命周期创建一个 hpack.Decoder,而不是为每个帧创建。这在多个请求/响应对之间保留了头部压缩上下文。

不完整帧处理: 在增量 SSL/TLS 捕获期间,帧可能被截断。解析器优雅地处理 io.ErrUnexpectedEOF pkg/event_processor/http2_response.go:137-144,方法是:

  1. 检查错误是否为 EOF 或 ErrUnexpectedEOF
  2. 仅记录其他错误
  3. 继续处理成功解析的帧

这可以防止在流式捕获期间出现虚假错误。

来源: pkg/event_processor/http2_response.go:46-224

HPACK 头部压缩

HPACK(HTTP/2 头部压缩)通过以下方式减少头部冗余:

机制描述
静态表预定义的常见头部(例如 :method: GET
动态表连接特定的头部缓存(默认 4096 字节)
Huffman 编码压缩头部字符串值

解析器使用 hpack.DecodeFull() 解码完整的头部块片段 pkg/event_processor/http2_request.go:133-141pkg/event_processor/http2_response.go:151-160。每个解码的头部字段包含一个名称-值对。解析器特别关注 content-encoding 头部以按流跟踪压缩。

CONTINUATION 帧限制: 当前实现不支持跨 CONTINUATION 帧拆分的 HEADERSpkg/event_processor/http2_request.go:143-144pkg/event_processor/http2_response.go:162-163。如果 HeadersEnded() 返回 false(表示头部不完整),它会记录警告。

来源: pkg/event_processor/http2_request.go:129-145pkg/event_processor/http2_response.go:146-163

HTTP/2 Gzip 解压缩

由于基于帧的流式传输,HTTP/2 gzip 处理与 HTTP/1.x 不同:

解析器维护两个按流 ID 键控的映射表 pkg/event_processor/http2_request.go:114-115pkg/event_processor/http2_response.go:132-133

  1. encodingMap[streamID] - 按流跟踪内容编码
  2. dataBufMap[streamID] - 按流累积 gzip 压缩的载荷

读取所有帧后,它解压缩累积的缓冲区 pkg/event_processor/http2_request.go:172-189pkg/event_processor/http2_response.go:190-207

  1. 检查流是否有 gzip 编码
  2. 从累积的缓冲区创建 gzip.NewReader()
  3. 通过 io.ReadAll() 读取解压缩的数据
  4. 附加到带有流 ID 注释的输出

来源: pkg/event_processor/http2_request.go:114-189pkg/event_processor/http2_response.go:132-207


默认解析器

当未检测到特定协议时,DefaultParser 作为回退:

go
type DefaultParser struct {
    reader *bytes.Buffer
    isdone bool
}

它提供最小处理 pkg/event_processor/iparser.go:117-166

  1. 在缓冲区中累积所有字节
  2. Display() 时,检查第一个字节的可打印性
  3. 如果字节 < 32 或 > 126(不可打印),输出十六进制转储
  4. 否则,将 C 风格的空终止字符串转换为 Go 字符串

这确保即使协议特定的解析失败,也可以查看所有捕获的数据。

来源: pkg/event_processor/iparser.go:117-166


与事件工作者的集成

解析器在事件处理流程中的事件工作者上下文中运行:

解析器实例化: 工作者在 parserEvents() 方法中懒惰地创建解析器 pkg/event_processor/iworker.go:248-260

  • 第 251 行:if ew.parser == nil { ew.parser = NewParser(ew.payload.Bytes()) } 在第一次解析时创建解析器
  • 对于 LifeCycleStateDefault 工作者(基于定时器的销毁),每次可能创建一个新解析器
  • 对于 LifeCycleStateSock 工作者(基于套接字的生命周期),解析器在多个事件之间持久存在
  • NewParser() 通过尝试每个已注册解析器的 detect() 方法来检测协议

两种生命周期模型:

生命周期状态解析器行为用例
LifeCycleStateDefault在第一次解析时创建解析器,当工作者在 1 秒不活动后超时时销毁 pkg/event_processor/iworker.go:279-289短期或一次性 SSL/TLS 连接
LifeCycleStateSock在第一次解析时创建解析器,重用直到套接字关闭 pkg/event_processor/iworker.go:280-284需要 HPACK 状态的长期连接(例如 HTTP/2 多路复用)

套接字生命周期模型对于 HTTP/2 连接至关重要,因为 HPACK 动态表必须在连接中的所有帧之间持久存在 pkg/event_processor/http2_response.go:94-100

解析器生命周期阶段:

  1. 累积阶段:事件载荷字节在 eventWorker.payload 缓冲区中累积 pkg/event_processor/iworker.go:230-245
  2. 解析阶段:定时器触发(MaxTickerCount = 10,或 1 秒)pkg/event_processor/iworker.go:279 并调用 Display()
  3. 懒惰实例化:第一次调用 parserEvents() 通过 NewParser() 创建解析器 pkg/event_processor/iworker.go:251-252
  4. 写入阶段parser.Write() 接收所有累积的字节 pkg/event_processor/iworker.go:254
  5. 显示阶段parser.Display() 返回格式化输出 pkg/event_processor/iworker.go:259
  6. 重置阶段:解析器状态通过 parser.Reset() 清除 pkg/event_processor/iworker.go:183,载荷缓冲区通过 ew.payload.Reset() 清除 pkg/event_processor/iworker.go:184

截断支持: 工作者遵守 truncateSize 配置 pkg/event_processor/iworker.go:236-242,在解析之前限制累积的载荷大小。达到限制时,载荷被截断并生成日志消息 pkg/event_processor/iworker.go:238-241

来源: pkg/event_processor/iworker.go:51-306pkg/event_processor/http2_response.go:94-100


关键设计决策

决策理由实现
通过 init() 自注册解析器使新协议解析器能够模块化添加而无需修改核心代码pkg/event_processor/iparser.go:64-73 Register() 函数,从解析器 init() 函数调用
通过试错检测比魔术字节检查更简单;利用标准库协议解析器pkg/event_processor/iparser.go:85-115 NewParser() 迭代已注册的解析器
两种工作者生命周期模型平衡内存效率(默认)与协议状态要求(套接字)pkg/event_processor/iworker.go:57-63 LifeCycleState 枚举和生命周期逻辑 pkg/event_processor/iworker.go:262-306
在套接字生命周期中重用解析器维护跨连接的 HTTP/2 HPACK 动态表pkg/event_processor/iworker.go:251 解析器持久存在,pkg/event_processor/http2_response.go:94-100 HPACK 解码器重用
优雅处理 io.ErrUnexpectedEOF增量 TLS 捕获期间的正常情况;不是错误pkg/event_processor/http_response.go:121-128pkg/event_processor/http2_response.go:137-144 错误检查
HTTP/2 中按流跟踪 gzipHTTP/2 多路复用流;压缩是按流的,而不是按连接的pkg/event_processor/http2_request.go:114-115 encodingMapdataBufMap 按流 ID 键控
十六进制转储回退确保即使协议未知也可以查看所有数据pkg/event_processor/iparser.go:117-166 DefaultParser 带有十六进制转储逻辑 pkg/event_processor/iparser.go:152-160

来源: pkg/event_processor/iparser.go:64-166pkg/event_processor/iworker.go:51-306pkg/event_processor/http2_request.go:114-189pkg/event_processor/http2_response.go:94-207pkg/event_processor/http_response.go:121-128

协议解析 has loaded