返回首页

Rust 上的 Traceroute:路由追踪如何工作

本文分解了 Rust 上的 traceroute 实现。它展示了 TTL 和 ICMP 消息如何构建路由地图。提供了 raw sockets 操作和网络数据包处理的解释代码。

traceroute 如何构建路由:Rust 上的技术分解
Advertisement 728x90

Traceroute 工作原理:用 Rust 从零实现

Traceroute 是一种网络诊断工具,用于揭示数据包穿越互联网的路径。它依赖于两个关键机制:IP 报头中的 TTL(生存时间)和 ICMP 错误消息。通过逐步递增 TTL,路由器会在数据包生存时间耗尽时发送通知,从而构建路由图。用 Rust 只需 80 行代码实现自己的版本,就能深入协议,让底层网络操作变得清晰明了。

UDP 探测架构

Traceroute 的核心元素是发送带有受限 TTL 的目标数据包。选择 UDP 而非 TCP 有三个原因:

  • 无需握手,减少开销
  • 无投递保证——数据包本就设计为被丢弃
  • 高端口号(从 33434 开始)可最小化与运行服务的冲突

代码初始化两个套接字:

Google AdInline article slot
let send_sock = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
send_sock.set_ttl_v4(ttl)?;

let recv_sock = Socket::new(
    Domain::IPV4,
    Type::from(libc::SOCK_RAW),
    Some(Protocol::ICMPV4),
)?;

第一个套接字发送带有指定 TTL 的注定失败数据包,第二个套接字拦截 ICMP 响应。重要提示:原始套接字需要 root 权限,因为它们在网络栈层级运行。

解析 ICMP 响应

当路由器在 TTL=0 时丢弃数据包,会发送类型 11(时间超过)的 ICMP 消息。其数据结构包含:

  • 前 20 字节——响应的 IP 报头
  • 字节 12-15——路由器的 IP 地址
  • 字节 20——ICMP 消息类型

初始实现仅解析 IP 地址:

Google AdInline article slot
if buf.len() >= 20 {
    let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]);
    Ok(Some(ip))
}

这导致检测端点时出错。正确处理需检查 ICMP 类型:

match buf[20] {
    11 => Ok(ProbeResult::Hop(ip)),
    3 if ip == target => Ok(ProbeResult::Reached(ip)),
    3 => Ok(ProbeResult::Hop(ip)),
    _ => Ok(ProbeResult::Timeout),
}

类型 3(目标不可达)仅当 IP 匹配时才表示已到达目标。

Traceroute 优化

原始 traceroute 使用了基本实现中缺少的两种技术:

Google AdInline article slot
  • 递增端口号——后续每个数据包发送至端口 +1。这通过 UDP 报头中的 Identification 字段,实现 ICMP 响应与请求的无歧义匹配。
  • TCP 模式支持——当防火墙阻挡 UDP 时,使用低 TTL 的 SYN 数据包。跳跃检测机制保持不变。

早期代码版本的一个关键错误——接收类型 3 时忽略 IP 地址检查。没有 if ip == target 条件,追踪会在第一个返回目标不可达的路由器处停止。

重要要点

  • TTL 作为控制工具——逐步增加数值允许依次发现每个跳跃
  • ICMP 消息——诊断基础——时间超过(类型 11)和目标不可达(类型 3)构成路由图
  • 原始套接字需要权限——无 root 权限无法进行低级网络操作
  • UDP 与 TCP——协议选择影响防火墙规避,但不改变基本算法
  • 解析二进制数据——直接访问数据包字节需了解 IP/ICMP 报头结构

实现停止条件

关键改进——正确检测何时到达目标。ProbeResult 枚举替换了简单的 Option<Ipv4Addr>

enum ProbeResult {
    Hop(Ipv4Addr),
    Reached(Ipv4Addr),
    Timeout,
}

主循环中结果处理如下:

match hop {
    ProbeResult::Hop(ip) => println!("{:>2}  {}", ttl, ip),
    ProbeResult::Reached(ip) => {
        println!("{:>2}  {}", ttl, ip);
        break;
    }
    ProbeResult::Timeout => println!("{:>2}  *", ttl),
}

这确保追踪在到达目标主机时停止,而非超时。在实际场景中,还需添加:

  • 每个 TTL 发送三个探测以统计延迟
  • 处理分片数据包
  • IPv6 支持
  • 基于距离的超时

总结与限制

所构建的实现展示了 traceroute 的核心,但存在局限:

  • 未考虑非对称路由
  • 未处理 ICMP 速率限制
  • 忽略现代网络中的 MPLS 标签
  • 需要超级用户权限

对于生产解决方案,使用如 pnet_packet 等库避免手动字节解析。然而,手写原始版本是理解网络基础的最佳途径。每位网络工程师至少应实现一次 traceroute,以把握数据如何穿越互联网。

— Editorial Team

Advertisement 728x90

继续阅读