ARP and ICMP
How a host finds the MAC address that belongs to an IP address, and how ICMP reports errors and makes ping and traceroute possible.
The problem ARP solves
Two layers of addressing are at work when a host sends a packet on an Ethernet or Wi-Fi network. The Internet Protocol (IP) address (layer 3) says which host the packet is for. But on the local link the packet travels inside a frame, and the frame must carry the destination's media access control (MAC) address (layer 2) — switches forward frames by MAC address and know nothing about IP (see Ethernet and MAC Addresses).
So a host that wants to reach 192.168.1.20 knows the IP address, usually from the Domain Name System (DNS) or configuration, but not the MAC address that goes with it. The Address Resolution Protocol (ARP), defined in Request for Comments (RFC) 826, fills that gap: it asks the local network "who has this IPv4 address?" and learns the answer. ARP works only within one broadcast domain; it never crosses a router.
How ARP works
Request and reply
In the example below, host A (192.168.1.10) wants to send to host B (192.168.1.20) in the same subnet, and has no entry for B yet.
-
ARP request (broadcast). A sends a frame to the
broadcast MAC address
ff:ff:ff:ff:ff:ff. It says, in effect, "Who has 192.168.1.20? Tell 192.168.1.10", and it includes A's own IP and MAC address. The switch floods it to every port in the virtual local area network (VLAN), so every host receives it. - Everyone else ignores it. Host C sees that 192.168.1.20 is not its address and discards the request.
- ARP reply (unicast). B recognizes its own address. It first stores A's IP-to-MAC mapping (it will probably need to answer A anyway), then sends a reply straight to A's MAC address: "192.168.1.20 is at 00:00:5e:00:53:14".
- A stores the answer in its cache and sends the waiting packet in a unicast frame to B.
The ARP cache
Broadcasting before every packet would be wasteful, so each host and router keeps an ARP cache (ARP table): a list of recently learned IP-to-MAC mappings. Entries expire after a while, so a device that changes its network card, or an address that moves to another device, is picked up again. Desktop operating systems re-check entries after seconds to minutes of silence; Cisco IOS keeps them for four hours by default.
On Linux, the ip neigh command shows the cache:
ip neigh show dev eth0
192.168.1.1 dev eth0 lladdr 00:00:5e:00:53:01 REACHABLE 192.168.1.20 dev eth0 lladdr 00:00:5e:00:53:14 STALE
REACHABLE means the mapping was confirmed recently;
STALE means it is still used but will be verified again
before long. On Windows, arp -a shows the same
information per interface:
arp -a
Interface: 192.168.1.10 --- 0xb Internet Address Physical Address Type 192.168.1.1 00-00-5e-00-53-01 dynamic 192.168.1.20 00-00-5e-00-53-14 dynamic 192.168.1.255 ff-ff-ff-ff-ff-ff static 224.0.0.22 01-00-5e-00-00-16 static
The static entries are fixed mappings for the subnet
broadcast and a multicast group, not learned through ARP. On a Cisco
router or layer 3 switch:
R1# show ip arp
Protocol Address Age (min) Hardware Addr Type Interface
Internet 192.168.1.1 - 0000.5e00.5301 ARPA GigabitEthernet0/0
Internet 192.168.1.10 3 0000.5e00.530a ARPA GigabitEthernet0/0
Internet 192.168.1.20 12 0000.5e00.5314 ARPA GigabitEthernet0/0
- Age (min) — minutes since the entry was learned; a dash marks the router's own interface address.
- Hardware Addr — the MAC address, in Cisco's dotted notation with three groups of four hex digits.
- Type ARPA — Ethernet (ARPA) encapsulation.
In practice, show ip arp is where you start when a
ticket says "host unreachable" but the link is up: no entry, or an
Incomplete entry, means the router asked and nobody
answered.
Remote destinations: ARP for the gateway
ARP only works on the local link, so what does host A do when it sends to 203.0.113.50 on the internet? It compares the destination with its own subnet (see IPv4 Addressing):
- Destination in my subnet: ARP for the destination itself and deliver the frame directly.
- Destination elsewhere: ARP for the default gateway's IP address, and send the frame to the gateway's MAC address. The IP packet inside still carries 203.0.113.50 as destination.
import ipaddress
me = ipaddress.ip_interface("192.168.1.10/24")
gateway = ipaddress.ip_address("192.168.1.1")
def arp_target(destination):
"""Which IP address does the host ARP for?"""
address = ipaddress.ip_address(destination)
if address in me.network:
return address # same subnet: deliver directly
return gateway # other subnet: hand to the router
for destination in ["192.168.1.20", "203.0.113.50", "192.168.2.20"]:
print(f"to {destination:13} -> ARP for {arp_target(destination)}")
to 192.168.1.20 -> ARP for 192.168.1.20 to 203.0.113.50 -> ARP for 192.168.1.1 to 192.168.2.20 -> ARP for 192.168.1.1
Each router along the way repeats the trick on its next link: it removes the old frame, looks up the next hop, and uses ARP (or its cache) to find that next hop's MAC address. The source and destination IP addresses stay the same end to end; the MAC addresses change at every hop.
Gratuitous ARP and ARP spoofing
Gratuitous ARP
A gratuitous ARP is an ARP message that nobody asked for: a host broadcasts its own IP-to-MAC mapping, with its own address as both sender and target IP. It is used to:
- Announce a new or changed mapping, so other hosts update their caches at once — for example when a server's network card is replaced.
- Fail over a shared address: when a standby router or firewall takes over a virtual gateway address (as with the Hot Standby Router Protocol, HSRP, or the Virtual Router Redundancy Protocol, VRRP), it sends gratuitous ARPs so hosts and switches move traffic to it immediately.
- Detect duplicates: before using an address, a host sends an ARP probe (sender IP 0.0.0.0, RFC 5227) and then announces it with a gratuitous ARP; an answer to the probe means someone else already has the address, which is how an operating system can warn about an IP address conflict.
ARP spoofing
ARP has no authentication: hosts accept replies, even unrequested ones, from anyone on the link. An attacker on the same VLAN can therefore send forged ARP messages claiming "the gateway's IP is at my MAC address". Victims then send their traffic to the attacker, who can read or change it before passing it on — a man-in-the-middle attack. On Cisco switches, Dynamic ARP Inspection (DAI) checks ARP messages against the Dynamic Host Configuration Protocol (DHCP) snooping table and drops forged ones. More on Network Security Basics.
IPv6: Neighbor Discovery instead of ARP
IPv6 does not use ARP. The Neighbor Discovery Protocol (NDP), defined in RFC 4861, performs address resolution and more, using Internet Control Message Protocol for IPv6 (ICMPv6) messages and multicast instead of broadcast (see IPv6 Basics).
| Task | IPv4 | IPv6 (NDP) |
|---|---|---|
| Ask for a MAC address | ARP request (broadcast) | Neighbor Solicitation (NS, ICMPv6 type 135), to a solicited-node multicast group |
| Answer | ARP reply (unicast) | Neighbor Advertisement (NA, type 136) |
| Announce own mapping | Gratuitous ARP | Unsolicited Neighbor Advertisement |
| Find routers and the prefix | (DHCP or manual) | Router Solicitation (133) and Router Advertisement (134) |
| View the cache | show ip arp, ip neigh |
show ipv6 neighbors, ip -6 neigh
|
ICMP
IP itself only delivers packets, and when delivery fails it says nothing. The Internet Control Message Protocol (ICMP) (RFC 792) is its companion for feedback: routers and hosts use it to report errors ("I cannot reach that network", "this packet's TTL ran out") and to run simple tests. ICMP messages travel inside IP packets (IP protocol number 1) but are part of the network layer; they have no ports. ICMPv6 plays the same role for IPv6 and adds Neighbor Discovery.
Common types and codes
Every ICMP message has a type (the kind of message) and a code (the detail).
| Type | Name | Common codes | Where you meet it |
|---|---|---|---|
| 0 | Echo reply | 0 | The answer to ping |
| 3 | Destination unreachable | 0 network, 1 host, 3 port unreachable, 4 fragmentation needed, 13 administratively prohibited | No route, no answer to ARP, closed User Datagram Protocol (UDP) port, filtered |
| 5 | Redirect | 1 for host | A router tells a host about a better gateway on the same link |
| 8 | Echo request | 0 | Sent by ping |
| 11 | Time exceeded | 0 TTL expired in transit, 1 reassembly time exceeded | Traceroute, routing loops |
How ping works
ping sends ICMP echo requests (type 8) to a target and
waits for echo replies (type 0). Each reply proves that the path
works in both directions, and the time between request and
reply is the round-trip time (RTT). Linux sends until you stop it,
so limit the count with -c:
ping -c 4 192.0.2.10
PING 192.0.2.10 (192.0.2.10) 56(84) bytes of data. 64 bytes from 192.0.2.10: icmp_seq=1 ttl=57 time=12.4 ms 64 bytes from 192.0.2.10: icmp_seq=2 ttl=57 time=11.9 ms 64 bytes from 192.0.2.10: icmp_seq=3 ttl=57 time=12.1 ms 64 bytes from 192.0.2.10: icmp_seq=4 ttl=57 time=12.0 ms --- 192.0.2.10 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 11.900/12.100/12.400/0.187 ms
Windows sends four requests by default:
ping 192.0.2.10
Pinging 192.0.2.10 with 32 bytes of data:
Reply from 192.0.2.10: bytes=32 time=12ms TTL=57
Reply from 192.0.2.10: bytes=32 time=13ms TTL=57
Reply from 192.0.2.10: bytes=32 time=12ms TTL=57
Reply from 192.0.2.10: bytes=32 time=12ms TTL=57
Ping statistics for 192.0.2.10:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 12ms, Maximum = 13ms, Average = 12ms
If the answer is not an echo reply but "Destination host unreachable" or "TTL expired in transit", that is an ICMP error message from a router on the way, and the address after "Reply from" tells you which router gave up. How to use ping in a structured search for a fault is covered on Troubleshooting.
TTL and traceroute
Time to live
Every IPv4 header has an 8-bit time to live (TTL) field. The sender sets it (Linux and macOS start at 64, Windows at 128, Cisco IOS at 255), and every router that forwards the packet lowers it by 1. A router that decrements it to 0 discards the packet and sends an ICMP time exceeded message (type 11, code 0) back to the source. This stops packets from circling forever when a routing loop exists. IPv6 has the same field under the name hop limit.
The simulation below follows one packet from host A through three routers:
ROUTERS = ["192.168.1.1", "198.51.100.1", "198.51.100.66"]
DESTINATION = "203.0.113.50"
def send(ttl):
"""Forward one packet; return who answers and with what."""
for router in ROUTERS:
ttl -= 1 # every router decrements the TTL
if ttl == 0:
return router, "ICMP time exceeded"
return DESTINATION, f"delivered with TTL {ttl}"
print(send(64))
print(send(2))
('203.0.113.50', 'delivered with TTL 61')
('198.51.100.1', 'ICMP time exceeded')
This is also why the ping output above says ttl=57: if
the server is a Linux machine that started at 64, the reply passed
64 − 57 = 7 routers on its way back.
How traceroute uses TTL
Traceroute turns the TTL rule into a map of the path. It sends a probe with TTL 1: the first router decrements it to 0, drops it and answers with time exceeded, revealing its address. Then TTL 2 reveals the second router, and so on, until a probe reaches the destination and gets a different answer.
The two common tools send different probes. Linux
traceroute sends UDP datagrams to unlikely high port
numbers (from 33434 up), so the destination answers with destination
unreachable, code 3 (port unreachable). Windows
tracert sends ICMP echo requests, so the destination
answers with an echo reply. Either way, the answer that is
not time exceeded tells the tool it has arrived. The same
logic in Python:
ROUTERS = ["192.168.1.1", "198.51.100.1", "198.51.100.66"]
DESTINATION = "203.0.113.50"
def probe(ttl):
for router in ROUTERS:
ttl -= 1
if ttl == 0:
return router, "time exceeded"
return DESTINATION, "port unreachable"
for ttl in range(1, 31): # 30 hops maximum, like traceroute
source, message = probe(ttl)
print(f"{ttl:2} {source:14} {message}")
if message != "time exceeded":
break
1 192.168.1.1 time exceeded 2 198.51.100.1 time exceeded 3 198.51.100.66 time exceeded 4 203.0.113.50 port unreachable
A real traceroute sends three probes per TTL and shows the round
trip time of each. The -n option skips reverse DNS
lookups, which makes the output appear faster:
traceroute -n 203.0.113.50
traceroute to 203.0.113.50 (203.0.113.50), 30 hops max, 60 byte packets 1 192.168.1.1 0.512 ms 0.478 ms 0.455 ms 2 198.51.100.1 8.231 ms 8.190 ms 8.402 ms 3 198.51.100.66 9.874 ms 9.802 ms 9.911 ms 4 203.0.113.50 12.307 ms 12.268 ms 12.291 ms
A line of * * * means no answer arrived for that TTL —
often a router that is configured not to send time-exceeded
messages, not necessarily a fault. Reading traceroute output in
practice is covered on
Troubleshooting.
Why blocking all ICMP breaks things
Because ping reveals which hosts exist, some administrators block ICMP entirely at the firewall. That hides echo replies, but it also drops the error messages that other protocols depend on.
The best-known victim is Path Maximum Transmission Unit Discovery (PMTUD, RFC 1191). A sender sets the "don't fragment" bit and sends packets as large as its own link allows, often 1500 bytes. When a router on the path has a smaller maximum transmission unit (MTU), for example on a virtual private network (VPN) tunnel, it drops the packet and returns destination unreachable, code 4 (fragmentation needed), with the MTU that would fit. The sender then makes its packets smaller. If a firewall drops that ICMP message, the sender never learns: the Transmission Control Protocol (TCP) handshake and small requests work, but large transfers hang. This is called a PMTUD black hole, and it produces some of the most confusing tickets ("the website loads, but downloads stall").
IPv6 depends on ICMPv6 even more: routers never fragment, so Packet Too Big messages are essential, and Neighbor Discovery runs over ICMPv6 too. The sensible policy is to allow the error types (destination unreachable, time exceeded, packet too big) and decide separately about echo requests, rather than blocking ICMP as a whole.