rtnetlink の RTM_GETLINK ダンプはシステム上のすべてのネットワークインターフェースを返す:ループバック、物理 NIC、ブリッジ、veth ペア、VLAN、WireGuard トンネル、TUN デバイスなど。物理有線 Ethernet アダプタだけが欲しい場合、3つのカテゴリをフィルタで除去する必要がある:
- 非 Ethernet リンクタイプ — ループバック(
ARPHRD_LOOPBACK)、WireGuard/TUN(ARPHRD_NONE)など。 - 仮想 Ethernet インターフェース — ブリッジ、veth、VLAN、bond、macvlan など。これらはすべて
ifi_type == ARPHRD_ETHERを報告するが、kind を識別するIFLA_LINKINFO属性を持つ。 - ワイヤレスインターフェース — Wi-Fi アダプタも
ARPHRD_ETHERを報告しIFLA_LINKINFOを持たないため、netlink だけでは有線 Ethernet と区別できない。
この記事では3つのフィルタすべてを適用し、物理有線 Ethernet リンクのみを出力するコンパクトな C++ プログラムを示す。
3つのフィルタ
フィルタ1:ifi_type == ARPHRD_ETHER
struct ifinfomsg の ifi_type フィールドは ARP ハードウェアタイプを保持。有線 Ethernet、Wi-Fi、ブリッジ、veth はすべて ARPHRD_ETHER(1)を報告。ループバックは 772(ARPHRD_LOOPBACK)、トンネルインターフェース(WireGuard、TUN)は 65534(ARPHRD_NONE)を報告。このフィルタは L2 で Ethernet フレーム化されないすべてを除去する。
フィルタ2:IFLA_LINKINFO なし
仮想 Ethernet インターフェース——ブリッジ(Kind: bridge)、veth(Kind: veth)、VLAN(Kind: vlan)、bond(Kind: bond)、macvlan など——はすべて、内部に IFLA_INFO_KIND を持つネストされた IFLA_LINKINFO 属性を持つ。物理インターフェース(有線・無線両方)は IFLA_LINKINFO を一切持たない。
つまり:属性リストに IFLA_LINKINFO が存在すれば、そのインターフェースは仮想でありスキップすべき。
フィルタ3:/sys/class/net/<name>/wireless なし
Wi-Fi アダプタは IFLA_LINKINFO を持たない物理デバイスなので、フィルタ1と2を通過する。カーネルは rtnetlink リンクダンプで「ワイヤレスかどうか」フラグを公開しない(IFLA_WIRELESS 属性は wireless extension イベントでのみ送信され、ダンプ応答には含まれない)。
Wi-Fi を検出する確実な方法は /sys/class/net/<name>/wireless の存在確認——カーネルはこのディレクトリをワイヤレスインターフェースにのみ作成する。候補インターフェースごとに stat() 呼び出しが必要だが、通常はごく少数なので無視できる。
プログラム
#include <iostream>
#include <cstdint>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_arp.h>
int main() {
int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock_fd < 0) { perror("socket"); return 1; }
struct sockaddr_nl sa{};
sa.nl_family = AF_NETLINK;
sa.nl_pid = getpid();
bind(sock_fd, reinterpret_cast<struct sockaddr*>(&sa), sizeof(sa));
// 全リンクのダンプを要求(AF_UNSPEC = すべてのアドレスファミリ)
struct {
struct nlmsghdr nlh;
struct ifinfomsg ifm;
} req{};
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_type = RTM_GETLINK;
req.ifm.ifi_family = AF_UNSPEC;
struct sockaddr_nl dest{};
dest.nl_family = AF_NETLINK;
struct iovec iov = { &req, req.nlh.nlmsg_len };
struct msghdr msg = { &dest, sizeof(dest), &iov, 1, nullptr, 0, 0 };
sendmsg(sock_fd, &msg, 0);
char buf[16384];
bool running = true;
while (running) {
ssize_t len = recv(sock_fd, buf, sizeof(buf), 0);
if (len < 0) break;
auto* nlh = reinterpret_cast<struct nlmsghdr*>(buf);
for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_DONE) { running = false; break; }
if (nlh->nlmsg_type != RTM_NEWLINK) continue;
auto* ifi = reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(nlh));
// フィルタ1:Ethernet ハードウェアタイプのみ(ループバック、
// WireGuard、TUN、その他の非 Ethernet リンクタイプを除外)。
if (ifi->ifi_type != ARPHRD_ETHER) continue;
// 属性を走査して名前を抽出し IFLA_LINKINFO を検出。
int attr_len = IFLA_PAYLOAD(nlh);
struct rtattr* rta = IFLA_RTA(ifi);
std::string name;
bool has_linkinfo = false;
for (; RTA_OK(rta, attr_len); rta = RTA_NEXT(rta, attr_len)) {
if (rta->rta_type == IFLA_IFNAME)
name = reinterpret_cast<char*>(RTA_DATA(rta));
if (rta->rta_type == IFLA_LINKINFO)
has_linkinfo = true;
}
// フィルタ2:仮想インターフェースをスキップ(bridge、veth、vlan、…)。
// すべて IFLA_LINKINFO と内部の IFLA_INFO_KIND を持つ。
if (has_linkinfo) continue;
// フィルタ3:Wi-Fi アダプタをスキップ。カーネルはワイヤレス
// インターフェースにのみ /sys/class/net/<name>/wireless
// ディレクトリを公開する;rtnetlink 自体にはダンプ応答に
// 「ワイヤレスかどうか」フラグはない。
std::string sysfs_wireless = "/sys/class/net/" + name + "/wireless";
struct stat st;
if (stat(sysfs_wireless.c_str(), &st) == 0) continue;
std::cout << name << " (ifindex=" << ifi->ifi_index
<< " flags=0x" << std::hex << ifi->ifi_flags
<< std::dec << ")\n";
}
}
close(sock_fd);
return 0;
}注意:<linux/if_arp.h> と <net/if.h> は両方とも IFF_* フラグを定義し、一緒にインクルードすると衝突する。このプログラムは <linux/if_arp.h> の ARPHRD_ETHER のみ必要なので <net/if.h> は省略。IFF_* フラグ定数が必要なら <net/if.h> のみをインクルードし、ARPHRD_ETHER を 1 とハードコードする。
ビルドと実行
g++ -o list_phys_eth list_phys_eth.cpp
./list_phys_eth特別な権限は不要。
出力例
ループバック、Wi-Fi、複数の Docker ブリッジと veth ペア、WireGuard トンネル、1つの USB Ethernet アダプタがあるホストで、プログラムは物理有線 Ethernet リンクのみを出力:
enx8e4f6eac342f (ifindex=467 flags=0x11043)比較として、同じホストの完全な rtnetlink ダンプには30以上のインターフェースがある——すべて上記3つのチェックで除去される。
なぜインターフェース名だけをチェックしないのか?
インターフェース名は慣例に従う(enX = 有線 Ethernet、wlX = Wi-Fi、brX = ブリッジ、vethX = veth ペア)が、これは慣例であり保証ではない。USB Ethernet アダプタはデフォルトで enx<MAC> を使うが、udev ルールや ip link set name でリネームできる。IFLA_LINKINFO + sysfs アプローチは、ユーザーが付けた名前ではなくカーネルがインターフェースについて報告するものを検査するため、リネームに対して堅牢。
このフィルタの派生元である完全なリンクダンププログラムは rtnetlink API ですべてのネットワークリンクを一覧する方法、補完となる VLAN フィルタは rtnetlink で VLAN インターフェースをフィルタする方法、属性走査イディオムは rtnetlink rtattr 属性を反復する方法 も参照。