Route Graph
Paste the output of netstat -rn from any machine and read its routing table as a picture.
Input
Optional: paste ifconfig -a or ip addr to label interfaces with their addresses and MACs
Optional: paste an earlier netstat -rn to see what changed (before and after connecting a VPN, plugging in Ethernet, ...)
What this table says
What changed since the earlier table
How to read this
Read left to right, the way a packet leaves the machine. Interfaces are the doors out. Each interface owns the prefixes it is directly attached to (routes without the G flag): anything in such a prefix is reached without a router, by asking the link for the destination's MAC address (ARP for IPv4, NDP for IPv6). The hosts the kernel has already resolved show up as neighbors under that prefix; a router / next hop is simply a neighbor that other routes point at.
Routes with the G flag are remote prefixes: the packet is handed to the next hop, which forwards it on. The default route (0.0.0.0/0 or ::/0) is the shortest prefix of all, so it only wins when nothing more specific matches, and that is what makes "everything else" work.
When several prefixes contain the destination, the kernel picks the longest prefix (most specific). Type an address in the trace box to watch that decision happen: the chosen path lights up, the packet walk shows the two headers the frame carries, and the range view shows why the smallest range wins. Hover any node for the raw netstat line with its flags decoded; click to pin the highlight.
What the filters hide
Link-local is 169.254.0.0/16 and fe80::/10: valid on one physical link only, never forwarded by a router. IPv4 hosts self-assign one when DHCP fails. In IPv6 every interface always has one, and it is what neighbor discovery and router advertisements run over, which is why an IPv6 default route usually points at an fe80:: gateway. The %en0 suffix exists because the same prefix lives on every interface, so the address alone does not say which link is meant.
Loopback & self is lo0, 127.0.0.0/8, ::1 and this host's own addresses. macOS installs a host route via lo0 for each of its own addresses so that a packet to yourself is delivered internally instead of being sent out the Wi-Fi card. Linux keeps those in a separate local table; Windows shows them as On-link /32 rows.
Multicast is 224.0.0.0/4 and ff00::/8, one-to-many group addresses. 224.0.0.251 is mDNS (Bonjour); its MAC 01:00:5e:00:00:fb is computed from the IP, not learned by ARP. ff02::/32 per interface is link-scope multicast, where NDP lives (ff02::1 all nodes, ff02::2 all routers).
Bookkeeping is what the OS adds for itself: host-sized cloning routes such as 192.168.1.1/32 UCS that pin the router and the host's own address, broadcast addresses, and on macOS one interface-scoped duplicate default route per tunnel (the I flag) so that a socket bound to that interface has something to use. Ordinary sockets never see those.
Neighbors, ARP and MAC addresses
ARP is lazy: nothing scans the subnet. A neighbor row appears only when this host tried to send to that address, or when that device sent an ARP request asking for this host. The kernel then clones a host route from the connected prefix (the W flag, "was cloned") and, if it was the one asking, broadcasts "who has X?" to ff:ff:ff:ff:ff:ff.
A resolved entry has a MAC in the gateway column and a countdown in the Expire column (about 20 minutes on macOS; the default gateway's entry is refreshed constantly). An incomplete entry shows link#N instead of a MAC and carries the R flag: the request went unanswered, typically because the device is asleep or gone, and the entry now rejects sends quickly instead of re-asking for every packet. The L flag is on both kinds; it marks an ARP-type entry, not a known MAC.
Two bits in the first byte of a MAC tell you a lot. The 0x02 bit set means locally administered: a randomized address, which phones, tablets and modern laptops use per network for privacy. The 0x01 bit set means multicast. A router's MAC is usually a burned-in one with neither bit set.
Tunnels and VPNs
A utun, tun or wg interface is a tunnel endpoint: a point-to-point interface with a process instead of hardware behind it. Whatever the kernel routes to it is handed to that process as raw packets, and whatever the process writes back looks like packets arriving on it. macOS creates several for its own services (Continuity, iMessage/FaceTime signaling, iCloud Private Relay); those carry only link-local addresses and scoped routes and never see ordinary traffic.
Traffic reaches a tunnel three ways. Routes: give it an address and point prefixes at it; every app's packets follow the longest-prefix match without knowing. VPNs add 0.0.0.0/1 and 128.0.0.0/1, which beat the real default route by one bit without deleting it, plus a host route for the VPN server via the real gateway so the encrypted traffic does not loop back in. Binding: an app binds its socket to the interface and uses that interface's scoped routes. Policy: the kernel steers flows by app or domain before routing happens, which is how per-app VPNs and Apple's relay work and why the table shows nothing about them. Paste a before-and-after pair above to watch a VPN take over.
The same idea on three operating systems
| Concept | macOS / BSD | Linux | Windows |
|---|---|---|---|
| directly on the link, no router | gateway link#12, flags UCS | gateway 0.0.0.0, flags U | gateway On-link |
| via a next hop | UGS with the router's IP | UG with the router's IP | gateway column holds the router's IP |
| default route | default | 0.0.0.0 mask 0.0.0.0 | 0.0.0.0 mask 0.0.0.0, lowest metric wins |
| prefix length | 10/24, classful shorthand when omitted (127 = /8) | Genmask column | Netmask column |
| ARP cache | inside the table as UHLW host routes | separate: ip neigh | separate: arp -a |
| own addresses | host routes via lo0 | separate local table | On-link /32 rows |
Flags used in this table
All routes
| destination | gateway | flags | iface | expire | what it means |
|---|