1
0
Files
DiAL/openspec/changes/add-icmp-checker/design.md
lanyuanxiaoyao 393e8da5fd feat: 新增 ICMP/Ping checker 设计提案
- 定义 ping target 配置:host、count、packetSize
- 定义 ping expect 断言:alive、maxPacketLoss、maxAvgLatencyMs、maxMaxLatencyMs
- 设计跨平台 ping 输出解析器(Linux/macOS/Windows 含多语言支持)
- 双重超时保障:ping 命令自身超时 + AbortSignal 兜底
- 扩展 checker-runner-abstraction spec 支持 ping checker 子进程控制
- 更新 probe-config spec 支持 ping type 配置
2026-05-18 00:33:11 +08:00

191 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
项目当前有 HTTP、CMD、DB、TCP 四种 checker均遵循 `CheckerDefinition<TResolved>` 接口规范。TCP checker 是最近实现的网络层 checker其模式Bun.spawn / 原生 socket + AbortSignal + 断言链)是 ICMP checker 的直接参考。
ICMP Ping 是最基础的网络探测手段,但 Node.js/Bun 生态中没有合适的纯 JS ICMP 实现(均依赖 `raw-socket` 原生 addonBun N-API 兼容性不确定且需要 root 权限)。现有的命令封装库(`ping``pingman`)虽然提供了跨平台解析,但它们封装了 `child_process.spawn` 且不暴露子进程引用,无法配合我们的 `ctx.signal` 超时控制机制。
经过调研和讨论,确定自行实现:用 `Bun.spawn` 调用系统 `ping` 命令 + 自行编写跨平台输出解析器。
## Goals / Non-Goals
**Goals:**
- 实现 `type: ping` checker支持主机存活检测、延迟监控、丢包率检查
- 跨平台支持 Linux、macOS、Windows含中文 Windows
- 完全复用现有 checker 架构registry、schema、expect、failure
- 零外部依赖
**Non-Goals:**
- 不实现 traceroute 功能
- 不实现 IPv6 专项支持(系统 ping 命令会自动处理 IPv6 地址)
- 不实现原始 ICMP socket权限要求过高
- 不提供 per-packet 逐包结果(只提供 summary 统计)
## Decisions
### Decision 1: 调用系统 `ping` 命令而非原始 ICMP socket
**选择**: 通过 `Bun.spawn` 调用系统 `ping` 可执行文件
**替代方案**:
- 原始 ICMP socket`raw-socket` addon需要 root/CAP_NET_RAW 权限Bun N-API 兼容性不确定
- 三方库 `pingman`/`ping`:封装了 spawn 但不暴露子进程引用,无法配合 AbortSignal 超时控制
**理由**: 系统 `ping` 命令无需特殊权限(大多数系统),`Bun.spawn` 给我们完全的子进程生命周期控制,与现有 cmd checker 模式一致。
### Decision 2: 自行实现跨平台解析器
**选择**: 在 `parse.ts` 中实现 `parsePingOutput(stdout, platform)` 函数,用正则匹配 summary 统计行
**替代方案**:
- 引入 `pingman` 作为依赖使用其 parser模块不是公开 APIdeep import 不稳定
- 引入 `ping`danielzzz的 parser同上且返回值类型全是 string
**理由**: 我们只需要 summary 行的统计数据transmitted/received/loss/min/avg/max不需要逐包 body 解析。三套正则约 40-50 行代码,完全可控且零依赖。
### Decision 3: 跨平台命令构建策略
```
平台判断: process.platform (win32 vs 其他)
Linux:
ping -c <count> -s <packetSize> -W <timeoutSec> <host>
macOS:
ping -c <count> -s <packetSize> -W <timeoutMs> <host>
Windows:
ping -n <count> -l <packetSize> -w <timeoutMs> <host>
```
**超时参数传递策略:双重保障**
传递平台对应的超时参数(`-W`/`-w`),同时保留外层 `ctx.signal` + `proc.kill()` 作为兜底:
- **ping 命令自身超时**:确保每个 ICMP 包在指定时间内超时返回,避免 ping 进程因网络异常而无限等待
- **外层 AbortSignal**:作为最终兜底,防止 ping 命令因任何原因卡死不退出
各平台超时参数单位差异:
- Linux `-W`:单位为**秒**(整数),需将 timeoutMs 转换为秒(向上取整)
- macOS `-W`:单位为**毫秒**(整数)
- Windows `-w`:单位为**毫秒**(整数)
超时值计算:使用外层 `timeoutMs` 作为 ping 命令的超时参数值。这样 ping 命令自身会在 timeout 内完成,外层 signal 作为额外保障。
### Decision 4: Windows 多语言输出解析策略
Windows `ping` 输出语言跟随系统 locale中文系统输出中文、英文系统输出英文、日文系统输出日文等
**选择**: 基于数字模式和行结构匹配,不依赖关键词
具体策略:
- **丢包行**: 匹配 `(\d+).*?(\d+).*?(\d+).*?(\d+(?:\.\d+)?%)` 模式——提取"已发送"、"已接收"、"丢失"和百分比数字,不依赖中英文关键词
- **延迟行**: 匹配 `(\d+)ms.*?(\d+)ms.*?(\d+)ms` 模式——提取 min/max/avg 三个数字Windows 输出顺序固定为 Minimum/Maximum/Average
**替代方案**: 枚举所有语言的关键词——维护成本高,且无法覆盖所有 locale
### Decision 5: 解析结果数据结构
```typescript
interface PingStats {
alive: boolean;
transmitted: number;
received: number;
packetLoss: number; // 0-100
minLatencyMs: number | null;
avgLatencyMs: number | null;
maxLatencyMs: number | null;
}
```
`latencyMs` 字段为 `null` 表示主机不可达时无延迟数据。
### Decision 6: 断言执行顺序(短路)
```
alive → packetLoss → avgLatency → maxLatency → duration
```
理由:
1. `alive` 是最基础的判断,不可达时后续断言无意义
2. `packetLoss` 比延迟更严重(丢包意味着部分请求完全失败)
3. `avgLatency``maxLatency` 是延迟质量指标
4. `duration` 是整体执行时间兜底
### Decision 7: ping 命令不存在时的错误处理
当系统未安装 `ping` 命令时(常见于精简容器镜像如 Alpine`Bun.spawn` 会抛出 ENOENT 错误。
处理方式:在 spawn 阶段 try/catch返回结构化错误
```typescript
failure: errorFailure("ping", "spawn", `ping 命令不可用: ${error.message}`)
statusDetail: "ping command not found"
```
文档中注明系统依赖:容器环境需确保 `ping` 命令可用(如 Alpine 需安装 `iputils-ping`)。
### Decision 8: configKey 和 type 命名
**选择**: `type: "ping"`, `configKey: "ping"`
**替代方案**: `type: "icmp"` — 但 `ping` 更贴近用户认知,且配置中 `ping.host``icmp.host` 更直观。
### Decision 9: 超时控制与子进程生命周期
与 cmd checker 相同的模式:
```typescript
ctx.signal.addEventListener("abort", () => {
try { proc.kill(); } catch { /* best-effort */ }
}, { once: true });
```
当 signal abort 时 kill 子进程,然后在结果中记录超时错误。这需要修改 `checker-runner-abstraction` spec 中"仅 cmd checker 可在 signal abort 时 proc.kill()"的约束。
### Decision 10: Linux/macOS 解析正则
```
丢包统计行:
Linux: "3 packets transmitted, 3 received, 0% packet loss, time 2003ms"
macOS: "3 packets transmitted, 3 packets received, 0.0% packet loss"
正则: /(\d+)\s+packets?\s+transmitted.*?(\d+)\s+(?:packets?\s+)?received.*?(\d+(?:\.\d+)?)%\s+packet\s+loss/
延迟统计行:
Linux: "rtt min/avg/max/mdev = 1.234/2.345/3.456/0.567 ms"
macOS: "round-trip min/avg/max/stddev = 1.234/2.345/3.456/0.567 ms"
正则: /(?:rtt|round-trip).*?=\s*([\d.]+)\/([\d.]+)\/([\d.]+)/
```
### Decision 11: Windows 解析正则
```
丢包统计行(数字模式,语言无关):
英文: "Packets: Sent = 3, Received = 3, Lost = 0 (0% loss)"
中文: "数据包: 已发送 = 3已接收 = 3丢失 = 0 (0% 丢失)"
正则: /=\s*(\d+).*?=\s*(\d+).*?=\s*(\d+).*?(\d+(?:\.\d+)?)%/
延迟统计行(数字模式,语言无关):
英文: "Minimum = 1ms, Maximum = 3ms, Average = 2ms"
中文: "最短 = 1ms最长 = 3ms平均 = 2ms"
正则: /=\s*(\d+)ms.*?=\s*(\d+)ms.*?=\s*(\d+)ms/
```
Windows 延迟顺序固定为 min/max/avg注意与 Linux/macOS 的 min/avg/max 不同)。
## Risks / Trade-offs
### [Risk] 系统未安装 ping 命令 → 清晰的错误提示 + 文档说明
容器环境Alpine、scratch可能不包含 ping。通过 spawn 阶段 catch ENOENT 给出明确提示,并在 README 中注明依赖。
### [Risk] 未知 locale 的 Windows 输出无法解析 → 降级为 alive=false + 原始输出
如果正则无法匹配任何统计行,将 alive 判定为 `received > 0`(通过检查 exit codeWindows ping 在全部丢包时 exit code 为 1延迟字段为 null。statusDetail 展示原始输出前 80 字符供用户排查。
### [Risk] ping 命令被防火墙/网络策略阻断 → 用户可预期的行为
ICMP 在某些网络环境中被阻断。这不是 checker 的 bug而是网络配置问题。checker 会正确报告 `alive=false` 和 100% packet loss。
### [Trade-off] 双重超时保障
传递 `-W`/`-w` 超时参数给 ping 命令,同时保留外层 AbortSignal + proc.kill() 兜底。
优势ping 命令自身会在超时后正常退出,不依赖外部 kill即使 ping 命令因异常卡死,外层 signal 仍能强制终止。
劣势需要处理三平台超时参数的单位差异Linux 秒 vs macOS/Windows 毫秒),增加少量命令构建复杂度。
### [Trade-off] 不引入三方库
优势:零依赖、完全可控、与项目规范一致。
劣势:需要自行维护跨平台解析正则。但 ping 输出格式极其稳定(几十年未变),维护成本极低。