开发者生态
morning
我的ping采取对策的那天
2026-05-24
1 阅读
moonleay
我的 ping 采取对策的那一天 2023-07-11 Marek Majkowski 读了 8 分钟 假期结束后,我发现自己不情愿地重新回到生者的世界。我打开公司笔记本电脑,不敢查看电子邮件收件箱。然而,在打开浏览器之前,显然我必须运行 ping。调试网络是启动后必须执行的第一步,对吗?正如预期的那样,网络非常健康,但让我措手不及的是这条消息:我没想到 ping 会在一天之初采取对策。天哪,我没想到周一会有任何反制措施!当我克服了最初的困惑后,我深吸了一口气,整理了思绪。您不必成为夏洛克·福尔摩斯才能弄清楚发生了什么。我真的很快 - 我在系统 NTP 守护进程同步时间之前就开始 ping。就我而言,计算机时钟向后滚动,导致 ping 值混乱。虽然这种情况并不经常发生,但计算机时钟可以自由地向前或向后调整。然而,像 ping 这样的常规网络实用程序很少尝试管理这样的情况。更不常见的是“采取对策”。我完全希望 ping 只打印一个无意义的时间值,然后毫不犹豫地继续。 Ping 开发者显然对此进行了一些思考。我想知道他们走了多远。他们处理两个方向的时钟变化吗?最终统计数据中是否排除了不良测量结果?他们如何测试软件?我不能就这么忽略对我的“采取对策”。现在我必须明白 ping 做了什么以及为什么。理解 ping 像这样的调查从快速浏览源代码开始: * P I N G 。 C ** 使用互联网控制消息协议 (ICMP)“ECHO”设施,* 测量网络路径上的往返延迟和数据包丢失。 * * 作者 - * Mike Muuss * 美国陆军弹道研究实验室 * 1983 年 12 月 Ping 的历史可以追溯到很久以前。它最初是由 Mike Muuss 于 1983 年在美国陆军弹道研究实验室写的,当时我还没有出生。我们正在寻找的代码位于 iputils/ping/ping_common.c Gather_statistics() 函数下:代码很简单:当测量的 RTT 为负时,会打印相关消息。在这种情况下,ping 会将延迟测量值重置为零。在这里:“采取对策”只不过是将错误的测量标记为 0ms。但 ping 到底测量什么?是挂钟吗?手册页可以解决这个问题。 Ping 有两种模式。 “旧”-U 模式,使用挂钟。此模式不太准确(抖动较多)。它在发送数据包之前和接收数据包之后调用 gettimeofday。使用“网络时间”的“新”默认模式。它在发送之前调用 gettimeofday,并从更准确的 SO_TIMESTAMP CMSG 获取接收时间戳。稍后会详细介绍这一点。跟踪 gettimeofday 很困难 让我们从一个好的旧 strace 开始: $ strace -e trace=gettimeofday,time,clock_gettime -f ping -n -c1 1.1 >/dev/null ... nil ... 它不显示任何对 gettimeofday 的调用。到底是怎么回事?在现代 Linux 上,某些系统调用并不是真正的系统调用。它们不会跳转到速度较慢的内核空间,而是保留在用户空间中并转到主机内核提供的特殊代码页。该代码页称为 vdso 。它作为 .so 库对程序可见: $ ldd `which ping` | grep vds linux-vdso.so.1 (0x00007ffff47f9000) 对 vdso 区域的调用不是系统调用,它们保留在用户空间中并且速度超快,但经典 strace 无法看到它们。为了调试,最好关闭 vdso 并回退到经典的慢速系统调用。说起来容易做起来难。无法阻止加载 vdso 。然而,有两种方法可以说服加载的程序不使用它。第一种技术是欺骗 glibc 使其认为 vdso 未加载。为了与古老的 Linux 兼容,必须处理这种情况。在新运行的进程中引导时,glibc 检查 ELF 加载程序提供的辅助向量。其中一个参数具有 vdso 指针的位置,手册页给出了以下示例: void *vdso = (uintptr_t) getauxval(AT_SYSINFO_EHDR); Stack Overflow 上提出的一种技术是这样工作的:让我们在 execve () 退出之前挂接一个程序并覆盖辅助向量 AT_SYSINFO_EHDR 参数。这是 novdso.c 代码。然而,链接的代码对我来说不太有效(一个太多的kill(SIGSTOP)),并且有一个更大的根本缺陷。为了挂钩 execve(),它使用 ptrace(),因此在我们的 strace 下不起作用! $ strace -f ./novdso ping 1.1 -c1 -n ... [pid 69316] ptrace(PTRACE_TRACEME) = -1 EPERM (不允许操作) 虽然这种重写 AT_SYSINFO_EHDR 的技术非常酷,但它对我们不起作用。 (我想知道是否有另一种方法可以做到这一点,但没有 ptrace。也许有一些 BPF?但那是另一个故事了。)第二种技术是使用 LD_PRELOAD 和 pr