Nerds like to ping
The most powerful network diagnostic tool you’ll ever use is 1,000 lines of code written in a single evening in 1983.
I was debugging a network issue at 2 AM. The college hostel WiFi was being weird again. My SSH session to the remote server kept timing out, but intermittently. Sometimes it worked, sometimes it didn’t. Classic network problem.
I did what everyone does first: ping google.com
64 bytes from 142.250.195.46: icmp_seq=1 ttl=117 time=12.4 ms64 bytes from 142.250.195.46: icmp_seq=2 ttl=117 time=11.8 ms64 bytes from 142.250.195.46: icmp_seq=3 ttl=117 time=13.1 msAnd that’s when it hit me. How does this even work? I’m getting a response in 12 milliseconds from a Google server that’s probably a few hundred kilometers away. Not just a response: precise round-trip timing, hop count information, and a guarantee that my packet made it there and back.
No TCP handshake. No protocol negotiation. No ports. Just… instant connectivity verification.
I realized I’d been using this tool for years without understanding the absolute genius behind it. That night turned into a week-long obsession.
Wait, What Protocol is This Even Using?
My first instinct was wrong. I assumed ping must be using UDP or TCP, because those are the transport layer protocols, right? That’s what we learned in networking class.
So I fired up Wireshark to see what ping was actually doing:
Internet Protocol Version 4 Protocol: ICMP (1)Internet Control Message Protocol Type: 8 (Echo Request) Code: 0 Checksum: 0x1c3d [correct] Identifier: 0x0001 Sequence Number: 1 Data (56 bytes)Wait. ICMP? Not UDP or TCP?
That’s when I started digging into RFC 792, published in September 1981. ICMP rides inside IP packets, just like a higher level protocol would. But the RFC is clear that it’s really a part of IP itself, and every IP implementation has to support it.
This is the first genius move: ICMP isn’t sitting on top of TCP or UDP. It’s a peer to them, living right alongside IP at the network layer. ICMP is a layer 3 protocol in the seven-layer OSI model.
I kept reading and found something even more interesting. RFC 1122 says “Every host MUST implement an ICMP Echo server function.” Not optional. Not “should implement.” Must. Every host’s network stack is supposed to know how to answer a ping. (Whether a firewall lets the ping through is another story. Plenty of them drop it.)
That’s when I realized: ping doesn’t need any server software running. No daemon listening on a port. The operating system kernel itself answers. You’re pinging the network stack directly.
The Elegant Simplicity of Type 8 and Type 0
Let me show you what an ICMP Echo Request actually looks like. This is from RFC 792:
Type: 8 for echo message; 0 for echo reply message.
That’s it. The entire protocol for ping is:
- You send a packet with Type 8 (Echo Request)
- Remote host sends back Type 0 (Echo Reply)
But here’s what makes it brilliant. Look at the packet structure:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Type | Code | Checksum |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Identifier | Sequence Number |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Data ...+-+-+-+-+-The RFC says the sender may use the identifier and sequence number to match replies to requests. That’s all the guidance you get.
This is beautiful. The Identifier field lets you track which ping process sent the request (useful when multiple programs are pinging simultaneously). The Sequence Number increments with each request, so you can detect packet loss, reordering, or duplication.
But the real genius? Whatever data you put in the request, the other side has to send back in the reply. Byte for byte.
The remote host doesn’t interpret the data. Doesn’t process it. Just echoes it back. This means ping can embed a timestamp in the data section, send it, and when it comes back, calculate the exact round-trip time by comparing the timestamp with the current time.
Here’s a real echo request, byte by byte. Change the sequence number or the data and watch the checksum follow. Then flip a bit and see what the other end thinks of it.
This figure is interactive.
Mike Muuss wrote ping in December 1983 while working at the Ballistic Research Laboratory. He named it after sonar. Sonar sends out a pulse of sound and listens for the echo, and the delay tells you how far away things are. Ping does the same with echo requests and replies: is that machine there, and how long does it take to answer?
He wrote it in one evening, about a thousand lines of code. The funny part: by the time he sent his very first ping, a colleague had already found and fixed the broken network hardware. So ping missed the one problem it was written for. It’s been used a few times since.
So here’s sonar for networks. Each pulse goes out through every router between you and Google, and comes back. Watch the TTL drop by one at each hop, turn up the packet loss, then switch to traceroute.
This figure is interactive.
Building a Raw Socket Ping to See It For Real
Reading specs is one thing. Actually implementing it is where it clicks. I needed to see this work at the packet level.
Here’s what I built, a minimal ping implementation using raw sockets in C:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/socket.h>#include <netinet/ip_icmp.h>#include <netinet/ip.h>#include <arpa/inet.h>#include <unistd.h>#include <sys/time.h>
// Checksum calculation (16-bit one's complement)unsigned short checksum(void *b, int len) { unsigned short *buf = b; unsigned int sum = 0;
for (sum = 0; len > 1; len -= 2) sum += *buf++; if (len == 1) sum += *(unsigned char *)buf;
sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16);
return ~sum;}
int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s <IP address>\n", argv[0]); return 1; }
// Create raw socket int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sockfd < 0) { perror("Socket creation failed (need root)"); return 1; }
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]);
// Build ICMP packet char packet[64]; memset(packet, 0, sizeof(packet));
struct icmphdr *icmp = (struct icmphdr *)packet; icmp->type = 8; // ICMP_ECHO icmp->code = 0; icmp->un.echo.id = htons(getpid() & 0xFFFF); icmp->un.echo.sequence = htons(1); // network byte order
// Embed timestamp in data section struct timeval *tv = (struct timeval *)(packet + sizeof(struct icmphdr)); gettimeofday(tv, NULL);
// Calculate checksum icmp->checksum = 0; icmp->checksum = checksum(packet, sizeof(packet));
// Send packet if (sendto(sockfd, packet, sizeof(packet), 0, (struct sockaddr *)&addr, sizeof(addr)) <= 0) { perror("Sendto failed"); return 1; }
printf("ICMP Echo Request sent to %s\n", argv[1]);
// Receive reply char recv_buf[512]; struct sockaddr_in recv_addr; socklen_t addr_len = sizeof(recv_addr);
int bytes = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&recv_addr, &addr_len);
if (bytes > 0) { // Skip IP header to get to ICMP struct iphdr *ip_hdr = (struct iphdr *)recv_buf; int ip_header_len = ip_hdr->ihl * 4;
struct icmphdr *icmp_reply = (struct icmphdr *)(recv_buf + ip_header_len);
if (icmp_reply->type == 0) { // ICMP_ECHOREPLY struct timeval *sent_tv = (struct timeval *)(recv_buf + ip_header_len + sizeof(struct icmphdr)); struct timeval recv_tv; gettimeofday(&recv_tv, NULL);
double rtt = (recv_tv.tv_sec - sent_tv->tv_sec) * 1000.0 + (recv_tv.tv_usec - sent_tv->tv_usec) / 1000.0;
printf("Reply from %s: icmp_seq=%d ttl=%d time=%.2f ms\n", inet_ntoa(recv_addr.sin_addr), ntohs(icmp_reply->un.echo.sequence), ip_hdr->ttl, rtt); } }
close(sockfd); return 0;}This is what I learned building this:
-
Raw sockets require root. On Linux that means root or the
CAP_NET_RAWcapabilityModern Linux . You’re bypassing the transport layer and talking directly to the network layer.pingusually doesn’t need either: the kernel offers unprivileged ICMP “datagram” sockets for echo requests, allowed by thenet.ipv4.ping_group_rangesetting. -
The checksum is critical. Add up the whole ICMP message as 16-bit words, wrapping every carry back around (one’s complement), then flip all the bits. If you get this wrong, the packet gets dropped silently. No error. Nothing.
-
When you receive a reply, you get the entire IP header too. That’s where the TTL value comes from. The remote host picks a starting TTL for its reply (usually 64, 128 or 255) and every router on the way back decrements it by 1. So TTL=117 doesn’t mean 138 routers; it most likely means a reply that started at 128 and crossed 11 routers to get back to you.
-
The sequence number is how you detect packet loss. Each reply carries its request’s sequence number, and ping prints it. A gap means a packet went missing. A number out of order, or twice, means reordering or duplication. IP only promises best effort, so all three happen.
Running this code against 8.8.8.8:
$ sudo ./minimal_ping 8.8.8.8ICMP Echo Request sent to 8.8.8.8Reply from 8.8.8.8: icmp_seq=1 ttl=115 time=11.23 msIt works. With one 64-byte packet and zero external dependencies beyond the kernel, I can verify network connectivity to any machine on the planet.
Why This Design is Timeless
Here’s what makes ping a masterpiece: it solves exactly one problem, perfectly.
Not “check if a service is running.” Not “establish a connection.” Just: “Can I reach this IP address, and how long does it take?”
The constraints that made it brilliant:
-
No state required. Each ping is independent. You can send 1 ping or 1 million. The protocol doesn’t care, and neither does the other host: it answers and forgets.
-
Mandatory implementation. Because RFC 1122 requires every host to implement an echo server, ping becomes the universal language of network reachability. You don’t need special server software. You don’t need open ports. If the network stack is alive and nothing in between drops ICMP, ping works.
-
Built-in packet loss detection. ping bumps the sequence number by one for every request, and each reply carries its number back. Replies 1, 2, 4? Number 3 got lost. 4 before 3? They got reordered.
-
RTT measurement by design. By embedding the timestamp in the payload and having it echoed back, you get microsecond-precision round-trip timing without any clock synchronization between sender and receiver.
-
TTL exposes the path. When TTL reaches 0, routers send back ICMP Time Exceeded messages. This is how
tracerouteworks: it sends packets with incrementing TTL values and watches where the Time Exceeded messages come from.
Look at the failure modes too. If a host is unreachable, you get an ICMP Destination Unreachable. If a router’s queue is full, no response (timeout). If the host is behind a firewall that blocks ICMP, timeout. The failure information is just as valuable as success.
It was so useful that it spread everywhere. First BSD and the other Unixes, then Windows, Mac OS X and pretty much everything else.
The Thing Nobody Teaches You About ping
Every networking textbook shows you the OSI model. Application, Presentation, Session, Transport, Network, Data Link, Physical. They tell you TCP is at layer 4, IP is at layer 3.
But ping breaks that mental model. It’s not at layer 4. It’s not using TCP or UDP. And unlike TCP and UDP, ICMP isn’t for carrying your data around. Normal apps almost never touch it. Diagnostic tools like ping and traceroute are the big exception.
ICMP exists between layers. It’s part of IP, but it’s also used by IP implementations to send error messages and control information. It’s the meta-protocol that lets the network talk about itself.
When you run ping, you’re not making a connection. You’re asking the network stack itself: “Are you there?” And the network stack replies: “Yes. Here’s proof.”
That’s why it’s the first tool everyone reaches for. Not because it does everything. Because it does one thing so well that it became the universal first step in network debugging.
You can’t telnet to a host but ping works? Routing is fine; it’s the port. A firewall, or no service listening. Ping works but HTTP doesn’t? Application issue. Ping shows 50% packet loss? Network congestion. Ping shows consistent 200ms RTT? That’s your baseline. Ping shows variable RTT jumping from 10ms to 500ms? Quality of service problem.
Mike Muuss wrote this in one evening to debug a network problem, and now it’s on nearly every computer in the world. He later said that if he’d known it would be his most famous work, he might have spent another day or two on it.
But maybe that’s the point. The best tools are the ones that solve a problem so cleanly that they become invisible. You don’t think about ping anymore. You just use it. And it just works.