virtio1.1/1.0/0.95 vnet设备初始化、接收包等流程

设备初始化

驱动程序会执行一个典型的初始化例程,如下所示:

  1. 识别和初始化接收和发送的 virtqueues,最多各 N 个。
    如果协商了 VIRTIO_NET_F_MQ 特性位,则 Nmax_virtqueue_pairs,否则识别 N=1
  2. 如果协商了 VIRTIO_NET_F_CTRL_VQ 特性位,识别控制 virtqueue。
  3. 填充接收队列的缓冲区
  4. 即使使用 VIRTIO_NET_F_MQ,默认情况下只使用 receiveq1transmitq1controlq 驱动程序将发送 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令,指定要使用的发送和接收队列的数量。
  5. 如果设置了 VIRTIO_NET_F_MAC 特性位,配置空间的 MAC 条目指示网络卡的“物理”地址;否则,驱动程序通常会生成一个随机的本地 MAC 地址。
  6. 如果协商了 VIRTIO_NET_F_STATUS 特性位,链接状态来自状态的最低位。 否则,驱动程序假定其处于活动状态。
  7. 一个高性能的驱动程序会通过协商 VIRTIO_NET_F_CSUM 特性来指示它将生成无校验和的数据包。
  8. 如果协商了该特性,驱动程序可以通过协商以下特性来使用 TCP 分段或 UDP 分段/碎片卸载:
    • VIRTIO_NET_F_HOST_TSO4(IPv4 TCP)
    • VIRTIO_NET_F_HOST_TSO6(IPv6 TCP)
    • VIRTIO_NET_F_HOST_UFO(UDP 分段/碎片)
    • VIRTIO_NET_F_HOST_USO(UDP 分段)
  1. 相反的特性也可用: 驱动程序可以通过协商这些特性来减少虚拟设备的一些工作量。

注意

  • 例如,如果网络数据包在同一系统上的两个 guest 之间传输,且双方都允许,那么可能完全不需要进行校验和或分段。如果 VIRTIO_NET_F_GUEST_CSUM 特性位指示可以接收部分校验和的数据包,并且如果设备能够做到这一点,那么 VIRTIO_NET_F_GUEST_TSO4VIRTIO_NET_F_GUEST_TSO6VIRTIO_NET_F_GUEST_UFOVIRTIO_NET_F_GUEST_ECN 就是上述特性的输入等效项。详见下面的 5.1.6.3 设置接收缓冲区5.1.6.4 处理传入数据包

真正最简的驱动程序 仅接受 VIRTIO_NET_F_MAC 并忽略其他所有特性。

设备操作

数据包通过将它们放置在 transmitq1...transmitqN 发送队列中进行传输,接收数据包的缓冲区则被放置在 receiveq1...receiveqN 接收队列中。在每种情况下,数据包本身前面都会有一个头部:

struct virtio_net_hdr {
    #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1
    #define VIRTIO_NET_HDR_F_DATA_VALID 2
    #define VIRTIO_NET_HDR_F_RSC_INFO 4
    u8 flags;
    #define VIRTIO_NET_HDR_GSO_NONE 0
    #define VIRTIO_NET_HDR_GSO_TCPV4 1
    #define VIRTIO_NET_HDR_GSO_UDP 3
    #define VIRTIO_NET_HDR_GSO_TCPV6 4
    #define VIRTIO_NET_HDR_GSO_UDP_L4 5
    #define VIRTIO_NET_HDR_GSO_ECN 0x80
    u8 gso_type;
    le16 hdr_len;
    le16 gso_size;
    le16 csum_start;
    le16 csum_offset;
    le16 num_buffers;
    le32 hash_value; // 仅在协商了 VIRTIO_NET_F_HASH_REPORT 时存在
    le16 hash_report; // 仅在协商了 VIRTIO_NET_F_HASH_REPORT 时存在
    le16 padding_reserved; // 仅在协商了 VIRTIO_NET_F_HASH_REPORT 时存在
}

说明

  • flags (u8 flags;): 标志位,用于指示数据包的相关信息。
    • VIRTIO_NET_HDR_F_NEEDS_CSUM (1): 指示数据包需要校验和。
    • VIRTIO_NET_HDR_F_DATA_VALID (2): 指示数据有效。
    • VIRTIO_NET_HDR_F_RSC_INFO (4): 指示有资源信息。
  • gso_type (u8 gso_type;): 分段卸载类型。
    • VIRTIO_NET_HDR_GSO_NONE (0): 无分段。
    • VIRTIO_NET_HDR_GSO_TCPV4 (1): IPv4 TCP 分段。
    • VIRTIO_NET_HDR_GSO_UDP (3): UDP 分段。
    • VIRTIO_NET_HDR_GSO_TCPV6 (4): IPv6 TCP 分段。
    • VIRTIO_NET_HDR_GSO_UDP_L4 (5): UDP 第4层分段。
    • VIRTIO_NET_HDR_GSO_ECN (0x80): ECN 标志。
  • hdr_len (le16 hdr_len;): 头部长度,使用小端格式。
  • gso_size (le16 gso_size;): 分段大小,使用小端格式。
  • csum_start (le16 csum_start;): 校验和开始位置,使用小端格式。
  • csum_offset (le16 csum_offset;): 校验和偏移,使用小端格式。
  • num_buffers (le16 num_buffers;): 使用的缓冲区数量,使用小端格式。
  • hash_value, hash_report, padding_reserved: 仅在协商了 VIRTIO_NET_F_HASH_REPORT 特性位时存在,用于哈希相关的报告和填充。

控制队列 (controlq) 用于控制设备的特性,如过滤功能等。

用途

  • VIRTIO_NET_F_CTRL_VQ 特性位:如果驱动程序和设备协商了此特性,控制队列将被用于发送控制命令,以管理和配置设备的特性和行为。
  • 示例:驱动程序可以通过控制队列发送命令来设置网络过滤规则、调整队列数量或配置其他设备相关参数。

操作步骤

  1. 初始化控制队列:在初始化过程中,如果协商了 VIRTIO_NET_F_CTRL_VQ 特性位,驱动程序需要识别并初始化控制队列。
  2. 发送控制命令:驱动程序通过控制队列发送命令,设备接收并执行这些命令。
  3. 接收响应:设备通过控制队列发送响应,驱动程序接收并处理这些响应,以确认命令的执行状态或获取设备信息。

传统接口:设备操作

在使用传统接口时,过渡设备和驱动程序 必须 根据 guest 系统的本机字节序来格式化 struct virtio_net_hdr 中的字段,而不是(通常是在不使用传统接口时)使用小端字节序。

传统驱动程序只在协商了 VIRTIO_NET_F_MRG_RXBUF 特性位时在 struct virtio_net_hdr 中呈现 num_buffers 字段;如果未协商该特性,则结构体缩短了2个字节。

在使用传统接口时,驱动程序 应当 忽略发送队列和控制队列的已用长度。

注意:历史上,一些设备即使实际上没有写入任何数据,也会在那里放置总描述符长度。

数据包传输

传输单个数据包很简单,但根据驱动程序协商的不同特性而有所不同。

  1. 驱动程序可以发送一个完全校验和的数据包。 在这种情况下,flags 将为零,gso_type 将为 VIRTIO_NET_HDR_GSO_NONE
  2. 如果驱动程序协商了 VIRTIO_NET_F_CSUM,则可以跳过对数据包的校验和计算:
    • flags 设置了 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,
    • csum_start 设置为开始校验和计算的包内偏移量,
    • csum_offset 表示设备将在 csum_start 之后多少字节处放置新的(16位一补校验和)。
    • 数据包中的 TCP 校验和字段被设置为 TCP 伪头部的和,因此用 TCP 头部和主体的一补校验和替换它将得到正确的结果。

注意:例如,考虑一个部分校验和的 TCP(IPv4)数据包。它将具有14字节的以太网头和20字节的IP头,后跟TCP头(TCP 校验和字段位于该头部的第16字节)。csum_start 将设置为14 + 20 = 34(TCP 校验和包括头部),csum_offset 将设置为16。

  1. 如果驱动程序协商了 VIRTIO_NET_F_HOST_TSO4TSO6USOUFO,并且数据包需要 TCP 分段、UDP 分段或碎片化,则 gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV4TCPV6UDP_L4UDP (否则,设置为 VIRTIO_NET_HDR_GSO_NONE)。在这种情况下,超过1514字节的数据包可以被传输:元数据指示如何复制数据包头以将其切割成更小的数据包。其他 gso 字段被设置为:
    • 如果协商了 VIRTIO_NET_F_GUEST_HDRLEN 特性,hdr_len 指示每个数据包需要复制的头部长度。它是从数据包开始到传输负载开始的字节数。否则,如果未协商 VIRTIO_NET_F_GUEST_HDRLEN 特性,hdr_len 是对设备的一个提示,表示需要复制到每个数据包中的头部的长度,通常设置为包括传输头在内的头部长度。

注意:一些设备受益于确切头部长度的知识。

    • gso_size 是每个数据包超出该头部的最大大小(即 MSS)。
    • 如果驱动程序协商了 VIRTIO_NET_F_HOST_ECN 特性,gso_type 中的 VIRTIO_NET_HDR_GSO_ECN 位表示 TCP 数据包设置了 ECN 位。
  1. num_buffers 设置为零。 此字段在传输的数据包中未使用。
  2. 将头部和数据包作为一个输出描述符添加到 transmitq,并通知设备有新的条目。

驱动程序要求:数据包传输

  • 驱动程序 必须num_buffers 设置为零。
  • 如果未协商 VIRTIO_NET_F_CSUM,驱动程序 必须flags 设置为零,并 应当 向设备提供一个完全校验和的数据包。
  • 如果协商了 VIRTIO_NET_F_HOST_TSO4,驱动程序 可以gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV4 以请求 TCPv4 分段,否则驱动程序 不得gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV4
  • 如果协商了 VIRTIO_NET_F_HOST_TSO6,驱动程序 可以gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV6 以请求 TCPv6 分段,否则驱动程序 不得gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV6
  • 如果协商了 VIRTIO_NET_F_HOST_UFO,驱动程序 可以gso_type 设置为 VIRTIO_NET_HDR_GSO_UDP 以请求 UDP 碎片化,否则驱动程序 不得gso_type 设置为 VIRTIO_NET_HDR_GSO_UDP
  • 如果协商了 VIRTIO_NET_F_HOST_USO,驱动程序 可以gso_type 设置为 VIRTIO_NET_HDR_GSO_UDP_L4 以请求 UDP 分段,否则驱动程序 不得gso_type 设置为 VIRTIO_NET_HDR_GSO_UDP_L4
  • 驱动程序 不应当 向设备发送需要分段卸载且设置了显式拥塞通知(ECN)位的 TCP 数据包,除非协商了 VIRTIO_NET_F_HOST_ECN 特性,此时驱动程序 必须gso_type 中设置 VIRTIO_NET_HDR_GSO_ECN 位。
  • 如果协商了 VIRTIO_NET_F_CSUM 特性,驱动程序 可以flags 中设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,如果设置了:
    1. 驱动程序 必须 验证从 csum_start 偏移量开始的 csum_offset 处的包校验和以及所有前面的偏移量;
    2. 驱动程序 必须 将缓冲区中存储的包校验和设置为 TCP/UDP 伪头部;
    3. 驱动程序 必须 设置 csum_startcsum_offset,以便从 csum_start 开始到包末尾计算一补校验和,并将结果存储在 csum_offset 处,生成一个完全校验和的数据包;
  • 如果未协商任何 VIRTIO_NET_F_HOST_TSO4TSO6USOUFO 选项,驱动程序 必须gso_type 设置为 VIRTIO_NET_HDR_GSO_NONE
  • 如果 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,则驱动程序 必须flags 中设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,并 必须 设置 gso_size 以指示所需的 MSS(最大报文段长度)。
  • 如果协商了 VIRTIO_NET_F_HOST_TSO4TSO6USOUFO 选项:
    • 如果协商了 VIRTIO_NET_F_GUEST_HDRLEN 特性,并且 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,驱动程序 必须hdr_len 设置为等于包括传输头在内的头部长度的值。
    • 如果未协商 VIRTIO_NET_F_GUEST_HDRLEN 特性,或者 gso_typeVIRTIO_NET_HDR_GSO_NONE,驱动程序 应当hdr_len 设置为不小于包括传输头在内的头部长度的值。
  • 驱动程序 应当 接受 VIRTIO_NET_F_GUEST_HDRLEN 特性(如果已提供),并且如果能够提供确切的头部长度。
  • 驱动程序 不得flags 中设置 VIRTIO_NET_HDR_F_DATA_VALIDVIRTIO_NET_HDR_F_RSC_INFO 位。

设备要求:数据包传输

  • 设备 必须 忽略它不识别的标志位。
  • 如果 flags 中未设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,设备 不得 使用 csum_startcsum_offset
  • 如果协商了 VIRTIO_NET_F_HOST_TSO4TSO6USOUFO 选项:
    • 如果协商了 VIRTIO_NET_F_GUEST_HDRLEN 特性,并且 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,设备 可以 使用 hdr_len 作为传输头的大小。

注意:实现时应小心,以防止恶意驱动程序通过设置不正确的 hdr_len 来攻击设备。

    • 如果未协商 VIRTIO_NET_F_GUEST_HDRLEN 特性,或者 gso_typeVIRTIO_NET_HDR_GSO_NONE,设备 只能hdr_len 视为传输头大小的提示。
    • 设备 不得 依赖 hdr_len 的正确性。

注意:这是由于实现中的各种错误所致。

  • 如果未设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,设备 不得 依赖数据包校验和的正确性。

数据包传输中断

通常,驱动程序会抑制发送 virtqueue 的中断,并在后续数据包的发送路径中检查已用的数据包。

在此中断处理程序中的正常行为是从 virtqueue 中检索已用的缓冲区,并释放相应的头部和数据包。

设置接收缓冲区

通常最好保持接收 virtqueue 尽可能充满:如果队列空了,网络性能会受到影响。

如果使用了 VIRTIO_NET_F_GUEST_TSO4VIRTIO_NET_F_GUEST_TSO6VIRTIO_NET_F_GUEST_UFO 特性,最大传入数据包长度将为 65550 字节(TCP 或 UDP 数据包的最大大小,加上 14 字节的以太网头);否则,最大长度为 1514 字节。在此之前,会预先添加 12 字节struct virtio_net_hdr,使总长度为 65562 字节1526 字节

驱动程序要求:设置接收缓冲区

  • 如果未协商 VIRTIO_NET_F_MRG_RXBUF 特性:
    • 如果协商了 VIRTIO_NET_F_GUEST_TSO4VIRTIO_NET_F_GUEST_TSO6 VIRTIO_NET_F_GUEST_UFO 特性,驱动程序应当 使用至少 65562 字节 的缓冲区填充接收队列。
    • 否则,驱动程序应当 使用至少 1526 字节 的缓冲区填充接收队列。
  • 如果协商了 VIRTIO_NET_F_MRG_RXBUF 特性, 每个缓冲区 必须 至少为 struct virtio_net_hdr 的大小。

注意:显然,每个缓冲区可以跨越多个描述符元素进行拆分。

  • 如果协商了 VIRTIO_NET_F_MQ 特性, 每个将被使用的 receiveq1...receiveqN 应当 填充接收缓冲区。

设备要求:设置接收缓冲区

  • 设备 必须num_buffers 设置为用于存放传入数据包的描述符数量。
  • 设备 必须 仅使用单个描述符,如果未协商 VIRTIO_NET_F_MRG_RXBUF 特性。

注意:这意味着如果未协商 VIRTIO_NET_F_MRG_RXBUF 特性,num_buffers 将始终为 1。

处理传入数据包

当数据包被复制到接收队列中的缓冲区时,最佳路径是禁用接收队列的进一步已用缓冲区通知,并处理数据包直到没有更多数据包被发现,然后重新启用通知。

处理传入数据包包括:

  1. num_buffers 表示该数据包分布在多少个描述符上(包括当前的描述符):
    • 如果未协商 VIRTIO_NET_F_MRG_RXBUF,则此值始终为 1。
    • 这允许接收大型数据包而无需分配大型缓冲区:一个无法容纳在单个缓冲区中的数据包可以继续流动到下一个缓冲区,依此类推。在这种情况下,virtqueue 中至少会有 num_buffers 个已用缓冲区,设备会将它们串联起来,形成一个单一的数据包,类似于将其存储在跨多个描述符的单个缓冲区中。其他缓冲区不会以 struct virtio_net_hdr 开头。
  1. 如果 num_buffers 为 1,整个数据包将包含在此缓冲区中,紧接在 struct virtio_net_hdr 之后。
  2. 如果协商了 VIRTIO_NET_F_GUEST_CSUM 特性,flags 中的 VIRTIO_NET_HDR_F_DATA_VALID 位可以被设置:
    • 如果设置了,设备已验证数据包的校验和。在多层封装协议的情况下,已经验证了一个层级的校验和。

此外,VIRTIO_NET_F_GUEST_CSUMTSO4TSO6UDPECN 特性启用了接收校验和、大型接收卸载和 ECN 支持,它们是传输校验和、传输分段卸载和 ECN 特性的输入等效项,如前文中所述:

  1. 如果协商了 VIRTIO_NET_F_GUEST_TSO4TSO6UFO 选项,gso_type 可能被设置为除 VIRTIO_NET_HDR_GSO_NONE 之外的其他值, 并且 gso_size 字段指示所需的 MSS(最大报文段长度)(参见 数据包传输 第 2 点)。
  2. 如果协商了 VIRTIO_NET_F_RSC_EXT 选项(这意味着协商了 VIRTIO_NET_F_GUEST_TSO4TSO6),设备还会处理重复的 ACK 段,在 csum_start 字段中报告联合的 TCP 段数量,并在 csum_offset 字段中报告重复的 ACK 段数量,同时在 flags 中设置 VIRTIO_NET_HDR_F_RSC_INFO 位。
  3. 如果协商了 VIRTIO_NET_F_GUEST_CSUM 特性,flags 中的 VIRTIO_NET_HDR_F_NEEDS_CSUM 位可以被设置:
    • 如果设置了,设备必须:
      1. 验证从 csum_start 偏移量开始的 csum_offset 处的数据包校验和以及所有前面的偏移量;
      2. 将存储在缓冲区中的数据包校验和设置为 TCP/UDP 伪头部;
      3. 设置 csum_startcsum_offset,以便从 csum_start 开始到数据包末尾计算一补校验和,并将结果存储在 csum_offset 处,生成一个完全校验和的数据包。
  • 如果未协商 VIRTIO_NET_F_HOST_TSO4TSO6UFO 选项,驱动程序必须将 gso_type 设置为 VIRTIO_NET_HDR_GSO_NONE
  • 如果 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,则驱动程序还必须在 flags 中设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,并且必须设置 gso_size 以指示所需的 MSS。
  • 如果协商了 VIRTIO_NET_F_HOST_TSO4TSO6UFO 选项:
    • 如果协商了 VIRTIO_NET_F_GUEST_HDRLEN 特性,并且 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,驱动程序必须将 hdr_len 设置为等于包括传输头在内的头部长度的值。
    • 如果未协商 VIRTIO_NET_F_GUEST_HDRLEN 特性,或者 gso_type VIRTIO_NET_HDR_GSO_NONE,驱动程序应当将 hdr_len 设置为不小于包括传输头在内的头部长度的值。
  • 驱动程序应当接受 VIRTIO_NET_F_GUEST_HDRLEN 特性(如果已提供),并且如果能够提供确切的头部长度。
  • 驱动程序不得在 flags 中设置 VIRTIO_NET_HDR_F_DATA_VALIDVIRTIO_NET_HDR_F_RSC_INFO 位。

设备要求:处理传入数据包

  • 如果未协商 VIRTIO_NET_F_MRG_RXBUF,设备必须将 num_buffers 设置为 1。
  • 如果协商了 VIRTIO_NET_F_MRG_RXBUF,设备必须设置 num_buffers 以指示数据包(包括头部)分布在多少个缓冲区上。
  • 如果接收的数据包分布在多个缓冲区上,设备必须完全使用所有缓冲区,除了最后一个缓冲区(即前 num_buffers - 1 个缓冲区),以驱动程序提供的每个缓冲区的完整长度。
  • 设备必须一起使用单个接收数据包使用的所有缓冲区,以便驱动程序至少看到 num_buffers 个已用缓冲区。
  • 如果未协商 VIRTIO_NET_F_GUEST_CSUM 特性,设备必须将 flags 设置为零,并应当向驱动程序提供一个完全校验和的数据包。
  • 如果未协商 VIRTIO_NET_F_GUEST_TSO4,设备不得将 gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV4
  • 如果未协商 VIRTIO_NET_F_GUEST_UDP,设备不得将 gso_type 设置为 VIRTIO_NET_HDR_GSO_UDP
  • 如果未协商 VIRTIO_NET_F_GUEST_TSO6,设备不得将 gso_type 设置为 VIRTIO_NET_HDR_GSO_TCPV6
  • 设备不应当向驱动程序发送需要分段卸载且设置了显式拥塞通知(ECN)位的 TCP 数据包,除非协商了 VIRTIO_NET_F_GUEST_ECN 特性,此时设备必须在 gso_type 中设置 VIRTIO_NET_HDR_GSO_ECN 位。
  • 如果协商了 VIRTIO_NET_F_GUEST_CSUM 特性,设备可以在 flags 中设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,如果设置了,设备必须:
    1. 验证从 csum_start 偏移量开始的 csum_offset 处的数据包校验和以及所有前面的偏移量;
    2. 将接收缓冲区中存储的数据包校验和设置为 TCP/UDP 伪头部;
    3. 设置 csum_start csum_offset,以便从 csum_start 开始到数据包末尾计算一补校验和,并将结果存储在 csum_offset 处,生成一个完全校验和的数据包。
  • 如果未协商任何 VIRTIO_NET_F_GUEST_TSO4TSO6UFO 选项,设备必须将 gso_type 设置为 VIRTIO_NET_HDR_GSO_NONE
  • 如果 gso_type 不等于 VIRTIO_NET_HDR_GSO_NONE,则设备必须在 flags 中设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,并必须设置 gso_size 以指示所需的 MSS。
  • 如果协商了 VIRTIO_NET_F_RSC_EXT,设备还必须在 flags 中设置 VIRTIO_NET_HDR_F_RSC_INFO 位,设置 csum_start 为联合的 TCP 段数量,并设置 csum_offset 为接收到的重复 ACK 段数量。
  • 如果未协商 VIRTIO_NET_F_RSC_EXT,设备不得在 flags 中设置 VIRTIO_NET_HDR_F_RSC_INFO 位。
  • 如果协商了 VIRTIO_NET_F_GUEST_TSO4TSO6UFO 选项,设备应当将 hdr_len 设置为不小于包括传输头在内的头部长度的值。
  • 如果协商了 VIRTIO_NET_F_GUEST_CSUM 特性,设备可以在 flags 中设置 VIRTIO_NET_HDR_F_DATA_VALID 位,如果设置了,设备必须验证数据包校验和(在多层封装协议的情况下,验证一个层级的校验和)。

驱动程序要求:处理传入数据包

  • 驱动程序必须忽略未识别的标志位。
  • 如果 flags 中未设置 VIRTIO_NET_HDR_F_NEEDS_CSUM 位,或者如果设置了 VIRTIO_NET_HDR_F_RSC_INFO 位,驱动程序不得使用 csum_startcsum_offset
  • 如果协商了 VIRTIO_NET_F_GUEST_TSO4TSO6UFO 选项,驱动程序可以仅将 hdr_len 视为传输头大小的提示。驱动程序不得依赖 hdr_len 的正确性。

注意:这是由于实现中的各种错误所致。

  • 如果 VIRTIO_NET_HDR_F_NEEDS_CSUMVIRTIO_NET_HDR_F_DATA_VALID 均未设置,驱动程序不得依赖数据包校验和的正确性。

对传入数据包进行哈希计算

设备在以下情况下尝试对每个数据包计算哈希:

  • 协商了 VIRTIO_NET_F_RSS 特性。设备使用哈希来确定接收 virtqueue 以放置传入数据包。
  • 协商了 VIRTIO_NET_F_HASH_REPORT 特性。设备将哈希值和哈希类型与数据包一起报告。

如果协商了 VIRTIO_NET_F_RSS 特性:

  • 设备使用 virtio_net_rss_config 结构中的 hash_types 作为“启用的哈希类型”位掩码。
  • 设备使用 virtio_net_rss_config 结构中的 hash_key_data hash_key_length 定义的密钥(请参见 5.1.6.5.7.1)。

如果未协商 VIRTIO_NET_F_RSS 特性:

  • 设备使用 virtio_net_hash_config 结构中的 hash_types 作为“启用的哈希类型”位掩码。
  • 设备使用 virtio_net_hash_config 结构中的 hash_key_data hash_key_length 定义的密钥(请参见 5.1.6.5.6.4)。

注意:如果设备提供了 VIRTIO_NET_F_HASH_REPORT,即使它只支持一对 virtqueues,它也必须支持至少一个 VIRTIO_NET_CTRL_MQ 类的命令来配置报告的哈希参数:

  • 如果设备提供了 VIRTIO_NET_F_RSS,则必须支持 5.1.6.5.7.1 中定义的 VIRTIO_NET_CTRL_MQ_RSS_CONFIG 命令。
  • 否则,设备必须支持 5.1.6.5.6.4 中定义的 VIRTIO_NET_CTRL_MQ_HASH_CONFIG 命令。
支持/启用的哈希类型

适用于 IPv4 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IPv4 (1 << 0)
#define VIRTIO_NET_HASH_TYPE_TCPv4 (1 << 1)
#define VIRTIO_NET_HASH_TYPE_UDPv4 (1 << 2)

适用于无扩展头的 IPv6 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IPv6 (1 << 3)
#define VIRTIO_NET_HASH_TYPE_TCPv6 (1 << 4)
#define VIRTIO_NET_HASH_TYPE_UDPv6 (1 << 5)

适用于有扩展头的 IPv6 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IP_EX (1 << 6)
#define VIRTIO_NET_HASH_TYPE_TCP_EX (1 << 7)
#define VIRTIO_NET_HASH_TYPE_UDP_EX (1 << 8)
IPv4 数据包

设备根据“启用的哈希类型”位掩码对 IPv4 数据包计算哈希,具体如下:

  • 如果设置了 VIRTIO_NET_HASH_TYPE_TCPv4 且数据包具有 TCP 头,设备会在以下字段上计算哈希:
    • 源 IP 地址
    • 目的 IP 地址
    • 源 TCP 端口
    • 目的 TCP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_UDPv4 且数据包具有 UDP 头,设备会在以下字段上计算哈希:
    • 源 IP 地址
    • 目的 IP 地址
    • 源 UDP 端口
    • 目的 UDP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_IPv4,设备会在以下字段上计算哈希:
    • 源 IP 地址
    • 目的 IP 地址
  • 否则,设备不计算哈希。
无扩展头的 IPv6 数据包

设备根据“启用的哈希类型”位掩码对无扩展头的 IPv6 数据包计算哈希,具体如下:

  • 如果设置了 VIRTIO_NET_HASH_TYPE_TCPv6 且数据包具有 TCPv6 头,设备会在以下字段上计算哈希:
    • 源 IPv6 地址
    • 目的 IPv6 地址
    • 源 TCP 端口
    • 目的 TCP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_UDPv6 且数据包具有 UDPv6 头,设备会在以下字段上计算哈希:
    • 源 IPv6 地址
    • 目的 IPv6 地址
    • 源 UDP 端口
    • 目的 UDP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_IPv6,设备会在以下字段上计算哈希:
    • 源 IPv6 地址
    • 目的 IPv6 地址
  • 否则,设备不计算哈希。
有扩展头的 IPv6 数据包

设备根据“启用的哈希类型”位掩码对有扩展头的 IPv6 数据包计算哈希,具体如下:

  • 如果设置了 VIRTIO_NET_HASH_TYPE_TCP_EX 且数据包具有 TCPv6 头,设备会在以下字段上计算哈希:
    • 来自 IPv6 目的地选项头中家庭地址选项的家庭地址。如果未存在扩展头,则使用源 IPv6 地址。
    • 来自关联扩展头中的路由头类型 2 的 IPv6 地址。如果未存在扩展头,则使用目的 IPv6 地址。
    • 源 TCP 端口
    • 目的 TCP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_UDP_EX 且数据包具有 UDPv6 头,设备会在以下字段上计算哈希:
    • 来自 IPv6 目的地选项头中家庭地址选项的家庭地址。如果未存在扩展头,则使用源 IPv6 地址。
    • 来自关联扩展头中的路由头类型 2 的 IPv6 地址。如果未存在扩展头,则使用目的 IPv6 地址。
    • 源 UDP 端口
    • 目的 UDP 端口
  • 否则,如果设置了 VIRTIO_NET_HASH_TYPE_IP_EX,设备会在以下字段上计算哈希:
    • 来自 IPv6 目的地选项头中家庭地址选项的家庭地址。如果未存在扩展头,则使用源 IPv6 地址。
    • 来自关联扩展头中的路由头类型 2 的 IPv6 地址。如果未存在扩展头,则使用目的 IPv6 地址。
  • 否则,跳过 IPv6 扩展头并按无扩展头的 IPv6 数据包计算哈希。
传入数据包的哈希报告
  • 如果协商了 VIRTIO_NET_F_HASH_REPORT 特性,并且设备已为数据包计算了哈希,设备会将计算的哈希报告类型填写到 hash_report 中,并将计算的哈希值填写到 hash_value 中。
  • 如果协商了 VIRTIO_NET_F_HASH_REPORT 特性但由于任何原因哈希未被计算,设备会将 hash_report 设置为 VIRTIO_NET_HASH_REPORT_NONE
  • 设备可以在 hash_report 中报告的可能值在下方定义。它们与在 5.1.6.4.3.1 中定义的支持的哈希类型对应如下:
    • VIRTIO_NET_HASH_TYPE_XXX = 1 << (VIRTIO_NET_HASH_REPORT_XXX - 1)
#define VIRTIO_NET_HASH_REPORT_NONE 0
#define VIRTIO_NET_HASH_REPORT_IPv4 1
#define VIRTIO_NET_HASH_REPORT_TCPv4 2
#define VIRTIO_NET_HASH_REPORT_UDPv4 3
#define VIRTIO_NET_HASH_REPORT_IPv6 4
#define VIRTIO_NET_HASH_REPORT_TCPv6 5
#define VIRTIO_NET_HASH_REPORT_UDPv6 6
#define VIRTIO_NET_HASH_REPORT_IPv6_EX 7
#define VIRTIO_NET_HASH_REPORT_TCPv6_EX 8
#define VIRTIO_NET_HASH_REPORT_UDPv6_EX 9
支持/启用的哈希类型

适用于 IPv4 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IPv4 (1 << 0)
#define VIRTIO_NET_HASH_TYPE_TCPv4 (1 << 1)
#define VIRTIO_NET_HASH_TYPE_UDPv4 (1 << 2)

适用于无扩展头的 IPv6 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IPv6 (1 << 3)
#define VIRTIO_NET_HASH_TYPE_TCPv6 (1 << 4)
#define VIRTIO_NET_HASH_TYPE_UDPv6 (1 << 5)

适用于有扩展头的 IPv6 数据包的哈希类型:

#define VIRTIO_NET_HASH_TYPE_IP_EX (1 << 6)
#define VIRTIO_NET_HASH_TYPE_TCP_EX (1 << 7)
#define VIRTIO_NET_HASH_TYPE_UDP_EX (1 << 8)

ctrl Virtqueue

如果协商了 VIRTIO_NET_F_CTRL_VQ 特性,驱动程序将使用控制 virtqueue 发送命令,以操控设备的各种特性,这些特性不易映射到配置空间中。所有命令的格式如下:

struct virtio_net_ctrl {
    u8 class;
    u8 command;
    u8 command-specific-data[];
    u8 ack;
};

/* ack 值 */
#define VIRTIO_NET_OK 0
#define VIRTIO_NET_ERR 1

classcommandcommand-specific-data 由驱动程序设置,ack 字节由设备设置。设备除了在 ack 不等于 VIRTIO_NET_OK 时发出诊断之外,几乎无其他操作。


数据包接收过滤

如果协商了 VIRTIO_NET_F_CTRL_RXVIRTIO_NET_F_CTRL_RX_EXTRA 特性,驱动程序可以发送控制命令来配置混杂模式、多播、单播和广播接收。

注意:通常,这些命令为尽力而为(best-effort):不需要的包仍可能到达。

#define VIRTIO_NET_CTRL_RX 0
#define VIRTIO_NET_CTRL_RX_PROMISC 0
#define VIRTIO_NET_CTRL_RX_ALLMULTI 1
#define VIRTIO_NET_CTRL_RX_ALLUNI 2
#define VIRTIO_NET_CTRL_RX_NOMULTI 3
#define VIRTIO_NET_CTRL_RX_NOUNI 4
#define VIRTIO_NET_CTRL_RX_NOBCAST 5

设备要求:数据包接收过滤

如果协商了 VIRTIO_NET_F_CTRL_RX 特性,设备 必须 支持以下 VIRTIO_NET_CTRL_RX 类命令:

  • VIRTIO_NET_CTRL_RX_PROMISC:开启和关闭混杂模式。command-specific-data 是一个字节,包含 0(关闭)或 1(开启)。开启混杂模式时,设备 应当 接收所有传入的数据包。这应当在通过 VIRTIO_NET_CTRL_RX 类命令设置的其他模式开启时仍然生效。
  • VIRTIO_NET_CTRL_RX_ALLMULTI:开启和关闭全多播接收。command-specific-data 是一个字节,包含 0(关闭多播接收)或 1(开启多播接收)。开启全多播接收时,设备 应当 允许所有传入的多播数据包。

如果协商了 VIRTIO_NET_F_CTRL_RX_EXTRA 特性,设备 必须 支持以下 VIRTIO_NET_CTRL_RX 类命令:

  • VIRTIO_NET_CTRL_RX_ALLUNI:开启和关闭全单播接收。command-specific-data 是一个字节,包含 0(关闭单播接收)或 1(开启单播接收)。开启全单播接收时,设备 应当 允许所有传入的单播数据包。
  • VIRTIO_NET_CTRL_RX_NOMULTI:抑制多播接收。command-specific-data 是一个字节,包含 0(允许多播接收)或 1(抑制多播接收)。抑制多播接收时,设备 不得 向驱动程序发送多播数据包。这应当在开启 VIRTIO_NET_CTRL_RX_ALLMULTI 时仍然生效。此过滤器 不得 适用于广播数据包。
  • VIRTIO_NET_CTRL_RX_NOUNI:抑制单播接收。command-specific-data 是一个字节,包含 0(允许单播接收)或 1(抑制单播接收)。抑制单播接收时,设备 不得 向驱动程序发送单播数据包。这应当在开启 VIRTIO_NET_CTRL_RX_ALLUNI 时仍然生效。
  • VIRTIO_NET_CTRL_RX_NOBCAST:抑制广播接收。command-specific-data 是一个字节,包含 0(允许广播接收)或 1(抑制广播接收)。抑制广播接收时,设备 不得 向驱动程序发送广播数据包。这应当在开启 VIRTIO_NET_CTRL_RX_ALLMULTI 时仍然生效。

驱动程序要求:数据包接收过滤

  • 如果未协商 VIRTIO_NET_F_CTRL_RX 特性,驱动程序 不得 发送 VIRTIO_NET_CTRL_RX_PROMISCVIRTIO_NET_CTRL_RX_ALLMULTI 命令。
  • 如果未协商 VIRTIO_NET_F_CTRL_RX_EXTRA 特性,驱动程序 不得 发送 VIRTIO_NET_CTRL_RX_ALLUNIVIRTIO_NET_CTRL_RX_NOMULTIVIRTIO_NET_CTRL_RX_NOUNIVIRTIO_NET_CTRL_RX_NOBCAST 命令。

设置 MAC 地址过滤

如果协商了 VIRTIO_NET_F_CTRL_RX 特性,驱动程序可以发送控制命令来配置 MAC 地址过滤。

struct virtio_net_ctrl_mac {
    le32 entries;
    u8 macs[entries][6];
};

#define VIRTIO_NET_CTRL_MAC 1
#define VIRTIO_NET_CTRL_MAC_TABLE_SET 0
#define VIRTIO_NET_CTRL_MAC_ADDR_SET 1

设备可以根据任意数量的目标 MAC 地址过滤传入的数据包。该表通过 VIRTIO_NET_CTRL_MAC 类和 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令进行设置。command-specific-data 是两个可变长度的 6 字节 MAC 地址表(如 struct virtio_net_ctrl_mac 中所述)。第一个表包含单播地址,第二个表包含多播地址。

VIRTIO_NET_CTRL_MAC_ADDR_SET 命令用于设置默认的 MAC 地址,接收过滤将接受该地址(如果协商了 VIRTIO_NET_F_MAC,这将在配置空间的 mac 中反映)。VIRTIO_NET_CTRL_MAC_ADDR_SETcommand-specific-data 是 6 字节的 MAC 地址。

设备要求:设置 MAC 地址过滤

  • 设备在重置时 必须 保持 MAC 过滤表为空。
  • 设备 必须 在消费 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令之前更新 MAC 过滤表。
  • 如果协商了 VIRTIO_NET_F_MAC 特性,设备 必须 在消费 VIRTIO_NET_CTRL_MAC_ADDR_SET 命令之前更新配置空间中的 mac
  • 设备 应当 丢弃目标 MAC 地址既不匹配配置空间中的 mac(或通过 VIRTIO_NET_CTRL_MAC_ADDR_SET 设置的 MAC)也不匹配 MAC 过滤表的传入数据包。

驱动程序要求:设置 MAC 地址过滤

  • 如果未协商 VIRTIO_NET_F_CTRL_RX 特性,驱动程序 不得 发送 VIRTIO_NET_CTRL_MAC 类命令。
  • 如果协商了 VIRTIO_NET_F_CTRL_RX 特性,驱动程序 应当 发送 VIRTIO_NET_CTRL_MAC_ADDR_SET 来设置默认的 mac,前提是其与配置空间中的 mac 不同。
  • 驱动程序 必须 按照 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令后跟一个 le32 数字,接着该数字数量的非多播 MAC 地址,再接一个 le32 数字,最后是该数字数量的多播地址的顺序发送命令特定数据。任意数字都可以为 0

VLAN 过滤

如果驱动程序协商了 VIRTIO_NET_F_CTRL_VLAN 特性,可以控制设备中的 VLAN 过滤表。

注意:类似于基于 MAC 地址的过滤,VLAN 过滤也是尽力而为(best-effort):不需要的包仍可能到达。

#define VIRTIO_NET_CTRL_VLAN 2
#define VIRTIO_NET_CTRL_VLAN_ADD 0
#define VIRTIO_NET_CTRL_VLAN_DEL 1

VIRTIO_NET_CTRL_VLAN_ADDVIRTIO_NET_CTRL_VLAN_DEL 命令都采用小端格式的 16 位 VLAN ID 作为命令特定数据。

传统接口:VLAN 过滤

在使用传统接口时,过渡设备和驱动程序 必须 根据来宾系统的本机字节序格式化 VLAN ID,而不是(通常是在不使用传统接口时)使用小端字节序。


发送 Gratuitous 数据包

如果驱动程序协商了 VIRTIO_NET_F_GUEST_ANNOUNCE(依赖于 VIRTIO_NET_F_CTRL_VQ),设备可以要求驱动程序发送 Gratuitous 数据包;这通常在来宾被物理迁移后执行,需要在新的网络链接上宣布其存在。(由于 Hypervisor 不了解来宾的网络配置,如 VLAN 标签,因此以这种方式通知来宾最为简便。)

#define VIRTIO_NET_CTRL_ANNOUNCE 3
#define VIRTIO_NET_CTRL_ANNOUNCE_ACK 0

驱动程序在检测到设备配置变化时,会检查设备配置状态字段中的 VIRTIO_NET_S_ANNOUNCE 位。命令 VIRTIO_NET_CTRL_ANNOUNCE_ACK 用于指示驱动程序已接收到通知,设备将在状态中清除 VIRTIO_NET_S_ANNOUNCE 位。

处理此通知包括:

  1. 发送 Gratuitous 数据包(例如 ARP)或标记有待发送的 Gratuitous 数据包,并让延迟的例程发送它们。
  2. 通过控制 virtqueue 发送 VIRTIO_NET_CTRL_ANNOUNCE_ACK 命令。

驱动程序要求:发送 Gratuitous 数据包

如果驱动程序协商了 VIRTIO_NET_F_GUEST_ANNOUNCE,在检测到设备配置状态字段中的 VIRTIO_NET_S_ANNOUNCE 位后,驱动程序 应当 通知网络对等体其新位置。驱动程序 必须 使用 VIRTIO_NET_CTRL_ANNOUNCE 类和 VIRTIO_NET_CTRL_ANNOUNCE_ACK 命令在命令队列中发送命令。

设备要求:发送 Gratuitous 数据包

如果协商了 VIRTIO_NET_F_GUEST_ANNOUNCE,设备 必须 在接收到包含 VIRTIO_NET_CTRL_ANNOUNCE 类和 VIRTIO_NET_CTRL_ANNOUNCE_ACK 命令的命令缓冲区后,在标记缓冲区为已用之前清除状态中的 VIRTIO_NET_S_ANNOUNCE 位。


多队列模式下的设备操作

本规范定义了设备在使用多个发送/接收 virtqueues 时 可以 实现的以下模式:

  • 自动接收引导。如果设备支持此模式,则提供 VIRTIO_NET_F_MQ 特性位。
  • 接收端扩展(RSS)。如果设备支持此模式,则提供 VIRTIO_NET_F_RSS 特性位。

设备 可以 支持这些特性中的一个或两个。驱动程序 可以 协商设备支持的任意特性集。多队列模式默认为禁用。

驱动程序通过使用 VIRTIO_NET_CTRL_MQ 类发送命令来启用多队列。该命令选择多队列操作的模式,具体如下:

#define VIRTIO_NET_CTRL_MQ 4
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0 // 用于自动接收引导
#define VIRTIO_NET_CTRL_MQ_RSS_CONFIG 1 // 用于可配置的接收引导
#define VIRTIO_NET_CTRL_MQ_HASH_CONFIG 2 // 用于可配置的哈希计算

如果协商了多个多队列模式,最终的设备配置由驱动程序发送的最后一个命令定义。


多队列模式下的自动接收引导

如果驱动程序协商了 VIRTIO_NET_F_MQ 特性(依赖于 VIRTIO_NET_F_CTRL_VQ),它 可以 在多个 transmitq1...transmitqN 中发送出去的数据包,并要求设备根据数据包流将传入的数据包排队到多个 receiveq1...receiveqN 中。

驱动程序通过发送 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令来启用多队列,指定要使用的发送和接收队列数量,最多为 max_virtqueue_pairs;随后,transmitq1...transmitqnreceiveq1...receiveqn(其中 n = virtqueue_pairs可以 被使用。

struct virtio_net_ctrl_mq_pairs_set {
    le16 virtqueue_pairs;
};

#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000

当多队列通过 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令启用后,设备 必须 基于数据包流使用自动接收引导。配置接收引导分类器是隐式的。在驱动程序通过 transmitqX 发送某个流的数据包后,设备 应当 将该流的传入数据包引导到 receiveqX。对于单向协议,或尚未发送任何数据包的情况,设备 可以 随机将数据包引导到指定的 receiveq1...receiveqn 中的任意队列。

通过 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令启用多队列,设备 必须 使用基于数据包流的自动接收引导。编程接收引导分类器是隐式的。驱动程序通过发送 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令,指定要使用的发送和接收队列数量,最多为 max_virtqueue_pairs;随后,transmitq1...transmitqnreceiveq1...receiveqn(其中 n=virtqueue_pairs可以 被使用。


驱动程序要求:多队列模式下的自动接收引导

  • 驱动程序 必须 在使用 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令启用多队列之前,配置 virtqueues。
  • 驱动程序 不得 请求 virtqueue_pairs0 或超过设备配置空间中 max_virtqueue_pairs 的值。
  • **驱动程序 必须仅在 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令发送之前,在任何 transmitq1 上排队数据包。
  • 驱动程序 不得 在将 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令放入可用环后,在超过 virtqueue_pairs 的发送队列上排队数据包。

设备要求:多队列模式下的自动接收引导

  • 初始化重置后,设备 必须 仅在 receiveq1 上排队数据包。
  • 设备 不得 在将 VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 命令放入已用缓冲区后,在超过 virtqueue_pairs 的接收队列上排队数据包。

传统接口:多队列模式下的自动接收引导

在使用传统接口时,过渡设备和驱动程序 必须 根据来宾系统的本机字节序格式化 virtqueue_pairs,而不是(通常是在不使用传统接口时)使用小端字节序。

哈希计算

如果协商了 VIRTIO_NET_F_HASH_REPORT 特性,并且设备使用自动接收引导,设备 必须 支持一个命令来配置哈希计算参数。驱动程序通过以下方式提供哈希计算参数:

类和命令:

class VIRTIO_NET_CTRL_MQ;
command VIRTIO_NET_CTRL_MQ_HASH_CONFIG;

命令特定数据的格式:

struct virtio_net_hash_config {
    le32 hash_types;
    le16 reserved[4];
    u8 hash_key_length;
    u8 hash_key_data[hash_key_length];
};
  • hash_types 包含在 5.1.6.4.3.1 中定义的允许哈希类型的位掩码。
  • reserved 必须包含零。这是为了使结构体与 virtio_net_rss_config 结构体布局保持一致(见 5.1.6.5.7)。
  • hash_key_lengthhash_key_data 定义用于哈希计算的密钥。

接收端扩展(RSS)

如果设备支持使用 Toeplitz 哈希计算和可配置参数的 RSS 接收引导,设备会提供 VIRTIO_NET_F_RSS 特性位。

驱动程序通过按照 5.1.4 中定义的方式读取设备配置,查询设备的 RSS 能力。

设置 RSS 参数

驱动程序使用以下格式的命令特定数据发送 VIRTIO_NET_CTRL_MQ_RSS_CONFIG 命令,以配置 RSS 参数:

struct virtio_net_rss_config {
    le32 hash_types;
    le16 indirection_table_mask;
    le16 unclassified_queue;
    le16 indirection_table[indirection_table_length];
    le16 max_tx_vq;
    u8 hash_key_length;
    u8 hash_key_data[hash_key_length];
};
  • hash_types 包含在 5.1.6.4.3.1 中定义的允许哈希类型的位掩码。
  • indirection_table_mask 是一个掩码,用于应用到计算的哈希值,以生成 indirection_table 数组中的索引。indirection_table 中的条目数量为 (indirection_table_mask + 1)
  • unclassified_queue 包含 virtio_net_rss_config 结构中的接收 virtqueue 的 0 基索引,用于放置未分类的数据包。索引 0 对应于 receiveq1
  • indirection_table 包含接收 virtqueus 的 0 基索引数组。索引 0 对应于 receiveq1
  • max_tx_vq 用于通知设备驱动程序可能使用的发送 virtqueues 数量(transmitq1...transmitq max_tx_vq)。
  • hash_key_lengthhash_key_data 定义用于哈希计算的密钥。

驱动程序要求:设置 RSS 参数

  • 如果未协商 VIRTIO_NET_F_RSS 特性,驱动程序 不得 发送 VIRTIO_NET_CTRL_MQ_RSS_CONFIG 命令。
  • 驱动程序 必须 仅使用启用的队列的索引填充 indirection_table 数组。索引 0 对应于 receiveq1
  • indirection_table 中的条目数量(indirection_table_mask + 1必须 是 2 的幂。
  • 驱动程序 必须 使用小于设备报告的 rss_max_indirection_table_lengthindirection_table_mask 值。
  • 驱动程序 不得 设置设备不支持的任何 VIRTIO_NET_HASH_TYPE_ 标志。

设备要求:RSS 处理

设备 必须 按如下方式确定网络数据包的目标队列:

  1. 计算数据包的哈希值,具体定义见 5.1.6.4.3。
  2. 如果设备未为特定数据包计算哈希值,设备将数据包指向 virtio_net_rss_config 结构中的 unclassified_queue 指定的接收 virtqueue(值 0 对应于 receiveq1)。
  3. 将计算的哈希值应用于 indirection_table_mask,并将结果用作在 indirection_table 中的索引,以获取目标接收 virtqueue 的 0 基编号(值 0 对应于 receiveq1)。

卸载状态配置

如果协商了 VIRTIO_NET_F_CTRL_GUEST_OFFLOADS 特性,驱动程序可以发送控制命令以动态配置卸载状态。

设置卸载状态

为了配置卸载,使用以下布局结构和定义:

le64 offloads;
#define VIRTIO_NET_F_GUEST_CSUM 1
#define VIRTIO_NET_F_GUEST_TSO4 7
#define VIRTIO_NET_F_GUEST_TSO6 8
#define VIRTIO_NET_F_GUEST_ECN 9
#define VIRTIO_NET_F_GUEST_UFO 10

#define VIRTIO_NET_CTRL_GUEST_OFFLOADS 5
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 0

VIRTIO_NET_CTRL_GUEST_OFFLOADS 类有一个命令:VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 用于应用新的卸载配置。作为命令数据传递的 le64 值是一个位掩码,设置的位定义要启用的卸载,清除的位定义要禁用的卸载。

每个卸载都有相应的设备特性。通过特性协商,相关卸载被启用以保持向后兼容性。

驱动程序要求:设置卸载状态

  • 驱动程序 不得 为未协商的适当特性启用卸载。

传统接口:设置卸载状态

在使用传统接口时,过渡设备和驱动程序 必须 根据来宾系统的本机字节序格式化 offloads,而不是(通常是在不使用传统接口时)使用小端字节序。


传统接口:帧格式要求

在使用传统接口时,未协商 VIRTIO_F_ANY_LAYOUT 的过渡驱动程序 必须 为发送和接收使用单个描述符来包装 struct virtio_net_hdr,并在随后的描述符中包含网络数据。

此外,在使用控制 virtqueue (见 5.1.6.5)时,未协商 VIRTIO_F_ANY_LAYOUT 的过渡驱动程序 必须

  • 对于所有命令,使用包含前两个字段(classcommand)的单个 2 字节描述符。
  • 对于所有命令,除 VIRTIO_NET_CTRL_MAC_TABLE_SET 外,使用包含命令特定数据且无填充的单个描述符。
  • 对于 VIRTIO_NET_CTRL_MAC_TABLE_SET 命令,使用恰好两个包含命令特定数据且无填充的描述符:第一个描述符 必须 包含用于单播地址的 virtio_net_ctrl_mac 表结构,第二个描述符 必须 包含用于多播地址的 virtio_net_ctrl_mac 表结构。
  • 对于所有命令,使用包含 ack 字段的单个 1 字节描述符。

示例代码结构

以下是 virtio_net_ctrl 和相关结构体的示例代码:

struct virtio_net_ctrl {
    u8 class;
    u8 command;
    u8 command-specific-data[];
    u8 ack;
};

/* ack 值 */
#define VIRTIO_NET_OK 0
#define VIRTIO_NET_ERR 1
struct virtio_net_ctrl_mac {
    le32 entries;
    u8 macs[entries][6];
};

#define VIRTIO_NET_CTRL_MAC 1
#define VIRTIO_NET_CTRL_MAC_TABLE_SET 0
#define VIRTIO_NET_CTRL_MAC_ADDR_SET 1

总结

上述章节详细描述了 VirtIO 网络设备在使用控制 virtqueue 时,驱动程序和设备之间的交互方式,包括命令的格式、数据包接收过滤、MAC 地址过滤、VLAN 过滤、发送 Gratuitous 数据包、以及在多队列模式下的自动接收引导和 RSS 配置。此外,还涵盖了在传统接口下的特殊要求,如字节序格式化和帧格式要求。

关键点总结:

  • 控制 virtqueue:用于发送与设备特性相关的控制命令,不易映射到配置空间。
  • 数据包接收过滤:通过控制命令配置混杂模式、多播、单播和广播接收。
  • MAC 地址过滤:配置设备只接收特定的 MAC 地址的数据包。
  • VLAN 过滤:配置设备只接收特定 VLAN ID 的数据包。
  • 发送 Gratuitous 数据包:在设备或网络配置变化后,驱动程序通知网络对等体 guest 的新位置。
  • 多队列模式:通过自动接收引导和 RSS 提高网络性能,支持多个发送和接收 virtqueues。
  • 卸载状态配置:动态配置卸载选项,如校验和计算和分段卸载。
  • 传统接口要求:需要处理不同字节序和特殊的帧格式,以确保与旧驱动程序和设备的兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值