
上周在 Hacker News 上看到一个项目叫 Hopsum。标题翻译过来是“让路由器用过期数据包做算术”。第一眼看到这个描述我有点懵。路由器过期数据包算术这几个词组合在一起听起来像是某种网络协议的边缘实验或者是一个极客的周末玩具。但点进去仔细看了讨论和代码我发现它的核心想法其实非常巧妙而且触及了一个我们平时很少会去思考的网络底层问题那些在网络中“迷路”或“过期”的数据包它们的最终命运是什么我们通常认为它们被路由器默默丢弃了就像垃圾被扫进垃圾桶。但 Hopsum 提出了一个有趣的问题在丢弃之前我们能不能让这些注定要消失的数据包最后再“发挥一点余热”这个项目本质上是一个概念验证它利用 IPv4 数据包中的TTLTime To Live字段和ICMPInternet Control Message Protocol协议构建了一个分布式的、由网络路径上的路由器共同完成的“加法器”。它不传输有效数据而是用数据包的生命周期本身来传递信息。这听起来很抽象但理解它能帮你从一个全新的角度看待网络协议栈的设计哲学和那些“废弃”字段的潜在价值。1. 核心谜题如何用“死亡”传递信息要理解 Hopsum我们得先抛开“传输文件”或“访问网页”这种常规网络用途。想象一下你有一群信使数据包但他们的任务不是送信而是在旅途中“牺牲”自己并通过牺牲的方式告诉终点站一个数字。这听起来很浪费但关键在于这个“牺牲”的过程是网络固有的、必然发生的。Hopsum 的聪明之处在于它没有创造新的牺牲而是利用了网络中本就大量存在的、因 TTL 耗尽而“牺牲”的数据包。TTL 是什么简单说它是 IP 数据包头里的一个 8 位字段值范围是 0-255。数据包每经过一个路由器跳TTL 值就减 1。当 TTL 减到 0 时当前路由器就不会再转发这个包而是会将其丢弃并通常向数据包的源地址发送一个ICMP Time Exceeded消息。这是为了防止数据包在网络中无限循环。Hopsum 的核心把戏就在这里发送端构造一批特殊的 ICMP Echo Request也就是ping请求数据包。每个包的 TTL 被精心设置成一个很小的值比如 1, 2, 3...确保它们在到达特定跳数的路由器时TTL 刚好减到 0。路由器端当这些包的 TTL 耗尽时沿途的路由器会按照协议规定丢弃它们并返回 ICMP Time Exceeded 消息给发送者。接收端也是发送端发送者监听这些返回的 ICMP Time Exceeded 消息。关键来了——Hopsum 在原始发出的 ICMP Echo Request 数据包的标识符Identifier字段里编码了需要计算的信息一个数字。当路由器返回 ICMP Time Exceeded 消息时这个消息里会包含原始 IP 包的头部包括标识符字段。于是发送者通过接收不同跳数路由器返回的、包含不同标识符的 ICMP 消息实际上完成了一次分布式的信息收集。如果把每个标识符看作一个加数那么发送者最终收集到的就是这些加数的“和”。而这个“和”的计算过程是由路径上多个路由器通过“丢弃数据包并回复”这一标准动作协同完成的。为什么说这是“算术”因为你可以控制发送多少个包每个包携带一个数字以及让它们在哪些跳数上“牺牲”。接收端统计来自不同跳数的回复就能还原出一个累加的结果。项目名 “Hopsum” 就是 “Hop”跳和 “Sum”和的组合非常贴切。2. 技术拆解IPv4 头部的“边角料”如何变废为宝Hopsum 没有使用任何非标准协议或修改路由器固件。它完全在标准协议框架内操作这既是其优雅之处也限制了其能力。我们来拆解它具体利用了哪些字段和协议。2.1 舞台主角IPv4 报文头一个 IPv4 数据包头部有多个字段Hopsum 主要利用了其中两个字段常规用途Hopsum 中的特殊用法TTL (Time To Live)防止环路限制数据包生存周期。被用作“定时引爆器”。通过精确设置 TTL 值如 1, 2, 3控制数据包在哪一跳路由器“死亡”。Identification (标识符)用于分片数据包的重组。对于未分片的包这个值通常任意。被用作“数据载体”。在这个 16 位字段里编码需要传递的数值加数。注意这里有一个精妙的细节。ICMP Time Exceeded 消息在返回时会包含触发它的原始 IP 包的头部前 8 个字节。这 8 个字节正好包含了 IP 头部的版本、长度、服务类型、总长度、标识符、标志位和片偏移字段。也就是说标识符字段被原封不动地“带回来”了。这正是 Hopsum 能实现信息传递的基础。2.2 信使协议ICMP 的双重角色ICMP 在这个系统里扮演了两个角色触发工具 (ICMP Echo Request)Hopsum 发送的是 ICMP Echo Request 包类型 8代码 0。选择它是因为它极其常见几乎所有主机和路由器都会处理并在 TTL 有效时响应 Echo Reply。更重要的是它结构简单易于构造。回传通道 (ICMP Time Exceeded)当 TTL0 时路由器丢弃原始包并返回 ICMP Time Exceeded 消息类型 11代码 0。这个消息是协议规定的标准行为是 Hopsum 机制得以成立的保障。2.3 工作流程与代码逻辑从发送者的视角看一次 Hopsum “计算”的流程如下定义计算任务比如想计算 5 10 15。编码与发送构造三个 ICMP Echo Request 包。将加数5, 10, 15分别放入这三个包的 IP 标识符字段。设置这三个包的 TTL 分别为 1, 2, 3目标是同一个远程主机。依次或同时发送这三个包。监听与接收开启原始套接字监听所有到来的 ICMP 包。过滤出 ICMP Time Exceeded 消息类型 11。从这些消息中解析出“原始 IP 头”并提取出里面的标识符字段。解码与求和记录下每个标识符值。因为知道发送顺序或通过其他方式关联将收到的标识符值相加得到结果 30。下面是一个高度简化的概念性代码逻辑帮助你理解# 伪代码展示核心逻辑 import socket import struct def send_hopsum_packet(destination_ip, ttl, identifier): # 创建原始套接字用于构造和发送ICMP包 sock socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 设置IP头部的TTL sock.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) # 构造ICMP Echo Request包 (类型8代码0) # 并在构造时将identifier填入IP标识符字段这需要手动构建IP头 icmp_packet construct_icmp_echo_request(identifier) # 发送到目标 sock.sendto(icmp_packet, (destination_ip, 0)) def listen_for_time_exceeded(): # 创建另一个原始套接字用于接收所有ICMP包 recv_sock socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) while True: packet, addr recv_sock.recvfrom(1024) # 解析IP头获取协议类型 ip_header packet[:20] protocol ip_header[9] # IP头部的协议字段 if protocol ! socket.IPPROTO_ICMP: continue # 解析ICMP头 icmp_header packet[20:28] icmp_type, code struct.unpack(!BB, icmp_header[:2]) if icmp_type 11 and code 0: # Time Exceeded # 从ICMP消息体中提取原始IP头前8字节 original_ip_header packet[28:36] # ICMP Time Exceeded包含原始IP头 original_identifier struct.unpack(!H, original_ip_header[4:6])[0] print(f收到来自 {addr} 的Time Exceeded消息携带标识符: {original_identifier}) # 将这个identifier加入求和列表关键点实际的 Hopsum 实现需要处理很多细节比如序列号匹配、超时重发、去重、以及处理可能丢失的 ICMP 回复网络是不可靠的即使是错误消息。3. 这不是玩具理解其背后的网络原理与限制看到这里你可能会觉得 Hopsum 只是一个巧妙的“黑客”技巧没有实际用处。从直接的生产力工具角度看确实如此。它的计算效率极低发一个包才能传递一个16位数可靠性存疑依赖 ICMP 错误消息的返回而这类消息常被防火墙过滤。但它的价值在于教学和启发。3.1 它揭示了什么协议的“副作用”可以被利用网络协议在设计时有其首要目标如 IP 负责寻址和分片ICMP 负责错误报告。但它们的实现细节如错误消息包含原始头会创造出一些非主流的、可被创造性利用的“副作用通道”。Hopsum 就是这种思维的典型。网络本身可以视为计算单元这个实验模糊了“计算”和“通信”的边界。它暗示在网络中传递信息的过程本身可以嵌入一种极其原始的计算形式。这让人联想到更宏大的概念如“网络计算”或“在网计算”In-Network Computing尽管 Hopsum 的规模微不足道。理解故障排查工具traceroute工具的原理就是基于 TTL 递增和 ICMP Time Exceeded。Hopsum 可以看作是一个“反向”或“数据承载”版的 traceroute。深入理解 Hopsum能让你更透彻地明白traceroute的每一个步骤到底在发生什么。3.2 它面临哪些现实限制如果你想自己实验 Hopsum必须清楚以下障碍限制因素原因与影响应对思路实验时防火墙与安全策略许多网络设备或主机防火墙会过滤外来的 ICMP Echo Request或者更常见的是过滤外出的ICMP Time Exceeded 等错误消息。这会导致发送端收不到回复。在可控的内网环境测试或确保测试路径上的设备允许相关 ICMP 消息。网络地址转换 (NAT)如果发送端位于 NAT 之后从公网路由器返回的 ICMP Time Exceeded 消息可能无法正确路由回发送主机。最好在两端都是公网 IP或同一内网的环境下测试。ICMP 消息丢失ICMP 错误消息本身是尽力交付的没有重传机制。网络拥塞时可能丢失。Hopsum 实现中需要加入超时和重传逻辑并将结果视为“可能不准确”。标识符字段冲突IP 标识符字段本用于分片。系统内核在发送普通 IP 包时会动态分配该字段。如果 Hopsum 程序与其他应用同时运行可能发生冲突导致分片重组错误。这是一个需要严肃对待的风险。实验应在独立的、无其他重要网络流量的系统上进行或使用虚拟网络。实用性极低计算一个 N 个数的和需要发送 N 个数据包并等待最多 N 个回复。延迟和开销巨大。明确其概念验证属性不追求实用效率。重要警告在生产环境或任何重要网络环境中请勿随意运行此类发送大量非标准 TTL ICMP 包的工具。它可能被误判为网络扫描或异常流量触发安全警报。4. 从 Hopsum 出发我们能学到什么工程思维Hopsum 项目给我们带来的远不止一个网络技巧。它更像是一个思维训练展示了工程师如何深入理解系统、发现隐藏接口并创造性地组合它们。4.1 逆向利用“错误处理”流程标准的网络编程关注的是成功路径建立连接、发送数据、接收确认。而 Hopsum 关注的是错误路径数据包如何死亡以及系统如何报告这种死亡。它把错误报告通道ICMP Time Exceeded变成了一个反向数据通道。这种“变废为宝”的思路在很多领域都有体现日志分析系统错误日志不仅是用来排查问题的积累到一定程度可以用于行为分析或预测故障。性能监控超时和重传本身是问题指标但大量统计这些指标能描绘出网络质量图谱。安全领域某些攻击检测正是通过分析异常流量模式即“错误”或“非预期”行为来实现的。4.2 对协议字段的“超纲”理解我们学习 TCP/IP 时知道 IP 标识符用于分片TTL 用于防环。考试就考这些。但在 Hopsum 里标识符成了临时寄存器TTL 成了精准定时器。这要求我们不仅知道字段的标准用途还要思考它的实现特性如长度、取值范围、是否被带回错误消息、是否被中间设备修改。这种深度理解是进行底层网络编程、协议分析或安全研究的基石。4.3 建立“可编程网络”的直觉虽然 Hopsum 离真正的可编程网络如 P4、OpenFlow还很远但它给了我们一种直觉如果网络设备路由器的行为可以通过发送特定的数据包来触发并产生包含特定信息的响应那么网络就在某种意义上是“可编程”的。这里的“程序”就是数据包本身的结构和字段值。现代可编程数据平面正是将这种思想发挥到极致允许用户自定义数据包的处理逻辑。4.4 实验者的安全与伦理边界最后也是最重要的一点Hopsum 是一个完美的案例用来讨论技术实验的边界。它使用了合法的协议字段但组合方式非典型。在进行此类实验时必须遵循一个清晰的框架环境隔离永远在完全可控的、隔离的网络环境如虚拟机局域网、家庭实验室网络中进行。切勿在办公网、生产网或公共网络上测试。目标明确明确实验目的是学习原理而非探测或干扰他人网络。目标 IP 应为自己控制的主机或明确允许测试的主机。流量克制严格控制发送速率和数据包数量避免产生洪水攻击般的流量。结果分析对失败有预期收不到回复是常态并学会从失败中分析原因是防火墙是 NAT还是路由问题。Hopsum 可能永远不会成为一个实用的工具但它成功地做到了两件事第一它用一个极简的模型揭示了网络协议栈中一些有趣且不常被审视的角落第二它像一道精致的谜题挑战并拓展了我们对“网络通信”和“分布式计算”的固有认知。下次当你使用ping或traceroute时或许会想起在这些基础工具的背后隐藏着一个充满可能性的、等待被创造性解读的协议世界。而理解这个世界是从熟练使用工具迈向真正掌握技术的必经之路。