traceroute命令原理

读万卷书,行万里路。

—— 佚名

一个 IP 包需要经过一系列路由的转发,才能到达目的地。下图是一个典型的例子,一个从主机 ant 出发,去往主机 apple 的 IP 包,需要经过中间路由 R1 、R2 以及 R3 :

traceroute命令

分析网络问题、排查网络故障时,我们经常需要知道:去往某个 IP 的数据包需要经过哪些路由?那么,有办法将通往某个 IP 的路由路径找出来吗?

其实是有的,traceroute 命令就可以做到。traceroute 命令是一个很常用的网络工具,你可能已经听过,甚至已经用过了。它可以用来探测、跟踪去往某个目的地 IP 包需要经过的路由路径。

那么,traceroute 命令该如何使用呢?我们以上图中的拓扑为实验环境,简单演示下它的用法。实验环境同样由 Docker 容器提供,只需执行下面这个 docker 命令即可一键打开:

1
docker run --name traceroute-lab --rm -it --privileged --cap-add=NET_ADMIN --cap-add=SYS_ADMIN -v /data fasionchan/netbox:0.5 bash /script/routine.sh

实验环境打开后,自动进入主机 ant 。我们先 ping 一下主机 apple ,确认网络是通的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@ant [ ~ ]  ➜ ping -n 10.0.2.2
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=1 ttl=61 time=0.082 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=61 time=0.207 ms
64 bytes from 10.0.2.2: icmp_seq=3 ttl=61 time=0.219 ms
64 bytes from 10.0.2.2: icmp_seq=4 ttl=61 time=0.218 ms
^C
--- 10.0.2.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3111ms
rtt min/avg/max/mdev = 0.082/0.181/0.219/0.057 ms

接下来,执行 traceroute 命令探测从到主机 apple 的网络路径:

1
2
3
4
5
6
root@ant [ ~ ]  ➜ traceroute -n 10.0.2.2
traceroute to 10.0.2.2 (10.0.2.2), 30 hops max, 60 byte packets
 1  10.0.1.1  0.771 ms  0.717 ms  0.603 ms
 2  10.2.0.1  0.573 ms  0.538 ms  0.516 ms
 3  10.4.0.1  0.487 ms  0.456 ms  0.437 ms
 4  10.0.2.2  0.406 ms  0.340 ms  0.303 ms

-n 选项表示,输出结果不用将 IP 地址反解析成主机名(域名)。由于域名解析可能有很大延迟,因此加上 -n 选项可以减少卡顿。很多网络工具都支持 -n 选项,ping 命令也是。

从 traceroute 命令的输出,我们可以获悉去往 10.0.2.2 时,需要经过 4 跳:

  • 第 1 跳是 10.0.1.1 ,即路由器 R1 ;
  • 第 2 跳是 10.2.0.1 ,即路由器 R2 ;
  • 第 3 跳是 10.4.0.1 ,即路由器 R3 ;
  • 最后一跳是目的地 10.0.2.2 ,即主机 apple 本身;

traceroute原理

你可能会很好奇,traceroute 是如何探测路由路径的呢?它是使用了什么黑科技吗?其实并没有什么黑科技,它的原理很简单:利用 IP 包 TTL 特性来完成路径探测。

IP 包每经过一跳路由, TTL 减一;当 TTL 减到零,路由器便将它丢弃。这样可避免 IP 包因陷入路由环路而在网络中永远存在。

路由器在将超时包丢弃的同时,负责向源 IP 发送一个 ICMP 报文,报告 传输超时time to live exceeded in transit )错误,ICMP 类型为 11

原包进入路由时 TTL 等于 1 ,路由转发前将 TTL 减 1 , TTL 便降为 0 了。因此对路由而言,入站包 TTL 为 1 即视为超时。

注意到,路由还将原 IP 包的头部以及数据的前 8 个字节作为数据附在 ICMP 报文中。发送者只需检查这部分数据,即可获悉超时包的上下文信息。

这样一来,ant 发一个 TTL=1 的 IP 包给 apple ,然后等待第一跳路由的超时差错,不就探测到第一跳路由了吗?

以此类推,ant 发 TTL=2 的 IP 包可以探测到第二跳路由,最终探测到 apple 本身。

问题来了,当 traceroute 收到路由发来的 ICMP 超时差错后,如何判读该路由是第几跳呢?问题答案就藏在 ICMP 差错报文中附带的原包头部中。

traceroute 发出的探测包,可以是一个 ICMP 回显请求,也可以是一个 UDP 请求。它默认使用兼容性更好的 UDP ,因为不少系统和网络都封杀了 ICMP 回显相关报文。

UDP 我们在后续章节才学习,因此这里先以 ICMP 为例进行讲解,但思路都是相通的。与 ping 命令类似,traceroute 通过 ICMP 报文中的 序号 字段来匹配探测包。

实际上,traceroute 默认会连续发 3 个 TTL=1 的探测包( ICMP 回显请求),再连续发 3 个 TTL=2 的探测包,以此类推。发包的同时,它在内存中建立一张从序号到 TTL 的映射表:

序号 TTL
1 1
2 1
3 1
4 2
5 2
6 2

探测包 TTL 耗尽后,路由将它丢弃,并向发送者报告 ICMP 超时差错。traceroute 收到超时差错后,取出原探测包的头部信息。然后根据序号查出该包发出去时的 TTL ,也就知道该差错是第几跳路由发出的。

下图是一个简单的例子:

  1. traceroute 发出第五个探测包,序号为 5 , TTL 为 2 ,并将对应关系保存在映射表;
  2. 该探测包是一个 ICMP 回显请求,类型为 8 ,目的地址 10.0.2.2 为探测目标 apple ;
  3. 该包走到第二跳路由 R2 时,TTL 就耗尽了,路由 R2 向原包发送方报告超时差错;
  4. 路由将原探测包的 IP 头部和数据前 8 字节( ICMP 头部)作为数据附在 ICMP 差错报文中;
  5. 差错包到达主机 ant 后,traceroute 取出差错包中附带的原探测包的头部;
  6. 检查原探测包 ICMP 头部中的标识符字段,traceroute 确认原探测包是自己发的(区分其他进程);
  7. 根据原探测包 ICMP 头部中的序号字段,traceroute 从映射表中查到原探测包的 TTL 为 2;
  8. traceroute 确定该差错包是第 2 跳路由发来的,源地址 10.2.0.1 就是路由的地址;

扩展阅读

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: