traceroute 是一个很常用的工具,用来检查当前设备到目的 IP 地址的路径以及每个中间设备产生的延迟。如下图所示:
traceroute to baidu.com (110.242.68.66), 64 hops max, 52 byte packets
1 10.43.244.2 (10.43.244.2) 4.132 ms 2.294 ms 2.683 ms
2 10.41.0.217 (10.41.0.217) 3.178 ms 1.846 ms 1.686 ms
3 10.40.0.54 (10.40.0.54) 2.554 ms 2.033 ms 2.174 ms
4 10.42.0.69 (10.42.0.69) 4.058 ms 2.905 ms 2.957 ms
5 10.42.0.54 (10.42.0.54) 3.304 ms 3.058 ms *
6 10.42.0.20 (10.42.0.20) 3.205 ms 3.199 ms 3.086 ms
7 14.17.22.130 (14.17.22.130) 4.497 ms 3.424 ms 3.195 ms
8 10.162.89.97 (10.162.89.97) 10.903 ms 4.797 ms 4.136 ms
9 10.200.52.57 (10.200.52.57) 5.684 ms
10.200.52.65 (10.200.52.65) 5.288 ms
10.200.52.73 (10.200.52.73) 7.327 ms
10 * * *
11 * * *
在 mac/linux/windows 下都有类似的工具。因为在 https://github.com/joyme123/gnt 中实现了 traceroute 的能力,这里记录一下。
traceroute 的实现中,利用了一些基本的网络协议的特性:
- IP (网络层)数据包在网络中传输时,可以通过 TTL 字段来控制这个数据包的生命周期。每经过一个三层设备的转发,这个 TTL 都会减少1,当 TTL 变为 0 时,三层设备就会丢弃这个数据包而不是继续转发它。这样的设计可以避免网络链路形成环时,数据包会被无限的转发。
- 中间的三层设备在丢弃数据包时,会使用 ICMP 协议,向数据包的源发送方(通过 src ip)发送 ICMP 包,来告知数据包因为 TTL 为 0 而被丢弃。当然有的三层设备不会发送这个 ICMP 消息,所以 traceroute 时部分中间环节会显示 *。并且这种 ICMP 包的 payload 都会包含源数据包的二层和三层 header。
- traceroute 支持多种协议: TCP、UDP 和 ICMP。当 TCP, UDP 的包到达目标设备后,如果 TCP, UDP 的目的端口不能访问,那么目标设备也会通过 ICMP 消息向源设备告知该端口不可达。如果是使用 ICMP echo request,那么目标设备会返回 ICMP echo reply 向源设备告知收到了 echo request。
有了以上的网络协议特性,traceroute 的实现可行性就有了。以使用 TCP 协议为例,具体的步骤如下:
- traceroute 构造 TCP 数据,源端口根据当前进程 ID 生成,目的端口选择一个不太可能使用的端口,比如 33434。
- 源端口根据当前进程 ID 生成,这样收到中间网络设备的 ICMP 包时,就可以通过 payload 中携带的源数据包信息判断出这个 ICMP 是响应哪个进程的。
- 目的端口选择一个不常用端口,是防止对目的端的 TCP 服务产生影响。并且这个目的端口每次请求都会加1。
- traceroute 构建 IP 头,TTL 一开始设置为 1。这样第一个中间设备收到后,就会丢弃这个包,并返回 ICMP 包了。后续 TTL 逐渐加 1,就可以探测到每一个中间设备了。
- 当 traceroute 收到 ICMP 包时,先根据源端口判断这个包属不属于当前进程,再根据目的端口判断这个包是第几个发出的。比如目的端口是 33436,那么已知起始目的端口是 33034 的情况下,就知道这个包是第三个发出的。这样根据第三个包发出的时间,就知道延迟情况了。
- 如果这个 ICMP 包是 ttl exceeded,说明中间网络设备返回的。
- 如果这个 ICMP 包是 destination unreachable,说明是目的设备返回的。