feat(tls): reassemble hello messages split across TLS records

A handshake message may span any number of records, and splitting the
ClientHello into tiny records hides the SNI from analyzers that expect it
in the first one. The tls analyzer read the first record header and then
cut the handshake body straight out of the stream, so fragmented hellos
either stalled until Limit() or failed to parse, and every SNI rule was
bypassed.

The record layer is now peeled off and the hello rebuilt from the record
bodies before parsing, for both ClientHello and ServerHello. The number of
records a hello spanned is exposed as tls.req_records / tls.resp_records,
kept outside req/resp so a non-nil req still means a fully parsed hello.
Fragmentation is published as soon as a second record shows up, since
1-byte records can stretch a hello past Limit() and it never completes.

docs: add TLS record fragmentation doc, correct the claim in the TCP
window evasion doc that tls is unaffected by splitting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 17:31:57 +08:00
co-authored by Claude Opus 5.5
parent 8f8cc1d4d9
commit ae7135ab64
5 changed files with 391 additions and 200 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ table inet filter {
- **快路径**:首个分段本身就够长时(正常流量的首包都是几百字节),直接在原始切片上分类,不拷贝、不分配,开销与改动前完全一致。只有小于 16 字节的首段才会走缓冲。
- **段数上限**:最多等 `fetMaxSegments`(4)个分段。否则一个只发 1 字节然后装死的对端,就能让流一直留在活跃状态、拿不到 connmark 快速通道,直到 TCP 超时(默认 10 分钟)为止——那会变成一个可被主动利用的资源消耗面。
`http`、`tls`、`minecraft` 等分析器本来就用 `utils.ByteBuffer` 做增量解析,不受这个手法影响。
`http`、`tls`、`minecraft` 等分析器本来就用 `utils.ByteBuffer` 做增量解析,不受这个手法影响。注意这只针对 TCP 分段;TLS 自己记录层上的分片是另一回事,见 [TLS record 分片规避与检测](tls-record-fragmentation.zh.md)。
## 第二层防御:把窗口改写本身当指纹