设备初始化
驱动程序会执行一个典型的初始化例程,如下所示:
- 识别和初始化接收和发送的 virtqueues,最多各 N 个。
如果协商了VIRTIO_NET_F_MQ
特性位,则N
为max_virtqueue_pairs
,否则识别N=1
。 - 如果协商了
VIRTIO_NET_F_CTRL_VQ
特性位,识别控制 virtqueue。 - 填充接收队列的缓冲区。
- 即使使用
VIRTIO_NET_F_MQ
,默认情况下只使用receiveq1
、transmitq1
和controlq
。 驱动程序将发送VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET
命令,指定要使用的发送和接收队列的数量。 - 如果设置了
VIRTIO_NET_F_MAC
特性位,配置空间的 MAC 条目指示网络卡的“物理”地址;否则,驱动程序通常会生成一个随机的本地 MAC 地址。 - 如果协商了
VIRTIO_NET_F_STATUS
特性位,链接状态来自状态的最低位。 否则,驱动程序假定其处于活动状态。 - 一个高性能的驱动程序会通过协商
VIRTIO_NET_F_CSUM
特性来指示它将生成无校验和的数据包。 - 如果协商了该特性,驱动程序可以通过协商以下特性来使用 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 分段)
- 相反的特性也可用: 驱动程序可以通过协商这些特性来减少虚拟设备的一些工作量。
注意:
- 例如,如果网络数据包在同一系统上的两个 guest 之间传输,且双方都允许,那么可能完全不需要进行校验和或分段。如果
VIRTIO_NET_F_GUEST_CSUM
特性位指示可以接收部分校验和的数据包,并且如果设备能够做到这一点,那么VIRTIO_NET_F_GUEST_TSO4
、VIRTIO_NET_F_GUEST_TSO6
、VIRTIO_NET_F_GUEST_UFO
和VIRTIO_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 特性位:如果驱动程序和设备协商了此特性,控制队列将被用于发送控制命令,以管理和配置设备的特性和行为。
- 示例:驱动程序可以通过控制队列发送命令来设置网络过滤规则、调整队列数量或配置其他设备相关参数。
操作步骤:
- 初始化控制队列:在初始化过程中,如果协商了
VIRTIO_NET_F_CTRL_VQ
特性位,驱动程序需要识别并初始化控制队列。 - 发送控制命令:驱动程序通过控制队列发送命令,设备接收并执行这些命令。
- 接收响应:设备通过控制队列发送响应,驱动程序接收并处理这些响应,以确认命令的执行状态或获取设备信息。
传统接口:设备操作
在使用传统接口时,过渡设备和驱动程序 必须 根据 guest 系统的本机字节序来格式化 struct virtio_net_hdr
中的字段,而不是(通常是在不使用传统接口时)使用小端字节序。
传统驱动程序只在协商了 VIRTIO_NET_F_MRG_RXBUF
特性位时在 struct virtio_net_hdr
中呈现 num_buffers
字段;如果未协商该特性,则结构体缩短了2个字节。
在使用传统接口时,驱动程序 应当 忽略发送队列和控制队列的已用长度。
注意:历史上,一些设备即使实际上没有写入任何数据,也会在那里放置总描述符长度。
数据包传输
传输单个数据包很简单,但根据驱动程序协商的不同特性而有所不同。
- 驱动程序可以发送一个完全校验和的数据包。 在这种情况下,
flags
将为零,gso_type
将为VIRTIO_NET_HDR_GSO_NONE
。 - 如果驱动程序协商了
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。
- 如果驱动程序协商了
VIRTIO_NET_F_HOST_TSO4
、TSO6
、USO
或UFO
,并且数据包需要 TCP 分段、UDP 分段或碎片化,则gso_type
设置为VIRTIO_NET_HDR_GSO_TCPV4
、TCPV6
、UDP_L4
或UDP
。 (否则,设置为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 位。
num_buffers
设置为零。 此字段在传输的数据包中未使用。- 将头部和数据包作为一个输出描述符添加到
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
位,如果设置了:
-
- 驱动程序 必须 验证从
csum_start
偏移量开始的csum_offset
处的包校验和以及所有前面的偏移量; - 驱动程序 必须 将缓冲区中存储的包校验和设置为 TCP/UDP 伪头部;
- 驱动程序 必须 设置
csum_start
和csum_offset
,以便从csum_start
开始到包末尾计算一补校验和,并将结果存储在csum_offset
处,生成一个完全校验和的数据包;
- 驱动程序 必须 验证从
- 如果未协商任何
VIRTIO_NET_F_HOST_TSO4
、TSO6
、USO
或UFO
选项,驱动程序 必须 将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_TSO4
、TSO6
、USO
或UFO
选项:
-
- 如果协商了
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_VALID
和VIRTIO_NET_HDR_F_RSC_INFO
位。
设备要求:数据包传输
- 设备 必须 忽略它不识别的标志位。
- 如果
flags
中未设置VIRTIO_NET_HDR_F_NEEDS_CSUM
位,设备 不得 使用csum_start
和csum_offset
。 - 如果协商了
VIRTIO_NET_F_HOST_TSO4
、TSO6
、USO
或UFO
选项:
-
- 如果协商了
VIRTIO_NET_F_GUEST_HDRLEN
特性,并且gso_type
不等于VIRTIO_NET_HDR_GSO_NONE
,设备 可以 使用hdr_len
作为传输头的大小。
- 如果协商了
注意:实现时应小心,以防止恶意驱动程序通过设置不正确的 hdr_len
来攻击设备。
-
- 如果未协商
VIRTIO_NET_F_GUEST_HDRLEN
特性,或者gso_type
是VIRTIO_NET_HDR_GSO_NONE
,设备 只能 将hdr_len
视为传输头大小的提示。 - 设备 不得 依赖
hdr_len
的正确性。
- 如果未协商
注意:这是由于实现中的各种错误所致。
- 如果未设置
VIRTIO_NET_HDR_F_NEEDS_CSUM
位,设备 不得 依赖数据包校验和的正确性。
数据包传输中断
通常,驱动程序会抑制发送 virtqueue 的中断,并在后续数据包的发送路径中检查已用的数据包。
在此中断处理程序中的正常行为是从 virtqueue 中检索已用的缓冲区,并释放相应的头部和数据包。
设置接收缓冲区
通常最好保持接收 virtqueue 尽可能充满:如果队列空了,网络性能会受到影响。
如果使用了 VIRTIO_NET_F_GUEST_TSO4
、VIRTIO_NET_F_GUEST_TSO6
或 VIRTIO_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_TSO4
、VIRTIO_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。
处理传入数据包
当数据包被复制到接收队列中的缓冲区时,最佳路径是禁用接收队列的进一步已用缓冲区通知,并处理数据包直到没有更多数据包被发现,然后重新启用通知。
处理传入数据包包括:
num_buffers
表示该数据包分布在多少个描述符上(包括当前的描述符):
-
- 如果未协商
VIRTIO_NET_F_MRG_RXBUF
,则此值始终为 1。 - 这允许接收大型数据包而无需分配大型缓冲区:一个无法容纳在单个缓冲区中的数据包可以继续流动到下一个缓冲区,依此类推。在这种情况下,virtqueue 中至少会有
num_buffers
个已用缓冲区,设备会将它们串联起来,形成一个单一的数据包,类似于将其存储在跨多个描述符的单个缓冲区中。其他缓冲区不会以struct virtio_net_hdr
开头。
- 如果未协商
- 如果
num_buffers
为 1,整个数据包将包含在此缓冲区中,紧接在struct virtio_net_hdr
之后。 - 如果协商了
VIRTIO_NET_F_GUEST_CSUM
特性,flags
中的VIRTIO_NET_HDR_F_DATA_VALID
位可以被设置:
-
- 如果设置了,设备已验证数据包的校验和。在多层封装协议的情况下,已经验证了一个层级的校验和。
此外,VIRTIO_NET_F_GUEST_CSUM
、TSO4
、TSO6
、UDP
和 ECN
特性启用了接收校验和、大型接收卸载和 ECN 支持,它们是传输校验和、传输分段卸载和 ECN 特性的输入等效项,如前文中所述:
- 如果协商了
VIRTIO_NET_F_GUEST_TSO4
、TSO6
或UFO
选项,gso_type
可能被设置为除VIRTIO_NET_HDR_GSO_NONE
之外的其他值, 并且gso_size
字段指示所需的 MSS(最大报文段长度)(参见 数据包传输 第 2 点)。 - 如果协商了
VIRTIO_NET_F_RSC_EXT
选项(这意味着协商了VIRTIO_NET_F_GUEST_TSO4
或TSO6
),设备还会处理重复的 ACK 段,在csum_start
字段中报告联合的 TCP 段数量,并在csum_offset
字段中报告重复的 ACK 段数量,同时在flags
中设置VIRTIO_NET_HDR_F_RSC_INFO
位。 - 如果协商了
VIRTIO_NET_F_GUEST_CSUM
特性,flags
中的VIRTIO_NET_HDR_F_NEEDS_CSUM
位可以被设置:
-
- 如果设置了,设备必须:
-
-
- 验证从
csum_start
偏移量开始的csum_offset
处的数据包校验和以及所有前面的偏移量; - 将存储在缓冲区中的数据包校验和设置为 TCP/UDP 伪头部;
- 设置
csum_start
和csum_offset
,以便从csum_start
开始到数据包末尾计算一补校验和,并将结果存储在csum_offset
处,生成一个完全校验和的数据包。
- 验证从
-
- 如果未协商
VIRTIO_NET_F_HOST_TSO4
、TSO6
或UFO
选项,驱动程序必须将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_TSO4
、TSO6
或UFO
选项:
-
- 如果协商了
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_VALID
和VIRTIO_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
位,如果设置了,设备必须:
-
- 验证从
csum_start
偏移量开始的csum_offset
处的数据包校验和以及所有前面的偏移量; - 将接收缓冲区中存储的数据包校验和设置为 TCP/UDP 伪头部;
- 设置
csum_start
和csum_offset
,以便从csum_start
开始到数据包末尾计算一补校验和,并将结果存储在csum_offset
处,生成一个完全校验和的数据包。
- 验证从
- 如果未协商任何
VIRTIO_NET_F_GUEST_TSO4
、TSO6
或UFO
选项,设备必须将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_TSO4
、TSO6
或UFO
选项,设备应当将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_start
和csum_offset
。 - 如果协商了
VIRTIO_NET_F_GUEST_TSO4
、TSO6
或UFO
选项,驱动程序可以仅将hdr_len
视为传输头大小的提示。驱动程序不得依赖hdr_len
的正确性。
注意:这是由于实现中的各种错误所致。
- 如果
VIRTIO_NET_HDR_F_NEEDS_CSUM
和VIRTIO_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
class
、command
和 command-specific-data
由驱动程序设置,ack
字节由设备设置。设备除了在 ack
不等于 VIRTIO_NET_OK
时发出诊断之外,几乎无其他操作。
数据包接收过滤
如果协商了 VIRTIO_NET_F_CTRL_RX
和 VIRTIO_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_PROMISC
或VIRTIO_NET_CTRL_RX_ALLMULTI
命令。 - 如果未协商
VIRTIO_NET_F_CTRL_RX_EXTRA
特性,驱动程序 不得 发送VIRTIO_NET_CTRL_RX_ALLUNI
、VIRTIO_NET_CTRL_RX_NOMULTI
、VIRTIO_NET_CTRL_RX_NOUNI
或VIRTIO_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_SET
的 command-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_ADD
和 VIRTIO_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
位。
处理此通知包括:
- 发送 Gratuitous 数据包(例如 ARP)或标记有待发送的 Gratuitous 数据包,并让延迟的例程发送它们。
- 通过控制 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...transmitqn
和 receiveq1...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...transmitqn
和 receiveq1...receiveqn
(其中 n=virtqueue_pairs
) 可以 被使用。
驱动程序要求:多队列模式下的自动接收引导
- 驱动程序 必须 在使用
VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET
命令启用多队列之前,配置 virtqueues。 - 驱动程序 不得 请求
virtqueue_pairs
为0
或超过设备配置空间中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_length
和hash_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_length
和hash_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_length
的indirection_table_mask
值。 - 驱动程序 不得 设置设备不支持的任何
VIRTIO_NET_HASH_TYPE_
标志。
设备要求:RSS 处理
设备 必须 按如下方式确定网络数据包的目标队列:
- 计算数据包的哈希值,具体定义见 5.1.6.4.3。
- 如果设备未为特定数据包计算哈希值,设备将数据包指向
virtio_net_rss_config
结构中的unclassified_queue
指定的接收 virtqueue(值0
对应于receiveq1
)。 - 将计算的哈希值应用于
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
的过渡驱动程序 必须:
- 对于所有命令,使用包含前两个字段(
class
和command
)的单个 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。
- 卸载状态配置:动态配置卸载选项,如校验和计算和分段卸载。
- 传统接口要求:需要处理不同字节序和特殊的帧格式,以确保与旧驱动程序和设备的兼容性。