首页 > 其他分享 >网络虚拟化场景下网络包的发送过程

网络虚拟化场景下网络包的发送过程

时间:2023-12-02 22:32:43浏览次数:31  
标签:场景 tap struct 虚拟化 调用 网络 virtio net qemu

网络虚拟化有和存储虚拟化类似的地方,例如,它们都是基于 virtio 的,因而在看网络虚拟化的过程中,会看到和存储虚拟化很像的数据结构和原理。但是,网络虚拟化也有自己的特殊性。例如,存储虚拟化是将宿主机上的文件作为客户机上的硬盘,而网络虚拟化需要依赖于内核协议栈进行网络包的封装与解封装。

当网络包经过客户机的协议栈到达 virtio_net 驱动的时候,按照 net_device_ops 的定义,start_xmit 会被调用。

static const struct net_device_ops virtnet_netdev = {
  .ndo_open            = virtnet_open,
  .ndo_stop          = virtnet_close,
  .ndo_start_xmit      = start_xmit,
  .ndo_validate_addr   = eth_validate_addr,
  .ndo_set_mac_address = virtnet_set_mac_address,
  .ndo_set_rx_mode     = virtnet_set_rx_mode,
  .ndo_get_stats64     = virtnet_stats,
  .ndo_vlan_rx_add_vid = virtnet_vlan_rx_add_vid,
  .ndo_vlan_rx_kill_vid = virtnet_vlan_rx_kill_vid,
  .ndo_xdp    = virtnet_xdp,
  .ndo_features_check  = passthru_features_check,
};

接下来的调用链为:start_xmit->xmit_skb-> virtqueue_add_outbuf->virtqueue_add,将网络包放入队列中,并调用 virtqueue_notify 通知接收方。

static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev)
{
  struct virtnet_info *vi = netdev_priv(dev);
  int qnum = skb_get_queue_mapping(skb);
  struct send_queue *sq = &vi->sq[qnum];
  int err;
  struct netdev_queue *txq = netdev_get_tx_queue(dev, qnum);
  bool kick = !skb->xmit_more;
  bool use_napi = sq->napi.weight;
......
  /* Try to transmit */
  err = xmit_skb(sq, skb);
......
  if (kick || netif_xmit_stopped(txq))
    virtqueue_kick(sq->vq);
  return NETDEV_TX_OK;
}

bool virtqueue_kick(struct virtqueue *vq)
{
  if (virtqueue_kick_prepare(vq))
    return virtqueue_notify(vq);
  return true;
}

写入一个 I/O 会使得 qemu 触发 VM exit,这个逻辑我们在解析 CPU 的时候看到过。

接下来,那会调用 VirtQueue 的 handle_output 函数。前面我们已经设置过这个函数了,其实就是 virtio_net_handle_tx_bh。

static void virtio_net_handle_tx_bh(VirtIODevice *vdev, VirtQueue *vq)
{
    VirtIONet *n = VIRTIO_NET(vdev);
    VirtIONetQueue *q = &n->vqs[vq2q(virtio_get_queue_index(vq))];

    q->tx_waiting = 1;

    virtio_queue_set_notification(vq, 0);
    qemu_bh_schedule(q->tx_bh);
}

virtio_net_handle_tx_bh 调用了 qemu_bh_schedule,而在 virtio_net_add_queue 中调用 qemu_bh_new,并把函数设置为 virtio_net_tx_bh。

virtio_net_tx_bh 函数调用发送函数 virtio_net_flush_tx。

static int32_t virtio_net_flush_tx(VirtIONetQueue *q)
{
    VirtIONet *n = q->n;
    VirtIODevice *vdev = VIRTIO_DEVICE(n);
    VirtQueueElement *elem;
    int32_t num_packets = 0;
    int queue_index = vq2q(virtio_get_queue_index(q->tx_vq));

    for (;;) {
        ssize_t ret;
        unsigned int out_num;
        struct iovec sg[VIRTQUEUE_MAX_SIZE], sg2[VIRTQUEUE_MAX_SIZE + 1], *out_sg;
        struct virtio_net_hdr_mrg_rxbuf mhdr;

        elem = virtqueue_pop(q->tx_vq, sizeof(VirtQueueElement));
        out_num = elem->out_num;
        out_sg = elem->out_sg;
......
        ret = qemu_sendv_packet_async(qemu_get_subqueue(n->nic, queue_index),out_sg, out_num, virtio_net_tx_complete);
    }
......
    return num_packets;
}

virtio_net_flush_tx 会调用 virtqueue_pop。这里面,我们能看到对于 vring 的操作,也即从这里面将客户机里面写入的数据读取出来。

然后,我们调用 qemu_sendv_packet_async 发送网络包。接下来的调用链为:qemu_sendv_packet_async->qemu_net_queue_send_iov->qemu_net_queue_flush->qemu_net_queue_deliver。

在 qemu_net_queue_deliver 中,我们会调用 NetQueue 的 deliver 函数。前面 qemu_new_net_queue 会把 deliver 函数设置为 qemu_deliver_packet_iov。它会调用 nc->info->receive_iov。

static NetClientInfo net_tap_info = {
    .type = NET_CLIENT_DRIVER_TAP,
    .size = sizeof(TAPState),
    .receive = tap_receive,
    .receive_raw = tap_receive_raw,
    .receive_iov = tap_receive_iov,
    .poll = tap_poll,
    .cleanup = tap_cleanup,
    .has_ufo = tap_has_ufo,
    .has_vnet_hdr = tap_has_vnet_hdr,
    .has_vnet_hdr_len = tap_has_vnet_hdr_len,
    .using_vnet_hdr = tap_using_vnet_hdr,
    .set_offload = tap_set_offload,
    .set_vnet_hdr_len = tap_set_vnet_hdr_len,
    .set_vnet_le = tap_set_vnet_le,
    .set_vnet_be = tap_set_vnet_be,
};

根据 net_tap_info 的定义调用的是 tap_receive_iov。他会调用 tap_write_packet->writev 写入这个字符设备。

在内核的字符设备驱动中,tun_chr_write_iter 会被调用。

static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
  struct file *file = iocb->ki_filp;
  struct tun_struct *tun = tun_get(file);
  struct tun_file *tfile = file->private_data;
  ssize_t result;

  result = tun_get_user(tun, tfile, NULL, from,
            file->f_flags & O_NONBLOCK, false);

  tun_put(tun);
  return result;
}

当我们使用 writev() 系统调用向 tun/tap 设备的字符设备文件写入数据时,tun_chr_write 函数将被调用。它会使用 tun_get_user,从用户区接收数据,将数据存入 skb 中,然后调用关键的函数 netif_rx_ni(skb) ,将 skb 送给 tcp/ip 协议栈处理,最终完成虚拟网卡的数据接收。

把网络虚拟化场景下网络包的发送过程总结一下。

  • 在虚拟机里面的用户态,应用程序通过 write 系统调用写入 socket。
  • 写入的内容经过 VFS 层,内核协议栈,到达虚拟机里面的内核的网络设备驱动,也即 virtio_net。
  • virtio_net 网络设备有一个操作结构 struct net_device_ops,里面定义了发送一个网络包调用的函数为 start_xmit。
  • 在 virtio_net 的前端驱动和 qemu 中的后端驱动之间,有两个队列 virtqueue,一个用于发送,一个用于接收。然后,我们需要在 start_xmit 中调用 virtqueue_add,将网络包放入发送队列,然后调用 virtqueue_notify 通知 qemu。
  • qemu 本来处于 KVM_RUN 的状态,收到通知后,通过 VM exit 指令退出客户机模式,进入宿主机模式。发送网络包的时候,virtio_net_handle_tx_bh 函数会被调用。
  • 接下来是一个 for 循环,我们需要在循环中调用 virtqueue_pop,从传输队列中获取要发送的数据,然后调用 qemu_sendv_packet_async 进行发送。
  • qemu 会调用 writev 向字符设备文件写入,进入宿主机的内核。
  • 在宿主机内核中字符设备文件的 file_operations 里面的 write_iter 会被调用,也即会调用 tun_chr_write_iter。
  • 在 tun_chr_write_iter 函数中,tun_get_user 将要发送的网络包从 qemu 拷贝到宿主机内核里面来,然后调用 netif_rx_ni 开始调用宿主机内核协议栈进行处理。
  • 宿主机内核协议栈处理完毕之后,会发送给 tap 虚拟网卡,完成从虚拟机里面到宿主机的整个发送过程。

网络虚拟化场景下网络包的发送过程_网络虚拟化


标签:场景,tap,struct,虚拟化,调用,网络,virtio,net,qemu
From: https://blog.51cto.com/key3feng/8659301

相关文章

  • js中?.、??、??=的用法及使用场景
    js中?.、??、??=的用法及使用场景小熊爱敲代码征途慢慢,唯有奋斗​关注她 7人赞同了该文章  上面这个错误,相信前端开发工程师应该经常遇到吧,要么是自己考虑不全造成的,要么是后端开发人员丢失数据或者传输错误数据类型造成的。因此对数据访问时......
  • sap 命名空间下 ux-specification 开发包的内容和使用场景介绍
    在SAPUI5项目中,package.json文件扮演了一个核心的角色,它是描述项目的关键元素,包括项目的元数据,脚本,依赖项等。其中,@sap/ux-specification是一个特别的依赖项,它提供了SAP的用户体验(UX)规范,用于定义和驱动SAPFiori应用的一致性和标准化。@sap/ux-specification提供了......
  • 2023-2024-1 20232310 《网络空间安全导论》第4周学习
    教材内容学习总结教材学习中的问题和解决过程问题1:不理解sql注入的原理解决方案:利用B站学习sqlmap的使用方法,了解深层逻辑问题2:对于入侵检测的具体概念理解解决方案:询问Chatgpt,查询有关具体例子。基于AI的学习感悟系统安全是一项很复杂的工程,需要我不断努力学习......
  • Linux虚拟机如何配置网络之Xshell远程连接
    一、下载远程连接工具Xshell二、使用Xshell远程连接虚拟机1、查看虚拟机ip命令:ipaddr2、打开Xshell软件,点击新建会话   连接成功3、检查网络状态,开启虚拟机输入命令   pingwww.baidu.com ......
  • 学习笔记4:JavaSE & API(网络编程 & 多线程)
    1、java.net.Socket:(1)定义:Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互。(2)方法getInputStream():获取输入流,返回值是InputStream的一个子类实例。ge......
  • 2023-2024-1 20232312 《网络空间安全导论》第四周学习
    2023-2024-120232312《网络空间安全导论》第二周学习教材学习内容总结4.1系统安全概述1.系统的定义计算机系统是由硬件和软件组成的复杂系统,它能够接受输入数据,进行处理,然后输出结果。理解计算机系统需要从多个层面进行分析,包括硬件、操作系统、应用软件等方面。2.整......
  • Angular Renderer2 的作用和使用场景介绍
    下图将cssclasscx-icon添加到hostdom上。最后效果如下:使用的renderer来自:import{Component,ElementRef,HostBinding,Input,Renderer2,}from'@angular/core';Angular的Renderer2是Angular框架中用于操作DOM元素的重要工具之一。Renderer2的主要......
  • 使用unity开发Pico程序,场景中锯齿问题
    1、问题使用unity【非HDR】开发Pico程序,场景中锯齿问题,设置了unity的抗锯齿和渲染方式,及悬挂抗锯齿的脚本,都不能很好的解决项目中图片、文字的锯齿问题,通过摸索找到了妥善的方法1、修改项目中图片的GenerateMIpMaps为勾选状态,MipMapsPreserveCoverage这个可以未勾选,若是勾选......
  • 聊聊 神经网络模型 传播计算逻辑
    概述预训练过程就是在不断地更新权重超参数与偏置超参数,最后选择合适的超参数,生成超参数文件。上一篇博客是使用已有的预训练超参数文件,要训练自己的超参数,需要对神经网络层中前向传播与反向传播计算熟悉,了解计算逻辑,才能不断地更新选择合适的超参数。神经网络计算详解整个神......
  • 数仓虚拟化技术:PieCloudDB Database 通过中国信通院 2023 「可信数据库」性能评测的强
    “可信数据库”是国内首个数据库的评测体系,被业界广泛认可为产品能力重要的衡量标准之一。PieCloudDBDatabase在该评测中展现出卓越的数据处理速度、稳定性和可扩展性,为用户提供了强大的数据分析和查询能力。6月15~16日,中国信通院2023上半年“可信数据库”评测专家评审会......