Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3566576
  • 博文数量: 205
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7385
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(205)

文章存档

2024年(8)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2021-10-23 15:50:47

DPDK virtio-user介绍及使用

——lvyilong316

最近在看DPDK和容器直接的对接方案,我们知道容器(如Docker)网络一般都采用tap+bridge方案,也就是基于内核的网络通信,即使使用ovs一般也是kernel ovs,而不是dpdk-ovs。那么当我们使用的vswitch是基于DPDK时如何和容器对接呢?下面我们重点分析一下。

首先,我们先确定目标——容器内部无感知且方案通用,所以排除SR-IOVSmartNIC方案。有了这个前提,那就说明容器首先还是需要用tap作为网卡(无感知),要将tap的数据流量连接到DPDK,我们自然就想到DPDKkernel的通信。所以我们先重点回顾一下DPDKkernel的常用方式。

DPDKkernel通信的常用方式

DPDK和kernel直接的通信路径也叫做exception path,DPDK支持几种方式让用户空间的报文重新进入内核协议栈。

TAP/TUN设备

可以使用内核提供的TAP/TUN设备,这种设备的使用需要使用系统调用,并涉及到copy_to_user()和copy_from_user()的开销。DPDK也支持了tun/tap pmd(详见:),可以通过vdev参数指定创建tap设备。对应命令行如下:

--vdev=net_tap0,iface=foo0  --vdev=net_tap1,iface=foo1, ...

但是是有tap设备有个缺陷是用户态部分(tap pmd)每次收发包都需要调用系统调用,我们看DPDKtap pmd发送函数tap_write_mbufs最终就是调用writev,这样必然导致频繁的上下文切换,影响转发性能。

点击(此处)折叠或打开

  1. /* copy the tx frame data */
  2. n = writev(process_private->txq_fds[txq->queue_id], iovecs, j);

Kernel NIC Interface(KNI)

DPDK提供了KNI接口用于提高用户态和内核态之间报文的处理效率。KNI是通过内核模块构造了一个虚拟网络接口,并且通过FIFO和用户态的DPDK应用交换报文。

正如DPDK官方所讲,使用DPDK KNI的好处是:

比现有的Linux TUN / TAP(通过消除系统调用和copy_to_user()/ copy_from_user()操作)性能更高效

允许使用标准Linux网络工具(如ethtoolifconfigtcpdump)管理DPDK端口。

允许与内核网络协议栈的交互。

但是KNI从内核收到报文后最后是通过netif_rx函数进入协议栈的,它并不能直接将流量和某个tap设备关联,从这点看也不符合我们和容器通信的需要。此外,该方案的缺点是KNI内核模块无法upstream,维护代价较大。

virtio-user+vhost-net

virtio-user最初是为了支持容器内部和DPDK通信的,如下图所示,如果vswitch使用的是vhost-user,那么如何和容器对接呢?我们知道在虚拟机场景时,vhost-user是通过mmap整个qemu进程的内存,进而进行共享内存,然后操作virtio ring进行通信。但在容器场景vhost-user怎么共享内存呢?或者说共享谁的内存。virtio-user就是解决这个问题,它的角色类似于虚拟机场景下的virtio-net+qemu,即承担网络驱动的角色(virtio-net),又承担和后端协商的作用(qemu)。这样后端vhost-user就仅需要共享容器中DPDK进程的内存,即可实现通过共享ring实现网络通信。

通过上面我们发现virtio-user充当了qemu+virtio-net的角色,另外我们知道qemu+virtio-net可以和vhost-net组合作为内核场景下的前后端,所以virtio-user一样可以和vhost-net组合构成一个前后端通信模型,这也是virtio-user的第二个作用——使用它与现有的vhost-kernel方案配合来实现exception path。需要内核中vhost.kovhost-net.ko两个模块。如下图所示

为了更加理解这个通信架构和虚拟机qemu场景的异同,我画了如下一个流程对比图。关键的一点,在虚拟机qemu场景中虚拟机中的网卡是qemu虚拟化出来的,而不是tap设备,甚至tap设备也不是虚拟网卡的后端,vhost-net才是后端,而tap设备是由qemu设置作为vhost-net的backend的;在virtio-user容器场景中tap设备是作为容器的网卡的(容器场景没有前后端之说),容器发出的流量时直接通过协议栈进入tap设备的。

可能还有一点容易让人有歧义,如下图,在没有vhost-net时(qemu直接作为后端对接tap的场景),qemu对接tapvirtio-user场景容器对接tap设备有什么不同。虽然都是经过tap设备从用户态发送数据流量,但两者的流量路径完全不同。前者是qemu作为一个普通的用户进程打开tap设备,得到一个fd,通过读写文件fd的方式发送/接收数据,后者是将tap设备作为一个网络设备,应用程序不直接操作感知tap,对于应用程序只是把数据包发给协议栈,由协议栈最终调用tap设备的xmit函数。

当然也可以采用如下图左侧方式使用virtio-user,即通过tap设备桥接到bridge(或直接使用veth-pair,在同另一个tap设备接入容器。这种方案相对右侧来说路径较长,不过它的好处是容器中的网卡(tap)设备不受外部DPDK-ovs的启停影响。这个我们后面再详述。

总之,virtio-uservirtio PMD的虚拟设备,启动DPDK virtio-user,系统就会创建一个内核态的虚拟设备tap, tap通过vhost-kthreadvirtio-user进行数据的发送接收;此外vhost-net的内核模块也是virtio-user的控制面,发送接收一些控制消息。这样一来,从DPDK收到的包进入到virtio-user,通过vhost-kthread进入到tap设备,tap设备支持内核协议栈,很好地实现了exception path的包处理。

从维护角度来看,本方案所依赖的vhost.kovhost-net.ko都是早已upsteam的内核模块,不需要维护out-of-tree的内核模块;从灵活性来看,本方案不依赖任何硬件功能;从线程模型来看,和KNI相似,本方案只需将数据包放到virtio ring上,数据拷贝操作由vhost kthread来完成。从网络功能来看,vhost-net本来就是为网络而生的,能通过checksum计算和验证、数据包分片offload到物理网卡来进行。由于对Multi-seg数据包的支持,和KNI的方案相比, 本方案将iperf的性能提升了2倍以上。

virtio-user+vhost-net实现

首先看一下virtio-user的初始化流程如下:

经过以上初始化流程得到以下数据结构关系:

其中在virtio_user_dev_setup中有如下语句:

点击(此处)折叠或打开

  1. /* 根据path是不是一个socket路径,决定ops是user还是kernel对应的ops */
  2.     if (is_vhost_user_by_type(dev->path)) {
  3.         dev->ops = &ops_user;
  4.     } else {
  5.         dev->ops = &ops_kernel;

  6.         dev->vhostfds = malloc(dev->max_queue_pairs * sizeof(int));
  7.         dev->tapfds = malloc(dev->max_queue_pairs * sizeof(int));
  8.         if (!dev->vhostfds || !dev->tapfds) {
  9.             PMD_INIT_LOG(ERR, "Failed to malloc");
  10.             return -1;
  11.         }

  12.         for (q = 0; q < dev->max_queue_pairs; ++q) {
  13.             dev->vhostfds[q] = -1;
  14.             dev->tapfds[q] = -1;
  15.         }
  16.     }
  17.     根据参数path是不是一个socket路径,决定ops是user(vhost-user)还是kernel(vhost-net)对应的ops,其定义分别如下所示:
  18. struct virtio_user_backend_ops ops_kernel = {
  19.     .setup = vhost_kernel_setup,
  20.     .send_request = vhost_kernel_ioctl,
  21.     .enable_qp = vhost_kernel_enable_queue_pair
  22. };

  23. struct virtio_user_backend_ops ops_user = {
  24.     .setup = vhost_user_setup,
  25.     .send_request = vhost_user_sock,
  26.     .enable_qp = vhost_user_enable_queue_pair
  27. };

我们这里只将kernelvhost-net)场景。我们还注意到后端是vhost-uservhost-net的一个区别是,在vhost-user场景virtio-user-dev设备字需要一个vhostfd,而在vhost-net场景却需要一个vhostfds数组。这是因为当tap设备支持多队列时,需要设置IFF_MULTI_QUEUE这个时候就需要open多次,并且每次设置一次TUNSETIFFtap name相同,每次open返回的fd对应一个tap设备队列,而vhostfds就是用来记录这些队列对应的fd的。

(关于tap设备的使用细节可以参考:参考:https://www.kernel.org/doc/Documentation/networking/tuntap.txt 

    但在上面流程中我们没有看到virtio-usertap设备直接的关系,这个要从上面的vtpci_set_status函数说起。

点击(此处)折叠或打开

  1. void vtpci_set_status(struct virtio_hw *hw, uint8_t status)
  2. {
  3.     if (status != VIRTIO_CONFIG_STATUS_RESET)
  4.         status |= VTPCI_OPS(hw)->get_status(hw);

  5.     VTPCI_OPS(hw)->set_status(hw, status);
  6. }
  7. #define VTPCI_OPS(hw)    (virtio_hw_internal[(hw)->port_id].vtpci_ops)

而在前面virtio_user_eth_dev_alloc的函数中有以下初始化语句:

virtio_hw_internal[hw->port_id].vtpci_ops = &virtio_user_ops;

所以vtpci_set_status就是调用virtio_user_opsset_status,由于virtio_user_ops定义如下,

点击(此处)折叠或打开

  1. const struct virtio_pci_ops virtio_user_ops = {
  2.     .read_dev_cfg    = virtio_user_read_dev_config,
  3.     .write_dev_cfg    = virtio_user_write_dev_config,
  4.     .reset        = virtio_user_reset,
  5.     .get_status    = virtio_user_get_status,
  6.     .set_status    = virtio_user_set_status,
  7.     .get_features    = virtio_user_get_features,
  8.     .set_features    = virtio_user_set_features,
  9.     .get_isr    = virtio_user_get_isr,
  10.     .set_config_irq    = virtio_user_set_config_irq,
  11.     .set_queue_irq    = virtio_user_set_queue_irq,
  12.     .get_queue_num    = virtio_user_get_queue_num,
  13.     .setup_queue    = virtio_user_setup_queue,
  14.     .del_queue    = virtio_user_del_queue,
  15.     .notify_queue    = virtio_user_notify_queue,
  16. };

所以其实是调用virtio_user_set_status

点击(此处)折叠或打开

  1. static void
  2. virtio_user_set_status(struct virtio_hw *hw, uint8_t status)
  3. {
  4.     struct virtio_user_dev *dev = virtio_user_get_dev(hw);

  5.     if (status & VIRTIO_CONFIG_STATUS_DRIVER_OK)
  6.         virtio_user_start_device(dev);
  7.     else if (status == VIRTIO_CONFIG_STATUS_RESET)
  8.         virtio_user_reset(hw);
  9.     dev->status = status;
  10. }

其中关键是VIRTIO_CONFIG_STATUS_DRIVER_OK VIRTIO_CONFIG_STATUS_RESET,我们重点看下前者,也就是virtio_user_start_device

点击(此处)折叠或打开

  1. int virtio_user_start_device(struct virtio_user_dev *dev)
  2. {
  3.     uint64_t features;
  4.     int ret;

  5.     /* Step 0: tell vhost to create queues */
  6.     if (virtio_user_queue_setup(dev, virtio_user_create_queue) < 0)
  7.         goto error;

  8.     /* Step 1: set features */
  9.     features = dev->features;
  10.     /* Strip VIRTIO_NET_F_MAC, as MAC address is handled in vdev init */
  11.     features &= ~(1ull << VIRTIO_NET_F_MAC);
  12.     /* Strip VIRTIO_NET_F_CTRL_VQ, as devices do not really need to know */
  13.     features &= ~(1ull << VIRTIO_NET_F_CTRL_VQ);
  14.     features &= ~(1ull << VIRTIO_NET_F_STATUS);
  15.     ret = dev->ops->send_request(dev, VHOST_USER_SET_FEATURES, &features);
  16.     if (ret < 0)
  17.         goto error;
  18.     PMD_DRV_LOG(INFO, "set features: %" PRIx64, features);

  19.     /* Step 2: share memory regions */
  20.     ret = dev->ops->send_request(dev, VHOST_USER_SET_MEM_TABLE, NULL);
  21.     if (ret < 0)
  22.         goto error;

  23.     /* Step 3: kick queues */
  24.     if (virtio_user_queue_setup(dev, virtio_user_kick_queue) < 0)
  25.         goto error;

  26.     /* Step 4: enable queues
  27.      * we enable the 1st queue pair by default.
  28.      */
  29.     dev->ops->enable_qp(dev, 0, 1);

  30.     return 0;
  31. error:
  32.     /* TODO: free resource here or caller to check */
  33.     return -1;
  34. }

这里注意一下VHOST_USER_SET_MEM_TABLE的设置好像没有传递参数,其实是在对应的ops中组装的参数。如当后端是vhost-user时,对应的send_requestvhost_user_sock

点击(此处)折叠或打开

  1. static int vhost_user_sock(struct virtio_user_dev *dev,
  2.         enum vhost_user_request req,
  3.         void *arg)
  4. {
  5.     struct vhost_user_msg msg;
  6.     struct vhost_vring_file *file = 0;
  7.     int need_reply = 0;
  8.     int fds[VHOST_MEMORY_MAX_NREGIONS];
  9.     int fd_num = 0;
  10.     int i, len;
  11.     int vhostfd = dev->vhostfd;

  12.     RTE_SET_USED(m);

  13.     PMD_DRV_LOG(INFO, "%s", vhost_msg_strings[req]);

  14.     msg.request = req;
  15.     msg.flags = VHOST_USER_VERSION;
  16.     msg.size = 0;

  17.     switch (req) {
  18.     ......
  19.     case VHOST_USER_SET_MEM_TABLE:
  20.         if (prepare_vhost_memory_user(&msg, fds) < 0)
  21.             return -1;
  22.         fd_num = msg.payload.memory.nregions;
  23.         msg.size = sizeof(m.payload.memory.nregions);
  24.         msg.size += sizeof(m.payload.memory.padding);
  25.         msg.size += fd_num * sizeof(struct vhost_memory_region);
  26.         break;
  27.     ......
  28.     }

  29.     ......
  30.     if (req == VHOST_USER_SET_MEM_TABLE)
  31.         for (i = 0; i < fd_num; ++i)
  32.             close(fds[i]);
  33. ......

  34. }

可以看到,VHOST_USER_SET_MEM_TABLE发送前进行了参数的准备,我们看一下 prepare_vhost_memory_user是如何得到一个个映射的memregion的。

点击(此处)折叠或打开

  1. static int prepare_vhost_memory_user(struct vhost_user_msg *msg, int fds[])
  2. {
  3.     int i, num;
  4.     struct hugepage_file_info huges[VHOST_MEMORY_MAX_NREGIONS];
  5.     struct vhost_memory_region *mr;
  6.     /* 从进程的/proc/self/maps中通过过滤map_%d关键字来得到进程当前使用的hugepage内存映射,进而得到memregion */
  7.     num = get_hugepage_file_info(huges, VHOST_MEMORY_MAX_NREGIONS);
  8.     if (num < 0) {
  9.         PMD_INIT_LOG(ERR, "Failed to prepare memory for vhost-user");
  10.         return -1;
  11.     }

  12.     for (i = 0; i < num; ++i) {
  13.         mr = &msg->payload.memory.regions[i];
  14.         mr->guest_phys_addr = huges[i].addr; /* use */
  15.         mr->userspace_addr = huges[i].addr;
  16.         mr->memory_size = huges[i].size;
  17.         mr->mmap_offset = 0;
  18.         fds[i] = open(huges[i].path, O_RDWR);
  19.     }

  20.     msg->payload.memory.nregions = num;
  21.     msg->payload.memory.padding = 0;

  22.     return 0;
  23. }

   通过以上分析可以看出virtio-user进行前后端内存共享就是共享本端的DPDK进程的hugepage。对于后端是vhost-net情况也是类似的,我们看下其对应的mem region是如何产生的。

点击(此处)折叠或打开

  1. static struct vhost_memory_kernel *
  2. prepare_vhost_memory_kernel(void)
  3. {
  4.     uint32_t i, j, k = 0;
  5.     struct rte_memseg *seg;
  6.     struct vhost_memory_region *mr;
  7.     struct vhost_memory_kernel *vm;

  8.     vm = malloc(sizeof(struct vhost_memory_kernel) +
  9.          max_regions *
  10.          sizeof(struct vhost_memory_region));
  11.     if (!vm)
  12.         return NULL;

  13.     for (i = 0; i < RTE_MAX_MEMSEG; ++i) {
  14.         seg = &rte_eal_get_configuration()->mem_config->memseg[i];
  15.         if (!seg->addr)
  16.             break;

  17.         int new_region = 1;

  18.         for (j = 0; j < k; ++j) {
  19.             mr = &vm->regions[j];

  20.             if (mr->userspace_addr + mr->memory_size ==
  21.              (uint64_t)(uintptr_t)seg->addr) {
  22.                 mr->memory_size += seg->len;
  23.                 new_region = 0;
  24.                 break;
  25.             }

  26.             if ((uint64_t)(uintptr_t)seg->addr + seg->len ==
  27.              mr->userspace_addr) {
  28.                 mr->guest_phys_addr =
  29.                     (uint64_t)(uintptr_t)seg->addr;
  30.                 mr->userspace_addr =
  31.                     (uint64_t)(uintptr_t)seg->addr;
  32.                 mr->memory_size += seg->len;
  33.                 new_region = 0;
  34.                 break;
  35.             }
  36.         }

  37.         if (new_region == 0)
  38.             continue;

  39.         mr = &vm->regions[k++];
  40.         /* use vaddr */
  41.         mr->guest_phys_addr = (uint64_t)(uintptr_t)seg->addr;
  42.         mr->userspace_addr = (uint64_t)(uintptr_t)seg->addr;
  43.         mr->memory_size = seg->len;
  44.         mr->mmap_offset = 0;

  45.         if (k >= max_regions) {
  46.             free(vm);
  47.             return NULL;
  48.         }
  49.     }

  50.     vm->nregions = k;
  51.     vm->padding = 0;
  52.     return vm;
  53. }

其实就是映射DPDK进程的memseg,也就是DPDK进程的所有hugepage。但这里注意memseg的个数不能大于 max_regions64),所以要保证DPDK大页内存的碎片不能超过64

如果使用vhost-net的时候关键是需要创建tap设备,以及设置tap设备和vhost-net的关系,而这一切都是在 virtio_user_start_device的最后一步,enable_qp。以vhost-net为例,这里dev->ops->enable_qp(dev, 0, 1)就是vhost_kernel_enable_queue_pair

点击(此处)折叠或打开

  1. static int
  2. vhost_kernel_enable_queue_pair(struct virtio_user_dev *dev,
  3.              uint16_t pair_idx,
  4.              int enable)
  5. {
  6.     int hdr_size;
  7.     int vhostfd;
  8.     int tapfd;
  9.     int req_mq = (dev->max_queue_pairs > 1);
  10.     /* 在vhost_kernel_setup初始化过,每一个qp对应一次vhost-net的open,对应一个vhostfd */
  11.     vhostfd = dev->vhostfds[pair_idx];

  12.     if (!enable) {
  13.         if (dev->tapfds[pair_idx] >= 0) {
  14.             close(dev->tapfds[pair_idx]);
  15.             dev->tapfds[pair_idx] = -1;
  16.         }
  17.         return vhost_kernel_set_backend(vhostfd, -1);
  18.     } else if (dev->tapfds[pair_idx] >= 0) {
  19.         return 0;
  20.     }

  21.     if ((dev->features & (1ULL << VIRTIO_NET_F_MRG_RXBUF)) ||
  22.      (dev->features & (1ULL << VIRTIO_F_VERSION_1)))
  23.         hdr_size = sizeof(struct virtio_net_hdr_mrg_rxbuf);
  24.     else
  25.         hdr_size = sizeof(struct virtio_net_hdr);
  26.     /* 对应每一个qp创建一个tap设备 */
  27.     tapfd = vhost_kernel_open_tap(&dev->ifname, hdr_size, req_mq);
  28.     if (tapfd < 0) {
  29.         PMD_DRV_LOG(ERR, "fail to open tap for vhost kernel");
  30.         return -1;
  31.     }
  32.     /* 将对应的tap设备设置为vhost-net的backend */
  33.     if (vhost_kernel_set_backend(vhostfd, tapfd) < 0) {
  34.         PMD_DRV_LOG(ERR, "fail to set backend for vhost kernel");
  35.         close(tapfd);
  36.         return -1;
  37.     }

  38.     dev->tapfds[pair_idx] = tapfd;
  39.     return 0;
  40. }

这样就将tap设备和vhost-net建立了关联,而由于前面virtio_user_dev_init等函数已经将vhost-user设备和内核的vhost-net建立了关系,所以这样就建立了virtio-uservhost-nettap设备三者的关系。

以上就是控制面的相关流程和数据结构关系,至于数据面流程和相对比较简单,很多都是和qemu vhost-net处理流程相似的。以容器发送方向为例子,首先容器发送数据包,最终经过协议栈调用tap设备的发送函数tun_net_xmit

tun_net_xmit-->vhost_poll_wakeup-->vhost_poll_queue-->vhost_work_queue-->wake_up_process会唤醒vhost_worker
vhost_worker-->handle_rx_kick-->handle_rx-->tun_recvmsg-->tun_do_read会进行报文的收取,然后通过virtio ring发送到用户态,而用户态的DPDK virtio-user处会轮询收取报文,调用rte_eth_rx_burst-->virtio_recv_pkts_vec会从相应的队列中读取数据。

验证测试

我们可以使用l2fwd做一个测试,如下图所示,两个tap设备分别各两个namespace,然后使用virtio-user接入l2fwd,主要给两个tap设备配置上ip后就可以相互通信了。

具体命令行参数如下:

sudo ./build/app/l2fwd  -m 1024 -c 0xc  -n 2 --vdev=virtio_user0,path=/dev/vhost-net,iface=tap0 --vdev=virtio_user1,path=/dev/vhost-net,iface=tap1 --huge-dir=/hugepage  -- -q 1 -p 0x3 --no-mac-updating

注意要加上参数no-mac-updating,否则l2fwd会修改mac,导致网络不通。

遗留问题

关于使用vhost-net+virtio-user做容器和DPDK对接需要注意以下几个问题:

1. DPDK重启后tap设备会丢失
解决方案:使用TUNSETPERSIST ioctl命令
diff --git a/drivers/net/virtio/virtio_user/vhost_kernel_tap.c b/drivers/net/virtio/virtio_user/vhost_kernel_tap.c
index a3faf1d..bf013ab 100644
--- a/drivers/net/virtio/virtio_user/vhost_kernel_tap.c
+++ b/drivers/net/virtio/virtio_user/vhost_kernel_tap.c
@@ -112,6 +112,11 @@ vhost_kernel_open_tap(char **p_ifname, int hdr_size, int req_mq,
                goto error;
        }
 
+       if(ioctl(tapfd, TUNSETPERSIST, 1) < 0) {
+               PMD_DRV_LOG(ERR, "TUNSETPERSIST failed: %s", strerror(errno));
+               goto error;
+       }
+
        fcntl(tapfd, F_SETFL, O_NONBLOCK);
 
        if (ioctl(tapfd, TUNSETVNETHDRSZ, &hdr_size) < 0) {
diff --git a/drivers/net/virtio/virtio_user/vhost_kernel_tap.h b/drivers/net/virtio/virtio_user/vhost_kernel_tap.h
index e0e95b4..4926b97 100644
--- a/drivers/net/virtio/virtio_user/vhost_kernel_tap.h
+++ b/drivers/net/virtio/virtio_user/vhost_kernel_tap.h
@@ -6,6 +6,7 @@
 
 /* TUN ioctls */
 #define TUNSETIFF     _IOW('T', 202, int)
+#define TUNSETPERSIST _IOW('T', 203, int)
 #define TUNGETFEATURES _IOR('T', 207, unsigned int)
 #define TUNSETOFFLOAD  _IOW('T', 208, unsigned int)
 #define TUNGETIFF      _IOR('T', 210, unsigned int)

2. 如果TAP设备加入到namespace的话,重启virtio-user就因为在当前的宿主机找不到TAP设备,会生成一个新的TAP设备,从这个角度看(可能使用brdigeveth-pair更合适
3. virtio-user创建的tap设备的mac地址不能指定;
  解决方案:通过SIOCSIFHWADDR ioctl命令设置(设置前网卡必须是down的)

点击(此处)折叠或打开

  1. int tap_set_mac(const unsigned char *interface_name, const unsigned char *str_macaddr)    
  2. {  
  3.      int             ret;    
  4.      int             sock_fd;    
  5.      struct ifreq    ifr;        
  6.      unsigned int    mac2bit[6];  
  7.  
  8.      //提取mac格式   
  9.      sscanf((char *)str_macaddr, "%02X:%02X:%02X:%02X:%02X:%02X", 
  10.             (unsigned int *)&mac2bit[0], (unsigned int *)&mac2bit[1], 
  11.             (unsigned int *)&mac2bit[2], (unsigned int *)&mac2bit[3], 
  12.             (unsigned int *)&mac2bit[4], (unsigned int *)&mac2bit[5]);
  13.  
  14.      sock_fd = socket(PF_INET, SOCK_DGRAM, 0);    
  15.      if (sock_fd < 0)    
  16.      {            
  17.          return -2;    
  18.      }    
  19.       
  20.      sprintf(ifr.ifr_ifrn.ifrn_name, "%s", interface_name);    
  21.      ifr.ifr_ifru.ifru_hwaddr.sa_family = 1;    
  22.      ifr.ifr_ifru.ifru_hwaddr.sa_data[0] = mac2bit[0];  
  23.      ifr.ifr_ifru.ifru_hwaddr.sa_data[1] = mac2bit[1];  
  24.      ifr.ifr_ifru.ifru_hwaddr.sa_data[2] = mac2bit[2];  
  25.      ifr.ifr_ifru.ifru_hwaddr.sa_data[3] = mac2bit[3];  
  26.      ifr.ifr_ifru.ifru_hwaddr.sa_data[4] = mac2bit[4];  
  27.      ifr.ifr_ifru.ifru_hwaddr.sa_data[5] = mac2bit[5];  
  28.       
  29.      ret = ioctl(sock_fd, SIOCSIFHWADDR, &ifr);    
  30.      if (ret != 0)    
  31.      {    
  32.          return -4;    
  33.      }          
  34.      close(sock_fd);
  35.      return 0;    
  36. }
https://blog.csdn.net/xxb249/article/details/86690067
4. tap设备重新生成后,是down状态
解决方案:使用SIOCGIFFLAGS ioctl命令

点击(此处)折叠或打开

  1. 2.int if_updown(char *ifname, int flag)
  2. {
  3.     int fd, rtn;
  4.     struct ifreq ifr;        
  5.  
  6.     if (!ifname) {
  7.         return -1;
  8.     }
  9.  
  10.     fd = socket(AF_INET, SOCK_DGRAM, 0 );
  11.     if ( fd < 0 ) {
  12.         perror("socket");
  13.         return -1;
  14.     }
  15.     
  16.     ifr.ifr_addr.sa_family = AF_INET;
  17.     strncpy(ifr.ifr_name, (const char *)ifname, IFNAMSIZ - 1 );
  18.  
  19.     if ( (rtn = ioctl(fd, SIOCGIFFLAGS, &ifr) ) == 0 ) {
  20.         if ( flag == DOWN )
  21.             ifr.ifr_flags &= ~IFF_UP;
  22.         else if ( flag == UP ) 
  23.             ifr.ifr_flags |= IFF_UP;
  24.         
  25.     }
  26.  
  27.     if ( (rtn = ioctl(fd, SIOCSIFFLAGS, &ifr) ) != 0) {
  28.         perror("SIOCSIFFLAGS");
  29.     }
  30.  
  31.     close(fd);
  32.  
  33.     return rtn;
  34. }

5. Disable queue导致tap设备close的问题,DPDK高版本已修复

commit 47ac9661b68275321fae0876cce743b9d17671fe
Author: Tiwei Bie 
Date:   Mon Nov 25 16:14:40 2019 +0800

    net/virtio-user: do not close tap when disabling queue pairs
    
    Do not close the tap fds when disabling queue pairs, instead,
    we just need to unbind the backend. Otherwise, tap port can be
    destroyed unexpectedly.
    
    Fixes: e3b434818bbb ("net/virtio-user: support kernel vhost")
    Cc: stable@dpdk.org
    
    Reported-by: Stephen Hemminger 
    Signed-off-by: Tiwei Bie 
    Reviewed-by: Maxime Coquelin 

阅读(8567) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~