Same Job, Different Shell Part 7: ARP and Neighbor Tables
This is the shortest table in the whole stack and one of the most useful when something on the local subnet isn’t behaving: the neighbor cache, the local mapping of IP address to MAC address for every host your box has actually talked to on-link recently.
Linux: ip neigh, and legacy arp
$ ip neigh show
192.0.2.1 dev eth0 lladdr 02:fc:00:00:00:05 REACHABLE
One entry per neighbor: IP, the interface it’s reachable through, the resolved MAC (lladdr), and a state. REACHABLE means the kernel has confirmed this MAC answers for this IP recently; other states you’ll see in the wild are STALE (was reachable, hasn’t been re-verified, still used optimistically), INCOMPLETE (ARP request sent, no reply yet), FAILED, and PERMANENT (statically configured, never expires).
The legacy tool, same information, older column layout:
$ arp -a
? (192.0.2.1) at 02:fc:00:00:00:05 [ether] on eth0
ip neigh is the one worth learning going forward, it also handles IPv6 neighbor discovery (ip -6 neigh show) in the same command, where arp is IPv4-only by name and by design.
Windows: Get-NetNeighbor, and legacy arp -a
C:\> arp -a
works the same way it always has, same terse IP-to-MAC listing. Get-NetNeighbor is the structured, PowerShell-native version:
PS> Get-NetNeighbor -AddressFamily IPv4 |
Format-Table IPAddress, LinkLayerAddress, State, InterfaceAlias -AutoSize
# just the confirmed-reachable ones
PS> Get-NetNeighbor -State Reachable
Where this one is genuinely worth a direct table: state names
Windows’ neighbor states map almost one-to-one onto Linux’s, close enough that if you already read ip neigh output, Get-NetNeighbor’s State column needs no new mental model, just a name change:
Windows Get-NetNeighbor state | Linux ip neigh state | What it means |
|---|---|---|
Reachable | REACHABLE | Confirmed responsive within the last minute |
Stale | STALE | Previously reachable, not re-verified, still usable |
Delay | (transient, no separate Linux label) | No longer confirmed reachable, delaying a probe to allow passive confirmation first |
Probe | PROBE | Actively sending unicast probes to re-verify |
Incomplete | INCOMPLETE | Resolution request sent, no reply yet |
Unreachable | (shows as the entry aging out / FAILED) | Resolution failed |
Permanent | PERMANENT | Statically configured, never expires |
The one genuine difference: Windows names Delay as its own explicit state (RFC 4861’s neighbor unreachability detection state machine, spelled out); Linux’s ip neigh output doesn’t surface that exact transient state as its own label the same way, it moves through it internally but you’re less likely to catch it mid-command.
Why this table matters more than it looks like it should
An empty or single-entry neighbor table, like the one this sandbox produced above, one entry, the gateway, is completely normal for a host that’s only ever talked to its own gateway. It gets interesting the moment two hosts on the same subnet can’t reach each other: ip neigh / Get-NetNeighbor tells you immediately whether the problem is Layer 2 (no MAC resolved at all, stuck INCOMPLETE) or something above it (MAC resolved fine, REACHABLE, so the failure is a firewall, a routing issue, or the application itself). That’s a five-second check that rules out an entire class of problem before you go looking anywhere more complicated, and it’s identical in value on either OS.
Clearing the cache
Both platforms let you drop a stale entry rather than wait for it to age out on its own.
# Linux: flush the whole table
$ sudo ip neigh flush all
# Windows: no single "flush all" switch, pipe every current entry through Remove-NetNeighbor instead
PS> Get-NetNeighbor | Remove-NetNeighbor -Confirm:$false
Remove-NetNeighbor takes filters (-IPAddress, -State, and so on) if you only want to drop specific entries rather than everything, the pipe-from-Get-NetNeighbor pattern above is just the “clear it all” case, mirroring ip neigh flush all without a single dedicated flag for it. The classic arp -d * still works too, and is the more familiar one-liner if you’re not in a PowerShell session already.
Quick reference
| What you want | Linux | Windows |
|---|---|---|
| Full neighbor/ARP table | ip neigh show | Get-NetNeighbor |
| Legacy tool | arp -a | arp -a |
| IPv6 neighbors | ip -6 neigh show | Get-NetNeighbor -AddressFamily IPv6 |
| Only confirmed-reachable entries | ip neigh show nud reachable | Get-NetNeighbor -State Reachable |
| Clear/flush the cache | ip neigh flush all | arp -d * (or Get-NetNeighbor | Remove-NetNeighbor -Confirm:$false) |
What’s next
Part 8 moves from “who’s on this subnet” to “is this specific service actually listening”: nc/curl//dev/tcp against Test-NetConnection -Port, and the real difference between a closed port and a filtered one.