摘要

  • 在所检查的 Sagan traceroute 代码中,is_success 要求输入列表最后一跳存在被判为真值的 RTT,且对应包没有错误标志;它不要求该包来自目标地址。目标是否回复由另一项 destination_ip_responded 判断。
  • last_median_rtt 从输入跳列表反向寻找一个满足条件的中位 RTT。末跳没有回复时,它仍可能保留前面某一跳的数值,不能自动称为“到目标的时延”。
  • 本地使用原始类源码运行的二十一项既有测试和五组合成输入均通过,没有网络调用。这证明的是选定代码的解析行为,不是现场事故、消费者误用或应用服务的可用性。

把一条虚构的 traceroute 交给 Sagan:最后一跳只有来自路由器的一条回复,RTT 是十毫秒,地址并非目标。结果中,is_success 为 true,而 destination_ip_responded 为 false。

两个值没有互相否定。它们回答的原本就不是同一个问题。

这个例子没有向路由器发送探测包,也不描述某位客户遇到的故障。它使用文档用途的地址构造输入,目的是看清“成功”究竟需要满足什么条件。若把一个字段直接拿来判定目标可达,条件比字段名字更重要。

Sagan 是 RIPE NCC 用于解析 RIPE Atlas 测量结果的库。项目介绍说明,格式会变化,错误和历史版本又有许多边缘情况,因此把这些处理集中起来,让用户获得可用的 Python 对象,比各自反复解释 JSON 更方便。这是实在的价值。本次检查锁定提交 45d01f754896ad648e5b306c4209153bab774af9,提交日期为 2026-06-03;提交标识界定的是被检查的源码,不是已安装版本的清单。项目说明。

最后一跳被问了三次

源码中的三项判断,都从输入列表的最后一跳寻找答案,但寻找的东西不同。

set_destination_ip_responded 查看是否有一个包的来源地址等于结果中的目标地址。set_last_hop_responded 查看是否存在一个 RTT 被 Python 判为真值的包。set_is_success 则寻找这样一个 RTT,且包没有被标记为错误。第三项并没有再加上目标地址比较。traceroute 判断方法。

正常的目标回复可以同时满足三项条件。这也解释了为什么在简单示例里,几个标志看起来像同一件事的不同写法。换成另一个地址的正常回复,最后一跳回复和成功条件仍然可以成立,目标回复条件却不成立。库没有把这个路由器改认成目标,而是把不同条件分别返回。

反过来也一样。另一组合成输入中,回复来自目标地址,RTT 仍为十毫秒,但包带有 p 错误代码。目标回复标志与末跳回复标志为 true,is_success 为 false,末跳错误列表包含“Port unreachable”。错误状态没有抹掉来源地址和时延数值。基础类的错误处理。

因此,“某地址回复了”和“这个包没有错误标志”可以向两个方向分开。二者也都不是应用交易完成的证明。这里运行的是地址比较与包状态分类,不是发送者认证,更不是替客户登录系统、读取内容或执行所需业务。

还应区分成功解析与解析结果中的成功条件。五组合成输入都生成了结果对象,末跳沉默的例子同样如此;对象存在,并不要求三个末端标志都为真。若报告先把“有结果记录”当作成功,再把末跳条件当作目标回复,最后把目标回复当作业务完成,就连续跨过了几道边界。这不是本次发现的某个消费者行为,而是报告设计应避免的含义替换。汇总前应先说明分子究竟计算什么、哪些输入进入分母。五组虚构输入不能给出真实成功率,更不能据此估计一个运营商的服务表现。

库已经给出重要的反证

不能据此指责 Sagan 不提供目标回复信息。destination_ip_responded 已经存在。类型文档把它与 last_hop_responded、is_success、末跳错误一起列出,使用者有条件选择更准确的问题。公开的属性说明。

这项现有能力,是任何批评都不能略去的正面证据。源码、文档与既有测试并不把所有结果塞进一个不可拆解的结论。相反,库提供了分开的组件。需要警惕的是组件离开库以后,报告是否仍然保留它们的含义。

假如某个消费者只保存 is_success,再把列名改成“目标可达”或“服务可用”,就会引入一次含义替换。不过,本次研究没有观察到某个具名产品这样做,也没有测量这种做法的普遍程度。这里讨论的是可能的影响机制,不能升级成对真实消费者的指控。

便利的名字可以帮助使用接口,却不能代替阅读判定条件。三项标志同时为真时,区分它们似乎多余;不同时为真时,区别往往正是下一步排查最需要的信息。

十毫秒可以来自不同位置

RTT 的选择还带来另一条边界。每个 Hop 根据保留下来的包 RTT 计算中位数。结果解析器再反向扫描输入列表,找到第一个被判为真值的跳中位数,写入 last_median_rtt。这一步不要求对应包的地址等于目标。

一条三跳的合成输入中,第一跳路由器回复五毫秒,第二跳回复十毫秒,第三跳只有没有回复的标记。目标回复、末跳回复、成功三个标志都是 false,但 last_median_rtt 仍为十毫秒。

这不是程序同时宣布“没有回复”和“目标十毫秒”。前者来自末跳条件,后者来自反向扫描选中的响应跳。若把后者写成“目标时延”,就是报告添加了计算本身没有要求的终点。

同样的十毫秒,还出现在正常目标回复、非目标路由器回复,以及带错误标志的目标回复中。相同的大小,没有让地址、末端状态和包分类变得相同。对这些数值再取平均或中位数,也不会自动修复对象混淆。首先要问哪些观察被纳入、来自哪里、为什么可以放在一起。

本地例子使用通常的顺序排列跳列表。反向扫描是一条关于输入顺序的规则,不是对实际物理路径的独立重建。这些输入没有检验任意乱序、畸形记录或所有异常格式,也不能保证在所有情况下找到物理上最远的路由器。“最后”应保留它在算法里的参照,不该顺手借来“到达目标”的意思。

少解析对象,不等于删除原始输入

Sagan 提供 parse_all_hops=False,让不需要全部跳对象的用户减少对象构造。文档说明,这种选择可能使 hops 只保留最后一个 Hop。对于仅需要少数派生属性的用途,这是合理的效率取舍。

在前述三跳、末跳沉默的合成例子中,最小解析只保留索引为三的 Hop;它没有响应。派生的中位 RTT 仍为十毫秒,来自此前被扫描的响应跳。完整输入依然保存在 raw_data。这不是源数据被删除,而是原始输入、保留的对象与派生数值展示不同范围。

若一个导出程序只拿走 hops 和最后中位数,接收者可能看不到提供这个数值的已解析对象。保留原始输入,或明确记下所选跳的上下文,可以让后来的人解释数值。未必需要永久保存每个 Python 对象,但必须知道接收者将失去什么、又从哪里找回。

两个用户也可能从同一输入保留不同的对象集合。比较导出结果时,解析选项就有意义。某个跳没有出现在便捷对象列表中,不等于它没有出现在原始输入中;便捷列表里只有沉默末跳,也不等于一个派生值必然来自这个末跳。

检查了什么,没有检查什么

项目已有的二十一项 traceroute 测试在本地运行。所用基础类、兼容性代码和 traceroute 类都是固定提交的原始源码,在内存中加载,没有修改。二十一项均通过。随后五组合成输入检查了非目标末跳回复、正常目标回复、末跳沉默但前跳有 RTT、目标错误回复,以及最小解析下的末跳沉默。五组输出都符合预先列出的条件。检查禁止网络操作,没有任何调用网络的尝试。既有测试。

这比从字段名猜测更可靠,也比现场运行结论窄得多。它验证选定的类和输入,不能认证完整软件安装、控制器接纳规则、每个分发版本或所有生产者格式。它没有统计真实探针里这些状态出现多少次,没有找出报告误用者,也没有估算客户损失。

既有测试本身还是正面证据:项目早已显式关注不同回复条件。不能把一次限定的解析检查,写成“一个库一直不认真处理结果”的故事。准确的发现是条件之间有边界,而不是程序应该替用户作出一切业务判断。

从包到服务,中间还有距离

Atlas 的通用结果文档也区分地址角色。顶层 from 由测量系统补入,src 来自探针,二者可以合理地不同。traceroute 类中嵌套包的 from 则被用作该包的来源地址。顶层来源、响应包来源和目标地址不能因字段拼写相似,就视为同一个身份。Atlas 的地址角色。

即使嵌套包地址等于目标,这项字符串比较也不是发送者认证。它更不证明目标应用完成了用户需要的操作。地址层面的回复与应用层面的履约,需要不同的证据;检查器不该承担一个未被交给它的任务。

RFC 792 描述了 TTL 到期时的 ICMP Time Exceeded,以及网关可以通知源端的情形。转发设备的回复能为操作者提供有用信息,却不等于目标应用执行了一笔交易。反面也不能走得太快:没有回复,不必然确定发送路径已断,回复是否产生、能否回来以及如何被处理,都是其他需要调查的环节。ICMP 规范。

RFC 2330 区分单次观察、由观察组成的样本,以及由样本计算的统计量;Type-P 的命名提醒读者注意被测流量的类型。跳级 RTT、一条 traceroute 的分类、某服务在一段时间内的可用性,不会因为出现在同一张报告里就成为同一种指标。性能度量框架。

这不要求用户拒绝一个有用的数字,而是要求数字与对象一起留下来。响应跳的时延应带着响应跳上下文,末跳判断应带着各自条件。更大的结论,需要相应的终点、应用、时间样本与观察范围,而不是仅靠一项方便的布尔值。

资料来源