| =========================== | 
 | eBPF RSS virtio-net support | 
 | =========================== | 
 |  | 
 | RSS(Receive Side Scaling) is used to distribute network packets to guest virtqueues | 
 | by calculating packet hash. Usually every queue is processed then by a specific guest CPU core. | 
 |  | 
 | For now there are 2 RSS implementations in qemu: | 
 | - 'in-qemu' RSS (functions if qemu receives network packets, i.e. vhost=off) | 
 | - eBPF RSS (can function with also with vhost=on) | 
 |  | 
 | eBPF support (CONFIG_EBPF) is enabled by 'configure' script. | 
 | To enable eBPF RSS support use './configure --enable-bpf'. | 
 |  | 
 | If steering BPF is not set for kernel's TUN module, the TUN uses automatic selection | 
 | of rx virtqueue based on lookup table built according to calculated symmetric hash | 
 | of transmitted packets. | 
 | If steering BPF is set for TUN the BPF code calculates the hash of packet header and | 
 | returns the virtqueue number to place the packet to. | 
 |  | 
 | Simplified decision formula: | 
 |  | 
 | .. code:: C | 
 |  | 
 |     queue_index = indirection_table[hash(<packet data>)%<indirection_table size>] | 
 |  | 
 |  | 
 | Not for all packets, the hash can/should be calculated. | 
 |  | 
 | Note: currently, eBPF RSS does not support hash reporting. | 
 |  | 
 | eBPF RSS turned on by different combinations of vhost-net, vitrio-net and tap configurations: | 
 |  | 
 | - eBPF is used: | 
 |  | 
 |         tap,vhost=off & virtio-net-pci,rss=on,hash=off | 
 |  | 
 | - eBPF is used: | 
 |  | 
 |         tap,vhost=on & virtio-net-pci,rss=on,hash=off | 
 |  | 
 | - 'in-qemu' RSS is used: | 
 |  | 
 |         tap,vhost=off & virtio-net-pci,rss=on,hash=on | 
 |  | 
 | - eBPF is used, hash population feature is not reported to the guest: | 
 |  | 
 |         tap,vhost=on & virtio-net-pci,rss=on,hash=on | 
 |  | 
 | If CONFIG_EBPF is not set then only 'in-qemu' RSS is supported. | 
 | Also 'in-qemu' RSS, as a fallback, is used if the eBPF program failed to load or set to TUN. | 
 |  | 
 | RSS eBPF program | 
 | ---------------- | 
 |  | 
 | RSS program located in ebpf/rss.bpf.skeleton.h generated by bpftool. | 
 | So the program is part of the qemu binary. | 
 | Initially, the eBPF program was compiled by clang and source code located at tools/ebpf/rss.bpf.c. | 
 | Prerequisites to recompile the eBPF program (regenerate ebpf/rss.bpf.skeleton.h): | 
 |  | 
 |         llvm, clang, kernel source tree, bpftool | 
 |         Adjust Makefile.ebpf to reflect the location of the kernel source tree | 
 |  | 
 |         $ cd tools/ebpf | 
 |         $ make -f Makefile.ebpf | 
 |  | 
 | Current eBPF RSS implementation uses 'bounded loops' with 'backward jump instructions' which present in the last kernels. | 
 | Overall eBPF RSS works on kernels 5.8+. | 
 |  | 
 | eBPF RSS implementation | 
 | ----------------------- | 
 |  | 
 | eBPF RSS loading functionality located in ebpf/ebpf_rss.c and ebpf/ebpf_rss.h. | 
 |  | 
 | The ``struct EBPFRSSContext`` structure that holds 4 file descriptors: | 
 |  | 
 | - ctx - pointer of the libbpf context. | 
 | - program_fd - file descriptor of the eBPF RSS program. | 
 | - map_configuration - file descriptor of the 'configuration' map. This map contains one element of 'struct EBPFRSSConfig'. This configuration determines eBPF program behavior. | 
 | - map_toeplitz_key - file descriptor of the 'Toeplitz key' map. One element of the 40byte key prepared for the hashing algorithm. | 
 | - map_indirections_table - 128 elements of queue indexes. | 
 |  | 
 | ``struct EBPFRSSConfig`` fields: | 
 |  | 
 | - redirect - "boolean" value, should the hash be calculated, on false  - ``default_queue`` would be used as the final decision. | 
 | - populate_hash - for now, not used. eBPF RSS doesn't support hash reporting. | 
 | - hash_types - binary mask of different hash types. See ``VIRTIO_NET_RSS_HASH_TYPE_*`` defines. If for packet hash should not be calculated - ``default_queue`` would be used. | 
 | - indirections_len - length of the indirections table, maximum 128. | 
 | - default_queue - the queue index that used for packet that shouldn't be hashed. For some packets, the hash can't be calculated(g.e ARP). | 
 |  | 
 | Functions: | 
 |  | 
 | - ``ebpf_rss_init()`` - sets ctx to NULL, which indicates that EBPFRSSContext is not loaded. | 
 | - ``ebpf_rss_load()`` - creates 3 maps and loads eBPF program from the rss.bpf.skeleton.h. Returns 'true' on success. After that, program_fd can be used to set steering for TAP. | 
 | - ``ebpf_rss_set_all()`` - sets values for eBPF maps. ``indirections_table`` length is in EBPFRSSConfig. ``toeplitz_key`` is VIRTIO_NET_RSS_MAX_KEY_SIZE aka 40 bytes array. | 
 | - ``ebpf_rss_unload()`` - close all file descriptors and set ctx to NULL. | 
 |  | 
 | Simplified eBPF RSS workflow: | 
 |  | 
 | .. code:: C | 
 |  | 
 |     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() | 
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
 |  | 
 | For now, ``set_steering_ebpf()`` method supported by Linux TAP NetClientState. The method requires an eBPF program file descriptor as an argument. |