How to list all network links using rtnetlink API

The Linux kernel exposes network interface (“link”) information through the NETLINK_ROUTE family of the netlink socket API. Unlike getifaddrs() or /proc/net/dev, rtnetlink gives you the same structured data that ip link uses: interface type, flags, MTU, hardware address, queueing discipline, link kind (bridge/veth/wireguard/…), slave relationships, and 64-bit statistics.

This post walks through a complete C++ program that sends an RTM_GETLINK dump request and prints every link the kernel reports, decoding the most useful IFLA_* attributes.

How it works

The program follows the standard rtnetlink dump pattern:

  1. Open a NETLINK_ROUTE socket with socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE).
  2. Bind it to your own PID (optional for a one-shot dump, but good practice).
  3. Send an RTM_GETLINK request with NLM_F_DUMP — this asks the kernel for all links. The request payload is just an nlmsghdr followed by an ifinfomsg with ifi_family = AF_UNSPEC (all address families).
  4. recv() replies in a loop until you hit an NLMSG_DONE message. The kernel may split the dump across several datagrams, so keep reading until the done marker.
  5. For each RTM_NEWLINK message, the ifinfomsg holds the fixed fields (index, type, flags) and is followed by a sequence of rtattrs carrying the variable-length details. Walk them with RTA_OK / RTA_NEXT and dispatch on rta_type.

See How to iterate rtnetlink rtattr attributes for a focused look at the attribute-walking idiom.

The full program

Building and running

Compile with any recent GCC or Clang — no external libraries are needed, only the kernel UAPI headers:

The program needs no special privileges: reading the link dump via rtnetlink is allowed for unprivileged users (the same way ip link show works without root).

Example output

The output below is an excerpt (counts trimmed) showing several link types the kernel reports on a typical Docker + WireGuard + Tailscale host: a loopback, a Wi-Fi interface, two bridges, a veth pair port enslaved to a bridge, a WireGuard interface, and a TUN interface.

A few things worth noting in the output:

  • Type (Family) is the ARP hardware type (ifi_type), not an address family. 1 is Ethernet (ARPHRD_ETHER), 772 is loopback (ARPHRD_LOOPBACK), and 65534 is the catch-all ARPHRD_NONE used by tunnel interfaces such as WireGuard and TUN.
  • Kind: comes from IFLA_LINKINFOIFLA_INFO_KIND and identifies the link driver: bridge, veth, wireguard, tun, etc. Loopback and physical interfaces usually omit it.
  • Slave Kind: is present when the link is the port of a master device. The veth822ece7 entry shows Kind: veth (what it is) and Slave Kind: bridge (what it’s attached to), plus Parent IfIndex: 2 pointing at the bridge.
  • Promiscuity: 1 on the veth is normal: bridges put their ports into promiscuous mode so they receive every frame, not just unicast destined for their MAC.
  • Link Mode: dormant (1) on the Wi-Fi interface means the driver uses the dormant state while the link is not yet fully operational (e.g. during association); once carrier comes up the operational state moves to UP.
  • Tunnel interfaces (wireguard, tun) report POINTOPOINT and NOARP flags and have no MAC/Broadcast attributes — they are L3-only, so there is no L2 address to report.

Also see How to iterate rtnetlink rtattr attributes for the attribute-walking idiom used throughout the program, How to filter physical ethernet links using rtnetlink for filtering the dump down to physical wired NICs, and How to filter VLAN interfaces using rtnetlink for filtering the dump down to VLAN interfaces.


Check out similar posts by category: C/C++ Linux