QEMU的网桥辅助程序

网络虚拟化 专栏收录该内容
20 篇文章 0 订阅

代码文件为qemu-bridge-helper.c,负责完成QEMU TAP设备的创建和网桥相关配置。

QEMU程序在安装之后,网桥帮助程序默认安装到位于/usr/local/libexec目录下,可执行文件为qemu-bridge-helper。其默认创建名称为br0(见宏定义DEFAULT_BRIDGE_INTERFACE)的网桥设备。

#define CONFIG_QEMU_HELPERDIR "/usr/local/libexec"
#define DEFAULT_BRIDGE_HELPER CONFIG_QEMU_HELPERDIR "/qemu-bridge-helper"
#define DEFAULT_BRIDGE_INTERFACE "br0"

#define CONFIG_QEMU_CONFDIR "/usr/local/etc/qemu"
#define DEFAULT_ACL_FILE CONFIG_QEMU_CONFDIR "/bridge.conf"

由其帮助可知,两个必须的参数为网桥的名称和一个UNIX类型套接口的描述符。

$ qemu-bridge-helper --help
Usage: qemu-bridge-helper [--use-vnet] --br=bridge --fd=unixfd


QEMU调用部分

在QEMU代码中,函数net_init_bridge初始化与帮助程序交互的参数,并且调用net_bridge_run_helper执行帮助函数。

int net_init_bridge(const Netdev *netdev, const char *name, NetClientState *peer, Error **errp)
{
    const NetdevBridgeOptions *bridge;
    const char *helper, *br;

    assert(netdev->type == NET_CLIENT_DRIVER_BRIDGE);
    bridge = &netdev->u.bridge;

    helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER;
    br     = bridge->has_br     ? bridge->br     : DEFAULT_BRIDGE_INTERFACE;
    
    fd = net_bridge_run_helper(helper, br, errp);

    fcntl(fd, F_SETFL, O_NONBLOCK);
    vnet_hdr = tap_probe_vnet_hdr(fd);
    s = net_tap_fd_init(peer, "bridge", name, fd, vnet_hdr);
}

函数net_bridge_run_helper执行具体的helper调用,其将使用fork创建一个子进程执行helper。 首先创建两个UNIX类型的套接口(socketpair)用于qemu与helper两个进程之间的通信,父进程QEMU使用sv[0]套接口,sv[1]由子进程helper使用。

    int sv[2];

    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        error_setg_errno(errp, errno, "socketpair() failed");
        return -1;
    }

    /* try to launch bridge helper */
    pid = fork();

对于子进程来说,其关闭了所有从父进程继承来的描述符,仅保留UNIX套接口描述符sv[1],并且将sv[1]作为helper程序的--fd=的参数值传递。另外一个参数--br=使用net_bridge_run_helper的第二个参数赋值,默认为br0。由以下代码可见,QEMU默认有--use-vnet选项。

    if (pid == 0) {
        int open_max = sysconf(_SC_OPEN_MAX), i;

        for (i = 3; i < open_max; i++) {
            if (i != sv[1])
                close(i);
        }

        snprintf(fd_buf, sizeof(fd_buf), "%s%d", "--fd=", sv[1]);

        if (strrchr(helper, ' ') || strrchr(helper, '\t')) {

        } else {
            snprintf(br_buf, sizeof(br_buf), "%s%s", "--br=", bridge);

            parg = args;
            *parg++ = (char *)helper;
            *parg++ = (char *)"--use-vnet";
            *parg++ = fd_buf;
            *parg++ = br_buf;
            *parg++ = NULL;

            execv(helper, args);
        }
    }


对于父进程来说,其关闭子进程使用的UNIX套接口sv[1],之后轮询UNIX套接口sv[0],等待helper子进程返回执行结果,随后关闭UNIX套接口,并且使用waitpid等待子进程结束。

	else {
        int fd;
        int saved_errno;

        close(sv[1]);

        do {
            fd = recv_fd(sv[0]);
        } while (fd == -1 && errno == EINTR);
        saved_errno = errno;

        close(sv[0]);

        while (waitpid(pid, &status, 0) != pid) {
            /* loop */
        }
        return fd;
    }

网桥帮助程序


再来看一下qemu-bridge-helper帮助程序的主函数。其在执行任何操作之前,先检查访问控制文件DEFAULT_ACL_FILE是否允许操作此网桥,位于目录/usr/local/etc/qemu/,文件名bridge.conf。只有在ACL_ALLOW_ALL后者明确允许此网桥ACL_ALLOW的条件下,并且没有拒绝的ACL控制条件下,才允许继续操作。

    if (parse_acl_file(DEFAULT_ACL_FILE, &acl_list) == -1) {
        fprintf(stderr, "failed to parse default acl file `%s'\n",
                DEFAULT_ACL_FILE);
        ret = EXIT_FAILURE;
        goto cleanup;
    }

    QSIMPLEQ_FOREACH(acl_rule, &acl_list, entry) {
        switch (acl_rule->type) {
        case ACL_ALLOW_ALL:
            access_allowed = 1;
            break;
        case ACL_ALLOW:
            if (strcmp(bridge, acl_rule->iface) == 0) {
                access_allowed = 1;
            }
            break;
    }

接下来,创建TAP设备,名称指定为tap%d,由内核决定其索引值,例如第一个TAP设备为tap0。随后指定其标志IFF_TAP确定为TAP设备,IFF_NO_PI标志标明不需要传输数据包信息(Packet Info),由于在之前的父进程中固定了--use-vnet选项,此处,如果支持vnet头部的话,指定IFF_VNET_HDR标明需要传输virtio头部信息。

    fd = open("/dev/net/tun", O_RDWR);

    prep_ifreq(&ifr, "tap%d");
    ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
    if (use_vnet && has_vnet_hdr(fd)) {
        ifr.ifr_flags |= IFF_VNET_HDR;
    }

    ioctl(fd, TUNSETIFF, &ifr);

接下来,获取到网桥的MTU数值,并将其设置给创建的TAP设备。

    /* get the mtu of the bridge */
    prep_ifreq(&ifr, bridge);
    ioctl(ctlfd, SIOCGIFMTU, &ifr);

    /* save mtu */
    mtu = ifr.ifr_mtu;

    /* set the mtu of the interface based on the bridge */
    prep_ifreq(&ifr, iface);
    ifr.ifr_mtu = mtu;
    ioctl(ctlfd, SIOCSIFMTU, &ifr);

由于网桥的MAC地址取自其子接口中MAC地址最小的接口, 为了不影响网桥目前的MAC地址,将目前TAP设备的MAC地址的第一个字节赋值为0xFE。

    ioctl(ctlfd, SIOCGIFHWADDR, &ifr);
    ifr.ifr_hwaddr.sa_data[0] = 0xFE;
    ioctl(ctlfd, SIOCSIFHWADDR, &ifr);

将TAP设备设置为网桥的一个子接口,由ioctl系统调用SIOCBRADDIF实现。

    /* add the interface to the bridge */
    prep_ifreq(&ifr, bridge);
    ifindex = if_nametoindex(iface);

    ifr.ifr_ifindex = ifindex;
    ret = ioctl(ctlfd, SIOCBRADDIF, &ifr);

将TAP设备设置为UP状态。

    /* bring the interface up */
    prep_ifreq(&ifr, iface);
    ioctl(ctlfd, SIOCGIFFLAGS, &ifr);

    ifr.ifr_flags |= IFF_UP;
    ioctl(ctlfd, SIOCSIFFLAGS, &ifr);

最后,将创建的TAP设备的文件描述符fd通过以参数传递进来的UNIX套接口发送给父进程。

    /* write fd to the domain socket */
    send_fd(unixfd, fd);

后记


在父进程QEMU接收到新创建的TAP设备的文件描述符之后,net_init_bridge函数调用net_tap_fd_init进行此fd在QEMU中的初始化。qemu_new_net_client函数创建网络客户端结构,及其相关参数。

static TAPState *net_tap_fd_init(NetClientState *peer, const char *model, const char *name, int fd, int vnet_hdr)
{
    NetClientState *nc;
    TAPState *s;

    nc = qemu_new_net_client(&net_tap_info, peer, model, name);

    s = DO_UPCAST(TAPState, nc, nc);
    s->fd = fd;

    tap_read_poll(s, true);

    return s;
}

函数tap_read_poll使能轮询读操作,注册tap_send函数接收TAP设备的数据包。tap_send函数随后将读取到的数据包挂载到之前创建的网络客户端结构(NetClientState)的成员incoming_queue所定义的队列上。

static void tap_update_fd_handler(TAPState *s)
{
    qemu_set_fd_handler(s->fd,
                        s->read_poll && s->enabled ? tap_send : NULL,
                        s->write_poll && s->enabled ? tap_writable : NULL,
                        s);
}
static void tap_read_poll(TAPState *s, bool enable)
{
    s->read_poll = enable;
    tap_update_fd_handler(s);
}

 

QEMU版本:qemu-3.1.0

 

 

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值