Networking for beginners. Part 7: ARP
All parts of the series “Networking for beginners”In the fifth part a host decided whether the destination was a neighbour or a gateway was needed, and in the sixth a router at every hop wrapped the packet in a new frame with the MAC address of the next router. In both cases we silently assumed that the neighbour's MAC address was somehow known. But the packet only contains the destination IP address, while the Ethernet frame needs a MAC. Someone has to translate one into the other. That is what ARP does — the Address Resolution Protocol, RFC 826, one of the oldest and simplest protocols in the stack, and at the same time one of the most important: without it, not a single packet leaves the machine.
The problem
Host 192.168.1.10 has an IP packet for 192.168.1.1 — the gateway. The algorithm from the fifth part said: a neighbour, send directly. That requires an Ethernet frame, and in it the destination MAC address. The host does not have it.
The mapping could be hard-wired into the configuration, and in 1982 that is what people did: a file with a list of "address — MAC" on every machine. Bearable with a dozen machines, not with a hundred, and replacing a network card means editing every file on the network. ARP replaces the file with a question asked out loud: the host asks the whole segment who has this IP address, and the owner answers.
An important boundary: ARP works only within a segment. A host never asks for the MAC address of a server on the internet: the "neighbour or gateway" decision is made earlier, and the ARP request goes out either for the neighbour's address or for the gateway's. Routers do not forward ARP requests: they are broadcast frames, and broadcast does not pass through a router.
Request and reply
An ARP packet lives directly in an Ethernet frame with EtherType 0x0806, without an IP header: it sits on a level of its own between L2 and L3. It is 28 bytes long:
bytes: 2 2 1 1 2 6 4 6 4
┌───────┬───────┬─────┬─────┬───────┬─────────┬────────┬─────────┬────────┐
│ HTYPE │ PTYPE │HLEN │PLEN │ OPER │ SHA │ SPA │ THA │ TPA │
│ =1 │=0x0800│ =6 │ =4 │ 1 / 2 │ MAC │ IP │ MAC │ IP │
│ │ │ │ │ │ sender │ sender │ target │ target │
│ │ │ │ │ │ │ │ │ │
└───────┴───────┴─────┴─────┴───────┴─────────┴────────┴─────────┴────────┘
The first four fields say which addresses we are translating: hardware type 1 (Ethernet) of length 6, protocol type 0x0800 (IPv4) of length 4. ARP was designed to be universal, but in practice it is only used for this pair. OPER is 1 for a request, 2 for a reply. Then come two "MAC, IP" pairs: the sender's (Sender Hardware/Protocol Address) and the target's.
The exchange:
192.168.1.10 192.168.1.1
3c:52:82:1a:2b:3c d8:47:32:0a:0b:0c
│ │
├── request, frame to ff:ff:ff:ff:ff:ff ──────────►│ (and to everyone else)
│ SHA 3c:52:82:1a:2b:3c SPA 192.168.1.10 │
│ THA 00:00:00:00:00:00 TPA 192.168.1.1 │
│ │
│◄─ reply, frame to 3c:52:82:1a:2b:3c ─────────────┤
│ SHA d8:47:32:0a:0b:0c SPA 192.168.1.1 │
│ THA 3c:52:82:1a:2b:3c TPA 192.168.1.10 │
The request goes out as a broadcast frame: everyone in the segment receives it. The THA field is zeros — that is exactly what we are looking for. Each host, on receiving the request, compares TPA with its own addresses. Not mine — drop it. Mine — reply.
The reply goes out as a unicast frame straight to the asker: its MAC is known from the request's SHA. In the reply the sender and target swap places, and SHA holds the address being looked for.
In tcpdump it is two lines:
$ sudo tcpdump -i eth0 -n -e arp
3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.1 tell 192.168.1.10, length 28
d8:47:32:0a:0b:0c > 3c:52:82:1a:2b:3c, ethertype ARP (0x0806), length 60: Reply 192.168.1.1 is-at d8:47:32:0a:0b:0c, length 46
"Who-has, tell" and "is-at" are tcpdump's wording, and they convey the meaning exactly. A length of 42 is 14 bytes of Ethernet and 28 of ARP; the reply is shown padded to 60 because it was captured after the network card.
A side effect built into the protocol: everyone who received the request learns the asker's MAC address from SHA and SPA. The gateway, when replying, already knows where to send the reply and does not need a request of its own in the other direction; hosts the request was not addressed to do not create an entry for someone else by default, but if they already had one, they update it. So one pair of frames saves a second pair.
The neighbour table
Asking before every packet would be wasteful: it is a broadcast frame to the whole segment and a wait for the reply. So the result is cached in the neighbour table (the ARP cache in older systems; in Linux, the neighbour table, because the same table also serves IPv6):
$ ip neigh
192.168.1.1 dev eth0 lladdr d8:47:32:0a:0b:0c REACHABLE
192.168.1.20 dev eth0 lladdr 00:11:22:33:44:55 STALE
192.168.1.30 dev eth0 FAILED
Entries do not live forever: a machine may change its card, an address may move to another machine. An entry goes through states, and in Linux they are:
| State | Means |
|---|---|
INCOMPLETE |
a request has been sent, no reply yet; packets for this address wait in a queue (up to three) |
REACHABLE |
a reply was received, or the neighbour recently confirmed itself; the entry is used without checks |
STALE |
more than ~30 seconds (randomly 15 to 45) have passed since confirmation; it can be used, but must be rechecked on first use |
DELAY |
a STALE entry was used; the stack gives the neighbour 5 seconds to confirm itself (for example, with a TCP reply) |
PROBE |
it did not confirm: send a unicast request straight to the known MAC, up to three times |
FAILED |
no reply; packets for this address are dropped with a "host unreachable" error |
PERMANENT |
a static entry, added by hand, not checked and never expires |
Two details are worth noting. First, rechecking uses a unicast request rather than broadcast, so as not to bother the segment: broadcast is reserved for when the MAC is not known at all. Second, confirmation is not only an ARP reply: if any upper-layer response arrives from the neighbour that the stack can tie to a packet it sent (a TCP ACK, for example), the entry goes back to REACHABLE. On a network with live traffic ARP is barely heard.
Windows follows similar logic: an entry stays Reachable for 15 to 45 seconds, then moves to Stale and is checked when accessed. The table is arp -a or Get-NetNeighbor.
What happens on the first packet
Let's put it all together. The host has just booted, the table is empty, and the user runs ping 203.0.113.10:
- The stack builds an ICMP packet; the routing table says: via gateway
192.168.1.1, interfaceeth0. 192.168.1.1is not in the neighbour table. The packet is queued, the entry goes toINCOMPLETE, and a broadcast ARP request goes out.- A fraction of a millisecond later the reply arrives, the entry becomes
REACHABLE, and the queued packet is sent in a frame tod8:47:32:0a:0b:0c. - Subsequent packets go out immediately using the entry.
If the gateway does not respond (it is off, the address is wrong, the cable is in the wrong VLAN), the stack repeats the request three times at one-second intervals, after which the entry becomes FAILED, and ping prints Destination Host Unreachable. Note: this message is generated by your own stack, it did not come from the network, and it means precisely "the neighbour did not answer ARP". It is the most precise diagnosis ping gives: if you see it, the problem is not on the internet but a metre away from you.
Gratuitous ARP and duplicate detection
The protocol allows a frame that nobody asked for. Gratuitous ARP is a request (more rarely a reply) in which SPA and TPA are the same address, the host's own, sent as a broadcast:
3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.10 tell 192.168.1.10, length 28
The host seemingly asks "who has 192.168.1.10?" while being the owner itself. There are two purposes:
- To announce itself. Everyone who has an entry for
192.168.1.10will update the MAC in it. This is needed when an address has moved: a failover pair of servers sharing a virtual address sends gratuitous ARP when switching to the standby, so that neighbours and switches stop sending frames to the dead MAC. Without it, neighbours would keep sending frames into the void for tens of seconds more, until the entries go stale. The same applies when a virtual machine migrates to another host. - To detect a duplicate. If someone answered such a request, the address is already taken. The stack reports a conflict.
For the second purpose there is also a careful variant, the ARP probe (RFC 5227): a request with SPA 0.0.0.0. Since it carries no sender address, nobody updates their tables based on an address that has not been confirmed yet. This is how an address is checked before it is put to use: a host that received an address from a DHCP server (part 11) sends a probe and, if it hears a reply, declines the lease. You can try it manually: arping -D -I eth0 192.168.1.10.
Address conflicts
Two machines with the same IP address in a segment is a classic. Both answer the ARP request, and the one whose reply arrived last ends up in the asker's table. Half a minute later comes a recheck, and the other one may win. Symptoms: connectivity to the address "flickers", the MAC of the entry in ip neigh keeps changing, the switch sees one address on different ports (but it does not care: to it these are different MACs), Windows shows "an IP address conflict was detected". The easiest way to catch the culprit is to capture ARP replies: tcpdump -n -e arp reply will show both MACs, and the OUI will tell you both devices.
Proxy ARP
A router can answer ARP requests for other people's addresses: for those it knows a route to, even though they are not in this segment. The host with the wrong mask from the fifth part, which considers an address from another network to be a neighbour, sends an ARP request; the real neighbour stays silent, while the router answers with its own MAC, the packet goes to the router, and the router delivers it. The host never learns about its mistake. This is proxy ARP, and it was in use back when default gateways could not yet be configured on every host.
Today it shows up in two places: VPN servers make clients "visible" on the local network by answering for their addresses, and some ISPs gather subscribers into one network with a /32 mask on each port, while traffic between them goes through the router thanks to proxy ARP. In other cases proxy ARP masks configuration errors more than it helps, and on Linux it is off (net.ipv4.conf.all.proxy_arp = 0).
Why ARP is so easy to fool
The protocol has neither authentication nor verification: whoever answered is believed. An attacker in the same segment sends the victim an ARP reply (or a gratuitous request) "192.168.1.1 is-at <attacker's MAC>", and the victim updates the gateway entry. Now all its packets to the internet go to the attacker, who forwards them to the real gateway, and the victim notices nothing. Do the same in the other direction — tell the gateway about the victim — and you get full interception both ways: ARP spoofing, the simplest way to become a man in the middle on a local network. Half of the "smart" guest portals are built on the same thing: that is how a café intercepts traffic before you log in.
Protection lives not in ARP but around it:
- Static entries
ip neigh add 192.168.1.1 lladdr d8:47:32:0a:0b:0c dev eth0 nud permanent— for critical addresses. Does not scale. - Dynamic ARP Inspection on managed switches: the switch watches which address the DHCP server gave to which port and drops ARP replies whose SPA/SHA do not match that table.
- Client isolation in guest networks: the switch or access point does not forward frames between client ports, and there is nobody left to ask about neighbours.
- Encryption higher up: TLS and VPNs do not prevent packets from being intercepted, but make what was intercepted useless.
In IPv6 the same problem is solved by a different protocol — Neighbor Discovery (part 14), with multicast requests instead of broadcast and the option of cryptographic protection, which, however, almost nobody uses.
Seeing it for yourself
The neighbour table and clearing it (after clearing, the first packet to each neighbour will go through an ARP request again — handy for watching the exchange):
$ ip neigh
$ sudo ip neigh flush all
Ask manually, without involving the stack:
$ sudo arping -I eth0 -c 3 192.168.1.1
ARPING 192.168.1.1 from 192.168.1.10 eth0
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c] 0.612ms
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c] 0.588ms
Unicast reply from 192.168.1.1 [d8:47:32:0a:0b:0c] 0.590ms
arping works where ICMP may be blocked by the host's firewall: ARP is answered by the stack itself and is not filtered. If an address is silent to ping but answers arping, the machine is alive and it is specifically ICMP that is blocked. Two replies with different MACs for one address mean a conflict.
To watch the whole exchange, together with the first packet that triggered it:
$ sudo ip neigh flush all && sudo tcpdump -i eth0 -n -e -c 4 'arp or icmp' &
$ ping -c 1 192.168.1.1
The output will show the request, the reply, and only then the echo request and echo reply. The time difference between the first and third lines is the cost of ARP, usually under a millisecond.
On Windows: arp -a shows the table, arp -d * clears it, Get-NetNeighbor -State Reachable in PowerShell filters by state.
Key takeaways
- ARP translates a neighbour's IP address into a MAC address: a broadcast request "who has this address", a unicast reply "I do, here is the MAC". 28 bytes directly in an Ethernet frame, without IP.
- The "neighbour or gateway" decision is made before ARP; requests do not leave the segment, and for other networks the gateway's MAC is requested.
- Replies are cached in the neighbour table; an entry goes REACHABLE → STALE → a check with a unicast request → FAILED. Ordinary traffic from the neighbour also confirms an entry.
Destination Host Unreachablein ping means the neighbour did not answer ARP: the problem is close by.- Gratuitous ARP announces an address and updates other hosts' tables when an address moves; an ARP probe checks whether an address is free.
- ARP has no checks: anyone in the segment can claim to be the gateway. Protection lives on the switch and in the layers above.
Next — Part 8: ICMP: the protocol the network uses to report errors and on which ping and traceroute are built. We will go through the message types, how traceroute finds every router along the path using TTL, how Path MTU Discovery works and why "a firewall that blocks all ICMP" breaks the network.
Only registered users can leave a comment.
Sign in or register to leave a comment.