tracepath
使用 Ruby 原生 socket 实现的 traceroute 库与命令行工具。支持 Linux、macOS,要求 Ruby 3.4 或更新版本;不调用系统 traceroute、tracepath 或 ping 命令。Ruby 的加载名为 tracepath,模块常量为 Tracepath。
面向日常网络运维:定位从哪一跳开始没有响应、比较 IPv4/IPv6 路径、使用 TCP SYN 检查特定端口方向,以及对数十或数百个目标执行有界批量探测。当前提供 UDP、ICMP Echo、TCP SYN 三种探测方式、结构化结果、逐跳/逐目标回调、取消、截止时间和共享批量速率限制。
这不是 Linux iputils tracepath 命令的 Ruby 包装。持续 MTR、Paris traceroute、主动路径 MTU 搜索、ASN/地理信息查询和 Windows 后端不在当前范围内。平台实现和实际验证要求见 平台说明。
开始使用
在项目目录安装开发依赖,并运行本地入口:
bundle install
bundle exec ruby -Ilib exe/tracepath --help
bundle exec ruby -Ilib exe/tracepath -4 127.0.0.1
bundle exec ruby -Ilib exe/tracepath -T -p 443 example.com
bundle exec rake test
gem build tracepath.gemspec命令行会在探测前检查 socket 权限,必要时自动调用系统 sudo,可直接运行 ruby exe/tracepath qq.com。系统需要验证身份时会提示输入密码;已有有效的 sudo 授权时直接继续。Linux UDP 使用内核错误队列,通常无需提权。权限与非交互行为见下文及平台说明;Ruby 库本身不自动提权。测试默认使用本地替身和报文样本,不要求外网或管理员权限。
业务模型
一次 trace 是针对一个已解析地址、指定协议与探测配置的有限测量。它由多个 TTL 对应的 Hop 组成;每个 Hop 保留各个 ProbeResult,不能用单一地址覆盖同一跳的多路径响应。
| 对象 | 职责 |
|---|---|
Request |
目标字符串和该目标的配置覆盖,适合异构批量任务 |
Target |
原始目标、实际选择的 IP、地址族和 IPv6 scope |
Probe |
一次发送尝试的 TTL、序号、关联字段和时间预算 |
ProbeResult |
回复、超时、发送失败或取消;保留响应者、RTT、ICMP/TCP 信息 |
Hop |
同一 TTL 的探针结果及地址、RTT、响应率统计 |
Result |
一次 trace 的配置、逐跳结果、终止原因和运行错误 |
BatchResult |
按输入顺序保存全部结果,提供状态计数和批量耗时 |
结果及其嵌套集合不可变。to_h / to_json 供存储和系统集成使用;started_at 序列化为 UTC ISO 8601,duration、rtt 和 RTT 统计以秒为单位。ProbeResult#rtt_ms 是毫秒便捷读方法。探针内部发送/截止时刻使用单调时钟,不能当作日历时间。
ProbeResult#hostname 保留原始 PTR 名称字节。输出到 Hash、JSON 或文本时,名称中的非 UTF-8 字节用三位十进制 \DDD 表示,原始反斜杠用 \092 表示;有效 UTF-8 保留。因此异常 DNS 名称不会中断整批 JSON 输出,序列化名称也能按这些转义还原原始字节。JSON 字符串中的反斜杠遵循 JSON 自身的转义规则。
Ruby 接口
require "tracepath"
result = Tracepath.trace(
"example.com",
protocol: :tcp,
port: 443,
family: :ipv4,
max_hops: 30,
timeout: 2.0,
max_duration: 60.0
) do |hop|
puts "#{hop.ttl}: #{hop.addresses.join(', ')}"
end
puts result.stop_reason
puts result.to_json单目标回调在每一跳完成时接收 Hop。一个 TTL 的探针全部获得回复、超时或终止后才继续下一 TTL;发现目标或终止性不可达后,不再发送下一跳。Tracepath.trace 也接受 Request,其目标配置优先于该次调用的公共配置。
requests = [
"router.example.com",
Tracepath::Request.new("service.example.com", protocol: :tcp, port: 443),
Tracepath::Request.new("service.example.com", family: :ipv6, protocol: :icmp)
]
batch = Tracepath.trace_many(
requests,
concurrency: 4,
rate_limit: 50.0,
timeout: 2.0,
max_duration: 60.0
) do |result, index|
puts "input #{index}: #{result.target.original} #{result.stop_reason}"
end
p batch.countsRequest 的配置覆盖批量公共配置。最多同时执行 concurrency 个目标,rate_limit 限制整个批次每秒发送的探针;每个目标仍受 max_inflight 和 send_interval 约束。重复目标独立保留。回调按完成顺序在调用线程串行执行,batch.results 保持输入顺序,index 从零开始。
批量输入可使用有限的 Enumerable,包括 Lazy 枚举器;整个计划在启动工作线程前完成物化与校验。
回调应及时返回。网络工作线程独立计算超时,但库不能安全中止任意调用方代码。回调抛出异常时,库停止调度、清理工作线程与网络资源,再向调用方重抛原异常。
token = Tracepath::CancellationToken.new
worker = Thread.new do
Tracepath.trace_many(requests, cancellation: token)
end
token.cancel # 也可由应用的停止事件调用;重复调用无副作用
batch = worker.value取消后保留已经获得的结果;尚未启动的目标也返回 :cancelled,因此批量结果不会无声缺项。库本身不安装信号处理器。
配置
| 关键字 | 默认值 | 含义 |
|---|---|---|
protocol |
:udp |
:udp、:icmp、:tcp
|
family |
:auto |
:auto、:ipv4、:ipv6
|
first_ttl / max_hops
|
1 / 30
|
首个与最后一个 TTL / IPv6 hop limit |
probes / max_inflight
|
3 / 3
|
每跳探针数、每目标同时等待的探针上限 |
timeout |
3.0 |
每个探针等待回复的秒数 |
max_duration |
120.0 |
单目标总时限,包含解析和探测 |
dns_timeout |
3.0 |
正向解析时间预算 |
reverse_dns / ptr_timeout
|
false / 1.0
|
是否查询响应者主机名、单次反向解析预算 |
send_interval |
0.05 |
同一目标相邻探针发送的最小间隔,秒 |
port |
UDP 33434;TCP 443
|
UDP 起始端口逐探针递增;TCP 目标端口固定;ICMP 不接受该项 |
source / interface
|
nil / nil
|
本机源 IP 和出接口 |
traffic_class |
0 |
IPv4 TOS / IPv6 traffic class,0..255
|
packet_size |
nil |
IP 包总字节数;默认 UDP/ICMP 载荷为 32 字节,TCP 无额外载荷 |
dont_fragment |
false |
IPv4 不分片;IPv6 不接受该项 |
cancellation |
无 | 可选共享 CancellationToken,运行控制对象不写入结果配置 |
运行参数 rate_limit 默认 50.0 探针/秒;单目标和批量均可指定,批量所有目标共享同一个限制。trace_many 另外接受 concurrency,默认 4,范围 1..256。
family: :auto 从系统解析结果中选择一个地址,不表示同时探测双栈,也不承诺在失败后切换另一个地址。比较双栈时应创建两个分别指定地址族的请求。IPv6 link-local 地址需要 %接口名、%接口索引 或 interface:,例如 fe80::1%en0。
配置非法抛出 ArgumentError;批量在启动探测前校验公共配置及各请求。DNS、权限和捕获等预期运行故障保存在 Result#error,含 code、message 和 details。调用方异常及程序错误不会伪装成网络失败结果。
发送或接收批次中途失败时,已经读取且通过关联和时间校验的回复仍保留在 Hop 中,故障另外写入 error。若这些回复已经证明到达,stop_reason 可以是 :reached,同时带有后续故障;调用方应分别检查到达证据与 error。
结果如何解释
stop_reason |
含义 |
|---|---|
:reached |
收到能关联到本次探针的目标响应 |
:unreachable |
收到终止性不可达响应;具体原因保留在探针中 |
:max_hops |
已用完配置的 TTL 范围,尚未证明到达 |
:deadline_exceeded |
单目标整体时间预算耗尽 |
:cancelled |
调用方或 CLI 中断了测量 |
:error |
解析、权限、发送/捕获等运行故障,见 error
|
reached? 只在 :reached 时为真。UDP 到达依靠目标发出的匹配端口不可达;ICMP 依靠匹配 Echo Reply;TCP 的匹配 SYN/ACK 或 RST 都能证明到达。TCP 到达不等于应用服务健康,也没有建立业务连接。
* 表示本探针在预算内没有得到可关联回复,不能直接证明链路丢包或目标故障。路由器可能限速、不回答 TTL 超时,或采用不同返回路径。每跳 RTT 是源端到该响应者的往返时间,不能用相邻跳 RTT 相减断言链路时延。地址重复不能单独证明路由环路。
Hop#response_rate 只在回复和真实超时之间计算比例;取消和发送失败不计为丢包。没有有效样本时返回 nil。ECMP 下同一跳可能出现多个地址;本项目未提供保持固定流标识的 Paris traceroute 保证。探针中的被动 MTU 信息也不等于主动完成路径 MTU 搜索。
校验和卸载可能使本机捕获的数据包尚未包含最终校验和。Linux 使用内核附带元信息处理;macOS 仅在已确认的本机 loopback TCP 捕获中使用明确的 unverified_local_capture 标记。具体边界见 平台说明,该标记不表示线上的报文已通过校验。
命令行
以下命令在安装本地构建的 gem 后使用 tracepath,在源码目录也可换成 bundle exec ruby -Ilib exe/tracepath。
tracepath -4 -U -m 20 -q 3 example.com
tracepath -6 -I --reverse-dns example.com
tracepath -T -p 443 --timeout 2 --max-duration 45 example.com
tracepath --concurrency 4 --rate-limit 50 --targets-file targets.txt --json
tracepath --jsonl router-a.example.com router-b.example.com
tracepath --no-sudo --json example.com命令行入口默认启用自动提权,也适用于 bundle exec 与安装后的 gem 命令。完成参数和目标列表校验后,只打开并关闭所需 socket 来检查权限,不发送探针;只有明确权限拒绝才尝试 sudo。root、Linux UDP 以及已有所需 socket 能力的进程直接运行。帮助、版本和参数校验失败不会触发 sudo。
提权继续使用当前 Ruby 的绝对路径、当前入口及已选运行依赖,不依赖 sudo 的 PATH,也不使用 sudo -E。密码完全由 sudo 处理,tracepath 不读取、保存或转发密码。目标文件和 stdin 只由原用户读取一次,已校验目标作为独立参数传递;不会提权后重读文件,也不会在已开始探测后重放任务。
没有控制终端时使用 sudo -n,仅在现有授权允许时继续,不会等待密码;sudo 拒绝时保留其退出状态和 stderr,stdout 不生成探测结果。自动化调用可用 --no-sudo 关闭重启,直接获得库的权限错误及 JSON 结果。Tracepath.trace、trace_many 和默认的程序化 CLI.new 不会请求提权;只有命令行入口显式启用此策略。自动提权不会修改 sudoers、系统能力或设备权限。
目标文件每行一个目标,去除行首尾空白后忽略空行和以 # 开头的整行注释,不支持行内注释;--targets-file - 从 stdin 读取。文件和位置参数按命令行出现顺序合并,重复目标保留。文本模式单目标先显示解析后的目标地址、协议和实际 IP 包长,再按探针顺序逐项输出。等待期间先显示当前跳号;同一响应地址只打印一次,地址发生变化时另起缩进行。批量按目标完成顺序输出。
例如,同一跳三个探针均收到同一地址的响应时:
tracepath to example.com (192.0.2.9), UDP, 30 hops max, 60 byte packets
1 192.0.2.1 0.282 ms 0.372 ms 0.272 ms
2 * * *
乱序回包按探针索引排列,后面的结果会等待前面的探针结束后一起显示。--reverse-dns 在整跳完成、有界 PTR 查询结束后显示名称,不会让 DNS 阻塞网络收发。Ctrl-C 保留已取得的 RTT,换行并显示一次 Trace interrupted;未完成探针不会变成星号或重复的取消列,结构化结果仍保留全部取消记录。
文本中的目标、PTR 名称和诊断信息会把终端控制字符显示为 \xNN,避免远端名称改变终端状态;这不修改原始观测。若尚无终止性网络证据,最后一跳 PTR 查询期间的取消或整体截止也会成为 stop_reason;已有终止性证据继续保留其原终止原因。
默认每跳最多并行 3 个探针、每探针超时 3 秒;macOS 系统 traceroute 默认依次探测、每探针超时 5 秒,因此两者出现后续星号的时间不同。需要依次观察每次超时时可用 --max-inflight 1 -w 5。静默跳不会自动终止探测,也不表示上一跳就是目标。
程序化单目标进度可通过 Tracepath.trace(host, on_event: callback) 获取;原有 block 仍只接收完成的 Hop。callback 在调用线程执行,事件为冻结的 Hash:
event[:type] |
其余字段 | 时机 |
|---|---|---|
:trace_started |
target、options、packet_size
|
完成目标解析并打开 transport 后 |
:hop_started |
ttl |
开始探测该跳时 |
:probe_finished |
result(ProbeResult) |
单个探针收到响应、超时、发送失败或被取消时 |
探针事件按完成顺序送达,使用 event[:result].probe.index 恢复探针顺序。事件中的探针不包含后续 PTR 查询名称;带名称的结果由原有 Hop 回调提供。回调抛出的异常会取消并等待网络线程清理后重新抛出。
--json 单目标输出一个 Result,多目标输出一个 BatchResult。--jsonl 每完成一个目标输出一行 {"index": 0, "result": {...}},没有额外汇总行;它适合长批次流式消费。机器输出使用 stdout,诊断使用 stderr。--json 与 --jsonl 不能同时指定。
文本不可达标记包括 !N(网络)、!H(主机/地址)、!P(协议)、!X(管理策略)、!S(源路由/范围)和 !F(包过大/需要分片)。完整 ICMP type/code 以结构化探针结果为准。
退出码:所有目标到达且无故障为 0;存在未到达结果为 1;配置错误或运行故障为 2,包括已到达但携带后续 error 的结果;探测期间取消/中断优先为 130。SIGINT 会请求取消并输出已取得的部分结果,然后恢复原信号处理器。sudo 认证阶段尚未开始探测,认证拒绝或取消保留 sudo 自身的退出码。
验证
bundle exec rake test
gem build tracepath.gemspecCI 配置覆盖 Linux/macOS 与 Ruby 3.4/4.0 的离线测试和 gem 构建。已执行的本地矩阵、Linux 实际网络和安装结果见 验收记录。离线报文、时钟与传输替身测试不能证明真实内核、BPF 权限或外网路由已经验收。实际网络验证需要主动执行 平台说明中的测试步骤,并保留成功、失败和未覆盖项。
设计参考
- Trippy(Rust)的 Probe 模型 将发送、等待和完成状态分开,支持将单次探针作为独立证据保存。本项目据此明确区分超时、取消和发送错误。
- NextTrace(Go)的 trace 模型 包含逐跳结果与实时输出边界。本项目将完成的 Hop 和 Result 用于回调,让展示与网络执行分别承担职责。
- Scapy(Python)的 TCP traceroute 展示多目标和并行探测能力。本项目针对运维批次采用有界目标并发,并逐 TTL 收敛结果,以便及时停止后续探测。
- iputils(C)的 tracepath 展示 Linux 错误队列的使用。本项目在 Linux UDP 后端使用相同内核接口,将平台机制限制在传输层。
- Apple traceroute 的输出循环 在响应地址变化时打印地址,并逐探针刷新 RTT 或超时;文本呈现参照这一行为,同时保留本项目的并行调度与结构化证据。
发布与许可
项目采用 MIT 许可,见 LICENSE。标签触发 RubyGems OIDC 可信发布, 不保存长期 API key。完整离线矩阵通过后发布同次 CI 的已验证包,流程见 发布说明。