如何用C++通过Netlink获取桥接接口成员及tun接口所属桥
如何通过C++识别桥接接口的成员TUN接口?
我想了解如何用C++语言显示桥接接口的成员接口。目前已能通过Netlink获取系统接口的目标ID、父ID及类型,但无法据此识别桥接接口的成员接口。
现有系统接口如下:
- lo
- ens2
- eno2
- eno1
- vethgw@if2
- vethbras@if2
- br0
- vlan4094@br0
- vlan4093@br0
- virbr0
- tap0
- tap1
以下是我的代码:
void function(void) { char buf[16192] = {0}; int err = 0; __u16 failcounter = 0; __u32 target_idx = 0; __u32 parent_idx = 0; int devtype_id = 0; int fd = EOF; size_t seq_num = 0; struct sockaddr_nl sa = {0}; struct iovec iov = {0}; struct msghdr msg = {0}; struct nlmsghdr *nh; nl_req_t req = {0}; std::string kinddevname; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if(fd < 0) THROW("Failed to open netlink socket: %s", fcerr()); req.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; req.header.nlmsg_type = RTM_GETLINK; req.header.nlmsg_seq = ++seq_num; req.msg.ifi_family = AF_UNSPEC; req.msg.ifi_change = 0xFFFFFFFF; req.msg.ifi_index = 9; sa.nl_family = AF_NETLINK; iov.iov_base = &req; iov.iov_len = req.header.nlmsg_len; msg.msg_name = &sa; msg.msg_namelen = sizeof sa; msg.msg_iov = &iov; msg.msg_iovlen = 1; if (sendmsg(fd, &msg, 0) < 0) THROW("Failed to sendmsg to netlink socket: %s", fcerr()); iov.iov_base = buf; iov.iov_len = sizeof buf; while (true) { if (_signal == SIGINT) THROW("Interrupted..."); if (failcounter > 64) THROW("Failed to rcvmsg to netlink socket: %s", fcerr()); ssize_t len = recvmsg(fd, &msg, MSG_DONTWAIT); if (len < 0) { if (errno == EINTR || errno == EAGAIN) { usleep(250000); continue; } LOG("Failed to read netlink: %s", fcerr()); failcounter++; continue; } failcounter = 0; for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, (__u32)len); nh = NLMSG_NEXT(nh, len)) { if (_signal == SIGINT) THROW("Interrupted..."); size_t len, info_len, devtype_len; struct ifinfomsg *msg; struct rtattr *rta, *info, *devtype; if(nh->nlmsg_type == NLMSG_DONE) goto nldone; if(nh->nlmsg_type != RTM_BASE) continue; msg = (struct ifinfomsg *)NLMSG_DATA(nh); // message payload if (msg->ifi_type != ARPHRD_ETHER) continue; // Display section if (target_idx > 0) std::cout << target_idx << ' ' << parent_idx << ' ' << kinddevname << std::endl; target_idx = 0; parent_idx = 0; devtype_id = 0; target_idx = msg->ifi_index; rta = IFLA_RTA(msg); // message attributes len = nh->nlmsg_len - NLMSG_LENGTH(sizeof *msg); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { if (_signal == SIGINT) THROW("Interrupted..."); if(rta->rta_type == IFLA_LINK) // there is a "parent" device parent_idx = *(__u16 *)((char *) rta + NLA_HDRLEN); if(rta->rta_type == IFLA_LINKINFO) { info = (rtattr *)RTA_DATA(rta); info_len = RTA_PAYLOAD(rta); while (RTA_OK(info, info_len)) { if (_signal == SIGINT) THROW("Interrupted..."); if (info->rta_type == IFLA_INFO_KIND) { kinddevname.clear(); if (strcmp((char *)RTA_DATA(info), "vlan") == 0) { kinddevname.assign("vlan"); info = RTA_NEXT(info, info_len); if (RTA_OK(info, info_len)) { if (info->rta_type == IFLA_INFO_DATA) { devtype = (rtattr *)RTA_DATA(info); devtype_len = RTA_PAYLOAD(info); while (RTA_OK(devtype, devtype_len)) { if (devtype->rta_type == IFLA_VLAN_ID) devtype_id = *(int *)RTA_DATA(devtype); devtype = RTA_NEXT(devtype, devtype_len); } } } else break; } else if (strcmp((char *)RTA_DATA(info), "bridge") == 0) kinddevname.assign("bridge"); else if (strcmp((char *)RTA_DATA(info), "tun") == 0) kinddevname.assign("tun"); else if (strcmp((char *)RTA_DATA(info), "veth") == 0) kinddevname.assign("veth"); } info = RTA_NEXT(info, info_len); } } } } } nldone: close(fd); fd = EOF; // Display section if (target_idx > 0) std::cout << target_idx << ' ' << parent_idx << ' ' << kinddevname << std::endl; }
该函数的输出如下:
2 0 3 0 4 0 5 2 veth 6 2 veth 7 0 bridge 8 7 vlan 9 7 vlan 10 0 bridge 11 0 tun 12 0 tun
但如您所见,tun接口仅显示了名称和目标索引,请问能否获取这些tun接口所属的桥接接口?
解决方案
问题本质
TAP/TUN接口加入网桥时,不会通过IFLA_LINK字段关联父网桥(该字段仅用于VLAN、VETH这类有直接父设备的接口)。要获取网桥的成员端口,需要通过专门的Netlink消息类型RTM_GETBRPORT来查询。
实现步骤
- 收集所有网桥接口的索引:通过
RTM_GETLINK遍历所有接口,标记出类型为bridge的接口索引(比如示例中的7和10)。 - 查询每个网桥的成员端口:针对每个网桥索引,发送
RTM_GETBRPORT请求,获取该网桥下的所有成员端口信息。 - 关联TUN接口与网桥:将查询到的成员端口索引与之前收集的TUN接口索引匹配,即可得到所属网桥。
修改后的代码示例
以下是新增的获取桥成员端口的函数,可集成到现有代码中:
#include <linux/if_bridge.h> // 获取指定网桥的成员端口列表 std::vector<__u32> get_bridge_ports(int nl_fd, size_t &seq_num, __u32 bridge_idx) { std::vector<__u32> ports; char buf[16192] = {0}; struct nlmsghdr *nh; struct sockaddr_nl sa = {0}; struct iovec iov = {0}; struct msghdr msg = {0}; nl_req_t req = {0}; req.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; req.header.nlmsg_type = RTM_GETBRPORT; req.header.nlmsg_seq = ++seq_num; req.msg.ifi_family = AF_UNSPEC; req.msg.ifi_change = 0xFFFFFFFF; req.msg.ifi_index = bridge_idx; // 指定要查询的网桥索引 sa.nl_family = AF_NETLINK; iov.iov_base = &req; iov.iov_len = req.header.nlmsg_len; msg.msg_name = &sa; msg.msg_namelen = sizeof sa; msg.msg_iov = &iov; msg.msg_iovlen = 1; if (sendmsg(nl_fd, &msg, 0) < 0) { LOG("Failed to send RTM_GETBRPORT: %s", fcerr()); return ports; } iov.iov_base = buf; iov.iov_len = sizeof buf; ssize_t len = recvmsg(nl_fd, &msg, 0); if (len < 0) { LOG("Failed to recv RTM_GETBRPORT: %s", fcerr()); return ports; } for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, (__u32)len); nh = NLMSG_NEXT(nh, len)) { if (nh->nlmsg_type == NLMSG_DONE) break; if (nh->nlmsg_type != RTM_NEWBRPORT) continue; struct ifinfomsg *ifmsg = (struct ifinfomsg *)NLMSG_DATA(nh); ports.push_back(ifmsg->ifi_index); // 成员端口的索引 } return ports; }
集成到现有流程
- 在原有
function中,先遍历所有接口,收集:- 所有网桥的索引(存入
std::vector<__u32> bridges) - 所有TUN接口的索引和名称(存入
std::map<__u32, std::string> tun_interfaces)
- 所有网桥的索引(存入
- 遍历每个网桥索引,调用
get_bridge_ports获取成员端口,然后匹配TUN接口:// 示例:在nldone标签后添加 std::vector<__u32> bridges; std::map<__u32, std::string> tun_interfaces; // 重新遍历接口收集信息(或在第一次遍历中同时收集) // ... 省略收集逻辑 ... for (__u32 br_idx : bridges) { std::vector<__u32> ports = get_bridge_ports(fd, seq_num, br_idx); for (__u32 port_idx : ports) { auto it = tun_interfaces.find(port_idx); if (it != tun_interfaces.end()) { std::cout << "TUN接口 " << it->second << " (索引" << port_idx << ") 属于网桥 br" << br_idx << std::endl; } } }
注意事项
- 需要确保代码包含
<linux/if_bridge.h>头文件,该文件定义了桥相关的Netlink消息类型和结构体。 - 发送
RTM_GETBRPORT时,必须指定ifi_index为目标网桥的索引,否则会返回所有网桥的端口信息。 - 操作Netlink路由套接字需要足够的权限,通常需要以root用户运行程序。
内容的提问来源于stack exchange,提问作者Curiosity
相关产品推荐
相关产品推荐

