virtio-net 的 eBPF RSS 支持
RSS(接收侧扩展)通过计算数据包哈希,将网络包分配到客户机 virtqueue。 通常每个队列由特定客户机 CPU 核心处理。
QEMU 目前有两种实现:
in-qemu RSS:QEMU 接收网络包时工作,即 vhost=off。
eBPF RSS:也可在 vhost=on 时工作。
eBPF 支持(CONFIG_EBPF)由 configure 脚本启用。 使用 ./configure --enable-bpf 启用 eBPF RSS。
若未为内核 TUN 模块设置分流 BPF,TUN 根据已发送包的对称哈希建立查找表, 自动选择接收 virtqueue。 若已设置,则由 BPF 计算包头哈希,返回应接收该包的队列号。
简化公式:
queue_index = indirection_table[hash(<packet data>)%<indirection_table size>]
并非所有数据包都能或都应该计算哈希。
注意:当前 eBPF RSS 不支持哈希报告。
vhost-net、virtio-net 和 tap 的组合决定所用实现:
使用 eBPF:
tap,vhost=off & virtio-net-pci,rss=on,hash=off
使用 eBPF:
tap,vhost=on & virtio-net-pci,rss=on,hash=off
使用 in-qemu RSS:
tap,vhost=off & virtio-net-pci,rss=on,hash=on
使用 eBPF,但不向客户机报告哈希填充功能:
tap,vhost=on & virtio-net-pci,rss=on,hash=on
未设置 CONFIG_EBPF 时,只支持 in-qemu RSS。 eBPF 程序加载失败或设置到 TUN 失败时,也会回退到 in-qemu RSS。
RSS eBPF 程序
RSS 程序位于 bpftool 生成的 ebpf/rss.bpf.skeleton.h,因此属于 QEMU 二进制文件的一部分。 原始 eBPF 程序由 Clang 编译,源码位于 tools/ebpf/rss.bpf.c。 重新编译,即重新生成 ebpf/rss.bpf.skeleton.h,需要:
llvm、clang、内核源码树、bpftool 调整 Makefile.ebpf,使其指向正确的内核源码树
$ cd tools/ebpf $ make -f Makefile.ebpf
当前实现使用较新内核中的有限循环和后向跳转指令,整体需要内核 5.8+。
eBPF RSS 实现
eBPF RSS 加载功能位于 ebpf/ebpf_rss.c 和 ebpf/ebpf_rss.h。
struct EBPFRSSContext 保存上下文及四个文件描述符:
ctx:libbpf 上下文指针。
program_fd:eBPF RSS 程序文件描述符。
map_configuration:configuration 映射描述符,包含一个 EBPFRSSConfig,决定程序行为。
map_toeplitz_key:Toeplitz key 映射描述符,包含一个供哈希算法使用的 40 字节密钥。
map_indirections_table:包含 128 个队列索引的映射。
struct EBPFRSSConfig 的字段:
redirect:是否计算哈希的布尔值,false 时使用 default_queue。
populate_hash:目前未使用,因为 eBPF RSS 不支持哈希报告。
hash_types:哈希类型位掩码,见 VIRTIO_NET_RSS_HASH_TYPE_*。 不应计算哈希的包使用 default_queue。
indirections_len:间接表长度,最大 128。
default_queue:不应计算哈希的数据包使用的队列。 某些包,例如 ARP,无法计算哈希。
函数:
ebpf_rss_init():将 ctx 设为 NULL,表示尚未加载上下文。ebpf_rss_load():创建三个映射并从 rss.bpf.skeleton.h 加载程序,成功返回 true。 之后可用 program_fd 设置 TAP 分流。ebpf_rss_set_all():设置映射值。 indirections_table 长度保存在 EBPFRSSConfig;toeplitz_key 是 VIRTIO_NET_RSS_MAX_KEY_SIZE 大小,即 40 字节的数组。ebpf_rss_unload():关闭全部文件描述符,将 ctx 设为 NULL。
简化流程:
struct EBPFRSSConfig config;
config.redirect = 1;
config.hash_types = VIRTIO_NET_RSS_HASH_TYPE_UDPv4 | VIRTIO_NET_RSS_HASH_TYPE_TCPv4;
config.indirections_len = VIRTIO_NET_RSS_MAX_TABLE_LEN;
config.default_queue = 0;
uint16_t table[VIRTIO_NET_RSS_MAX_TABLE_LEN] = {...};
uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE] = {...};
struct EBPFRSSContext ctx;
ebpf_rss_init(&ctx);
ebpf_rss_load(&ctx);
ebpf_rss_set_all(&ctx, &config, table, key);
if (net_client->info->set_steering_ebpf != NULL) {
net_client->info->set_steering_ebpf(net_client, ctx->program_fd);
}
...
ebpf_unload(&ctx);
NetClientState SetSteeringEBPF()
当前 Linux TAP NetClientState 支持 set_steering_ebpf(), 参数为 eBPF 程序文件描述符。