Project

tracepath

0.0
The project is in a healthy, maintained state
IPv4/IPv6 UDP, ICMP and TCP SYN traceroute with immutable observations, cancellation, and structured batch results.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 1.1, < 2
>= 2.9, < 4
>= 0.6, < 1
 Project Readme

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.counts

Request 的配置覆盖批量公共配置。最多同时执行 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.gemspec

CI 配置覆盖 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 的已验证包,流程见 发布说明。