A Monday-Morning Ping Mystery

Returning from holidays, I powered on my corporate laptop and, before checking email, ran my usual post-boot ping. The network was healthy, but the output included an unexpected line: ping was taking countermeasures. I wasn't expecting countermeasures of any kind that early on a Monday.

After the initial confusion, the cause dawned on me. I'd started ping before the system's NTP daemon had synchronized the clock. My computer's clock was rolled backward, and this confused ping's round-trip time (RTT) calculation.

Clock adjustments like this are uncommon but not impossible. What's rarer is for a utility like ping to handle the situation gracefully. I'd expect nonsensical output and a shrug, not a deliberate message. The developers clearly thought this through, which made me want to dig into how deep that handling goes and whether it covers both clock directions.

What Ping Actually Measures

A quick look at the source code in iputils/ping/ping_common.c shows the gather_statistics() function prints the message when the measured RTT is negative, resetting the latency to zero. That's the "countermeasure": marking an erroneous measurement as if it took 0ms.

But what exactly does ping measure? The man page defines two modes. The older -U mode uses the wall clock, calling gettimeofday before sending and after receiving. The default, "newer" mode uses network time: it calls gettimeofday before sending but gets the receive timestamp from a more accurate SO_TIMESTAMP CMSG.

The Trouble With Tracing

I started debugging with strace, but no calls to gettimeofday appeared. That's because on modern Linux, these clock functions aren't true syscalls. They run in userspace via a special kernel-provided code page called vdso, which strace can't see.

There are two ways to work around vdso. One involves hooking execve() to overwrite the AT_SYSINFO_EHDR in the Auxiliary Vector, as shown in novdso.c. But this approach requires ptrace(), which doesn't work alongside strace.

The second technique uses LD_PRELOAD to overload clock functions and force real syscalls. This works for clock_gettime, so I expected to see gettimeofday from ping. I didn't — because ping often has elevated privileges.

Permissions Block the Path

Historically, ping had the suid bit set, which ignores LD_PRELOAD. But modern Linux allows rootless ICMP via "ping sockets," so ping shouldn't need suid. My binary wasn't suid, but it held CAP_NET_RAW, which also blocks library preloading.

I suspect this capability exists only for misconfigured systems where net.ipv4.ping_group_range is wrong. Removing the capability made my LD_PRELOAD hack work. With strace running, I confirmed ping in the default mode: sets SO_TIMESTAMP, calls gettimeofday before sending, and reads the receive timestamp from the CMSG.

Fault Injection Works

strace has a little-known "tampering" feature that can overwrite syscall results. Injecting a forward-shifted gettimeofday value reliably triggered the "taking countermeasures" message.

It's not possible to alter the CMSG timestamp this way. Time namespaces might work in theory, but the network stack doesn't account for them. Programs using SO_TIMESTAMP must handle comparing it to a system clock that can roll backwards.

Two Kinds of Fooling

ping doesn't store send timestamps in a hash table. Instead, it puts the timestamp in the ICMP Echo Request payload:

  1. Set SO_TIMESTAMP_OLD on the socket.
  2. Read the wall clock with gettimeofday.
  3. Embed the timestamp in the first bytes of the ICMP payload.
  4. On reply, compare the send timestamp from the payload to the CMSG receive timestamp.
  5. Calculate the RTT delta.

This design allows unlimited packets in flight. If a payload is shorter than 16 bytes, ping simply skips RTT output entirely.

Both weaknesses are exploitable. Truncated payloads remove RTT entirely. Spoofed timestamps can move the clock forward, producing the "taking countermeasures" message, or backward, generating nonsensical RTT values. These tests prove the countermeasures only work in one direction.

When Do Clocks Actually Change?

In practice, NTP makes small continuous adjustments for drift. Big shifts happen at boot, after wake from sleep, or on systems with unreliable connectivity or virtualized environments. One notable global event is the leap second, which moves the clock backward and caused issues for Cloudflare in 2016.

Leap seconds are so troublesome that the consensus is to deprecate them by 2035, though proposals suggest letting discrepancies grow to a full minute over 50–100 years and handling it with a "kind of smear." Many environments already use leap second smear to avoid backward jumps entirely.

For measuring time durations, CLOCK_MONOTONIC is bulletproof. Daylight saving time isn't a problem here — it's a user interface illusion. The integer timestamps remain sequential no matter what timezone is displayed.

Hard Lessons

Clock jumps backward are rare, and testing for them is difficult. I'm genuinely surprised ping attempts any handling. Developers already use CLOCK_MONOTONIC elsewhere in the codebase, but it won't work here: the CMSG timestamp uses the non-monotonic system clock. Linux APIs don't always make time easy.

For now, clock adjustments will continue to confuse ping. If you see it "taking countermeasures," check your NTP daemon status before investigating further.