📝 术语别名
  • ICMP: Internet Control Message Protocol, IP 层的"控制/反馈"协议;
  • RTT: Round-Trip Time, 一来一回的总时延;
  • Echo Request / Echo Reply: ICMP type 8 / type 0, 就是 ping 用到的两种报文;

前置: ICMP 在协议栈中的位置

ICMP 经常被人当成"网络层之上的小协议", 但它实际上是 IP 协议的一个直接附庸, 跑在 IP 之上, 但属于 network layer 的控制平面;

  • IP 包的 header 里 Protocol 字段为 1 时, 上层就是 ICMP (对比 TCP=6, UDP=17);
  • ICMP 报文不走传输层, 没有端口号; 它的"复用"由 ICMP type + code 这两个字节自己完成;
  • ICMP 的存在意义是给 IP 这个 best-effort 协议一个反馈信道, 比如告诉 src “你这个包 TTL 跑光了”, “目标不可达”, “需要分片但 DF 置位了”;

所以 ICMP 本质上是 网络层在跨主机之间互相吐槽的协议, 它不保证可靠, 也不保证一定会被回复;

ICMP 报文结构

最小的 ICMP header 只有 8 bytes:

1
2
3
4
5
6
7
8
9
 0               1               2               3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+-------------------------------+
| Type | Code | Checksum |
+---------------+---------------+-------------------------------+
| Rest of Header (4 bytes) |
+---------------------------------------------------------------+
| Data ... |
+---------------------------------------------------------------+
  • Type: 报文大类, 例如 8 = Echo Request, 0 = Echo Reply, 3 = Destination Unreachable, 11 = Time Exceeded, 5 = Redirect;
  • Code: 在同一 type 下细化原因, 例如 type 3 下 code 0 = network unreachable, code 1 = host unreachable, code 4 = fragmentation needed but DF set;
  • Checksum: 覆盖整个 ICMP 报文 (不仅 header);
  • Rest of Header: 对 Echo 来说是 Identifier + Sequence Number, 这两个字段是 ping 用来配对请求和回复的关键;

下面这张表是 ping 平时最常碰到的几种 ICMP 类型:

Type Code 例子 含义 谁会发
0 0 Echo Reply 被 ping 的 host
8 0 Echo Request 发起 ping 的 host
3 0/1/3/4 Destination Unreachable 中间路由器或目标 host
11 0 Time Exceeded (TTL=0) 中间路由器
5 0/1 Redirect, 提示更优网关 路由器

ping 命令究竟在做什么

ping example.com 在 Unix 下做的事情按时序展开是这样的:

  1. 通过 DNS (应用层, UDP/53 或 TCP/53) 把域名解析成 IP; 这一步不算 ping 的延迟, 但很多新手会把 DNS 抖动算到 ping 头上;
  2. 进程构造一个 ICMP Echo Request, type=8, code=0, 填好 Identifier (一般是 PID) 和 Sequence Number (从 0 递增);
  3. 报文交给内核, 内核走 IP 层 (填 src IP, dst IP, TTL, 算 checksum), 再下沉到 link layer (ARP 查 MAC, 封以太网帧), 然后从 NIC 发出去;
  4. 中间每跳路由器只看 IP header 做转发, 不关心载荷是 ICMP 还是 TCP;
  5. 目标 host 收到包, 内核 IP 层根据 protocol=1 投递给 ICMP 模块, ICMP 模块识别 type=8 后直接构造一个 type=0 的 Echo Reply, 把原 payload 原样塞回去送回 src;
  6. src 内核收到 Echo Reply, 通过 Identifier + Sequence Number 配对, 计算从发出到收到的时间差, 这就是 RTT;

ping 不需要目标进程参与, Echo Reply 是目标 host 的 kernel 直接回的; 这是为什么 ping 通了不代表上层服务能通, 反过来 ping 不通也不一定代表服务挂了;

ping 测试的到底是哪部分网络

这是这篇笔记最想说清楚的事情, 因为很多 debug 场景下大家把 ping 当万能探针用, 其实它的覆盖面是很有限的:

ping 是否覆盖 说明
物理层 / 链路层 必须有线路 + ARP 能解析 MAC, 否则连第一跳都出不去
网络层 (IP 路由) ping 测的核心就是这一层的双向可达性
传输层 (TCP/UDP) ICMP 不走 TCP/UDP, 所以 TCP 端口被防火墙封了 ping 仍能通
应用层 服务进程死活与 ping 无关

具体一点说, ping 实际验证的是:

  • IP 层 forward path 能把包送到目标 host;
  • IP 层 reverse path 能把 reply 送回来;
  • 目标 host 的 kernel 没有完全屏蔽 ICMP;

所以 ping 是一个 L3 双向连通性 + RTT 探针, 它和 curl http://... 不在一个语义层级:

  • 单向丢包不会被 ping 准确反映, ping 看到的是两段路径合在一起的丢包率;
  • 现代 cloud / CDN / 企业网关经常会对 ICMP 做 rate limit 或静默丢弃, 导致 ping 抖动甚至完全不通, 但 TCP 业务一切正常; 这时 ping 的失败结果不能直接推论"网络坏了";
  • 反过来, ping 100% 通也不代表 TCP 三次握手没问题, 因为 stateful firewall 可能只放行 ICMP;

结论: ping 只是 IP 层的 sanity check, 不要拿它来证明应用层的健康;

RTT 延迟由哪些部分组成

ping 给出的那个 time=23.4 ms 数字, 是一个 包括去和回两段路径 的累加延迟, 把每一跳拆开看, 单跳延迟由四部分组成:

dhop=dproc+dqueue+dtrans+dpropd_{\text{hop}} = d_{\text{proc}} + d_{\text{queue}} + d_{\text{trans}} + d_{\text{prop}}

组成 物理意义 主要影响因素 量级直觉
Processing delay dprocd_{\text{proc}} 路由器/host 解析 header, 查转发表, 算 checksum 的时间 设备性能, 是否软件转发 μs\mu s
Queueing delay dqueued_{\text{queue}} 包在出端口缓冲队列里排队的时间 链路负载, buffer 占用 0 到几百 ms (拥塞时主导)
Transmission delay dtransd_{\text{trans}} 把包的所有比特推到线上的时间, =L/R= L / R 包大小 LL, 链路带宽 RR ping 默认 64B, 在 Gbps 链路上 ns 级
Propagation delay dpropd_{\text{prop}} 信号在介质中传播的时间, =d/v= d / v 物理距离 dd, 介质中光速 v2×108m/sv \approx 2 \times 10^8 m/s 跨洲一般 100ms+

整条 ping RTT 把去回两端所有跳累加起来:

RTT=i=1Nforwarddhop,i+dreply-gen+j=1Nreversedhop,j\text{RTT} = \sum_{i=1}^{N_{\text{forward}}} d_{\text{hop}, i}^{\rightarrow} + d_{\text{reply-gen}} + \sum_{j=1}^{N_{\text{reverse}}} d_{\text{hop}, j}^{\leftarrow}

其中 dreply-gend_{\text{reply-gen}} 是目标 host 内核构造 Echo Reply 那一下的开销, 一般 μs\mu s 级, 可以忽略, 但在目标主机被 CPU 打满或 ICMP rate-limit 排队时会显著抬高;

各部分的实际占比

  • 本机到同 LAN 设备: dpropd_{\text{prop}} 几乎为 0, RTT 几乎全是 dproc+dtransd_{\text{proc}} + d_{\text{trans}}, 0.x ms 级;
  • 跨城: dpropd_{\text{prop}} 开始上场, 北京到上海光纤约 1300 km, 单程 propagation 1.3×1062×108=6.5\approx \frac{1.3 \times 10^6}{2 \times 10^8} = 6.5 ms, RTT 物理下限就在 13 ms 左右;
  • 跨洲: 美西到东亚单程 propagation 大约 70-80 ms, RTT 下限 150 ms 左右; 这个值是光速级硬下限, 砸钱也降不了, 除非走更短的物理路径;
  • 拥塞场景: dqueued_{\text{queue}} 主导, ping 时延会从平时的 30 ms 跳到几百 ms, 而且方差极大 (mdev 字段会爆炸);
  • WiFi / 移动网络第一跳: 无线接入这一跳的 retransmission 和 contention 会让 dqueue+dtransd_{\text{queue}} + d_{\text{trans}} 浮动很大, 这就是为什么家里 ping 网关都能看到 1-50 ms 跳;

一些 ping 输出里值得读的字段

典型 Linux ping 输出:

1
2
3
4
5
64 bytes from 142.250.198.14: icmp_seq=1 ttl=115 time=23.4 ms
...
--- google.com ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9012ms
rtt min/avg/max/mdev = 22.871/24.103/26.512/1.054 ms
  • icmp_seq: Echo Request 的 Sequence Number, 用来发现丢包和乱序;
  • ttl=115: 这是回程包到达 src 时剩余的 TTL; 大多数 OS 默认初始 TTL 是 64/128/255, 用 初始值 - 当前值 可以反推回程经过了多少跳, 这里 128 - 115 = 13 跳是常见解读;
  • time=23.4 ms: 这一发包的 RTT;
  • mdev: mean deviation, 抖动指标; 这个比 avg 更值得盯, 抖动大说明路径上某段在排队;
  • 0% packet loss: 双向合计的丢包率, 不区分去丢还是回丢;

ping 的几个常见误用

  • 用 ping 判断带宽: 不行, ping 包小, dtransd_{\text{trans}} 几乎为 0, 它根本不在带宽瓶颈上跑;
  • 用 ping 判断业务健康: 不行, ICMP 经常被单独 rate-limit, 业务 TCP 才是真相;
  • 看到 RTT 抖动就归因为"网络坏了": 抖动可能来自目标 host CPU 打满导致 ICMP 回复排队, 不一定是路径问题; 想区分用 mtrtraceroute 看每跳的延迟分布;
  • 以为 ping 不通就 host 不在: 很多公网主机默认 drop ICMP, 比如 AWS 默认 SG 不放 ICMP 入站; 这种情况下 host 活得好好的, 只是不和你聊 ICMP;
📝 这一篇要带走的几句话
  1. ping 是一个 L3 层的双向可达性 + RTT 探针, 它走 ICMP 而不是 TCP/UDP;
  2. ping 验证的是 IP forward path + reverse path + 目标 kernel 愿意回 ICMP, 不验证任何应用层服务;
  3. RTT 由 processing / queueing / transmission / propagation 四段组成, 拥塞时 queueing 主导, 跨洲时 propagation 是硬下限;
  4. ping 不通 ≠ host 死, ping 通 ≠ 服务好, ICMP 经常被独立 rate-limit 或 drop;
  5. 想看每跳延迟用 mtrtraceroute, 想测端到端服务用 TCP 探针 (curl, nc -zv);