How to list physical or VLAN network interfaces using the Tether NetworkInterface API

Tether’s HAL layer includes a platform-agnostic NetworkInterfaceEnumerator that uses rtnetlink to list all network interfaces on a Linux system and classify them by type — physical Ethernet, Wi-Fi, bridge, veth, VLAN, tunnel, etc. This post shows how to use it in a standalone project with Tether as a git submodule, compiling only the single source file needed and linking zero Tether libraries.

The API

The enumerator lives in <tether/hal/NetworkInterfaceEnumerator.hpp> and provides:

  • enumerateNetworkInterfaces() — returns a std::vector<NetworkInterface> of all interfaces
  • getPhysicalEthernetInterfaces() — filters to physical wired Ethernet adapters only
  • getInterfacesByType(InterfaceType type) — filters by any type (VLAN, bridge, veth, tunnel, …)
  • getInterfacesByCategory(InterfaceCategory category) — filters to Physical or Virtual

Each NetworkInterface struct carries the interface name, ifindex, ARP hardware type, link kind (from IFLA_INFO_KIND), MAC address, MTU, flags, carrier state, wireless flag, parent ifindex, and a classified InterfaceType + InterfaceCategory.

Project setup

1. Add Tether as a submodule

setup.sh
mkdir my-project && cd my-project
git init
git submodule add https://github.com/ulikoehler/Tether.git third_party/Tether
git submodule update --init --recursive

The --recursive flag is needed because Tether itself has submodules (the enumerator only needs magic_enum, but recursive init is simplest).

2. CMakeLists.txt

The key insight is that LinuxNetworkInterfaceEnumerator.cpp is self-contained: it only depends on HALTypes.hpp (for the MacAddress type) and magic_enum (a header-only library). It does not depend on tether_common, tether_hal, or any other Tether library. So we compile just that one file directly:

CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(network_iface_demo CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Tether submodule path
set(TETHER_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/third_party/Tether)

# Only the single source file we need — no Tether libraries linked.
add_executable(list_interfaces
    main.cpp
    ${TETHER_ROOT}/src/hal/LinuxNetworkInterfaceEnumerator.cpp
)

target_include_directories(list_interfaces PRIVATE
    ${TETHER_ROOT}/include
    ${TETHER_ROOT}/include/tether
    ${TETHER_ROOT}/dependencies/magic_enum/include
)

No add_subdirectory(third_party/Tether), no target_link_libraries(... tether_hal), no EtherCAT, no Drogon, no Klipper — just one .cpp file and three include paths.

3. The example program

main.cpp
#include <tether/hal/NetworkInterfaceEnumerator.hpp>
#include <format>
#include <iostream>

using namespace EtherCAT::HAL;

int main() {
    // --- List all physical wired Ethernet interfaces ---
    std::cout << "Physical Ethernet interfaces:\n";
    for (const auto& iface : getPhysicalEthernetInterfaces()) {
        std::cout << std::format("  {} (ifindex={}, mtu={})\n",
            iface.name, iface.ifindex, iface.mtu);
    }

    // --- List all VLAN interfaces ---
    std::cout << "\nVLAN interfaces:\n";
    for (const auto& iface : getInterfacesByType(InterfaceType::Vlan)) {
        std::cout << std::format("  {} (ifindex={}", iface.name, iface.ifindex);
        if (iface.parentIfindex)
            std::cout << std::format(", parent={}", *iface.parentIfindex);
        std::cout << ")\n";
    }

    // --- List all interfaces with their classification ---
    std::cout << "\nAll interfaces:\n";
    for (const auto& iface : enumerateNetworkInterfaces()) {
        std::cout << std::format("  {:<20} {:<10} {}\n",
            iface.name,
            interfaceTypeToString(iface.type),
            interfaceCategoryToString(iface.category));
    }

    return 0;
}

4. Build and run

build.sh
cmake -B build
cmake --build build
./build/list_interfaces

Example output

On a host with a USB Ethernet adapter, Wi-Fi, Docker bridges, veth pairs, and WireGuard tunnels:

list_interfaces-output.txt
Physical Ethernet interfaces:
  enx8e4f6eac342f (ifindex=467, mtu=1500)

VLAN interfaces:

All interfaces:
  lo                  Loopback   Virtual
  wlo1                Wireless   Physical
  virbr0              Bridge     Virtual
  docker0             Bridge     Virtual
  br-4986fb404ec2     Bridge     Virtual
  veth822ece7         Veth       Virtual
  veth89db15f         Veth       Virtual
  ztno5qdlwc          Tunnel     Virtual
  MyWireguard         Tunnel     Virtual
  tailscale0          Tunnel     Virtual
  enx8e4f6eac342f     Ethernet   Physical

The getPhysicalEthernetInterfaces() filter correctly excludes the Wi-Fi adapter (wlo1), all bridges, veths, and tunnels, returning only the USB Ethernet adapter. The VLAN list is empty because no VLAN interfaces exist on this host — see How to filter VLAN interfaces using rtnetlink for what a VLAN entry looks like.

How the classification works

The enumerator applies three layers of classification:

  1. ARP hardware type (ifi_type): ARPHRD_ETHER (1) for Ethernet/Wi-Fi/bridges/veths, ARPHRD_LOOPBACK (772) for loopback, ARPHRD_NONE (65534) for tunnels.

  2. IFLA_INFO_KIND from the nested IFLA_LINKINFO attribute: "bridge", "veth", "vlan", "wireguard", "tun", etc. Physical interfaces have no IFLA_LINKINFO at all.

  3. /sys/class/net/<name>/wireless existence: distinguishes Wi-Fi from wired Ethernet, since both report ARPHRD_ETHER with no IFLA_LINKINFO.

The InterfaceCategory (Physical vs. Virtual) is derived from the InterfaceType: Ethernet and Wireless are Physical; everything else is Virtual.

Available InterfaceType values

TypeKind stringDescription
LoopbackLoopback interface (lo)
EthernetPhysical wired Ethernet adapter
WirelessWi-Fi adapter (detected via sysfs)
BridgebridgeSoftware bridge
VethvethVirtual ethernet pair endpoint
Vlanvlan802.1Q VLAN interface
BondbondLink aggregation
Macvlanmacvlan / macvtapMAC-based virtual interface
Tunnelwireguard / tun / tap / gre / …L3 tunnel
Other(unknown kind)Virtual interface of unrecognized kind
UnknownCould not be classified

Also see How to list all network links using rtnetlink API for the raw rtnetlink approach without Tether, How to filter physical ethernet links using rtnetlink for the underlying filtering logic, and How to iterate rtnetlink rtattr attributes for the attribute-walking idiom.


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