ping 一次到底发了什么?RTT 是从哪里量出来的?-s 改了载荷大小以后线上有什么变化?这篇在 lo0 和 en0 上抓 ICMP echo 的请求与应答,手算校验和,对比 ping 报的时间和抓包的时间,再用不需要 root 的 ICMP 套接字自己发一个 echo。
127.0.0.1(lo0):抓到 2 个包
请求:IP 总长 84,TTL 64,IP 头里的标识 65089,ICMP 类型 8 代码 0,标识符 0x441a,序号 0,载荷 56 字节
应答:IP 总长 84,TTL 64,IP 头里的标识 7578,ICMP 类型 0 代码 0,标识符 0x441a,序号 0,载荷 56 字节
请求的载荷前 8 字节是发送时间:秒 1792027662、微秒 468410;与抓包时间戳相差 25 µs;后 48 字节是 0x08 起逐字节加 1 的图案(确认递增)
应答的标识符、序号、载荷与请求完全相同:True;IP 头 + ICMP 头 + 载荷 = 20 + 8 + 56 = 84 字节
默认网关(en0):抓到 2 个包
请求:IP 总长 84,TTL 64,IP 头里的标识 40976,ICMP 类型 8 代码 0,标识符 0x481a,序号 0,载荷 56 字节
应答:IP 总长 84,TTL 64,IP 头里的标识 14738,ICMP 类型 0 代码 0,标识符 0x481a,序号 0,载荷 56 字节
请求的载荷前 8 字节是发送时间:秒 1792027663、微秒 128071;与抓包时间戳相差 215 µs;后 48 字节是 0x08 起逐字节加 1 的图案(确认递增)
应答的标识符、序号、载荷与请求完全相同:True;IP 头 + ICMP 头 + 载荷 = 20 + 8 + 56 = 84 字节类型 8 是请求、0 是应答,代码都是 0;标识符、序号、载荷在应答里原样回送。ping 把发送时间放在载荷前 8 字节里,收到应答后直接取出来算 RTT。
lo0 请求:抓到的校验和 0x618a,自己算的 0x618a,一致;把整个 ICMP 报文(含校验和)再求和得 0x0000 lo0 应答:抓到的校验和 0x698a,自己算的 0x698a,一致;把整个 ICMP 报文(含校验和)再求和得 0x0000 en0 请求:抓到的校验和 0x1070,自己算的 0x1070,一致;把整个 ICMP 报文(含校验和)再求和得 0x0000 en0 应答:抓到的校验和 0x1870,自己算的 0x1870,一致;把整个 ICMP 报文(含校验和)再求和得 0x0000 规则:校验和字段先填 0,对整个 ICMP 报文(头 + 载荷,不含 IP 头)按 16 位字求和、进位折回、取反;收到的人对整个报文求和,结果应该是 0
只覆盖 ICMP 报文,不含 IP 头。自己算的值和 lo0、en0 上四个包完全一致;含校验和再求一次和等于 0。
默认网关:发 30 个,收到应答 30 个;ping 报告的 RTT 最小 2.613 / 中位 2.958 / 最大 35.607 ms 抓包得到的 RTT(请求到应答之间的时间差)最小 2.364 / 中位 2.751 / 最大 35.458 ms 逐个对比 30 个:ping 报的比抓包多 0.084~0.534 ms(中位 0.194)——ping 从发出前到应答被读到,比网卡上看到的多一段 最慢的 3 个 RTT: [25.177, 29.527, 35.607];最快的 3 个: [2.613, 2.638, 2.658] 127.0.0.1:收到 30 个;ping 报告 最小 0.074 / 中位 0.132 / 最大 0.290 ms;抓包 RTT 中位 0.043 ms 网关的 RTT 中位是 127.0.0.1 的 22 倍(Wi-Fi 链路上一来一回 + 网关处理)
局域网网关的 RTT 中位约 3 ms,127.0.0.1 约 0.13 ms。ping 报告的时间总比抓包多一点本机内部的耗时;Wi-Fi 上偶尔出现几十毫秒的尖峰。
载荷 0 字节:第一个包 IP 总长 28;收到 15/15,ping 打印出 time= 的有 0 个;抓包 RTT 最小 2.348 / 中位 4.087 ms;抓到 30 个 IP 包,其中是分片的(MF 置位或偏移非 0)有 0 个 载荷 56 字节:第一个包 IP 总长 84;收到 15/15,ping 打印出 time= 的有 15 个;抓包 RTT 最小 2.256 / 中位 3.623 ms;抓到 30 个 IP 包,其中是分片的(MF 置位或偏移非 0)有 0 个 载荷 512 字节:第一个包 IP 总长 540;收到 15/15,ping 打印出 time= 的有 15 个;抓包 RTT 最小 2.397 / 中位 2.850 ms;抓到 30 个 IP 包,其中是分片的(MF 置位或偏移非 0)有 0 个 载荷 1472 字节:第一个包 IP 总长 1500;收到 15/15,ping 打印出 time= 的有 15 个;抓包 RTT 最小 2.496 / 中位 2.867 ms;抓到 30 个 IP 包,其中是分片的(MF 置位或偏移非 0)有 0 个 载荷 4000 字节:第一个包 IP 总长 1500;收到 15/15,ping 打印出 time= 的有 15 个;抓包 RTT 最小 2.963 / 中位 3.395 ms;抓到 90 个 IP 包,其中是分片的(MF 置位或偏移非 0)有 90 个 载荷 0 → 4000 字节,抓包 RTT 中位的变化: 4.087 → 3.623 → 2.850 → 2.867 → 3.395 ms
载荷小于 8 字节放不下时间戳,ping 不显示 time。载荷超过 MTU 时请求和应答都会分片。15 个样本的实验里 RTT 受抖动影响比受大小影响大。
我填的:类型 8 代码 0 标识符 0x1234 序号 7 载荷 34 字节 线上发出的请求:标识符 0x1234 序号 7 载荷 34 字节;载荷与我填的相同:True;标识符保持了 线上收到的应答:标识符 0x1234 序号 7 载荷相同:True recvfrom 拿到 62 字节:开头是 IP 头(版本/IHL=0x45,头长 20),后面是 ICMP 类型 0 代码 0;应用层测得 RTT 3.269 ms 应用里收到的 ICMP 标识符 0x1234(线上是 0x1234) 同时开着另一个 ICMP 套接字 s2(自己从没发过包),s 又发了一次请求:s2 收到了一个 62 字节的包(ICMP 类型 0,标识符 0x1234,序号 7)——也就是说在这台机器上,ICMP 数据报套接字并不只收自己发出的请求的应答(这里只观察到现象,没有进一步验证内核的分发规则)
macOS 允许普通用户用 SOCK_DGRAM + IPPROTO_ICMP 发 echo,标识符、序号和载荷原样上线;recvfrom 收到的数据带 IP 头。