feat(tcp): implement FETAnalyzer with segment buffering feat(tcp): enhance TCP engine with window scale detection test(tcp): add tests for Minecraft and FET analyzers docs: add documentation for Minecraft analyzer and TCP evasion
68 lines
3.4 KiB
Markdown
68 lines
3.4 KiB
Markdown
# Minecraft 分析器
|
||
|
||
识别 Minecraft Java 版客户端在连接建立后发出的第一个数据包(Handshake,packet ID `0x00`),从中取出客户端请求的服务器地址、协议版本和后续状态。基岩版走 UDP/RakNet,不在本分析器范围内。
|
||
|
||
分析器名是 `minecraft`。和其他分析器一样,**只有当某条规则的表达式里出现了 `minecraft` 这个标识符时,它才会被启用**(见 `ruleset/expr.go` 的依赖收集逻辑),不写规则就不会有任何开销。
|
||
|
||
## 属性
|
||
|
||
| 属性 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `yes` | bool | 确认是 Minecraft 握手 |
|
||
| `candidate` | bool | 数据还不完整,暂时无法判定,仍在等待后续分段 |
|
||
| `protocol` | int | 协议版本号,**不是游戏版本号**,对照表见 [minecraft.wiki](https://minecraft.wiki/w/Protocol_version)(例如 767 对应 1.21) |
|
||
| `server_addr` | string | 客户端在握手包里填写的服务器地址 |
|
||
| `server_port` | uint16 | 客户端填写的端口 |
|
||
| `next_state` | int | 1 = status,2 = login,3 = transfer |
|
||
| `next_state_name` | string | 上面的可读形式:`status` / `login` / `transfer` |
|
||
| `packet_length` | int | 握手包声明的长度(不含长度字段自身) |
|
||
|
||
`yes` 和 `candidate` 一定存在,其余字段只在 `yes` 为 true 时出现。
|
||
|
||
## 规则示例
|
||
|
||
只拦截真正进服的连接,放过服务器列表里的 ping:
|
||
|
||
```yaml
|
||
- name: block-minecraft-login
|
||
action: block
|
||
log: true
|
||
expr: minecraft != nil && minecraft.yes && minecraft.next_state_name == "login"
|
||
```
|
||
|
||
只允许连接指定的服务器,其余一律拦截:
|
||
|
||
```yaml
|
||
- name: minecraft-allowlist
|
||
action: block
|
||
expr: >
|
||
minecraft != nil && minecraft.yes &&
|
||
!hasPrefix(minecraft.server_addr, "mc.example.com")
|
||
```
|
||
|
||
按协议版本卡老客户端:
|
||
|
||
```yaml
|
||
- name: block-old-minecraft-clients
|
||
action: block
|
||
expr: minecraft != nil && minecraft.yes && minecraft.protocol < 763
|
||
```
|
||
|
||
纯记录不拦截,先摸清楚流量情况:
|
||
|
||
```yaml
|
||
- name: log-minecraft
|
||
log: true
|
||
expr: minecraft != nil && minecraft.yes
|
||
```
|
||
|
||
## 写规则时要注意的几点
|
||
|
||
**`server_addr` 不一定是干净的域名。** 握手包里的地址字段被生态里几个东西复用了,客户端可能在真实域名后面追加以 `\0` 分隔的内容:Forge/FML 客户端会加 `\0FML\0`、`\0FML2\0`、`\0FML3\0` 之类的标记,BungeeCord 和 Velocity 的 IP 转发会把玩家真实 IP 和 UUID 拼在后面。分析器保存的是原始字节,不做任何清洗。所以不要用 `==` 精确比较,用 `hasPrefix(minecraft.server_addr, ...)`,或者先 `split(minecraft.server_addr, "\x00")[0]` 再比。
|
||
|
||
**这个字段是客户端自己填的,不可信。** 它反映客户端想连哪个域名(对 SRV 记录的情况是解析前的原始主机名),不是它实际连到的 IP。要认真限制目标,得配合 `ip.dst` 一起写。
|
||
|
||
**判定发生在第一个数据包。** 握手之后的登录、加密、游戏数据都不会再看。`Limit()` 是 2053 字节,正常握手包只有几十字节,绰绰有余;超过这个量还没解析成功的连接会被判为非 Minecraft。
|
||
|
||
**握手包被拆到多个 TCP 分段时也能正确解析。** 分析器内部有缓冲区,数据不够时返回 `candidate = true` 并继续等待,不会像只看单个分段的分析器那样被切包绕过。相关背景见 [TCP 窗口操纵规避](tcp-window-evasion.zh.md)。
|