Linux ip neigh: Manage the ARP Table Like a Pro - 夜莺博客

Linux ip neigh: Manage the ARP Table Like a Pro

The neighbor table - known as the ARP table for IPv4 - maps IP addresses to link-layer MAC addresses and sits in the hot path of every frame the host sends. When connectivity breaks at Layer 2, the neighbor table is usually where the truth is hiding: incomplete entries mean ARP resolution is failing, while stale or failed states point to reachability or probing problems. The modern way to inspect and manipulate this table is the ip neigh command from iproute2, which replaces the legacy arp tool. This guide covers ip neigh syntax, the neighbor states you will actually see, and the troubleshooting moves that fix broken resolution.

Showing the Neighbor Table

$ ip neigh show
192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
192.168.1.20 dev eth0 lladdr aa:bb:cc:dd:ee:ff STALE
10.0.0.5 dev eth1 FAILED

Filter per device, prefix or state with the usual selectors:

$ ip neigh show dev eth0
$ ip neigh show to 192.168.1.0/24
$ ip neigh show nud failed

Two flags are worth memorising. nud all matches every non-permanent state, and nud noarp shows the entries that were added administratively without a resolution attempt. Combine them with standard tools to build small reports:

$ ip neigh show nud all | wc -l
$ ip neigh show | grep -v REACHABLE

Neighbor States (NUD) Explained

The kernel tracks every entry through the Neighbour Unreachability Detection state machine. The states you will see in ip neigh output:

  • REACHABLE - the entry is valid and confirmed; traffic flows normally.
  • STALE - valid but unconfirmed for a while; the kernel will probe it before use. This is a normal resting state, not an error.
  • DELAY / PROBE - the kernel is verifying a stale entry before using it.
  • INCOMPLETE - resolution is in progress; no MAC learned yet. Persistent incomplete entries mean ARP requests are unanswered.
  • FAILED - probing exceeded the retry limit; the neighbor is unreachable at Layer 2.
  • PERMANENT / NOARP - administratively static entries (for example on a bridge or point-to-point link).

The state transitions follow a predictable cycle. An entry becomes REACHABLE when a confirmation arrives, then ages to DELAY after gc_stale_time seconds. If traffic still needs the entry, DELAY moves to PROBE and unicast ARP requests are sent; a reply returns it to REACHABLE, and a failure after ucast_solicit attempts marks it FAILED. Reading a table full of STALE entries is therefore perfectly normal on a quiet host - the kernel simply has not needed to reconfirm anything lately.

How Neighbor Resolution Works Under the Hood

When the kernel needs to send a packet but has no usable neighbor entry, it queues the packet and broadcasts (for IPv4 ARP) or multicasts (for IPv6 ND) a solicitation. Only when a reply arrives does the entry leave INCOMPLETE and the queued packet go out. This is why the very first packet to a new host can be silently dropped if the queue is full or the reply never comes: the entry stays INCOMPLETE and every subsequent send is queued or discarded.

The practical implication for troubleshooting is that you should look at the table while traffic is flowing, not after it has stopped. An entry that reads STALE is healthy; an entry that reads INCOMPLETE while you are actively pinging means the solicitation is not getting a reply, which points at the peer, the VLAN, or a filter rather than at the local stack.

Adding, Changing and Deleting Entries

# add a static entry
$ sudo ip neigh add 192.168.1.50 lladdr 00:11:22:33:44:55 dev eth0 nud permanent

# replace or delete
$ sudo ip neigh replace 192.168.1.50 lladdr 00:11:22:33:44:66 dev eth0
$ sudo ip neigh del 192.168.1.50 dev eth0

Static (nud permanent) entries are useful for devices that never send gratuitous ARP, for security-sensitive links where you want to pin a MAC, and for routers on an isolated management segment. Remember that a permanent entry does not age out and will not be corrected by the network - if the device's MAC changes, you must update the entry by hand.

The proxy keyword is a rarer but powerful variant: it tells the host to answer ARP requests on behalf of another address, which is the mechanism behind proxy ARP and some publishing schemes. Use it deliberately, because a stray proxy entry can hijack traffic for an address you did not intend to publish.

Flushing the ARP Table

After VLAN changes, MAC moves or NIC replacement, flush the stale entries so the kernel re-resolves:

# flush everything on eth0
$ sudo ip neigh flush dev eth0

# flush dynamic entries for a prefix
$ sudo ip neigh flush to 192.168.1.0/24

# verify and regenerate
$ ip neigh show
$ ping -c 1 192.168.1.1

Flushing with nud all also removes permanent entries - use it only when you really want a clean slate. A targeted flush of a single address is almost always the better move during an incident:

$ sudo ip neigh flush to 192.168.1.50 dev eth0

Tuning the Neighbor Table with sysctl

Most hosts never need tuning, but high-density systems - routers, hypervisors, load balancers with thousands of peers - can benefit from adjusting the garbage-collection thresholds:

# how long a REACHABLE entry stays before it goes STALE
$ sysctl net.ipv4.neigh.default.gc_stale_time

# thresholds that trigger neighbour garbage collection
$ sysctl net.ipv4.neigh.default.gc_thresh1
$ sysctl net.ipv4.neigh.default.gc_thresh2
$ sysctl net.ipv4.neigh.default.gc_thresh3

When the table approaches gc_thresh3, the kernel stops adding new entries and resolution fails with "neighbour table overflow" in the logs. Raising these three values on a busy router is a common fix; the same settings exist per interface under net.ipv4.neigh.<dev>. The mcast_solicit and ucast_solicit parameters control the retry counts before an entry is marked FAILED.

IPv6 Neighbor Discovery and ip -6 neigh

The same command manages the IPv6 neighbor cache, which is populated by Neighbor Discovery rather than ARP:

$ ip -6 neigh show
$ ip -6 neigh show dev eth0
$ sudo ip -6 neigh flush dev eth0

IPv6 entries include the same NUD states, so the troubleshooting logic carries over. If IPv6 hosts cannot talk but IPv4 can, check the ND entries and make sure Router Advertisement and the on-link prefix are correct; the details are in IPv6 neighbor discovery and router advertisement.

Legacy arp Command Mapping

arp -a equals ip neigh show, arp -d equals ip neigh del, and arp -s equals ip neigh add ... nud permanent. Prefer the ip form on modern distributions; it supports IPv6 neighbor discovery entries and state filtering that arp cannot.

arping and Gratuitous ARP

The neighbor table tells you what the kernel believes; arping tells you what is actually true on the wire. It sends an ARP request (or a gratuitous ARP) on a specific interface and reports the reply, which is the cleanest way to detect a duplicate IP or to confirm a peer is alive at Layer 2 when ICMP is filtered:

# is 192.168.1.50 reachable at Layer 2 on eth0?
$ sudo arping -I eth0 -c 3 192.168.1.50

# probe for a duplicate address (two different MACs answering)
$ sudo arping -I eth0 -c 5 -D 192.168.1.50

The -D (duplicate address detection) mode is the useful one during an incident: if two hosts answer for the same IP, arping prints both MACs, which confirms the conflict that a wrong MAC in the neighbor table only hinted at. A gratuitous ARP - an unsolicited announcement - is also how a host updates its peers after a failover, which is why a floating VIP sometimes takes a few seconds to become reachable again while the peers refresh their entries.

Neighbor Tables in Bridges and Containers

The neighbor table is per network namespace, and a Linux bridge has its own forwarding database that is separate from it. When you debug a host that runs containers or VMs, remember that the entries you see in the root namespace are not the entries the workload uses. A container's neighbor table lives inside its own namespace and is reached with ip netns exec; the full workflow for scoping commands this way is in Linux network namespaces guide.

On a bridge, the forwarding database answers a different question - which port a MAC was last seen on - and is inspected with bridge fdb show. A symptom that looks like a stale ARP entry is often a stale FDB entry: the MAC moved ports but the bridge still floods to the old one. Checking both tables is what separates a Layer-2 learning problem from an actual neighbour problem.

A Small Neighbor Health-Check Script

Because the states are textual, the neighbour table is easy to monitor. The snippet below flags entries that are not healthy, which you can run from cron or a monitoring agent:

#!/bin/bash
# report any neighbour that is not REACHABLE, STALE, DELAY or PROBE
ip neigh show | while read line; do
  state=$(echo "$line" | awk '{print $NF}')
  case "$state" in
    REACHABLE|STALE|DELAY|PROBE|PERMANENT) ;;
    *) echo "NEIGH ISSUE: $line" ;;
  esac
done

Treat a FAILED or INCOMPLETE entry as a signal, not noise: on a stable LAN they should be transient. A host that accumulates FAILED entries for a whole subnet is usually missing routes, has a VLAN mismatch, or is racing a neighbour whose MAC keeps changing.

Troubleshooting Common Neighbor Problems

  • INCOMPLETE for a local IP - check the other host is up, VLAN tags match on both sides, and no firewall drops ARP.
  • FAILED after a MAC change - flush the entry; the old MAC is cached.
  • Wrong MAC in the table - duplicate IP on the LAN; check for IP conflicts with arping.
  • Table overflow - the neighbor table hit gc_thresh3; raise the thresholds or reduce churn.
  • Everything times out but the table looks fine - suspect duplex or VLAN mismatch rather than L3; the counters that reveal it are covered in ethtool network diagnostics.

Below the neighbor table is the wire itself. When you need to see the actual ARP requests and replies, capture them and read the frame headers, using the filters in our tcpdump filters and troubleshooting guide. And when the hosts you are debugging live inside containers, the entries belong to a namespace rather than the host root, so scope the commands with ip netns exec as shown in Linux network namespaces guide. For static addressing that avoids ARP surprises altogether, see nmcli static IP configuration.

原文链接:https://man7.org/linux/man-pages/man8/ip-neighbour.8.html