ICMP: how the network talks back
The messages routers and hosts send about packets: what ping and traceroute really do, port unreachable, fragmentation needed and the path MTU black hole, ICMPv6, and why blocking ICMP breaks things.
On this page
IP delivers packets and says nothing when it cannot. ICMP is the small set of messages that fill that silence: "that host is unreachable", "your packet was too big", "it ran out of hops here". Nothing you write ever sends ICMP on purpose, except ping, yet every connection you make depends on it arriving. Block it and things break in ways that look like anything but a firewall rule.
The map
Read this first when short on time. Every branch is a section below.
What ICMP is: the network's own messages
ICMP is the Internet Control Message Protocol. It rides directly inside IP, as protocol number 1, next to TCP (6) and UDP (17). It has no ports. Every message is a type, a code, a checksum, and a few bytes of data. It is how a router or a host tells the sender of a packet something about that packet.
There are two kinds of messages. Errors are sent by a router or host that could not deliver a packet, and they carry the first bytes of that packet, the IP header and the first 8 bytes after it, so the sender can match the error to the connection that caused it. Queries are a question and an answer: echo request and echo reply, which is ping.
| Type | Name | Sent by | Means |
|---|---|---|---|
| 0 / 8 | Echo reply / echo request | Any host | ping. Are you there, and how long does it take? |
| 3 | Destination unreachable | A router or the destination | Could not deliver. The code says why: net, host, protocol, port, fragmentation needed, administratively prohibited. |
| 11 | Time exceeded | A router | The TTL reached zero here. What traceroute collects. |
| 5 | Redirect | A router on your link | There is a better router for that destination. Usually ignored now. |
| 4 | Source quench | Nobody | Slow down. Deprecated; congestion is TCP's job. |
| 13 / 14 | Timestamp | Any host | Rarely used; blocked by most firewalls. |
Two rules keep it from feeding on itself. A host never sends an ICMP error about an ICMP error, so a storm cannot start. And it never sends an error about a packet addressed to a broadcast or multicast group, because a thousand replies to one packet is an attack, not a diagnostic.
The kernel handles all of it. When an ICMP error arrives about one of your TCP connections, the kernel finds the socket from the embedded header and reacts, for instance by shrinking the packet size; your program sees nothing, or at most an error code on the next call. This is why ICMP is invisible to application developers and why they are surprised when it matters.
tcpdump icmpon a busy server shows a steady trickle: echo requests from monitoring, port unreachables from UDP scans, occasional time exceededs from someone's traceroute.- Wireshark decodes the embedded original packet inside every ICMP error, so you can see which connection a "host unreachable" was about.
- Cloud provider load balancers answer echo requests themselves and never forward them to your instances; a ping to a load balancer proves nothing about the backend.
- ICMP
- Internet Control Message Protocol: error and query messages carried inside IP, protocol 1.
- Type and code
- The message class and its subtype. Type 3 code 3 is "port unreachable".
ping: one echo, one reply
ping sends an ICMP echo request and waits for the echo reply. It measures whether the other machine's IP stack is reachable and how long a round trip takes. That is all it measures, and it is worth being precise about it.
sequenceDiagram autonumber participant A as Laptop participant B as example.com A->>B: echo request, id 4321, seq 1, 56 bytes of payload B-->>A: echo reply, id 4321, seq 1, same 56 bytes Note over A: 1 second later A->>B: echo request, id 4321, seq 2 B-->>A: echo reply, id 4321, seq 2 Note over A: seq 3 lost, no reply, printed as missing
$ ping -c 4 example.com
PING example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=54 time=181 ms
64 bytes from 93.184.216.34: icmp_seq=2 ttl=54 time=180 ms
64 bytes from 93.184.216.34: icmp_seq=4 ttl=54 time=183 ms
--- example.com ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 3004ms
rtt min/avg/max/mdev = 180.1/181.3/183.0/1.2 ms
Read it line by line. ttl=54 is the TTL left on the reply: the server started at 64, so the reply crossed 10 routers, which tells you roughly how far away it is. time is the round trip. icmp_seq=3 is missing: one of four was lost, and a 25 percent loss from one run of four means nothing, but a steady 1 percent over a thousand means a bad link. mdev is the jitter.
What ping does not tell you is whether anything is listening. A server can answer echo requests and have nginx dead, a firewall blocking port 443, or its disk full. It can also refuse echo requests and be serving perfectly. A ping proves the machine's kernel is up and the path to it works, in both directions, for ICMP. To test a service, connect to its port: curl, nc -zv host 443, or a TCP-based check.
Sending an echo request used to need a raw socket and therefore root, which is why ping was a setuid binary for decades, as the permissions article mentions. Linux now has a dedicated "ping socket" type that any user may open, and most distributions grant ping the CAP_NET_RAW capability instead of setuid.
- Monitoring systems ping every host every few seconds and alert on loss; it is the cheapest possible liveness check and is still the first one to look at.
- Windows and many cloud images block echo requests by default in their firewalls, so "it does not ping" is often just policy.
ping -s 1472with the don't-fragment flag is the manual way to find the largest packet a path carries, which the path MTU section below explains.
A host that pings but does not serve, and a host that serves but does not ping, are both normal. Use ping to ask "is the path up", never "is the service up".
- Echo request / reply
- ICMP types 8 and 0. The reply carries back whatever payload the request had.
- Jitter
- Variation in round trip time between packets.
mdevin ping's output.
traceroute: abusing the TTL on purpose
traceroute draws the list of routers between you and a destination. It has no special protocol. It uses the TTL rule described in the packet journey, that a router which decrements a TTL to zero drops the packet and sends back an ICMP time exceeded, and turns it into a survey.
sequenceDiagram autonumber participant L as Laptop participant R1 as Router 1 participant R2 as Router 2 participant D as Destination L->>R1: probe, TTL 1 R1-->>L: time exceeded (type 11) from R1 L->>R2: probe, TTL 2 (passes R1) R2-->>L: time exceeded from R2 L->>D: probe, TTL 3 (passes R1, R2) D-->>L: port unreachable (type 3) from D: the end
- Send a probe with TTL 1. The first router decrements it to 0, drops it, and sends time exceeded. The source address of that error is the router's address. Hop 1 found.
- Send with TTL 2. The first router passes it, the second kills it and replies. Hop 2.
- Keep going. Three probes per TTL by default, so each line shows three round trip times.
- When the probe reaches the destination, the reply is different: for a UDP probe to a random high port, a port unreachable; for an ICMP echo probe, an echo reply. That is the signal to stop.
The probe type matters in practice. Classic Unix traceroute sends UDP to high ports, which firewalls often drop, so the last hops show as stars. traceroute -I sends ICMP echo, which is what Windows tracert does. traceroute -T -p 443 sends TCP SYNs to port 443, which reaches through firewalls that allow the real service, and is the right tool when "the web server is reachable but slow".
$ traceroute -T -p 443 example.com
1 192.168.1.1 1.2 ms 0.9 ms 1.0 ms
2 10.20.0.1 4.1 ms 3.8 ms 4.0 ms
3 * * *
4 203.0.113.45 22.3 ms 21.9 ms 22.5 ms
5 198.51.100.2 118.0 ms 117.6 ms 118.4 ms
6 93.184.216.34 181.2 ms 180.8 ms 181.5 ms
Reading it needs care. The stars at hop 3 mean that router did not send time exceeded, or sent it too slowly; nearly all routers rate-limit ICMP generation to protect their CPU, so a star at one hop with normal hops after it is not loss. Latency that jumps at one hop and stays high afterwards is a long link, typically a cable between cities. Latency that is high at one hop and low at the next means that router is slow to generate ICMP, not slow to forward. And the whole picture is the outbound path only; the replies came back by whatever route each router's network preferred, which may be different, so a high number can be the return path's fault.
mtrruns traceroute continuously and shows loss and latency per hop as a live table; it is the tool to open when a user says "the site is slow from here".tracepathon Linux needs no root and also reports the path MTU as it goes.- Load-balanced paths make traceroute show different routers at the same hop on different probes; paris-traceroute keeps the probes on one path by holding the port numbers steady.
- Cloud networks often hide their internal hops entirely, so a traceroute into AWS shows stars for several hops and then the instance.
- Time exceeded
- ICMP type 11: a router dropped the packet because its TTL reached zero.
- Probe
- One traceroute packet: UDP to a high port, an ICMP echo, or a TCP SYN.
Destination unreachable: the error that names the reason
Type 3 is the workhorse. It is what a router sends when it has no route, what a host sends when nothing is listening, and what a firewall sends when it has been told to be polite. The code says which.
| Code | Meaning | Sent by | You see it as |
|---|---|---|---|
| 0 | Network unreachable | A router with no route to that network | "Network is unreachable" from connect |
| 1 | Host unreachable | The last router, when ARP for the host got no answer | "No route to host" |
| 2 | Protocol unreachable | The host, when nothing handles that IP protocol | Rare |
| 3 | Port unreachable | The host, when no UDP socket is bound to that port | "Connection refused" on a UDP socket |
| 4 | Fragmentation needed and DF set | A router whose next link is too small | Nothing, until it is blocked. Next section. |
| 9, 10, 13 | Administratively prohibited | A firewall configured to reject rather than drop | Fast "connection refused" instead of a slow timeout |
Port unreachable deserves a closer look because it is UDP's only feedback. TCP has RST: connect to a closed port and the host answers with a reset, and connect fails at once. UDP has no such packet, so the host sends ICMP port unreachable instead. A UDP socket that has been connected, as the UDP article describes, will report it as "connection refused" on the next call. An unconnected socket just sees silence, which is why DNS clients with a wrong server address wait for a timeout rather than failing immediately.
The difference between drop and reject in a firewall is exactly this message. A rule that drops makes the sender wait for a timeout, typically a minute or more for TCP, before it gives up. A rule that rejects sends an ICMP administratively-prohibited (or a TCP RST) and the sender fails in one round trip. Reject is kinder to your own users and your own services; drop reveals less to strangers, which is why internet-facing firewalls usually drop and internal ones usually reject.
- nmap's UDP scan works by sending an empty datagram to each port and treating a port unreachable as "closed" and silence as "open or filtered", which is why UDP scans are slow and vague.
iptables -j REJECT --reject-with icmp-admin-prohibitedis the polite way to refuse;-j DROPis the silent one.- "No route to host" from an application usually means the last router could not ARP for the machine: it is off, or the address is wrong, or it is on a different VLAN than the router thinks.
- Port unreachable
- Type 3 code 3: the packet reached the host but no program is bound to that UDP port.
- Reject versus drop
- A firewall answering with an ICMP error or RST, versus silently discarding. Reject fails fast; drop times out.
Fragmentation needed and the path MTU black hole
This is the ICMP message that matters most and is blocked most, and the combination produces the strangest failure in networking: connections that work perfectly for small transfers and hang forever on large ones.
Every link has an MTU, the largest packet it carries, 1500 bytes on Ethernet and less through tunnels, VPNs and some DSL lines. A sender does not know the smallest MTU on the path. It could let routers fragment big packets, but fragmentation is slow and fragile, as the UDP article explains, so modern senders set the don't fragment bit on every packet. Now a router that meets a too-big packet must drop it and send back type 3 code 4, fragmentation needed, with the MTU of the link that would fit. The sender's kernel reads that number, remembers it for the destination, and resends in smaller packets. This is path MTU discovery, and it happens silently on every connection.
sequenceDiagram autonumber participant S as Server participant F as Firewall (drops ICMP) participant R as Router (next link 1400) participant C as Client S->>R: 1500-byte packet, DF set R-->>F: fragmentation needed, MTU 1400 Note over F: dropped, never reaches the server S->>R: retransmit, 1500 bytes, DF set Note over S: retransmit again, and again, then give up Note over C: small replies arrived fine, the big one never comes
The symptoms are distinctive once you know them. A TCP handshake works, because SYN packets are tiny. A small HTTP request works. A page under about 1 KB works. Then a bigger response, or an upload, or an SSH session the moment you run ls in a large directory, hangs. Nothing times out for a long while, because TCP is dutifully retransmitting a packet that can never arrive. The fix is never on the application.
- Let the message through. Allow ICMP type 3 code 4 (and ICMPv6 packet too big) inbound at every firewall you control. This is the actual fix.
- Clamp the MSS. A router on the small link can rewrite the TCP handshake so both sides agree to smaller segments up front, and no big packet is ever sent. Every home router doing PPPoE at 1492 does this, and
iptables ... -j TCPMSS --clamp-mss-to-pmtuis the Linux form. It fixes TCP only. - Probe without ICMP. Linux can be told to discover the MTU by trial, sending smaller packets when big ones are not acknowledged;
net.ipv4.tcp_mtu_probingturns it on. Slower, but immune to black holes.
- VPNs and WireGuard are the usual trigger: the tunnel's MTU is 1420 or lower, and a firewall somewhere blocks the message; WireGuard clients that "connect but cannot load pages" are this.
- Cloud VPCs use 9001-byte jumbo frames inside and 1500 outside, and the gateways between them rely on path MTU discovery; AWS documents the required ICMP rule for exactly this reason.
ping -M do -s 1472 hostsends a 1500-byte packet with DF set; if it fails with "message too long" or a fragmentation-needed reply, lower the size until it passes to find the path MTU by hand.tracepathautomates it.
"Small works, large hangs" is a path MTU black hole until proven otherwise. Check before touching the application, the database, or the load balancer configuration. The TCP article lists the same symptom from the other side.
- Path MTU discovery
- Learning the smallest MTU on a path by sending with DF set and reading the fragmentation-needed errors that come back.
- Black hole
- A path where big packets are dropped and the ICMP error is blocked, so the sender never learns to shrink them.
- MSS clamping
- A router rewriting the maximum segment size in TCP handshakes so segments fit the small link without discovery.
ICMPv6: the same idea with more jobs
IPv6 has its own ICMP, protocol 58, and it does everything ICMPv4 does plus several things IPv4 handled elsewhere. Blocking it does not degrade an IPv6 network. It disables it.
- Neighbour discovery replaces ARP. Finding the MAC address for an address on the local link is an ICMPv6 neighbour solicitation and advertisement. No ICMPv6, no way to send a single frame to a neighbour.
- Router advertisements tell hosts which router to use and what prefix the network has, which is how IPv6 hosts configure themselves without DHCP. No ICMPv6, no default route.
- Packet too big (type 2) is the fragmentation-needed message, and it is mandatory, because IPv6 routers are not allowed to fragment at all. Only the sender can, and only if it is told.
- Echo, time exceeded and destination unreachable work as in IPv4, with new type numbers (128, 129, 3, 1).
The practical consequence: a firewall rule copied from IPv4 that blocks "all ICMP" is a minor annoyance on IPv4 and a total outage on IPv6. Firewall guidance for IPv6 lists specific ICMPv6 types that must always be allowed, and every serious firewall product has that list built in.
- A dual-stack site that "works on IPv4 only" is very often an ICMPv6 rule; the IPv6 path silently fails to discover its neighbours or its MTU.
- Cloud security groups have a separate ICMPv6 protocol entry for this reason.
- ICMPv6
- ICMP for IPv6, protocol 58. Also carries neighbour discovery and router advertisements, so it cannot be blocked.
- Neighbour discovery
- IPv6's replacement for ARP, done with ICMPv6 messages.
Should you block ICMP?
The instinct to block ICMP comes from a real history. In the 1990s an oversized ping crashed unpatched machines (the "ping of death"), a broadcast ping with a spoofed source could flood a victim with thousands of replies (smurf attacks), and ICMP echo payloads were used to tunnel data past firewalls. Every one of those is fixed in every current stack, and the cure of blocking everything is now worse than the disease.
The sensible policy has three parts:
- Always allow destination unreachable (type 3, all codes, especially 4), time exceeded (type 11), and every ICMPv6 type the standards mark as required. These are what make TCP, UDP and IPv6 work at all.
- Rate limit, do not drop, echo requests. A host answering a few hundred pings a second is fine;
net.ipv4.icmp_ratelimitalready does this on Linux. Blocking ping makes your own monitoring and debugging harder and protects against nothing. - Drop the obsolete types: redirect, timestamp, source quench, address mask. Nothing legitimate uses them across a network boundary.
On Linux, the kernel already declines to answer broadcast pings, limits ICMP output, and validates every error against a real socket before acting on it, so the remaining risk from allowing ICMP is close to zero.
- AWS security groups deny everything inbound by default including ICMP; the standard first rule after SSH is "allow ICMP from anywhere" so path MTU and ping work.
- Cloudflare and other edges answer ping and generate proper unreachables for their addresses, because their customers' connections depend on it.
- Kubernetes network policies that select only TCP and UDP ports quietly allow ICMP, because the policy authors learned this lesson.
Recap
- ICMP is IP's feedback channel: messages inside IP, protocol 1, with a type and code, no ports. Errors carry the start of the packet they are about; queries are echo request and reply.
- No errors about errors and none about broadcasts, so it cannot storm.
- The kernel handles ICMP for you. Applications never see it, which is why it is forgotten until it is blocked.
pingmeasures reachability of the IP stack and round trip time, and nothing about services. The reply's TTL hints at distance.traceroutesends probes with TTL 1, 2, 3 and collects time exceeded from each router; the destination answers with port unreachable or echo reply. Stars are usually rate limiting; the return path is invisible; TCP probes to the real port get through firewalls.- Destination unreachable's code names the reason. Port unreachable is UDP's only "connection refused". Reject sends it and fails fast; drop stays silent and times out.
- Path MTU discovery depends on fragmentation-needed (type 3 code 4). Block it and big packets vanish while small ones work: the black hole. Fix by allowing the message, clamping MSS, or enabling MTU probing.
- ICMPv6 also does neighbour discovery and router advertisements and its packet-too-big is mandatory; blocking it turns IPv6 off.
- Policy: allow unreachable, time exceeded and required ICMPv6; rate limit echo; drop redirects and other relics.
Questions
Try answering each one out loud before opening it. Lead with the one-line answer, then a couple of points, then one extra detail.
What is ICMP for, and how does it differ from TCP and UDP?
It carries control and error messages about IP packets, sent by routers and hosts back to the sender; unlike TCP and UDP it has no ports and carries no application data, just a type, a code and the first bytes of the packet it is reporting on.
- It is IP protocol 1, handled by the kernel, invisible to applications.
- Errors are never sent about other errors or about broadcasts.
Echo request and reply are the only ICMP messages a user sends deliberately, through ping.
What does a successful ping prove, and what does it not?
It proves the remote IP stack is up and the path works in both directions for ICMP, and it gives the round trip time; it says nothing about whether any service is listening, whether TCP to a port will work, or whether the application is healthy.
- Hosts commonly block echo and still serve; load balancers answer echo without touching backends.
- Use a TCP connect or an HTTP check for services.
The reply's TTL, subtracted from 64 or 128, estimates the hop count to the host.
How does traceroute discover the routers on a path?
It sends probes with TTL 1, then 2, then 3 and so on; each router that decrements the TTL to zero drops the probe and sends back an ICMP time exceeded from its own address, which names that hop. The destination answers with port unreachable or an echo reply, which ends the trace.
- Probes can be UDP to high ports, ICMP echo, or TCP SYN to a real port for getting through firewalls.
- Three probes per hop give three round trip samples.
It shows the outbound path only; replies came back by whatever route each router preferred.
Traceroute shows stars at hop 3 but later hops are fine. Is there a problem?
Almost certainly not: that router is rate limiting or not generating ICMP time exceeded, while still forwarding normally, as the successful later hops prove.
- Loss that matters shows as loss at every hop after the faulty one.
- mtr's per-hop loss column over many probes separates the two cases.
A high latency at one hop followed by lower latency at the next means slow ICMP generation, not a slow link.
How does a UDP client learn that nothing is listening on a port?
The destination host sends ICMP destination unreachable, code 3 (port unreachable), and the kernel reports it as "connection refused" on the next call of a connected UDP socket; an unconnected socket sees only silence and must rely on a timeout.
- TCP uses a RST for the same purpose, which is why TCP connect fails immediately.
- nmap's UDP scan reads port unreachable as closed and silence as open or filtered.
Traceroute uses this same message to know its probe reached the destination.
What is the difference between a firewall dropping and rejecting a packet?
Drop discards silently so the sender waits for a timeout; reject sends back an ICMP administratively-prohibited or a TCP RST so the sender fails in one round trip.
- Reject is friendlier to your own users and services; drop reveals less to scanners.
- Internet-facing edges usually drop, internal firewalls usually reject.
Dropping also makes every misconfigured client hang for a minute per attempt instead of failing fast.
Explain path MTU discovery.
Senders set the don't-fragment bit on every packet; a router whose next link is too small drops the packet and returns ICMP fragmentation needed with the MTU that would fit; the sender's kernel records that per destination and resends in smaller packets.
- It happens silently on every TCP connection and is why fragmentation is rare.
- IPv6 makes it mandatory because routers there never fragment.
The cached path MTU per destination is visible with ip route get on Linux.
Connections work for small transfers but hang on large ones. What is happening and how do you fix it?
A path MTU black hole: a link on the path has a small MTU, and a firewall is dropping the fragmentation-needed message, so full-size packets are discarded and the sender never learns to shrink them while small packets pass. Fix by allowing ICMP type 3 code 4 (and ICMPv6 packet too big), by clamping the TCP MSS at the router on the small link, or by enabling MTU probing on the sender.
- Handshakes and small requests succeed, so it looks like an application bug.
- VPNs, tunnels and PPPoE lines are the usual small links.
ping -M do -s 1472 or tracepath finds the real path MTU by hand.
Why can ICMPv6 not be blocked the way ICMPv4 sometimes is?
Because ICMPv6 also carries neighbour discovery, which replaces ARP, and router advertisements, which give hosts their default route and prefix; and its packet-too-big message is mandatory since IPv6 routers never fragment. Block it and hosts cannot find neighbours, routers or a working MTU.
- Firewall standards list specific ICMPv6 types that must always pass.
- "Works on IPv4, not IPv6" is very often an ICMPv6 rule.
Cloud security groups treat ICMPv6 as a separate protocol entry for this reason.
What is a sensible ICMP firewall policy?
Always allow destination unreachable, time exceeded and the required ICMPv6 types; rate limit rather than drop echo requests; drop obsolete types like redirect, timestamp and source quench.
- The historical attacks (ping of death, smurf) are fixed in every current stack.
- Blocking echo makes your own monitoring harder and protects nothing.
Linux already rate limits ICMP output and ignores broadcast pings by default.
Why does an ICMP error include the first bytes of the original packet?
So the receiving kernel can match the error to the connection that caused it: the embedded IP header and first eight bytes contain the addresses and ports, which identify the socket to notify or the path MTU entry to update.
- Wireshark decodes this embedded packet, showing what a "host unreachable" was about.
- The kernel validates the embedded data against a real socket before acting, which blocks spoofed errors.
Modern stacks include more than eight bytes when they can, for the same reason.
Why does ping no longer need to be setuid root?
Sending raw ICMP used to require a raw socket and therefore root, so ping was setuid; Linux added an unprivileged ping socket type, and distributions grant the binary the CAP_NET_RAW capability instead of full root where the raw socket is still needed.
- Both remove a setuid-root binary from the system, shrinking the attack surface.
- traceroute using UDP probes never needed root; the ICMP and TCP variants did.
Inside a container without CAP_NET_RAW, ping may fail with "operation not permitted" for this reason.