Networking
ICMP and Ping
The control and error-reporting layer of IP — and the protocol behind ping and traceroute
ICMP (Internet Control Message Protocol) is the error-reporting and diagnostic companion to IP, defined in RFC 792 in 1981 by Jon Postel. It rides directly inside IP packets — protocol number 1 for IPv4, next-header 58 for IPv6 — and carries control messages: echo request/reply that powers ping, time-exceeded that powers traceroute, destination-unreachable, and redirect. Crucially, ICMP is not a transport protocol: it has no ports, no connections, and no reliable delivery, and routers routinely rate-limit it.
- Defined inRFC 792 (1981) · RFC 4443 (ICMPv6)
- IP protocol number1 (IPv4) · 58 (IPv6)
- LayerNetwork — alongside IP, not above it
- Ports / connectionsNone (uses id + sequence instead)
- Echo Request / ReplyType 8 / 0 (IPv4) · 128 / 129 (IPv6)
- Powersping, traceroute, Path MTU Discovery
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
Why ICMP matters
IP is a best-effort protocol: it forwards packets and, when something goes wrong, silently drops them. Left alone, that would make the internet undebuggable — a packet that vanished would give the sender no signal at all. ICMP is the feedback channel that fixes this. When a router runs out of TTL, discovers a packet too big to forward, or has no route to the destination, it emits an ICMP message back to the source telling it exactly what happened.
Two everyday tools are built entirely on top of that feedback: ping (is this host reachable, and how far away in time?) and traceroute (what path do my packets take, hop by hop?). Beyond diagnostics, ICMP is load-bearing for correctness: Path MTU Discovery depends on the "Fragmentation Needed" and "Packet Too Big" errors to size packets so they cross the whole path without fragmentation. In IPv6, ICMP goes further still — Neighbor Discovery, address autoconfiguration, and MTU signaling all ride on ICMPv6, so an IPv6 network with ICMPv6 fully blocked simply does not work.
How ICMP works, step by step
An ICMP message is a small structure carried as the payload of an IP packet. Every ICMP message begins with the same three fields:
- Type (1 byte) — the category of message, e.g. 8 = Echo Request, 0 = Echo Reply, 11 = Time Exceeded, 3 = Destination Unreachable.
- Code (1 byte) — a sub-reason within a type, e.g. under Destination Unreachable, code 1 = host unreachable, code 3 = port unreachable, code 4 = fragmentation needed.
- Checksum (2 bytes) — a 16-bit one's-complement checksum over the ICMP message.
What follows depends on the type. For echo messages, the next fields are a 16-bit Identifier and a 16-bit Sequence Number, followed by an arbitrary payload. For error messages, the body instead contains the IP header plus the first 8 bytes of the packet that triggered the error — just enough for the original sender to match the error to a specific flow (those 8 bytes cover the source and destination ports of the embedded TCP/UDP header).
The lifecycle of a ping is the cleanest illustration:
- The sender builds an Echo Request (type 8), stamps in a process-specific
Identifierand a startingSequence Number, fills the payload (often a timestamp), and computes the checksum. - The kernel wraps it in an IP header (protocol = 1), sets a TTL (Linux default 64), and hands it to the network.
- Each router decrements the IP TTL and forwards the packet toward the destination.
- The destination's IP stack recognizes protocol 1, sees type 8, and generates an Echo Reply (type 0), copying the identifier, sequence number, and payload back verbatim.
- The reply routes home. The sender matches the identifier and sequence number, computes RTT = arrival time − send time, and increments the sequence number for the next probe.
Traceroute reuses the same machinery but weaponizes TTL. It sends a probe with TTL = 1. The first router decrements it to 0, drops the packet, and sends back an ICMP Time Exceeded (type 11, code 0) — whose source address is that first router. Traceroute records it, then sends TTL = 2 to reveal the second hop, and so on, until a probe finally reaches the destination.
Measuring round-trip time
RTT is simply the wall-clock interval between emitting a probe and receiving its answer. Ping typically prints per-packet RTT plus a summary: minimum, average, maximum, and mdev (mean deviation / jitter). Because ICMP has no sequence acknowledgement of its own, ping embeds its own identifier and sequence number to pair replies with requests and to count loss and reordering.
A subtle trap: the RTT you measure is control-plane latency, not necessarily forwarding latency. Many routers handle ICMP generation on a slower path than the fast-path silicon that forwards ordinary traffic, and they rate-limit ICMP. So a hop that shows 80 ms in traceroute while later hops show 20 ms is almost always not a slow router — it is a router that deprioritized answering your TTL-exceeded probe. Interpret intermediate-hop RTTs with skepticism; only the final destination's RTT reflects real end-to-end latency.
Common ICMP message types
| Type (IPv4) | Name | Purpose | Query or Error? |
|---|---|---|---|
| 0 | Echo Reply | Answer to a ping | Query |
| 3 | Destination Unreachable | Packet undeliverable; code says why (incl. frag-needed) | Error |
| 5 | Redirect | "Use a better next-hop gateway" (often disabled for security) | Error |
| 8 | Echo Request | The outgoing ping | Query |
| 11 | Time Exceeded | TTL hit 0 (code 0) — powers traceroute; or reassembly timeout (code 1) | Error |
| 12 | Parameter Problem | Malformed IP header field | Error |
ICMPv6 keeps the type/code/checksum shape but renumbers everything: Destination Unreachable is type 1, Packet Too Big is type 2, Time Exceeded is type 3, Echo Request/Reply are 128/129, and Neighbor Discovery occupies 133–137. The rule of thumb: in ICMPv6, types 0–127 are errors and 128–255 are informational.
ICMP vs a transport protocol
The single most common misconception is that ICMP is "like UDP but for control." It is not — it lives one layer down. This table makes the distinction concrete:
| ICMP | UDP | TCP | |
|---|---|---|---|
| Layer | Network (inside IP) | Transport (on IP) | Transport (on IP) |
| IP protocol number | 1 / 58 | 17 | 6 |
| Ports | None (id + seq) | Yes | Yes |
| Connection / handshake | No | No | Yes (3-way) |
| Reliable / ordered | No | No | Yes |
| Carries app data | No — control only | Yes | Yes |
| Typical rate-limiting | Yes, by routers/kernels | No (by protocol) | No (by protocol) |
How ping works in Python
A minimal ping shows every field in one place: building the Echo Request, computing the one's-complement checksum, sending it over a raw socket, and matching the reply by identifier. (Raw sockets require root/administrator privileges.)
import socket, struct, os, time, select
ICMP_ECHO_REQUEST = 8 # IPv4 Echo Request
ICMP_ECHO_REPLY = 0
def checksum(data: bytes) -> int:
"""16-bit one's-complement sum, per RFC 1071."""
if len(data) % 2:
data += b'\x00'
total = 0
for i in range(0, len(data), 2):
total += (data[i] << 8) + data[i + 1]
total = (total >> 16) + (total & 0xFFFF) # fold carries
total += total >> 16
return (~total) & 0xFFFF
def build_echo(identifier: int, seq: int, payload: bytes) -> bytes:
# header with checksum = 0, then recompute over the whole message
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, 0, 0, identifier, seq)
chk = checksum(header + payload)
header = struct.pack('!BBHHH', ICMP_ECHO_REQUEST, 0, chk, identifier, seq)
return header + payload
def ping(host: str, seq: int = 1, timeout: float = 1.0) -> float | None:
dest = socket.gethostbyname(host)
ident = os.getpid() & 0xFFFF # distinguish our replies
packet = build_echo(ident, seq, struct.pack('!d', time.time()))
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
sent = time.time()
sock.sendto(packet, (dest, 0))
while True:
remaining = timeout - (time.time() - sent)
if remaining <= 0 or not select.select([sock], [], [], remaining)[0]:
return None # timed out (or rate-limited away)
recv, _ = sock.recvfrom(1024)
ip_hdr_len = (recv[0] & 0x0F) * 4 # IHL is in 32-bit words
icmp = recv[ip_hdr_len:]
typ, _code, _chk, rid, rseq = struct.unpack('!BBHHH', icmp[:8])
if typ == ICMP_ECHO_REPLY and rid == ident and rseq == seq:
return (time.time() - sent) * 1000.0 # RTT in milliseconds
rtt = ping('1.1.1.1')
print('no reply' if rtt is None else f'{rtt:.1f} ms')
Two details carry the whole protocol. First, the checksum is computed with the checksum field zeroed, then written back — a receiver that recomputes over the message including the stored checksum must get all-ones. Second, the reply is matched on rid == ident: with no ports, the 16-bit identifier is the only way one ping process tells its replies apart from another's on the same host.
Common misconceptions and pitfalls
- "No ping reply means the host is down." Not reliable. Many hosts and firewalls drop or rate-limit ICMP Echo by policy while still serving TCP on port 443. Absence of a reply is inconclusive.
- "Block all ICMP for security." A classic self-inflicted outage. Blocking ICMPv4 "Fragmentation Needed" (type 3, code 4) or ICMPv6 "Packet Too Big" (type 2) breaks Path MTU Discovery — connections hang after the handshake, transferring small requests fine but stalling on large responses (the notorious "PMTUD black hole").
- "ICMP has ports." It does not. The port-like matching is done by the 16-bit identifier and sequence number for echo, and by the embedded original-packet header for errors.
- "traceroute shows the router is slow." Intermediate-hop RTTs measure how fast a router chose to answer a TTL-exceeded probe on its control plane, not forwarding latency. Rising then falling RTTs across hops are normal.
- "An ICMP error is delivered reliably." ICMP itself is best-effort and unacknowledged. An error message can be dropped or rate-limited, and there is no retransmission. Higher layers must not depend on receiving it.
A little history: from RFC 792 to the Smurf era
ICMP shipped with the original IP specification — RFC 792, September 1981, authored by Jon Postel — because the designers understood from the start that a best-effort datagram network needs an in-band way to report trouble. The ping utility itself was written by Mike Muuss in December 1983 in a single evening, named after the sound of sonar; it remains one of the most-run network programs ever written.
ICMP's openness later made it a weapon. The Smurf attack of the late 1990s spoofed a victim's address as the source of an Echo Request sent to a network's broadcast address, so every host on that network replied to the victim at once — an amplification flood. The fix (routers no longer forwarding directed broadcasts, and pervasive ICMP rate-limiting) is exactly why modern equipment throttles ICMP so aggressively. ICMPv6 (RFC 4443, 2006) then re-centered the protocol for IPv6, folding in Neighbor Discovery and making a rich subset of ICMPv6 mandatory rather than optional — a deliberate reversal of the "just block it" reflex that IPv4 tolerated.
Frequently asked questions
Is ICMP a transport protocol like TCP or UDP?
No. ICMP sits at the same layer as IP — it is carried directly inside IP packets (IPv4 protocol number 1, IPv6 next-header 58) rather than on top of a transport protocol. It has no port numbers, no connections, no handshake, and no reliable or ordered delivery. It exists purely to report errors and carry diagnostic queries about IP traffic, not to move application data. Because it lacks ports, firewalls and NAT devices treat it specially.
How does ping actually work?
Ping sends an ICMP Echo Request (type 8 in IPv4, type 128 in IPv6) to a target host. The target's IP stack replies with an ICMP Echo Reply (type 0 in IPv4, type 129 in IPv6), echoing back the identifier, sequence number, and payload it received. Ping records the time between sending the request and receiving the reply — that is the round-trip time (RTT). The identifier lets one process distinguish its replies from another's, and the incrementing sequence number lets ping detect lost or reordered packets.
How does traceroute use ICMP?
Traceroute sends probes with a deliberately small IP TTL (Time To Live), starting at 1. Each router that forwards a packet decrements its TTL by one; when the TTL reaches zero the router discards the packet and returns an ICMP Time Exceeded message (type 11) whose source address is that router. By sending TTL=1, then 2, then 3, and so on, traceroute learns the address of each hop along the path. Classic Unix traceroute sends UDP probes and reads the ICMP replies; Windows tracert sends ICMP Echo Requests directly.
What does 'Destination Unreachable' mean?
Destination Unreachable (type 3 in IPv4) is an ICMP error returned when a packet cannot be delivered. The code field explains why: code 0 = network unreachable, code 1 = host unreachable, code 3 = port unreachable (no process listening on that UDP/TCP port), code 4 = fragmentation needed but the Don't-Fragment bit was set. Code 4 is critical — it carries the next-hop MTU and drives Path MTU Discovery. The message includes the IP header plus the first 8 bytes of the original packet so the sender can match the error to a flow.
Why is ICMP rate-limited?
Generating ICMP replies costs a router CPU cycles, and ICMP has historically been abused — Smurf attacks amplified broadcast pings, and floods of TTL-exceeded errors can be used to overwhelm a control plane. To protect themselves, routers rate-limit ICMP generation (for example, Linux defaults to a token bucket of roughly one error per few milliseconds per destination). This is why traceroute sometimes shows asterisks: the hop is fine, it just declined to answer that particular probe. Rate-limiting means you cannot treat ICMP loss as proof that a host is down.
How is ICMPv6 different from ICMPv4?
ICMPv6 (RFC 4443) is far more central to IPv6 than ICMPv4 is to IPv4. Besides echo and error messages, it carries Neighbor Discovery — Router Solicitation/Advertisement and Neighbor Solicitation/Advertisement — which replaces ARP and DHCP-style autoconfiguration in IPv6. It also carries Multicast Listener Discovery. Types are split at 128: types 0–127 are errors, 128–255 are informational. Echo Request/Reply are 128/129 instead of 8/0. Because IPv6 has no in-network fragmentation, Packet Too Big (type 2) is mandatory for Path MTU Discovery, so blocking ICMPv6 wholesale breaks IPv6 connectivity.
What is the ICMP checksum computed over?
In IPv4, the ICMP checksum is the standard 16-bit one's-complement sum over the entire ICMP message — the type, code, checksum (as zero), and the rest of the message — but not over the IP header. In IPv6, the ICMPv6 checksum additionally covers a pseudo-header made of the IPv6 source and destination addresses, the payload length, and the next-header value (58). That difference means an ICMPv6 checksum computed without the pseudo-header will be rejected, a common bug when hand-crafting IPv6 packets.