← Back to Logs

How ARP Actually Works: The Invisible Protocol That Maps IP to Hardware

Try the interactive lab for this articleTake the quiz (6 questions · ~4 min)

Every time you open a browser, SSH into a server, or ping a host on your local network, a small, silent exchange happens before any IP packet can leave your machine. No TCP handshake, no DNS query, nothing at the application layer. The protocol responsible is ARP, the Address Resolution Protocol, and it has been doing this job since RFC 826 was published in November 1982. David Plummer wrote the original specification in four pages. Forty-four years later, the protocol is functionally unchanged, and every Ethernet-connected device on every IPv4 LAN in the world depends on it.

This post is a thorough technical walkthrough. We will look at packet formats down to the byte, trace real tcpdump captures, examine the Linux kernel's ARP state machine, explore attacks and defences, and discuss why a protocol designed before most of its current users were born remains critical infrastructure.

1. What ARP Does and Why It Exists

The internet protocol stack has a well-known layering problem. IP (Layer 3) operates with logical addresses: 32-bit numbers like 192.168.1.42. Ethernet (Layer 2) operates with hardware addresses: 48-bit MAC addresses like 02:ab:cd:ef:01:23. These two addressing schemes exist in completely separate namespaces. A router, a switch, a NIC; none of them can derive one from the other through calculation alone.

When host A wants to send an IP packet to host B on the same LAN segment, it knows B's IP address (from DNS, configuration, or some higher-layer protocol). But to construct an Ethernet frame, A needs B's MAC address in the destination field of the L2 header. Without that MAC address, the frame cannot be built, the NIC cannot transmit it, and the switch has no idea where to forward it.

ARP bridges this gap. It provides a dynamic mapping between Layer 3 (protocol) addresses and Layer 2 (hardware) addresses. The mapping is:

IP address  -->  MAC address

That is the entire purpose of the protocol. No authentication, no encryption, no sequencing, no retransmission. A host broadcasts a question ("Who has 192.168.1.1? Tell 192.168.1.42"), and the owner of that IP address responds with its MAC address. The asking host caches the result, builds the Ethernet frame, and sends the IP packet on its way.

A few things worth noting up front:

  • ARP is not IP. ARP frames carry EtherType 0x0806, not 0x0800 (IPv4). ARP sits beside IP, not above it.
  • ARP is specific to the link layer technology. The protocol was designed to be generic (it supports different hardware types and protocol types), but in practice, the overwhelming majority of ARP traffic is Ethernet-to-IPv4 resolution.
  • ARP operates within a single broadcast domain. It never crosses a router boundary (unless a proxy is involved, which we will cover later).

The Alternative: Static Mapping

You could, in theory, manually configure every IP-to-MAC mapping on every host. Some high-security environments do exactly this. But on a network with hundreds or thousands of devices, with laptops joining and leaving, VMs spinning up and shutting down, and DHCP leases rotating, static mapping is impractical. ARP automates what would otherwise be an operational nightmare.

2. The ARP Packet Format

An ARP message is small. The fixed header is 8 bytes, and for Ethernet/IPv4 (the common case), the addresses add another 20 bytes, giving a total ARP payload of 28 bytes. This payload rides inside an Ethernet frame with no IP header, no UDP header, nothing else.

Here is the field-by-field breakdown for an Ethernet/IPv4 ARP packet:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Hardware Type         |         Protocol Type         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  HW Addr Len  | Proto Addr Len|           Opcode              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Sender Hardware Address (bytes 0-3)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sender HW Addr (bytes 4-5)   |  Sender Protocol Addr (0-1)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sender Proto Addr (bytes 2-3)|  Target HW Addr (bytes 0-1)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Target Hardware Address (bytes 2-5)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Target Protocol Address (bytes 0-3)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Field Descriptions

Hardware Type (2 bytes): Identifies the link-layer technology. Ethernet is 0x0001. You will almost never see anything else in the wild, but the field exists because ARP was designed to be generic. IANA maintains the full list; values exist for Token Ring (0x0006), FDDI, and others that are now museum pieces.

Protocol Type (2 bytes): Identifies the network-layer protocol whose address we are resolving. For IPv4, this is 0x0800, which is the same value used as the EtherType for IPv4 frames. This symmetry is intentional.

Hardware Address Length (HLEN, 1 byte): The length in bytes of a hardware address. For Ethernet (48-bit MAC), this is 6.

Protocol Address Length (PLEN, 1 byte): The length in bytes of a protocol address. For IPv4 (32-bit), this is 4.

Opcode (2 bytes): Specifies the operation:

  • 1 = ARP Request
  • 2 = ARP Reply
  • 3 = RARP Request (reverse ARP, obsolete)
  • 4 = RARP Reply (obsolete)

Other opcodes exist (InARP, for example, uses 8 and 9), but they are niche.

Sender Hardware Address (6 bytes for Ethernet): The MAC address of the host sending this ARP message. In a request, this is the MAC of the machine asking the question. In a reply, this is the MAC of the machine answering.

Sender Protocol Address (4 bytes for IPv4): The IP address of the sender.

Target Hardware Address (6 bytes for Ethernet): In a request, this field is typically all zeros (00:00:00:00:00:00) because the sender does not yet know the target's MAC. In a reply, this is the MAC address of the original requester.

Target Protocol Address (4 bytes for IPv4): The IP address being resolved. In a request, this is the IP whose MAC we want to discover. In a reply, this is the IP of the original requester.

The Encapsulating Ethernet Frame

The ARP payload does not travel alone. It is wrapped in a standard Ethernet frame:

+------------------+------------------+-----------+-------------+-----+
| Dst MAC (6 bytes)| Src MAC (6 bytes)| EtherType | ARP Payload | FCS |
|                  |                  |  0x0806   |  (28 bytes) |     |
+------------------+------------------+-----------+-------------+-----+

For an ARP request, the destination MAC is ff:ff:ff:ff:ff:ff (broadcast). For an ARP reply, the destination MAC is the unicast address of the original requester.

Because the minimum Ethernet frame payload is 46 bytes, and the ARP payload is only 28 bytes, the frame is padded with 18 bytes of zeros to meet the minimum. You will see this padding in Wireshark captures.

3. ARP Request and Reply, Step by Step

Let us trace a concrete example. Suppose Dimitris has a workstation at 192.168.1.42 (MAC aa:bb:cc:dd:ee:01) and wants to SSH into a server at 192.168.1.10 (MAC aa:bb:cc:dd:ee:02). Both are on the same /24 subnet.

Step 1: The Workstation Checks Its ARP Cache

Before sending any ARP traffic, the kernel checks its local ARP cache for an existing mapping of 192.168.1.10. If a valid entry exists, no ARP is needed; the kernel constructs the Ethernet frame using the cached MAC and sends the IP packet immediately.

If no entry exists (or the existing entry has expired), the kernel must resolve the address.

Step 2: The ARP Request (Broadcast)

The workstation constructs an ARP request:

Ethernet Header:
  Dst: ff:ff:ff:ff:ff:ff  (broadcast)
  Src: aa:bb:cc:dd:ee:01
  EtherType: 0x0806
 
ARP Payload:
  Hardware Type: 0x0001 (Ethernet)
  Protocol Type: 0x0800 (IPv4)
  HLEN: 6
  PLEN: 4
  Opcode: 1 (Request)
  Sender MAC: aa:bb:cc:dd:ee:01
  Sender IP:  192.168.1.42
  Target MAC: 00:00:00:00:00:00
  Target IP:  192.168.1.10

This frame is broadcast. Every device on the Layer 2 segment receives it. The switch floods the frame out all ports (or all ports in the relevant VLAN).

Step 3: Every Host Processes the Request

Every host on the segment receives the broadcast frame. Each host examines the Target Protocol Address field. If the target IP does not match any of the host's own IP addresses, the host silently discards the frame. Most hosts on the segment will do exactly this.

However, the receiving hosts also examine the Sender Protocol Address and Sender Hardware Address. If a host already has an ARP cache entry for the sender's IP, it updates that entry with the sender's MAC address. This is a passive cache update and is important for keeping mappings fresh without generating additional ARP traffic.

Step 4: The Target Replies (Unicast)

The server at 192.168.1.10 recognises its own IP in the Target Protocol Address field. It constructs an ARP reply:

Ethernet Header:
  Dst: aa:bb:cc:dd:ee:01  (unicast to requester)
  Src: aa:bb:cc:dd:ee:02
  EtherType: 0x0806
 
ARP Payload:
  Hardware Type: 0x0001 (Ethernet)
  Protocol Type: 0x0800 (IPv4)
  HLEN: 6
  PLEN: 4
  Opcode: 2 (Reply)
  Sender MAC: aa:bb:cc:dd:ee:02
  Sender IP:  192.168.1.10
  Target MAC: aa:bb:cc:dd:ee:01
  Target IP:  192.168.1.42

Note: this reply is unicast, not broadcast. Only the original requester receives it. The switch forwards it based on the destination MAC in its MAC address table.

Step 5: Cache Update and Packet Transmission

The workstation receives the reply, creates (or updates) an ARP cache entry mapping 192.168.1.10 to aa:bb:cc:dd:ee:02, and then constructs the original IP packet (the SSH SYN) inside an Ethernet frame addressed to aa:bb:cc:dd:ee:02. From this point forward, all traffic to 192.168.1.10 uses the cached MAC until the entry expires or is invalidated.

Seeing It in tcpdump

On a Linux box, you can watch ARP exchanges in real time:

sudo tcpdump -i eth0 -n arp

Typical output:

14:32:01.234567 ARP, Request who-has 192.168.1.10 tell 192.168.1.42, length 28
14:32:01.234891 ARP, Reply 192.168.1.10 is-at aa:bb:cc:dd:ee:02, length 28

For more detail, add the -e flag to show Ethernet headers:

sudo tcpdump -i eth0 -n -e arp
14:32:01.234567 aa:bb:cc:dd:ee:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.10 tell 192.168.1.42, length 28
14:32:01.234891 aa:bb:cc:dd:ee:02 > aa:bb:cc:dd:ee:01, ethertype ARP (0x0806), length 42: Reply 192.168.1.10 is-at aa:bb:cc:dd:ee:02, length 28

And for the full hex dump of the ARP payload:

sudo tcpdump -i eth0 -n -XX arp

A request might look like this (with annotation):

0x0000:  ffff ffff ffff aabb ccdd ee01 0806  ........ ......  <- Eth: dst=broadcast, src, type=ARP
0x000e:  0001 0800 0604 0001                  ........         <- HW=Eth, Proto=IPv4, HLEN=6, PLEN=4, Op=Request
0x0016:  aabb ccdd ee01 c0a8 012a             ..........*      <- Sender MAC, Sender IP (192.168.1.42)
0x0020:  0000 0000 0000 c0a8 010a             ..........       <- Target MAC (zeros), Target IP (192.168.1.10)

The hex c0a8 012a is 192.168.1.42 (0xC0=192, 0xA8=168, 0x01=1, 0x2A=42), and c0a8 010a is 192.168.1.10.

4. The ARP Cache: Timeouts and State Machines

Every host maintains an ARP cache (sometimes called the ARP table or neighbour table). This cache maps IP addresses to MAC addresses and avoids the need to broadcast an ARP request for every single outbound packet.

Viewing the Cache

On Linux, the modern way:

ip neigh show

Output:

192.168.1.1 dev eth0 lladdr aa:bb:cc:11:22:33 REACHABLE
192.168.1.10 dev eth0 lladdr aa:bb:cc:dd:ee:02 STALE
192.168.1.55 dev eth0  FAILED

The older net-tools command:

arp -a
gateway (192.168.1.1) at aa:bb:cc:11:22:33 [ether] on eth0
server (192.168.1.10) at aa:bb:cc:dd:ee:02 [ether] on eth0

On BSD and macOS:

arp -an

The Linux Neighbour State Machine

The Linux kernel does not treat ARP cache entries as simple key-value pairs with a TTL. Instead, each entry moves through a state machine defined in the kernel's "neighbour" subsystem (net/core/neighbour.c). This state machine is shared between ARP (IPv4) and NDP (IPv6). The states are:

INCOMPLETE: An ARP request has been sent, but no reply has been received yet. The kernel will retransmit the request up to ucast_solicit + mcast_solicit times (configurable via sysctl). If no reply arrives, the entry transitions to FAILED.

REACHABLE: A valid mapping exists and has been confirmed recently (either by an ARP reply or by upper-layer confirmation such as a TCP ACK). This is the healthy state. The entry remains REACHABLE for base_reachable_time_ms (default: 30 seconds on most distributions), randomised by a factor between 0.5 and 1.5 to prevent synchronised expiry across hosts.

STALE: The entry was once REACHABLE but the reachable timer expired without confirmation. The mapping is still considered usable, but if the host needs to send a packet to this address, the entry transitions to DELAY rather than being used indefinitely.

DELAY: The host has sent a packet using a STALE entry and is waiting for upper-layer confirmation (for example, a TCP ACK from the remote host). If confirmation arrives within delay_first_probe_time (default: 5 seconds), the entry goes back to REACHABLE. If not, it transitions to PROBE.

PROBE: The kernel sends unicast ARP requests directly to the cached MAC address (not broadcast) to verify the mapping is still valid. It will send up to ucast_solicit probes (default: 3). If a reply arrives, the entry goes back to REACHABLE. If all probes fail, it transitions to FAILED.

FAILED: Resolution failed entirely. The entry is effectively dead. Any packets queued for this destination are dropped and an ICMP Host Unreachable may be sent to the originating process.

You can see these states directly in the ip neigh show output. The state machine is more sophisticated than most people expect from "a simple ARP cache."

Tuning Cache Behaviour

The relevant sysctl parameters live under /proc/sys/net/ipv4/neigh/<interface>/:

# View all ARP-related parameters for eth0
ls /proc/sys/net/ipv4/neigh/eth0/
 
# Key parameters:
cat /proc/sys/net/ipv4/neigh/eth0/base_reachable_time_ms   # Default: 30000 (30s)
cat /proc/sys/net/ipv4/neigh/eth0/gc_stale_time             # Default: 60 (seconds)
cat /proc/sys/net/ipv4/neigh/eth0/mcast_solicit              # Default: 3
cat /proc/sys/net/ipv4/neigh/eth0/ucast_solicit              # Default: 3

The garbage collector also has global thresholds:

cat /proc/sys/net/ipv4/neigh/default/gc_thresh1   # Min entries before GC runs (128)
cat /proc/sys/net/ipv4/neigh/default/gc_thresh2   # Soft max (512)
cat /proc/sys/net/ipv4/neigh/default/gc_thresh3   # Hard max (1024)

On large networks, you may need to increase gc_thresh3. A host connected to a /16 with thousands of active peers can easily exhaust the default table size, leading to "Neighbour table overflow" messages in dmesg.

Windows and macOS Differences

Windows uses a simpler model. ARP cache entries default to a timeout of roughly 15 to 45 seconds for dynamic entries (this varied between Windows versions and was unified under a reachable time approach starting with Vista). You can view the cache with:

arp -a

macOS (Darwin/BSD) uses a 20-minute default for ARP entries, which is significantly longer than Linux. You can view and manage entries with:

arp -an
arp -d 192.168.1.10   # Delete a specific entry

5. Gratuitous ARP

A gratuitous ARP is an ARP packet sent without being solicited by a prior request. It is "gratuitous" in the sense that no one asked for it. There are two distinct forms, both defined in RFC 5227 (IPv4 Address Conflict Detection):

ARP Probe

An ARP probe is used during address configuration to check whether an IP address is already in use on the network. The probe has a specific structure:

Sender MAC: <host's own MAC>
Sender IP:  0.0.0.0
Target MAC: 00:00:00:00:00:00
Target IP:  <IP being tested>
Opcode: 1 (Request)

The critical detail: the Sender IP is set to 0.0.0.0. This prevents other hosts from learning a mapping from this probe, because no host should cache a mapping for 0.0.0.0. The host sends three probes, spaced one second apart (with randomisation). If any host replies, or if any host sends its own ARP announcement for that IP, the probing host knows there is a conflict and must not use the address.

DHCP clients on most operating systems perform ARP probing after receiving a lease offer, before committing to the address. If a conflict is detected, the client sends a DHCPDECLINE and requests a different address.

ARP Announcement

An ARP announcement is sent after a host has successfully claimed an address (no conflict detected during probing). The announcement looks like:

Sender MAC: <host's own MAC>
Sender IP:  <host's own IP>
Target MAC: ff:ff:ff:ff:ff:ff (or 00:00:00:00:00:00, varies by implementation)
Target IP:  <host's own IP>
Opcode: 1 (Request) or 2 (Reply), varies by implementation

The distinguishing characteristic: the Sender IP and Target IP are the same. This tells every other host on the segment, "This IP address belongs to this MAC address. Update your caches."

Use Cases for Gratuitous ARP

Failover and high availability. When a VRRP or HSRP router takes over a virtual IP from a failed peer, it sends a gratuitous ARP to tell all hosts on the segment that the virtual IP now maps to the new router's MAC. Without this, hosts would continue sending traffic to the old router's MAC until their ARP caches expired. In a production network in, say, an Amsterdam data centre, waiting 30 to 60 seconds for cache expiry during a failover would be catastrophic. Gratuitous ARP makes the failover nearly instantaneous.

NIC replacement. If you replace a network card in a server (new MAC, same IP), a gratuitous ARP broadcast tells the rest of the network about the new mapping. Without it, the server would be unreachable until every peer's ARP cache expired.

Virtual machine migration. When a VM live-migrates from one physical host to another, the new hypervisor sends a gratuitous ARP (or more precisely, a RARP or gratuitous ARP depending on implementation) to redirect traffic to the new physical NIC.

Seeing Gratuitous ARP in tcpdump

sudo tcpdump -i eth0 -n 'arp and arp[6:2] = 1' # All ARP requests

A gratuitous ARP announcement looks like:

14:35:22.110234 ARP, Request who-has 192.168.1.42 tell 192.168.1.42, length 28

The giveaway: "who-has X tell X." The sender IP and target IP are identical.

6. Proxy ARP

Proxy ARP is a technique where a router (or firewall, or other intermediary) answers ARP requests on behalf of hosts that are on a different network segment. The router replies with its own MAC address, so that traffic destined for the remote host is sent to the router, which then forwards it at Layer 3.

How It Works

Consider this scenario. Sofia has a workstation at 10.0.1.50/24 on VLAN 10. She needs to reach a server at 10.0.2.30/24 on VLAN 20. Normally, Sofia's workstation would recognise that 10.0.2.30 is on a different subnet, consult its routing table, and send the packet to the default gateway. The gateway would then route it to VLAN 20.

But suppose Sofia's workstation is misconfigured with a /16 mask instead of /24. The workstation now thinks 10.0.2.30 is on the same local subnet and tries to ARP for it directly. The ARP broadcast goes out on VLAN 10, and since 10.0.2.30 is not on VLAN 10, nobody answers. Communication fails.

If the router connecting VLAN 10 and VLAN 20 has Proxy ARP enabled, it sees the ARP request for 10.0.2.30, recognises that it has a route to that address, and replies with its own MAC address. Sofia's workstation caches the router's MAC as the mapping for 10.0.2.30 and sends IP packets to the router. The router then routes them to VLAN 20. From Sofia's perspective, everything works, even though her subnet mask is wrong.

Enabling and Disabling Proxy ARP on Linux

# Check current setting
cat /proc/sys/net/ipv4/conf/eth0/proxy_arp
 
# Enable
echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp
 
# Or persistently via sysctl.conf
# net.ipv4.conf.eth0.proxy_arp = 1

Legitimate Use Cases

  • Legacy systems that cannot be reconfigured with correct subnet masks.
  • Dial-up and VPN concentrators in older deployments where remote clients were given addresses from the local LAN range. The concentrator would proxy-ARP for these addresses so local hosts could reach them.
  • Bridging isolated segments without routing changes. Some network engineers in Berlin's older ISP infrastructure relied on proxy ARP to merge subnets without renumbering.

Why Proxy ARP Is Dangerous

Proxy ARP masks configuration errors. Sofia's workstation has a wrong subnet mask, and proxy ARP hides this from her and from anyone troubleshooting the network. The symptom (everything works) contradicts the underlying problem (misconfigured host), making diagnosis harder.

More critically, proxy ARP increases the ARP broadcast domain effectively. Every IP on the remote subnet now generates ARP traffic on the local segment, because the router has to answer for all of them. On large networks, this can generate significant broadcast overhead.

Most modern network deployments disable proxy ARP explicitly. Cisco IOS enables it by default on interfaces, which has caused countless confused junior engineers to wonder why traffic is flowing when it logically should not be.

7. ARP Spoofing and Cache Poisoning

ARP has no authentication. Any host on the segment can send an ARP reply claiming any IP-to-MAC mapping, and receivers will blindly accept it. This is not a bug; it is how the protocol was designed. In 1982, the LAN was a trusted environment (typically a university lab or corporate office with physical access controls). The internet of 2026 is a different place.

The Attack

ARP spoofing (also called ARP cache poisoning) is a technique where an attacker sends forged ARP replies to associate their own MAC address with a victim's IP address. The classic man-in-the-middle (MITM) scenario works like this:

Nikos (the attacker, at 192.168.1.66, MAC aa:bb:cc:66:66:66) wants to intercept traffic between two hosts: a workstation at 192.168.1.42 and the gateway at 192.168.1.1.

Step 1: Nikos sends a forged ARP reply to the workstation:

Sender MAC: aa:bb:cc:66:66:66  (Nikos's MAC)
Sender IP:  192.168.1.1         (gateway's IP, but Nikos's MAC!)
Target MAC: aa:bb:cc:dd:ee:01   (workstation's MAC)
Target IP:  192.168.1.42

The workstation now thinks the gateway is at aa:bb:cc:66:66:66.

Step 2: Nikos sends a forged ARP reply to the gateway:

Sender MAC: aa:bb:cc:66:66:66  (Nikos's MAC)
Sender IP:  192.168.1.42        (workstation's IP, but Nikos's MAC!)
Target MAC: aa:bb:cc:11:22:33   (gateway's MAC)
Target IP:  192.168.1.1

The gateway now thinks the workstation is at aa:bb:cc:66:66:66.

Step 3: Nikos enables IP forwarding on his machine:

echo 1 > /proc/sys/net/ipv4/ip_forward

Now, traffic from the workstation to the gateway flows through Nikos's machine, and traffic from the gateway to the workstation flows through Nikos's machine. Nikos can inspect, modify, or drop packets. He must keep sending the forged ARP replies periodically (every few seconds) to prevent the legitimate ARP entries from being restored.

Tools for Performing ARP Spoofing

These tools exist for both offensive security testing and network analysis:

  • arpspoof (part of dsniff): arpspoof -i eth0 -t 192.168.1.42 192.168.1.1
  • ettercap: Full MITM framework with ARP spoofing as one component
  • bettercap: Modern replacement for ettercap, written in Go
  • Scapy: Python library for crafting arbitrary packets, including ARP

Using these tools on networks you do not own or have explicit authorisation to test is illegal in most jurisdictions.

Detection

arpwatch is a classic daemon that monitors ARP traffic and alerts on changes. It maintains a database of IP-to-MAC mappings and sends email notifications when a mapping changes:

sudo apt install arpwatch
sudo systemctl start arpwatch

When arpwatch detects a MAC change for a known IP, it logs:

Apr 12 14:22:33 host arpwatch: changed ethernet address 192.168.1.1 aa:bb:cc:66:66:66 (aa:bb:cc:11:22:33)

arping can be used to manually verify an IP's MAC address:

arping -I eth0 192.168.1.1

If you get replies from two different MAC addresses, something is wrong.

Wireshark has built-in ARP anomaly detection. The "Expert Info" panel will flag duplicate IP addresses with different MACs.

Defences

Dynamic ARP Inspection (DAI) is a switch-level feature available on managed switches from Cisco, Juniper, Arista, and others. DAI intercepts all ARP packets on untrusted ports and validates them against the DHCP snooping binding table. If an ARP reply claims an IP-to-MAC mapping that does not match the DHCP snooping database, the switch drops the packet and can shut down the offending port.

Configuration example on a Cisco switch:

ip arp inspection vlan 10
interface GigabitEthernet0/1
  ip arp inspection trust    ! Uplink to router/DHCP server
interface GigabitEthernet0/2
  ! Untrusted by default, DAI validates ARP here

Static ARP entries can be configured on critical hosts (servers, gateways) to prevent their ARP caches from being poisoned:

# Linux
arp -s 192.168.1.1 aa:bb:cc:11:22:33
 
# Or with ip command
ip neigh add 192.168.1.1 lladdr aa:bb:cc:11:22:33 nud permanent dev eth0

Static entries are immune to spoofing but create maintenance overhead. They are practical for a small number of critical mappings (the default gateway, for example) but not for an entire network.

802.1X port-based authentication does not prevent ARP spoofing directly, but it ensures that only authenticated hosts can connect to the network in the first place, reducing the attack surface.

ARP rate limiting on switches can throttle the number of ARP packets a port can send per second, making sustained ARP spoofing attacks more difficult:

! Cisco IOS example
ip arp inspection limit rate 15

What ARP Spoofing Does Not Get You (Anymore)

In 2026, most sensitive traffic is encrypted with TLS 1.3. An ARP MITM attacker can see the encrypted ciphertext but cannot read the plaintext without also compromising the TLS session (which requires a separate attack, such as SSL stripping or certificate forgery). ARP spoofing is still dangerous for unencrypted protocols (HTTP, FTP, Telnet, some DNS configurations), for traffic analysis (metadata, timing, endpoints), and as a component in more complex attack chains.

8. ARP in VLANs and Modern Data Centres

ARP was designed for a flat Ethernet segment, a single broadcast domain. Modern networks are anything but flat. VLANs, overlays, and data-centre fabrics introduce complexity that ARP was never designed to handle.

ARP and VLANs

A VLAN is a logical broadcast domain. ARP requests are broadcast, so they are contained within a VLAN. A host on VLAN 10 cannot ARP for a host on VLAN 20 (unless proxy ARP is involved). This is by design: VLANs provide isolation.

On a trunk port carrying tagged VLAN traffic (802.1Q), ARP frames are tagged just like any other frame. The switch strips the VLAN tag before delivering the frame to access ports in the appropriate VLAN. From ARP's perspective, VLANs are invisible; the protocol works identically within each VLAN.

The Broadcast Problem at Scale

In a small office with 50 devices, ARP broadcast traffic is negligible. In a data centre with 10,000 virtual machines across a Layer 2 domain, ARP broadcast becomes a serious problem.

Every ARP request is flooded to every host in the broadcast domain. With 10,000 hosts, a single ARP request generates 10,000 frames (one per host). Each host's CPU must process the interrupt and examine the packet, even if the request is not for it. If hosts are generating ARP requests at a rate of even one per second per host, the network is flooded with 10,000 broadcasts per second, and each host processes 10,000 interrupts per second just for ARP.

This "ARP storm" effect is one of the primary reasons that large Layer 2 domains are considered harmful. It drove the industry toward Layer 3 leaf-spine architectures, where each rack or row is its own broadcast domain.

VXLAN and EVPN ARP Suppression

Modern data-centre overlay networks (VXLAN, NVGRE) extend Layer 2 domains across Layer 3 infrastructure. A VXLAN network identifier (VNI) can span multiple racks, rows, or even data centres. Without mitigation, ARP broadcasts in a VXLAN segment would be encapsulated and flooded across the entire overlay, consuming bandwidth on underlay links and generating processing overhead on every VTEP (VXLAN Tunnel Endpoint).

EVPN (Ethernet VPN, RFC 7432 and related) solves this through ARP suppression. The mechanism works like this:

  1. When a host sends an ARP request, the local VTEP (typically a leaf switch) intercepts it.
  2. The VTEP checks its local EVPN table, which is populated via BGP from all other VTEPs in the fabric. If the target IP-to-MAC mapping is known, the VTEP generates a local ARP reply on behalf of the target and sends it back to the requesting host. The ARP request is never flooded across the overlay.
  3. If the mapping is not known (new host, for example), the VTEP floods the ARP request into the overlay, but only to VTEPs that have hosts in the same VNI. The target's VTEP receives the request, delivers it to the target host, and the reply flows back. The mapping is then distributed via BGP to all VTEPs for future suppression.

This is proxy ARP at a fabric level, but with a distributed, BGP-synchronised database instead of a single router answering blindly. The result is that ARP broadcast in the overlay is reduced by 90% or more, making large Layer 2 segments feasible.

ARP Suppression in Practice

On an Arista switch running EVPN:

interface Vxlan1
  vxlan source-interface Loopback1
  vxlan udp-port 4789
  vxlan vlan 100 vni 10100
 
router bgp 65001
  address-family evpn
    neighbor SPINE activate
  vlan 100
    rd auto
    route-target both 100:10100
    redistribute learned

The ARP suppression happens automatically when EVPN type-2 (MAC/IP) routes are learned. You can verify suppression is working:

show arp suppression-cache vlan 100

9. IPv6 NDP: ARP's Replacement

IPv6 does not use ARP. Instead, it uses the Neighbour Discovery Protocol (NDP), defined in RFC 4861. NDP performs the same address resolution function as ARP, but it is a more sophisticated protocol built on ICMPv6 rather than being a standalone Layer 2.5 protocol.

Neighbour Solicitation and Advertisement

The IPv6 equivalents of ARP Request and ARP Reply are:

  • Neighbour Solicitation (NS): ICMPv6 type 135. "What is the link-layer address of 2001:db8::42?"
  • Neighbour Advertisement (NA): ICMPv6 type 136. "2001:db8::42 is at aa:bb:cc:dd:ee:02."

Key Differences from ARP

Multicast instead of broadcast. ARP requests use Ethernet broadcast (ff:ff:ff:ff:ff:ff), which means every host on the segment must process them. NDP uses solicited-node multicast addresses. For a target IPv6 address 2001:db8::1:42, the solicited-node multicast address is ff02::1:ff01:0042 (derived from the last 24 bits of the target address). Only hosts whose IPv6 addresses share those last 24 bits will have joined this multicast group. In practice, this often means only the target host processes the solicitation, dramatically reducing unnecessary CPU interrupts compared to ARP broadcast.

The corresponding Ethernet multicast address is 33:33:ff:01:00:42 (derived from the last 32 bits of the IPv6 multicast address).

ICMPv6, not a separate protocol. NDP messages are ICMPv6 packets, carried inside IPv6 packets, carried inside Ethernet frames. This means NDP benefits from IPv6 header features (hop limits, extension headers) and can be secured with IPsec. ARP, by contrast, sits directly on top of Ethernet with no IP involvement.

SLAAC (Stateless Address Autoconfiguration). NDP includes Router Advertisement (RA) messages that allow hosts to configure their own IPv6 addresses without DHCP. A router periodically broadcasts (well, multicasts to ff02::1) its prefix information, and hosts generate addresses by combining the prefix with their interface identifier (historically derived from the MAC via EUI-64, now typically randomised for privacy per RFC 8981).

Duplicate Address Detection (DAD). Before using a new IPv6 address, a host sends a Neighbour Solicitation for that address with an unspecified source (::). If any host responds, the address is already in use. This is analogous to ARP probing in RFC 5227, but it is a mandatory part of the IPv6 specification rather than an optional add-on.

CGA and SEND. Cryptographically Generated Addresses (CGA, RFC 3972) and Secure Neighbour Discovery (SEND, RFC 3971) provide cryptographic proof that a host owns the IPv6 address it claims. The address itself is derived from a hash of the host's public key, making spoofing computationally expensive. SEND has seen limited deployment in practice (it requires a PKI and has performance overhead), but it represents a genuine security improvement over ARP's complete lack of authentication.

NDP in tcpdump

sudo tcpdump -i eth0 -n icmp6 and 'ip6[40] == 135 or ip6[40] == 136'

Output:

14:40:01.567890 IP6 fe80::1 > ff02::1:ff00:42: ICMP6, neighbor solicitation, who has 2001:db8::42, length 32
14:40:01.568234 IP6 2001:db8::42 > fe80::1: ICMP6, neighbor advertisement, tgt is 2001:db8::42, length 32

Will ARP Ever Go Away?

Not soon. IPv4 is not going away soon, and wherever there is IPv4 on Ethernet, there is ARP. Dual-stack networks (which remain the majority in 2026) run both ARP and NDP simultaneously on the same interfaces. The transition to IPv6-only is happening, slowly, but ARP will be with us for years to come.

10. ARP Edge Cases and Debugging

ARP is simple in theory, but real networks produce complicated ARP behaviour. Here are the edge cases and debugging techniques that network engineers encounter in practice.

Incomplete ARP Entries

An incomplete entry means the kernel sent an ARP request but never received a reply. In ip neigh show, these appear as:

192.168.1.99 dev eth0  INCOMPLETE

Common causes:

  • The target host is down or disconnected.
  • The target host is on a different VLAN or subnet (misconfigured mask).
  • A firewall is dropping ARP packets (rare but possible with ebtables or nftables bridge filtering).
  • The target host has ARP disabled at the kernel level (possible but rare in production).

You can force a resolution attempt:

arping -I eth0 192.168.1.99

If arping gets no response, the host is unreachable at Layer 2.

ARP Storms

An ARP storm occurs when hosts generate ARP requests at a rate that overwhelms the network or the hosts' CPUs. This can happen when:

  • A host has a route to a large subnet (say, a /16) and tries to reach many addresses that do not exist. For each unreachable IP, the kernel sends ARP requests. With 65,534 possible addresses, this generates a flood.
  • A network loop causes ARP broadcasts to circulate endlessly (STP failure).
  • A misconfigured application rapidly connects to many IPs on the local segment.

Linux mitigates ARP storms through rate limiting in the neighbour subsystem. The gc_thresh3 parameter (default 1024) caps the maximum number of neighbour entries, and the kernel will refuse to create new entries (and log "Neighbour table overflow") when this limit is reached.

You can detect ARP storms with:

sudo tcpdump -i eth0 -n arp -c 1000 | sort | uniq -c | sort -rn | head -20

This captures 1000 ARP packets, counts unique request/reply lines, and shows the top 20. If a single IP is generating hundreds of requests, you have found your storm source.

ARP Rate Limiting on Linux

The kernel rate-limits ARP solicitations through the mcast_solicit and ucast_solicit parameters and the timing between them. However, for more aggressive rate limiting (useful on servers with many outbound connections), you can use tc (traffic control) or ebtables:

# Rate limit ARP requests to 50 per second using ebtables
ebtables -A OUTPUT -p ARP --arp-opcode 1 -m limit --limit 50/second -j ACCEPT
ebtables -A OUTPUT -p ARP --arp-opcode 1 -j DROP

Docker Bridge ARP

Docker's default bridge networking creates an interesting ARP scenario. Each container gets a veth pair: one end in the container's network namespace, one end attached to the docker0 bridge on the host. From ARP's perspective:

  • Containers on the same docker0 bridge can ARP for each other directly, as they are in the same Layer 2 domain.
  • Containers reaching the outside world ARP for the docker0 bridge IP (typically 172.17.0.1), which is the gateway. The host then routes (and NATs) the traffic.

You can observe container ARP behaviour by running tcpdump on the docker0 bridge:

sudo tcpdump -i docker0 -n arp

Or inside a specific container's veth:

# Find the container's veth interface
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' mycontainer)
VETH=$(ip link | grep -A1 "if$(nsenter -t $CONTAINER_PID -n ip link show eth0 | head -1 | cut -d: -f1)" | head -1 | awk '{print $2}' | tr -d ':')
sudo tcpdump -i $VETH -n arp

A common Docker ARP issue: when containers are on user-defined bridge networks with ICC (inter-container communication) disabled, ARP requests between containers are blocked at the bridge level, and the containers cannot discover each other even though they share a bridge. This is deliberate isolation, but it confuses people who expect Layer 2 connectivity.

ARP in Kubernetes

Kubernetes networking adds another layer of ARP complexity. In a typical cluster, each node runs a CNI (Container Network Interface) plugin that manages pod networking. The most common deployment models handle ARP differently:

Flannel (VXLAN mode): Flannel creates a flannel.1 VXLAN interface on each node. Pods on the same node ARP for each other via the local cni0 bridge (identical to Docker's bridge model). For cross-node traffic, the pod ARPs for the gateway (cni0 bridge IP), and Flannel routes the packet through a VXLAN tunnel. The VTEP on the destination node resolves the destination pod's MAC via a locally maintained FDB (forwarding database) populated by flanneld, bypassing ARP entirely on the overlay.

Calico (BGP mode): Calico avoids overlay networks when possible. It assigns each pod a routable IP and uses BGP to distribute routes between nodes. ARP resolution still happens on the local node: the pod ARPs for its gateway (a cali* veth endpoint on the host side), and the host routes the packet to the destination node at Layer 3. Because there is no overlay, ARP broadcast stays confined to each node's local bridge, keeping broadcast domains small.

MetalLB and ARP announcements: In bare-metal Kubernetes clusters without a cloud load balancer, MetalLB provides LoadBalancer-type services. In its Layer 2 mode, MetalLB elects a leader node for each service IP and that node sends gratuitous ARP announcements to claim the IP. If the leader fails, a different node takes over and sends new gratuitous ARPs. This is the same failover mechanism used by VRRP and keepalived, adapted for Kubernetes service IPs. You can observe this with:

sudo tcpdump -i eth0 -n arp | grep "is-at"

During a MetalLB failover, you will see a burst of gratuitous ARP packets as the new leader announces itself for every service IP it now owns.

ARP and Bonded/Teamed Interfaces

When two or more NICs are bonded (LACP, active-backup, etc.), ARP behaviour depends on the bonding mode:

  • Active-backup (mode 1): Only the active NIC sends and receives ARP. When a failover occurs, the new active NIC sends a gratuitous ARP to update switch MAC tables. The arp_validate and arp_interval bonding parameters can use ARP as a link health check (the bond driver sends ARP requests to a configured target and monitors replies).
# Configure ARP-based link monitoring for a bond
echo "arp_interval=1000" > /sys/class/net/bond0/bonding/arp_interval
echo "192.168.1.1" > /sys/class/net/bond0/bonding/arp_ip_target
  • LACP (mode 4, 802.3ad): ARP traffic is distributed across member links according to the transmit hash policy. All member links share the same MAC (the bond MAC), so ARP replies always contain the bond MAC regardless of which physical link carries the reply.

ARP with Multiple IPs on One Interface

A Linux host with multiple IP addresses on a single interface (aliases, or secondary addresses) has a subtlety: by default, the kernel will respond to ARP requests for any of its IPs on any of its interfaces. This is controlled by the arp_filter and arp_ignore sysctls.

# Default behaviour (arp_ignore=0): respond to ARP for any local IP on any interface
cat /proc/sys/net/ipv4/conf/all/arp_ignore
 
# Set arp_ignore=1: only respond if the target IP is configured on the incoming interface
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
 
# arp_announce controls which source IP is used in ARP requests
# 0 = any local address (default)
# 1 = prefer address from same subnet as target
# 2 = always use best local address for target
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

These settings are critical for Linux-based load balancers using Direct Server Return (DSR). In DSR, multiple servers share a virtual IP (VIP) on a loopback alias, and the load balancer directs traffic to them at Layer 2. If the servers respond to ARP for the VIP on their physical interfaces, they will hijack traffic directly from the network. Setting arp_ignore=1 and arp_announce=2 prevents this.

Debugging with Wireshark Filters

Wireshark provides rich ARP filtering:

# All ARP traffic
arp
 
# ARP requests only
arp.opcode == 1
 
# ARP replies only
arp.opcode == 2
 
# ARP involving a specific IP
arp.src.proto_ipv4 == 192.168.1.42 || arp.dst.proto_ipv4 == 192.168.1.42
 
# Gratuitous ARP (sender IP == target IP)
arp.isgratuitous == 1
 
# ARP with duplicate IP detection (potential spoofing)
arp.duplicate-address-detected

Wireshark's "Expert Info" (Analyse > Expert Information) will flag ARP anomalies automatically: duplicate addresses, IP conflicts, and gratuitous ARPs. This is often the fastest way to diagnose ARP-related issues on a capture file.

Debugging with arping

The arping utility sends ARP requests and displays replies, similar to ping but at Layer 2:

# Basic ARP ping
arping -I eth0 192.168.1.1
 
# Send 5 requests
arping -I eth0 -c 5 192.168.1.1
 
# Detect duplicate IPs (send ARP probe with source 0.0.0.0)
arping -D -I eth0 192.168.1.1

The -D flag is particularly useful. It sends an ARP probe (source IP 0.0.0.0), and if it gets a reply, the IP is already in use. The exit code is 0 if a reply is received (duplicate found), 1 if no reply (address available). This is the same mechanism used by DHCP clients for address conflict detection.

Clearing the ARP Cache

Sometimes the fastest fix is clearing stale entries:

# Delete a specific entry
ip neigh del 192.168.1.10 dev eth0
 
# Flush all entries for an interface
ip neigh flush dev eth0
 
# Flush all entries (nuclear option)
ip neigh flush all

On production systems, be cautious with flush all. It forces re-resolution of every active connection, causing a brief interruption as ARP requests are sent and replies received. On a busy server with thousands of active connections, this can produce a spike of ARP traffic and a momentary pause in packet delivery.

11. Why ARP Still Matters Despite Its Age

ARP is 44 years old. It has no security, no scalability features, no versioning, no extension mechanism. It generates broadcast traffic that wastes CPU cycles on every host in the broadcast domain. It is trivially spoofable. It was designed for a world where a "large" network had a few hundred hosts connected by coaxial cable.

And yet, it remains indispensable.

Every IPv4 Ethernet network on the planet runs ARP. Every operating system implements it. Every switch understands it. Every network engineer learns it in their first week. The protocol is so simple that it is implemented in firmware on NICs, in bootloaders (PXE boot relies on ARP), in embedded systems with kilobytes of RAM, and in billion-dollar data-centre fabrics.

The attempts to replace ARP have not removed it; they have built on top of it. EVPN ARP suppression does not eliminate ARP; it intercepts ARP at the switch and responds locally. VPP (Vector Packet Processing) and DPDK-based stacks still implement ARP in their data planes. Even container networking (Kubernetes, with its overlay networks and CNI plugins) ultimately resolves to ARP on the underlying physical network.

ARP's longevity is a consequence of its simplicity. The protocol does one thing, does it well enough, and stays out of the way. The four-page RFC is still the authoritative specification. No amendments, no version 2, no compatibility matrices. The ARP packet your laptop sends today is byte-for-byte identical in structure to the ARP packet a VAX sent in 1982.

There is a lesson here for protocol designers: a protocol that is simple enough to implement correctly, everywhere, in every context, will outlive protocols that are more sophisticated but harder to deploy. ARP is not elegant. It is not secure. It is not efficient. But it works on every Ethernet segment on Earth, and that reliability is worth more than any amount of architectural purity.

The next time you are troubleshooting a network issue and everything above Layer 2 looks correct, check the ARP cache. Run ip neigh show. Watch the traffic with tcpdump -n arp. More often than not, the invisible protocol that you never think about is the one that is broken.

# One last command. Run this and read the output carefully.
# You might be surprised what you find.
ip neigh show