Skip to main content

MAC Addresses and ARP

Every host has two addresses that matter here, working at two different layers:

  • A MAC address - a 48-bit hardware address baked into the network interface (aa:bb:cc:11:22:33). It is flat and permanent, and it only means anything on the local link.
  • An IP address - a logical, hierarchical address (10.0.0.5) assigned by the network. It can change, and its structure is what lets routers forward packets across the whole Internet.

Why both exist

IP addresses make global routing possible: their hierarchy lets a router decide "send anything for 10.0.0.0/24 out this interface" without knowing every host. But the actual delivery over one physical link is done by the link layer, which only understands MAC addresses. So a frame carries the destination MAC of the next hop on this link, while the packet inside carries the destination IP of the final host. To fill in that next-hop MAC, a host must translate an IP it knows into a MAC it does not - that is ARP, the Address Resolution Protocol.

Resolving an IP to a MAC

ARP works entirely within one subnet. When a host needs the MAC for a local IP:

  1. It checks its ARP cache first. A hit means no traffic at all.
  2. On a miss, it broadcasts an ARP request to every host on the link: "Who has 10.0.0.2? Tell 10.0.0.1."
  3. Only the host that owns that IP unicasts a reply back with its MAC. Everyone else ignores the request.
  4. The requester caches the IP -> MAC mapping so the next lookup is a hit. Entries time out after a while so stale mappings do not linger.

Step through a resolution - watch the broadcast reach every host, the owner reply, and the ARP table fill in. Resolve the same IP twice to see the cache hit:

requester (this host)broadcast recipientsreplying host
not cached
This host
10.0.0.1
aa:aa:aa:00:00:01
Host B
10.0.0.2
bb:bb:bb:00:00:02
Host C
10.0.0.3
cc:cc:cc:00:00:03
Host D
10.0.0.4
dd:dd:dd:00:00:04
ARP table (this host)
empty
Pick an IP and resolve its MAC address.

A worked example

Host 10.0.0.5 (MAC aa:bb:cc:11:22:33) needs to send to 10.0.0.9 on the same /24 and has no cache entry yet:

  1. It broadcasts an ARP request in a frame with destination MAC ff:ff:ff:ff:ff:ff, source MAC aa:bb:cc:11:22:33, carrying the question "Who has 10.0.0.9? Tell 10.0.0.5 (aa:bb:cc:11:22:33)". Every host on the subnet receives and decodes this frame, but only 10.0.0.9 matches.
  2. 10.0.0.9 (MAC dd:ee:ff:44:55:66) unicasts a reply straight back to aa:bb:cc:11:22:33: "10.0.0.9 is at dd:ee:ff:44:55:66".
  3. 10.0.0.5 stores 10.0.0.9 -> dd:ee:ff:44:55:66 in its ARP cache. A typical default timeout is around 60 seconds of inactivity (Linux's gc_stale_time is tunable but defaults in this range) - short enough that a host that changes its NIC or gets a new IP is not stuck with a stale mapping for long, long enough that a chatty pair of hosts almost never re-ARPs mid-conversation.
  4. Any packet to 10.0.0.9 sent within that window reuses the cached MAC with zero ARP traffic; one broadcast and one reply amortize over potentially thousands of subsequent packets.
ARP spoofing enables man-in-the-middle

ARP has no authentication - any host can send an ARP reply (even an unsolicited "gratuitous" one nobody asked for) claiming to own any IP, and receivers just overwrite their cache with it. An attacker on the same LAN exploits this by replying "10.0.0.1 (the gateway) is at <attacker's MAC>" to the victim, and "10.0.0.5 (the victim) is at <attacker's MAC>" to the gateway. Both sides now send the attacker their traffic, which it silently forwards on after reading (or altering) it - a classic Layer-2 man-in-the-middle, invisible to anything watching only IP-layer traffic. Mitigations include switch-level Dynamic ARP Inspection and static ARP entries for critical hosts; see Common Network Attacks for the fuller picture.

ARP is local-only

ARP never crosses a router. It resolves addresses only within the same subnet, because a broadcast stays on the local link. So how does a host reach something off-subnet, like a server on the Internet?

It sends the frame to the default gateway instead. The host keeps the destination IP of the far-off server in the packet, but ARPs for the gateway's MAC and puts that in the frame. The router receives the frame, strips the link-layer header, looks up the destination IP, and forwards on - re-doing ARP for the next hop on the next link. IP addressing stays end-to-end; MAC addressing is rewritten hop by hop.

caution

The MAC address in a frame only ever names something on the current link. If the destination is off-subnet, the destination MAC is the gateway's, not the final host's - even though the destination IP is the final host's. Confusing these two is a classic source of "it pings locally but not remotely" bugs.