You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C++通过Netlink获取桥接接口成员及tun接口所属桥

如何通过C++识别桥接接口的成员TUN接口?

我想了解如何用C++语言显示桥接接口的成员接口。目前已能通过Netlink获取系统接口的目标ID、父ID及类型,但无法据此识别桥接接口的成员接口。

现有系统接口如下:

  1. lo
  2. ens2
  3. eno2
  4. eno1
  5. vethgw@if2
  6. vethbras@if2
  7. br0
  8. vlan4094@br0
  9. vlan4093@br0
  10. virbr0
  11. tap0
  12. 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来查询。

实现步骤

  1. 收集所有网桥接口的索引:通过RTM_GETLINK遍历所有接口,标记出类型为bridge的接口索引(比如示例中的7和10)。
  2. 查询每个网桥的成员端口:针对每个网桥索引,发送RTM_GETBRPORT请求,获取该网桥下的所有成员端口信息。
  3. 关联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;
}

集成到现有流程

  1. 在原有function中,先遍历所有接口,收集:
    • 所有网桥的索引(存入std::vector<__u32> bridges)
    • 所有TUN接口的索引和名称(存入std::map<__u32, std::string> tun_interfaces)
  2. 遍历每个网桥索引,调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 19:52:02