traceroute & tracert

Traceroute(路由追踪)的原理及实现

traceroute 和 tracert 是Linux和Windows平台下用于追踪网络设备距离远近的工具,向目标设备发包,从 TTL=1 开始向外发包,逐渐增加 TTL 的值,直到目标主机。在介绍traceroute和tracert的原理之前,需要了解几个技术名词:

IP,协议是TCP/IP协议族中最核心的部分,它的作用是在两台主机之间传输数据,所有上层协议的数据(HTTP、TCP、UDP等)都会被封装在一个个的IP数据包中被发送到网络上。

ICMP,即 Internet Control Message Protocol,互联网控制报文协议,它常用于传递错误信息,ICMP协议是IP层的一部分,它的报文也是通过IP数据包来传输的。

TTL,即 time-to-live,是IP数据包中的一个字段,它指定了数据包最多能经过几次路由器。从我们源主机发出去的数据包在到达目的主机的路上要经过许多个路由器的转发,在发送数据包的时候源主机会设置一个TTL的值,每经过一个路由器TTL就会被减去一,当TTL为0的时候该数据包会被直接丢弃(不再继续转发),并发送一个超时ICMP报文给源主机。

实现方案

tracert (windows)只支持基于 ICMP 报文发送,而 traceroute(Linux/Mac) 支持多种报文协议(UDP、ICMP、TCP)的发送,但不带任何选项(Options)时默认使用的是UDP。(具体参数和选项自行请自行 man 以查看手册)

下方所示图片分别为 tracert (windows)的帮助手册,以及在traceroute(Linux)上分别使用三种协议追踪 baidu.com 的结果截图。后续仅对UDP及ICMP追踪做较为详细的说明。

tracert的帮助说明

使用 TCP 报文的 traceroute:

基于TCP报文的traceroute

使用 ICMP 报文的 traceroute:

基于ICMP报文的traceroute

使用 UDP 报文的 traceroute:

基于UDP报文的traceroute

基于UDP实现

在基于UDP的实现中,客户端发送的数据包是通过UDP协议来传输的,使用了一个大于 30000 的端口号,服务器在收到这个数据包的时候会返回一个端口不可达的ICMP错误信息,客户端通过判断收到的错误信息是TTL超时还是端口不可达来判断数据包是否到达目标主机,具体的流程如图:

image-20211022085752243

实现流程

  1. 客户端发送一个TTL为 1 ,端口号大于 30000 的UDP数据包,到达第一站路由器之后TTL被减去 1 ,返回了一个超时的ICMP数据包,客户端得到第一跳路由器的地址。
  2. 客户端发送一个TTL为 2 的数据包,在第二跳的路由器节点处超时,得到第二跳路由器的地址。
  3. 客户端发送一个TTL为 3 的数据包,数据包成功到达目标主机,返回一个端口不可达错误,traceroute结束。

Linux和macOS系统自带了一个traceroute指令,可以结合Wireshark抓包来看看它的实现原理。首先对百度的域名进行traceroute:traceroute www.baidu.com,每一跳默认发送三个数据包,我们会看到下面这样的输出:

image-20211022085720428

对该域名的IP:115.239.210.27进行traceroute,此时Wireshark抓包的结果如下:

image-20211022085739900

抓包结果

注意看红框处的内容,跟第一张图对比,可以看到traceroute程序首先通过UDP协议向目标地址115.239.210.27发送了一个TTL为1的数据包,然后在第一个路由器中TTL超时,返回一个错误类型为Time-to-live exceeded的ICMP数据包,此时我们通过该数据包的源地址可知第一站路由器的地址为10.242.0.1。之后只需要不停增加TTL的值就能得到每一跳的地址了。

然而一直跑下去会发现,traceroute并不能到达目的地,当TTL增加到一定大小之后就一直拿不到返回的数据包了:

image-20211022085834275

结果全是丢失,其实这个时候数据包已经到达目标服务器了,但是因为安全问题大部分的应用服务器都不提供UDP服务(或者被防火墙挡掉),所以我们拿不到服务器的任何返回,程序就理所当然的认为还没有结束,一直尝试增加数据包的TTL。

目前在网上找到许多开源iOS traceroute实现大多都是基于UDP的方案,实际用起来并不能达到想要的效果,所以我们需要采用另一种方案来实现。

基于ICMP实现

上述方案失败的原因是由于服务器对于UDP数据包的处理,所以在这一种实现中我们不使用UDP协议,而是直接发送一个ICMP回显请求(echo request)数据包,服务器在收到回显请求的时候会向客户端发送一个ICMP回显应答(echo reply)数据包,在这之后的流程还是跟第一种方案一样。这样就避免了我们的traceroute数据包被服务器的防火墙策略墙掉。

采用这种方案的实现流程如下:

image-20211022085752243

实现流程

  1. 客户端发送一个TTL为1的ICMP请求回显数据包,在第一跳的时候超时并返回一个ICMP超时数据包,得到第一跳的地址。
  2. 客户端发送一个TTL为2的ICMP请求回显数据包,得到第二跳的地址。
  3. 客户端发送一个TTL为3的ICMP请求回显数据包,到达目标主机,目标主机返回一个ICMP回显应答,traceroute结束。

可以看出与第一种实现相比,区别主要在发送的数据包类型以及对于结束的判断上,大体的流程还是一致的。