[RFC PATCH v3 0/6] Added virtio-net RSS with eBPF support.

This series of rfc patches adds support for loading the RSS eBPF program and passing it to the QEMU. Comments and suggestions would be useful. QEMU with vhost may work with RSS through eBPF. To load eBPF, the capabilities required that Libvirt may provide. eBPF program and maps may be unique for particular QEMU and Libvirt retrieves eBPF through qapi. For now, there is only "RSS" eBPF object in QEMU, in the future, there may be another one(g.e. network filters). That's why in Libvirt added logic to load and store any eBPF object that QEMU provides using qapi schema. One of the reasons why this series of patches is in RFC are tests. To this series of patches, the tests were added. For now, the tests are synthetic, the proper "reply" file should be generated with a new "caps" file. Currently, there are changes in caps-9.0.0* files. There was added support for ebpf_rss_fds feature, and request-ebpf command. Also, there was added new config for qemuConfig - allowEBPF. This config allows to enable/disable eBPF blob loading explicitly. This is required for qemuxmlconf tests - where some test expects that RSS would not support eBPF. So, overall, the tests are required for review, comment, and discussion how we want them to be implemented in the future. For virtio-net RSS, the document has not changed. ``` <interface type="network"> <model type="virtio"/> <driver queues="4" rss="on" rss_hash_report="off"/> <interface type="network"> ``` Simplified routine for RSS: * Libvirt retrieves eBPF "RSS" and load it. * Libvirt passes file descriptors to virtio-net with property "ebpf_rss_fds" ("rss" property should be "on" too). * if fds was provided - QEMU using eBPF RSS implementation. * if fds was not provided - QEMU tries to load eBPF RSS in own context and use it. * if eBPF RSS was not loaded - QEMU uses "in-qemu" RSS(vhost not supported). Changes since RFC v2: * refactored and rebased. * applied changes according to the Qemu. * added basic test. Changes since RFC v1: * changed eBPF format saved in the XML cache. * refactored and checked with syntax test. * refactored patch hunks. Andrew Melnychenko (6): qemu_monitor: Added QEMU's "request-ebpf" support. qemu_capabilities: Added logic for retrieving eBPF objects from QEMU. qemu_interface: Added routine for loading the eBPF objects. qemu_command: Added "ebpf_rss_fds" support for virtio-net. qemu_conf: Added configuration to optionally disable eBPF loading. tests: Added tests for eBPF blob loading. meson.build | 7 + meson_options.txt | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_capabilities.c | 126 +++++++++++ src/qemu/qemu_capabilities.h | 6 + src/qemu/qemu_command.c | 63 ++++++ src/qemu/qemu_conf.c | 2 + src/qemu/qemu_conf.h | 2 + src/qemu/qemu_domain.c | 4 + src/qemu/qemu_domain.h | 3 + src/qemu/qemu_interface.c | 83 ++++++++ src/qemu/qemu_interface.h | 4 + src/qemu/qemu_monitor.c | 13 ++ src/qemu/qemu_monitor.h | 3 + src/qemu/qemu_monitor_json.c | 26 +++ src/qemu/qemu_monitor_json.h | 3 + .../caps_9.0.0_x86_64.replies | 199 ++++++++++-------- .../caps_9.0.0_x86_64.xml | 4 + tests/qemuxml2argvmock.c | 21 ++ .../net-virtio-rss-bpf.x86_64-latest.args | 37 ++++ .../net-virtio-rss-bpf.x86_64-latest.xml | 46 ++++ tests/qemuxmlconfdata/net-virtio-rss-bpf.xml | 46 ++++ tests/qemuxmlconftest.c | 4 + 23 files changed, 612 insertions(+), 92 deletions(-) create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.xml -- 2.44.0

Added code for monitor and monitor_json. The "request-ebpf" return's eBPF binary object encoded in base64. QEMU provides eBPF that can be loaded and passed to it from Libvirt. QEMU requires exact eBPF program/maps, so it can be retrieved using QAPI. To load eBPF program - administrative capabilities are required, so Libvirt may load it and pass it to the QEMU instance. For now, there is only "RSS"(Receive Side Scaling) for virtio-net eBPF program and maps. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_monitor.c | 13 +++++++++++++ src/qemu/qemu_monitor.h | 3 +++ src/qemu/qemu_monitor_json.c | 26 ++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 +++ 4 files changed, 45 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 34e2ccab97..1dc2fa3fac 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4511,3 +4511,16 @@ qemuMonitorDisplayReload(qemuMonitor *mon, return qemuMonitorJSONDisplayReload(mon, type, tlsCerts); } + +const char * +qemuMonitorGetEbpf(qemuMonitor *mon, const char *ebpfName) +{ + QEMU_CHECK_MONITOR_NULL(mon); + + if (ebpfName == NULL) { + virReportInvalidNonNullArg(ebpfName); + return NULL; + } + + return qemuMonitorJSONGetEbpf(mon, ebpfName); +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 6e81945201..fe8853a2c4 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1586,3 +1586,6 @@ int qemuMonitorDisplayReload(qemuMonitor *mon, const char *type, bool tlsCerts); + +const char * +qemuMonitorGetEbpf(qemuMonitor *mon, const char *ebpfName); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index eb84a3d938..585a645e66 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -8896,3 +8896,29 @@ int qemuMonitorJSONDisplayReload(qemuMonitor *mon, return 0; } + +const char * +qemuMonitorJSONGetEbpf(qemuMonitor *mon, const char *ebpfName) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) reply = NULL; + virJSONValue *ret = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand("request-ebpf", "s:id", ebpfName, NULL))) + return NULL; + + if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) + return NULL; + + if (qemuMonitorJSONCheckError(cmd, reply) < 0) + return NULL; + + ret = virJSONValueObjectGet(reply, "return"); + if (!ret) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("request-ebpf reply was missing 'return' data")); + return NULL; + } + + return g_strdup(virJSONValueObjectGetString(ret, "object")); +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 9684660d86..4a6fc9324e 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -829,3 +829,6 @@ qemuMonitorJSONQueryStats(qemuMonitor *mon, int qemuMonitorJSONDisplayReload(qemuMonitor *mon, const char *type, bool tlsCerts); + +const char * +qemuMonitorJSONGetEbpf(qemuMonitor *mon, const char *ebpfName); -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
Added code for monitor and monitor_json. The "request-ebpf" return's eBPF binary object encoded in base64.
QEMU provides eBPF that can be loaded and passed to it from Libvirt. QEMU requires exact eBPF program/maps, so it can be retrieved using QAPI. To load eBPF program - administrative capabilities are required, so Libvirt may load it and pass it to the QEMU instance. For now, there is only "RSS"(Receive Side Scaling) for virtio-net eBPF program and maps.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_monitor.c | 13 +++++++++++++ src/qemu/qemu_monitor.h | 3 +++ src/qemu/qemu_monitor_json.c | 26 ++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 +++ 4 files changed, 45 insertions(+)
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 34e2ccab97..1dc2fa3fac 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4511,3 +4511,16 @@ qemuMonitorDisplayReload(qemuMonitor *mon,
return qemuMonitorJSONDisplayReload(mon, type, tlsCerts); } + +const char * +qemuMonitorGetEbpf(qemuMonitor *mon, const char *ebpfName)
I'm going to mentioned it here, because this is the first occurrence and then I'll stop pointing it out: libvirt's coding style is a bit different. I think the pre-exiting code around may give you couple of examples. Anyway, this is a nitpick.
+{ + QEMU_CHECK_MONITOR_NULL(mon); + + if (ebpfName == NULL) { + virReportInvalidNonNullArg(ebpfName); + return NULL; + }
We don't usually do this kind of check here. Usually there's just one (or very few) callers of these functions and they can verify arguments passed).
+ + return qemuMonitorJSONGetEbpf(mon, ebpfName); +}
Michal

eBPF objects stored in the hash table during runtime. eBPF objects cached encoded in base64 in the .xml cache file. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_capabilities.c | 122 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + 2 files changed, 125 insertions(+) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 45525db803..09bb6ca36e 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -799,6 +799,9 @@ struct _virQEMUCaps { virQEMUCapsAccel kvm; virQEMUCapsAccel hvf; virQEMUCapsAccel tcg; + + /* Hash of ebpf objects encoded in base64 */ + GHashTable *ebpfObjects; }; struct virQEMUCapsSearchData { @@ -846,6 +849,13 @@ const char *virQEMUCapsArchToString(virArch arch) } +const char * +virQEMUCapsGetEbpf(virQEMUCaps *qemuCaps, const char *id) +{ + return virHashLookup(qemuCaps->ebpfObjects, id); +} + + /* Checks whether a domain with @guest arch can run natively on @host. */ bool @@ -1823,6 +1833,8 @@ virQEMUCapsNew(void) qemuCaps->invalidation = true; qemuCaps->flags = virBitmapNew(QEMU_CAPS_LAST); + qemuCaps->ebpfObjects = virHashNew(g_free); + return qemuCaps; } @@ -1965,6 +1977,9 @@ virQEMUCaps *virQEMUCapsNewCopy(virQEMUCaps *qemuCaps) { g_autoptr(virQEMUCaps) ret = virQEMUCapsNewBinary(qemuCaps->binary); size_t i; + GHashTableIter iter; + const char *key; + const char *value; ret->invalidation = qemuCaps->invalidation; ret->kvmSupportsNesting = qemuCaps->kvmSupportsNesting; @@ -2003,6 +2018,12 @@ virQEMUCaps *virQEMUCapsNewCopy(virQEMUCaps *qemuCaps) ret->hypervCapabilities = g_memdup(qemuCaps->hypervCapabilities, sizeof(virDomainCapsFeatureHyperv)); + ret->ebpfObjects = virHashNew(g_free); + g_hash_table_iter_init(&iter, qemuCaps->ebpfObjects); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + g_hash_table_insert(ret->ebpfObjects, g_strdup(key), g_strdup(value)); + } + return g_steal_pointer(&ret); } @@ -2045,6 +2066,8 @@ void virQEMUCapsDispose(void *obj) g_free(qemuCaps->hypervCapabilities); + g_hash_table_destroy(qemuCaps->ebpfObjects); + virQEMUCapsAccelClear(&qemuCaps->kvm); virQEMUCapsAccelClear(&qemuCaps->hvf); virQEMUCapsAccelClear(&qemuCaps->tcg); @@ -4560,6 +4583,40 @@ virQEMUCapsValidateArch(virQEMUCaps *qemuCaps, xmlXPathContextPtr ctxt) } +static int +virQEMUCapsParseEbpfObjects(virQEMUCaps *qemuCaps, xmlXPathContextPtr ctxt) +{ + g_autofree xmlNodePtr *nodes = NULL; + size_t i; + int n; + + if ((n = virXPathNodeSet("./ebpf/object", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse qemu cached eBPF object")); + return -1; + } + + for (i = 0; i < n; i++) { + g_autofree char *id = NULL; + g_autofree char *ebpf = NULL; + + if (!(id = virXMLPropStringRequired(nodes[i], "id"))) + return -1; + + if (!(ebpf = virXMLPropStringRequired(nodes[i], "data"))) + return -1; + + if (virHashAddEntry(qemuCaps->ebpfObjects, id, ebpf) < 0) + return -1; + + /* steal the ebpf if it was added to the hash without issues */ + g_steal_pointer(&ebpf); + } + + return 0; +} + + /* * Parsing a doc that looks like * @@ -4707,6 +4764,9 @@ virQEMUCapsLoadCache(virArch hostArch, if (skipInvalidation) qemuCaps->invalidation = false; + if (virQEMUCapsParseEbpfObjects(qemuCaps, ctxt) < 0) + return -1; + return 0; } @@ -4944,6 +5004,16 @@ virQEMUCapsFormatHypervCapabilities(virQEMUCaps *qemuCaps, } +static int +virQEMUCapsFormatEbpfObjectsIterator(void *payload, const char *name, void *opaque) +{ + virBuffer *buf = opaque; + + virBufferAsprintf(buf, "<object id='%s' data='%s'/>\n", name, (const char *)payload); + + return 0; +} + char * virQEMUCapsFormatCache(virQEMUCaps *qemuCaps) { @@ -5034,6 +5104,14 @@ virQEMUCapsFormatCache(virQEMUCaps *qemuCaps) if (qemuCaps->kvmSupportsSecureGuest) virBufferAddLit(&buf, "<kvmSupportsSecureGuest/>\n"); + if (virHashSize(qemuCaps->ebpfObjects) > 0) { + virBufferAddLit(&buf, "<ebpf>\n"); + virBufferAdjustIndent(&buf, 2); + virHashForEachSorted(qemuCaps->ebpfObjects, virQEMUCapsFormatEbpfObjectsIterator, &buf); + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</ebpf>\n"); + } + virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, "</qemuCaps>\n"); @@ -5456,6 +5534,47 @@ virQEMUCapsInitProcessCaps(virQEMUCaps *qemuCaps) } +static int +virQEMUCapsProbeQMPEbpfObject(virQEMUCaps *qemuCaps, const char *id, qemuMonitor *mon) +{ + const char *ebpfObject = NULL; + + ebpfObject = qemuMonitorGetEbpf(mon, id); + if (ebpfObject == NULL) + return -1; + + return virHashAddEntry(qemuCaps->ebpfObjects, id, (void *)ebpfObject); +} + + +static int +virQEMUCapsProbeQMPSchemaEbpf(virQEMUCaps *qemuCaps, GHashTable *schema, qemuMonitor *mon) +{ + virJSONValue *ebpfIdsArray; + virJSONValue *ebpfIdsSchema; + size_t i; + + if (virQEMUQAPISchemaPathGet("request-ebpf/arg-type/id", schema, &ebpfIdsSchema) != 1) + return 0; + + if (!(ebpfIdsArray = virJSONValueObjectGetArray(ebpfIdsSchema, "values"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed QMP schema of 'request-ebpf'")); + return -1; + } + + /* Try to request every eBPF */ + for (i = 0; i < virJSONValueArraySize(ebpfIdsArray); i++) { + virJSONValue *id = virJSONValueArrayGet(ebpfIdsArray, i); + + if (virQEMUCapsProbeQMPEbpfObject(qemuCaps, virJSONValueGetString(id), mon) < 0) + return -1; + } + + return 0; +} + + static int virQEMUCapsProbeQMPSchemaCapabilities(virQEMUCaps *qemuCaps, qemuMonitor *mon) @@ -5489,6 +5608,9 @@ virQEMUCapsProbeQMPSchemaCapabilities(virQEMUCaps *qemuCaps, virQEMUQAPISchemaPathExists("block-stream/arg-type/backing-mask-protocol", schema)) virQEMUCapsSet(qemuCaps, QEMU_CAPS_BLOCKJOB_BACKING_MASK_PROTOCOL); + if (virQEMUCapsProbeQMPSchemaEbpf(qemuCaps, schema, mon) < 0) + return -1; + return 0; } diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 00b4066e9a..371ea19bd0 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -906,3 +906,6 @@ int virQEMUCapsProbeQMPMachineTypes(virQEMUCaps *qemuCaps, virDomainVirtType virtType, qemuMonitor *mon); + +const char * +virQEMUCapsGetEbpf(virQEMUCaps *qemuCaps, const char *id); -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
eBPF objects stored in the hash table during runtime. eBPF objects cached encoded in base64 in the .xml cache file.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_capabilities.c | 122 +++++++++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + 2 files changed, 125 insertions(+)
'meson test' fails after this one.
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 45525db803..09bb6ca36e 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -799,6 +799,9 @@ struct _virQEMUCaps { virQEMUCapsAccel kvm; virQEMUCapsAccel hvf; virQEMUCapsAccel tcg; + + /* Hash of ebpf objects encoded in base64 */ + GHashTable *ebpfObjects; };
struct virQEMUCapsSearchData { @@ -846,6 +849,13 @@ const char *virQEMUCapsArchToString(virArch arch) }
+const char * +virQEMUCapsGetEbpf(virQEMUCaps *qemuCaps, const char *id) +{ + return virHashLookup(qemuCaps->ebpfObjects, id); +} + + /* Checks whether a domain with @guest arch can run natively on @host. */ bool @@ -1823,6 +1833,8 @@ virQEMUCapsNew(void) qemuCaps->invalidation = true; qemuCaps->flags = virBitmapNew(QEMU_CAPS_LAST);
+ qemuCaps->ebpfObjects = virHashNew(g_free); + return qemuCaps; }
@@ -1965,6 +1977,9 @@ virQEMUCaps *virQEMUCapsNewCopy(virQEMUCaps *qemuCaps) { g_autoptr(virQEMUCaps) ret = virQEMUCapsNewBinary(qemuCaps->binary); size_t i; + GHashTableIter iter; + const char *key; + const char *value;
ret->invalidation = qemuCaps->invalidation; ret->kvmSupportsNesting = qemuCaps->kvmSupportsNesting; @@ -2003,6 +2018,12 @@ virQEMUCaps *virQEMUCapsNewCopy(virQEMUCaps *qemuCaps) ret->hypervCapabilities = g_memdup(qemuCaps->hypervCapabilities, sizeof(virDomainCapsFeatureHyperv));
+ ret->ebpfObjects = virHashNew(g_free); + g_hash_table_iter_init(&iter, qemuCaps->ebpfObjects); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + g_hash_table_insert(ret->ebpfObjects, g_strdup(key), g_strdup(value)); + } +
I'd move this into a separate function for code estetics purpose.
return g_steal_pointer(&ret); }
Michal

Also, added dependencies for libbpf with bpf option. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- meson.build | 7 ++++ meson_options.txt | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_interface.c | 83 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_interface.h | 4 ++ 5 files changed, 96 insertions(+) diff --git a/meson.build b/meson.build index e8b0094b91..e12a703a4d 100644 --- a/meson.build +++ b/meson.build @@ -998,6 +998,12 @@ else libkvm_dep = dependency('', required: false) endif +libbpf_version = '1.1.0' +libbpf_dep = dependency('libbpf', version: '>=' + libbpf_version, required: get_option('libbpf')) +if libbpf_dep.found() + conf.set('WITH_BPF', 1) +endif + libiscsi_version = '1.18.0' libiscsi_dep = dependency('libiscsi', version: '>=' + libiscsi_version, required: get_option('libiscsi')) @@ -2283,6 +2289,7 @@ libs_summary = { 'dlopen': dlopen_dep.found(), 'fuse': fuse_dep.found(), 'glusterfs': glusterfs_dep.found(), + 'libbpf': libbpf_dep.found(), 'libiscsi': libiscsi_dep.found(), 'libkvm': libkvm_dep.found(), 'libnbd': libnbd_dep.found(), diff --git a/meson_options.txt b/meson_options.txt index 9d729b3e1f..9b7bd9d1f8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -48,6 +48,7 @@ option('udev', type: 'feature', value: 'auto', description: 'udev support') option('wireshark_dissector', type: 'feature', value: 'auto', description: 'wireshark support') option('wireshark_plugindir', type: 'string', value: '', description: 'wireshark plugins directory for use when installing wireshark plugin') option('yajl', type: 'feature', value: 'auto', description: 'yajl support') +option('libbpf', type: 'feature', value: 'auto', description: 'qemu libbpf support') # build driver options diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 907893d431..de7ae87d5b 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -105,6 +105,7 @@ if conf.has('WITH_QEMU') selinux_dep, src_dep, xdr_dep, + libbpf_dep, ], include_directories: [ conf_inc_dir, diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index c2007c7043..ec8cf18d86 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -38,6 +38,10 @@ #include <sys/stat.h> #include <fcntl.h> +#ifdef WITH_BPF +#include <bpf/libbpf.h> +#endif + #define VIR_FROM_THIS VIR_FROM_QEMU VIR_LOG_INIT("qemu.qemu_interface"); @@ -432,3 +436,82 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, virDomainAuditNetDevice(vm->def, net, vhostnet_path, vhostfdSize); return 0; } + +#ifdef WITH_BPF + +int +qemuInterfaceLoadEbpf(const char *ebpfObject, void **retLibbpfObj, int *fds, size_t nfds) +{ + int err = 0; + size_t i = 0; + struct bpf_program *prog; + struct bpf_map *map; + struct bpf_object *obj; + size_t ebpfSize = 0; + g_autofree void *ebpfRawData = NULL; + + ebpfRawData = g_base64_decode(ebpfObject, &ebpfSize); + if (ebpfRawData == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't decode the eBPF from base64")); + return -1; + } + + obj = bpf_object__open_mem(ebpfRawData, ebpfSize, NULL); + err = libbpf_get_error(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't open eBPF object")); + return -1; + } + + + err = bpf_object__load(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't load eBPF object")); + return -1; + } + + bpf_object__for_each_program(prog, obj) { + fds[i] = bpf_program__fd(prog); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + bpf_object__for_each_map(map, obj) { + fds[i] = bpf_map__fd(map); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + *retLibbpfObj = obj; + + return i - 1; +} + + +void +qemuInterfaceCloseEbpf(void *libbpfObj) +{ + if (libbpfObj) + bpf_object__close(libbpfObj); +} +#else + +int +qemuInterfaceLoadEbpf(const char *ebpfObject G_GNUC_UNUSED, void **retLibbpfObj G_GNUC_UNUSED, + int *fds G_GNUC_UNUSED, size_t nfds G_GNUC_UNUSED) +{ + return -1; +} + +void +qemuInterfaceCloseEbpf(void *libbpfObj G_GNUC_UNUSED) +{ +} + +#endif diff --git a/src/qemu/qemu_interface.h b/src/qemu/qemu_interface.h index aee5f9efb0..b6f39550cd 100644 --- a/src/qemu/qemu_interface.h +++ b/src/qemu/qemu_interface.h @@ -44,3 +44,7 @@ int qemuInterfaceOpenVhostNet(virDomainObj *def, int qemuInterfacePrepareSlirp(virQEMUDriver *driver, virDomainNetDef *net); + +int qemuInterfaceLoadEbpf(const char *ebpfObject, void **retLibbpfObj, int *fds, size_t nfds); + +void qemuInterfaceCloseEbpf(void *libbpfObj); -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
Also, added dependencies for libbpf with bpf option.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- meson.build | 7 ++++ meson_options.txt | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_interface.c | 83 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_interface.h | 4 ++ 5 files changed, 96 insertions(+)
libvirt.spec.in should be changed too to either selectively disable this feature or enable it and drag in the requirement (preferred).
diff --git a/meson.build b/meson.build index e8b0094b91..e12a703a4d 100644 --- a/meson.build +++ b/meson.build @@ -998,6 +998,12 @@ else libkvm_dep = dependency('', required: false) endif
+libbpf_version = '1.1.0' +libbpf_dep = dependency('libbpf', version: '>=' + libbpf_version, required: get_option('libbpf')) +if libbpf_dep.found() + conf.set('WITH_BPF', 1) +endif + libiscsi_version = '1.18.0' libiscsi_dep = dependency('libiscsi', version: '>=' + libiscsi_version, required: get_option('libiscsi'))
@@ -2283,6 +2289,7 @@ libs_summary = { 'dlopen': dlopen_dep.found(), 'fuse': fuse_dep.found(), 'glusterfs': glusterfs_dep.found(), + 'libbpf': libbpf_dep.found(), 'libiscsi': libiscsi_dep.found(), 'libkvm': libkvm_dep.found(), 'libnbd': libnbd_dep.found(), diff --git a/meson_options.txt b/meson_options.txt index 9d729b3e1f..9b7bd9d1f8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -48,6 +48,7 @@ option('udev', type: 'feature', value: 'auto', description: 'udev support') option('wireshark_dissector', type: 'feature', value: 'auto', description: 'wireshark support') option('wireshark_plugindir', type: 'string', value: '', description: 'wireshark plugins directory for use when installing wireshark plugin') option('yajl', type: 'feature', value: 'auto', description: 'yajl support') +option('libbpf', type: 'feature', value: 'auto', description: 'qemu libbpf support')
# build driver options diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 907893d431..de7ae87d5b 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -105,6 +105,7 @@ if conf.has('WITH_QEMU') selinux_dep, src_dep, xdr_dep, + libbpf_dep,
We tend to keep this kind of lists sorted alphabetically.
], include_directories: [ conf_inc_dir, diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index c2007c7043..ec8cf18d86 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -38,6 +38,10 @@ #include <sys/stat.h> #include <fcntl.h>
+#ifdef WITH_BPF +#include <bpf/libbpf.h>
s/#include/# include/ if you'd install 'cppi' then a syntax-check rule of ours would have warned you about this.
+#endif + #define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_interface"); @@ -432,3 +436,82 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, virDomainAuditNetDevice(vm->def, net, vhostnet_path, vhostfdSize); return 0; } + +#ifdef WITH_BPF + +int +qemuInterfaceLoadEbpf(const char *ebpfObject, void **retLibbpfObj, int *fds, size_t nfds) +{ + int err = 0; + size_t i = 0; + struct bpf_program *prog; + struct bpf_map *map; + struct bpf_object *obj; + size_t ebpfSize = 0; + g_autofree void *ebpfRawData = NULL; + + ebpfRawData = g_base64_decode(ebpfObject, &ebpfSize); + if (ebpfRawData == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't decode the eBPF from base64")); + return -1; + } + + obj = bpf_object__open_mem(ebpfRawData, ebpfSize, NULL); + err = libbpf_get_error(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't open eBPF object")); + return -1; + }
IIUC, libbpf_get_error() is deprecated and upon failure bpf_object_*() APIs return NULL and set errno. Thus this (and the rest) could look something like this: obj = bpf_object__open_mem(...); if (!obj) { virReportSystemError(errno, "%s", _("can't open eBPF object")); return -1; }
+ + + err = bpf_object__load(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't load eBPF object")); + return -1; + } + + bpf_object__for_each_program(prog, obj) { + fds[i] = bpf_program__fd(prog); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + bpf_object__for_each_map(map, obj) { + fds[i] = bpf_map__fd(map); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + *retLibbpfObj = obj; + + return i - 1; +} + + +void +qemuInterfaceCloseEbpf(void *libbpfObj) +{ + if (libbpfObj) + bpf_object__close(libbpfObj); +} +#else + +int +qemuInterfaceLoadEbpf(const char *ebpfObject G_GNUC_UNUSED, void **retLibbpfObj G_GNUC_UNUSED, + int *fds G_GNUC_UNUSED, size_t nfds G_GNUC_UNUSED) +{ + return -1;
Maybe this can return -2 so that callers can distinguish this version and the version above failing?
+}
Michal

Hi all, On Fri, May 17, 2024 at 5:00 PM Michal Prívozník <mprivozn@redhat.com> wrote:
On 5/12/24 21:45, Andrew Melnychenko wrote:
Also, added dependencies for libbpf with bpf option.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- meson.build | 7 ++++ meson_options.txt | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_interface.c | 83 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_interface.h | 4 ++ 5 files changed, 96 insertions(+)
libvirt.spec.in should be changed too to either selectively disable this feature or enable it and drag in the requirement (preferred).
Yes, I'll "enable it" in the next version.
diff --git a/meson.build b/meson.build index e8b0094b91..e12a703a4d 100644 --- a/meson.build +++ b/meson.build @@ -998,6 +998,12 @@ else libkvm_dep = dependency('', required: false) endif
+libbpf_version = '1.1.0' +libbpf_dep = dependency('libbpf', version: '>=' + libbpf_version, required: get_option('libbpf')) +if libbpf_dep.found() + conf.set('WITH_BPF', 1) +endif + libiscsi_version = '1.18.0' libiscsi_dep = dependency('libiscsi', version: '>=' + libiscsi_version, required: get_option('libiscsi'))
@@ -2283,6 +2289,7 @@ libs_summary = { 'dlopen': dlopen_dep.found(), 'fuse': fuse_dep.found(), 'glusterfs': glusterfs_dep.found(), + 'libbpf': libbpf_dep.found(), 'libiscsi': libiscsi_dep.found(), 'libkvm': libkvm_dep.found(), 'libnbd': libnbd_dep.found(), diff --git a/meson_options.txt b/meson_options.txt index 9d729b3e1f..9b7bd9d1f8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -48,6 +48,7 @@ option('udev', type: 'feature', value: 'auto', description: 'udev support') option('wireshark_dissector', type: 'feature', value: 'auto', description: 'wireshark support') option('wireshark_plugindir', type: 'string', value: '', description: 'wireshark plugins directory for use when installing wireshark plugin') option('yajl', type: 'feature', value: 'auto', description: 'yajl support') +option('libbpf', type: 'feature', value: 'auto', description: 'qemu libbpf support')
# build driver options diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 907893d431..de7ae87d5b 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -105,6 +105,7 @@ if conf.has('WITH_QEMU') selinux_dep, src_dep, xdr_dep, + libbpf_dep,
We tend to keep this kind of lists sorted alphabetically.
], include_directories: [ conf_inc_dir, diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index c2007c7043..ec8cf18d86 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -38,6 +38,10 @@ #include <sys/stat.h> #include <fcntl.h>
+#ifdef WITH_BPF +#include <bpf/libbpf.h>
s/#include/# include/ if you'd install 'cppi' then a syntax-check rule of ours would have warned you about this.
+#endif + #define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_interface"); @@ -432,3 +436,82 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, virDomainAuditNetDevice(vm->def, net, vhostnet_path, vhostfdSize); return 0; } + +#ifdef WITH_BPF + +int +qemuInterfaceLoadEbpf(const char *ebpfObject, void **retLibbpfObj, int *fds, size_t nfds) +{ + int err = 0; + size_t i = 0; + struct bpf_program *prog; + struct bpf_map *map; + struct bpf_object *obj; + size_t ebpfSize = 0; + g_autofree void *ebpfRawData = NULL; + + ebpfRawData = g_base64_decode(ebpfObject, &ebpfSize); + if (ebpfRawData == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't decode the eBPF from base64")); + return -1; + } + + obj = bpf_object__open_mem(ebpfRawData, ebpfSize, NULL); + err = libbpf_get_error(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't open eBPF object")); + return -1; + }
IIUC, libbpf_get_error() is deprecated and upon failure bpf_object_*() APIs return NULL and set errno. Thus this (and the rest) could look something like this:
obj = bpf_object__open_mem(...); if (!obj) { virReportSystemError(errno, "%s", _("can't open eBPF object")); return -1; }
Ok. originally I've tied to omit errno and use VIR_ERR* macros.
+ + + err = bpf_object__load(obj); + if (err) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("can't load eBPF object")); + return -1; + } + + bpf_object__for_each_program(prog, obj) { + fds[i] = bpf_program__fd(prog); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + bpf_object__for_each_map(map, obj) { + fds[i] = bpf_map__fd(map); + ++i; + if (i > nfds) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("to much file descriptors in eBPF")); + return -1; + } + } + + *retLibbpfObj = obj; + + return i - 1; +} + + +void +qemuInterfaceCloseEbpf(void *libbpfObj) +{ + if (libbpfObj) + bpf_object__close(libbpfObj); +} +#else + +int +qemuInterfaceLoadEbpf(const char *ebpfObject G_GNUC_UNUSED, void **retLibbpfObj G_GNUC_UNUSED, + int *fds G_GNUC_UNUSED, size_t nfds G_GNUC_UNUSED) +{ + return -1;
Maybe this can return -2 so that callers can distinguish this version and the version above failing?
Ok.
+}
Michal

Added new capability ebpf_rss_fds for QEMU. Added logic for loading the "RSS" eBPF program. eBPF file descriptors passed to the QEMU. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_capabilities.c | 4 +++ src/qemu/qemu_capabilities.h | 3 ++ src/qemu/qemu_command.c | 61 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.c | 4 +++ src/qemu/qemu_domain.h | 3 ++ 5 files changed, 75 insertions(+) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 09bb6ca36e..bb6a587725 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -708,6 +708,9 @@ VIR_ENUM_IMPL(virQEMUCaps, "usb-mtp", /* QEMU_CAPS_DEVICE_USB_MTP */ "machine.virt.ras", /* QEMU_CAPS_MACHINE_VIRT_RAS */ "virtio-sound", /* QEMU_CAPS_DEVICE_VIRTIO_SOUND */ + + /* 460 */ + "virtio-net.ebpf_rss_fds", /* QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS */ ); @@ -1452,6 +1455,7 @@ static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioBlk[] = { static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioNet[] = { { "acpi-index", QEMU_CAPS_ACPI_INDEX, NULL }, { "rss", QEMU_CAPS_VIRTIO_NET_RSS, NULL }, + { "ebpf-rss-fds", QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, NULL }, }; static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsPCIeRootPort[] = { diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 371ea19bd0..e4bb137b21 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -688,6 +688,9 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */ QEMU_CAPS_MACHINE_VIRT_RAS, /* -machine virt,ras= */ QEMU_CAPS_DEVICE_VIRTIO_SOUND, /* -device virtio-sound-* */ + /* 460 */ + QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, /* virtio-net ebpf_rss_fds feature */ + QEMU_CAPS_LAST /* this must always be the last item */ } virQEMUCapsFlags; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 9859ea67a4..77715cf6fe 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -3805,6 +3805,25 @@ qemuBuildLegacyNicStr(virDomainNetDef *net) } +static virJSONValue * +qemuBuildEbpfRssArg(virDomainNetDef *net) +{ + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + g_autoptr(virJSONValue) props = NULL; + + GSList *n; + + if (netpriv->ebpfrssfds) + props = virJSONValueNewArray(); + + for (n = netpriv->ebpfrssfds; n; n = n->next) { + virJSONValueArrayAppendString(props, qemuFDPassDirectGetPath(n->data)); + } + + return g_steal_pointer(&props); +} + + virJSONValue * qemuBuildNicDevProps(virDomainDef *def, virDomainNetDef *net, @@ -3813,6 +3832,7 @@ qemuBuildNicDevProps(virDomainDef *def, g_autoptr(virJSONValue) props = NULL; char macaddr[VIR_MAC_STRING_BUFLEN]; g_autofree char *netdev = g_strdup_printf("host%s", net->info.alias); + g_autoptr(virJSONValue) ebpffds = NULL; if (virDomainNetIsVirtioModel(net)) { const char *tx = NULL; @@ -3863,6 +3883,8 @@ qemuBuildNicDevProps(virDomainDef *def, if (!(props = qemuBuildVirtioDevProps(VIR_DOMAIN_DEVICE_NET, net, qemuCaps))) return NULL; + ebpffds = qemuBuildEbpfRssArg(net); + if (virJSONValueObjectAdd(&props, "S:tx", tx, "T:ioeventfd", net->driver.virtio.ioeventfd, @@ -3887,6 +3909,7 @@ qemuBuildNicDevProps(virDomainDef *def, "T:hash", net->driver.virtio.rss_hash_report, "p:host_mtu", net->mtu, "T:failover", failover, + "A:ebpf-rss-fds", &ebpffds, NULL) < 0) return NULL; } else { @@ -4170,6 +4193,39 @@ qemuBuildWatchdogDevProps(const virDomainDef *def, } +static void +qemuOpenEbpfRssFds(virDomainNetDef *net, virQEMUCaps *qemuCaps) +{ + const char *ebpfRSSObject = NULL; + int fds[16]; + int nfds = 0; + size_t i = 0; + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + + netpriv->libbpfRSSObject = NULL; + netpriv->ebpfrssfds = NULL; + + /* Add ebpf values */ + if (net->driver.virtio.rss == VIR_TRISTATE_SWITCH_ON + && net->driver.virtio.rss_hash_report != VIR_TRISTATE_SWITCH_ON + && virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS)) { + ebpfRSSObject = virQEMUCapsGetEbpf(qemuCaps, "rss"); + nfds = qemuInterfaceLoadEbpf(ebpfRSSObject, &netpriv->libbpfRSSObject, fds, 16); + + if (nfds > 0) { + for (i = 0; i < nfds; ++i) { + g_autofree char *name = g_strdup_printf("ebpfrssfd-%s-%zu", net->info.alias, i); + + netpriv->ebpfrssfds = + g_slist_prepend(netpriv->ebpfrssfds, qemuFDPassDirectNew(name, fds + i)); + } + } + + netpriv->ebpfrssfds = g_slist_reverse(netpriv->ebpfrssfds); + } +} + + static int qemuBuildWatchdogCommandLine(virCommand *cmd, const virDomainDef *def, @@ -8725,6 +8781,7 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, g_autoptr(virJSONValue) hostnetprops = NULL; qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); GSList *n; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); if (qemuDomainValidateActualNetDef(net, qemuCaps) < 0) return -1; @@ -8860,6 +8917,10 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, qemuFDPassDirectTransferCommand(netpriv->slirpfd, cmd); qemuFDPassTransferCommand(netpriv->vdpafd, cmd); + qemuOpenEbpfRssFds(net, qemuCaps); + for (n = netpriv->ebpfrssfds; n; n = n->next) + qemuFDPassDirectTransferCommand(n->data, cmd); + if (!(hostnetprops = qemuBuildHostNetProps(vm, net))) goto cleanup; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index bda62f2e5c..894a6af348 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -38,6 +38,7 @@ #include "qemu_checkpoint.h" #include "qemu_validate.h" #include "qemu_namespace.h" +#include "qemu_interface.h" #include "viralloc.h" #include "virlog.h" #include "virerror.h" @@ -1079,6 +1080,7 @@ qemuDomainNetworkPrivateClearFDs(qemuDomainNetworkPrivate *priv) g_clear_pointer(&priv->vdpafd, qemuFDPassFree); g_slist_free_full(g_steal_pointer(&priv->vhostfds), (GDestroyNotify) qemuFDPassDirectFree); g_slist_free_full(g_steal_pointer(&priv->tapfds), (GDestroyNotify) qemuFDPassDirectFree); + g_slist_free_full(g_steal_pointer(&priv->ebpfrssfds), (GDestroyNotify) qemuFDPassDirectFree); } @@ -1090,6 +1092,8 @@ qemuDomainNetworkPrivateDispose(void *obj G_GNUC_UNUSED) qemuSlirpFree(priv->slirp); qemuDomainNetworkPrivateClearFDs(priv); + + qemuInterfaceCloseEbpf(priv->libbpfRSSObject); } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index a3089ea449..d15789f9ac 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -434,6 +434,9 @@ struct _qemuDomainNetworkPrivate { GSList *tapfds; /* qemuFDPassDirect */ GSList *vhostfds; /* qemuFDPassDirect */ qemuFDPass *vdpafd; + + void *libbpfRSSObject; + GSList *ebpfrssfds; /* qemuFDPassDirect eBPF RSS fds from helper */ }; -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
Added new capability ebpf_rss_fds for QEMU. Added logic for loading the "RSS" eBPF program. eBPF file descriptors passed to the QEMU.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_capabilities.c | 4 +++ src/qemu/qemu_capabilities.h | 3 ++ src/qemu/qemu_command.c | 61 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.c | 4 +++ src/qemu/qemu_domain.h | 3 ++ 5 files changed, 75 insertions(+)
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 09bb6ca36e..bb6a587725 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -708,6 +708,9 @@ VIR_ENUM_IMPL(virQEMUCaps, "usb-mtp", /* QEMU_CAPS_DEVICE_USB_MTP */ "machine.virt.ras", /* QEMU_CAPS_MACHINE_VIRT_RAS */ "virtio-sound", /* QEMU_CAPS_DEVICE_VIRTIO_SOUND */ + + /* 460 */ + "virtio-net.ebpf_rss_fds", /* QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS */ );
@@ -1452,6 +1455,7 @@ static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioBlk[] = { static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioNet[] = { { "acpi-index", QEMU_CAPS_ACPI_INDEX, NULL }, { "rss", QEMU_CAPS_VIRTIO_NET_RSS, NULL }, + { "ebpf-rss-fds", QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, NULL }, };
static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsPCIeRootPort[] = { diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 371ea19bd0..e4bb137b21 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -688,6 +688,9 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */ QEMU_CAPS_MACHINE_VIRT_RAS, /* -machine virt,ras= */ QEMU_CAPS_DEVICE_VIRTIO_SOUND, /* -device virtio-sound-* */
+ /* 460 */ + QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, /* virtio-net ebpf_rss_fds feature */ + QEMU_CAPS_LAST /* this must always be the last item */ } virQEMUCapsFlags;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 9859ea67a4..77715cf6fe 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -3805,6 +3805,25 @@ qemuBuildLegacyNicStr(virDomainNetDef *net) }
+static virJSONValue * +qemuBuildEbpfRssArg(virDomainNetDef *net) +{ + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + g_autoptr(virJSONValue) props = NULL; + + GSList *n; + + if (netpriv->ebpfrssfds) + props = virJSONValueNewArray();
nitpick: A bit more convoluted than needed be. if (!netpriv->ebpfrssfds) return NULL; props = virJSONValueNewArray();
+ + for (n = netpriv->ebpfrssfds; n; n = n->next) { + virJSONValueArrayAppendString(props, qemuFDPassDirectGetPath(n->data)); + } + + return g_steal_pointer(&props); +} + + virJSONValue * qemuBuildNicDevProps(virDomainDef *def, virDomainNetDef *net, @@ -3813,6 +3832,7 @@ qemuBuildNicDevProps(virDomainDef *def, g_autoptr(virJSONValue) props = NULL; char macaddr[VIR_MAC_STRING_BUFLEN]; g_autofree char *netdev = g_strdup_printf("host%s", net->info.alias); + g_autoptr(virJSONValue) ebpffds = NULL;
We like to declare variables in their smallest possible scope. IOW, this should be declared ..
if (virDomainNetIsVirtioModel(net)) { const char *tx = NULL;
.. in this block.
@@ -3863,6 +3883,8 @@ qemuBuildNicDevProps(virDomainDef *def, if (!(props = qemuBuildVirtioDevProps(VIR_DOMAIN_DEVICE_NET, net, qemuCaps))) return NULL;
+ ebpffds = qemuBuildEbpfRssArg(net); + if (virJSONValueObjectAdd(&props, "S:tx", tx, "T:ioeventfd", net->driver.virtio.ioeventfd, @@ -3887,6 +3909,7 @@ qemuBuildNicDevProps(virDomainDef *def, "T:hash", net->driver.virtio.rss_hash_report, "p:host_mtu", net->mtu, "T:failover", failover, + "A:ebpf-rss-fds", &ebpffds, NULL) < 0) return NULL; } else { @@ -4170,6 +4193,39 @@ qemuBuildWatchdogDevProps(const virDomainDef *def, }
+static void +qemuOpenEbpfRssFds(virDomainNetDef *net, virQEMUCaps *qemuCaps) +{ + const char *ebpfRSSObject = NULL; + int fds[16];
So there can be 16 FDs top? Isn't the actual value related to number of virtio queues?
+ int nfds = 0; + size_t i = 0; + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + + netpriv->libbpfRSSObject = NULL; + netpriv->ebpfrssfds = NULL;
This looks suspicious. When allocating private data, g_new0() made sure this is zeroed out.
+ + /* Add ebpf values */ + if (net->driver.virtio.rss == VIR_TRISTATE_SWITCH_ON + && net->driver.virtio.rss_hash_report != VIR_TRISTATE_SWITCH_ON + && virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS)) { + ebpfRSSObject = virQEMUCapsGetEbpf(qemuCaps, "rss"); + nfds = qemuInterfaceLoadEbpf(ebpfRSSObject, &netpriv->libbpfRSSObject, fds, 16);
s/G_N_ELEMENTS(fds)/16/ Also, at this point if nfds == -1 we're not sure whether we're lacking libbpf, or libpf failed to load eBPF program. While the former should be skipped over, the latter is a fatal error, isn't it?
+ + if (nfds > 0) {
To increase readability of this code (by decrasing level of nesting), I'd write this as: if (nfds <= 0) return;
+ for (i = 0; i < nfds; ++i) { + g_autofree char *name = g_strdup_printf("ebpfrssfd-%s-%zu", net->info.alias, i); + + netpriv->ebpfrssfds = + g_slist_prepend(netpriv->ebpfrssfds, qemuFDPassDirectNew(name, fds + i)); + } + } + + netpriv->ebpfrssfds = g_slist_reverse(netpriv->ebpfrssfds); + } +} + +
Michal

Hi all, On Fri, May 17, 2024 at 5:00 PM Michal Prívozník <mprivozn@redhat.com> wrote:
On 5/12/24 21:45, Andrew Melnychenko wrote:
Added new capability ebpf_rss_fds for QEMU. Added logic for loading the "RSS" eBPF program. eBPF file descriptors passed to the QEMU.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_capabilities.c | 4 +++ src/qemu/qemu_capabilities.h | 3 ++ src/qemu/qemu_command.c | 61 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.c | 4 +++ src/qemu/qemu_domain.h | 3 ++ 5 files changed, 75 insertions(+)
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 09bb6ca36e..bb6a587725 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -708,6 +708,9 @@ VIR_ENUM_IMPL(virQEMUCaps, "usb-mtp", /* QEMU_CAPS_DEVICE_USB_MTP */ "machine.virt.ras", /* QEMU_CAPS_MACHINE_VIRT_RAS */ "virtio-sound", /* QEMU_CAPS_DEVICE_VIRTIO_SOUND */ + + /* 460 */ + "virtio-net.ebpf_rss_fds", /* QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS */ );
@@ -1452,6 +1455,7 @@ static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioBlk[] = { static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsVirtioNet[] = { { "acpi-index", QEMU_CAPS_ACPI_INDEX, NULL }, { "rss", QEMU_CAPS_VIRTIO_NET_RSS, NULL }, + { "ebpf-rss-fds", QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, NULL }, };
static struct virQEMUCapsDevicePropsFlags virQEMUCapsDevicePropsPCIeRootPort[] = { diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 371ea19bd0..e4bb137b21 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -688,6 +688,9 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */ QEMU_CAPS_MACHINE_VIRT_RAS, /* -machine virt,ras= */ QEMU_CAPS_DEVICE_VIRTIO_SOUND, /* -device virtio-sound-* */
+ /* 460 */ + QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS, /* virtio-net ebpf_rss_fds feature */ + QEMU_CAPS_LAST /* this must always be the last item */ } virQEMUCapsFlags;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 9859ea67a4..77715cf6fe 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -3805,6 +3805,25 @@ qemuBuildLegacyNicStr(virDomainNetDef *net) }
+static virJSONValue * +qemuBuildEbpfRssArg(virDomainNetDef *net) +{ + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + g_autoptr(virJSONValue) props = NULL; + + GSList *n; + + if (netpriv->ebpfrssfds) + props = virJSONValueNewArray();
nitpick: A bit more convoluted than needed be.
if (!netpriv->ebpfrssfds) return NULL;
props = virJSONValueNewArray();
I'll refactor it in the next patch.
+ + for (n = netpriv->ebpfrssfds; n; n = n->next) { + virJSONValueArrayAppendString(props, qemuFDPassDirectGetPath(n->data)); + } + + return g_steal_pointer(&props); +} + + virJSONValue * qemuBuildNicDevProps(virDomainDef *def, virDomainNetDef *net, @@ -3813,6 +3832,7 @@ qemuBuildNicDevProps(virDomainDef *def, g_autoptr(virJSONValue) props = NULL; char macaddr[VIR_MAC_STRING_BUFLEN]; g_autofree char *netdev = g_strdup_printf("host%s", net->info.alias); + g_autoptr(virJSONValue) ebpffds = NULL;
We like to declare variables in their smallest possible scope. IOW, this should be declared ..
if (virDomainNetIsVirtioModel(net)) { const char *tx = NULL;
.. in this block.
Ok.
@@ -3863,6 +3883,8 @@ qemuBuildNicDevProps(virDomainDef *def, if (!(props = qemuBuildVirtioDevProps(VIR_DOMAIN_DEVICE_NET, net, qemuCaps))) return NULL;
+ ebpffds = qemuBuildEbpfRssArg(net); + if (virJSONValueObjectAdd(&props, "S:tx", tx, "T:ioeventfd", net->driver.virtio.ioeventfd, @@ -3887,6 +3909,7 @@ qemuBuildNicDevProps(virDomainDef *def, "T:hash", net->driver.virtio.rss_hash_report, "p:host_mtu", net->mtu, "T:failover", failover, + "A:ebpf-rss-fds", &ebpffds, NULL) < 0) return NULL; } else { @@ -4170,6 +4193,39 @@ qemuBuildWatchdogDevProps(const virDomainDef *def, }
+static void +qemuOpenEbpfRssFds(virDomainNetDef *net, virQEMUCaps *qemuCaps) +{ + const char *ebpfRSSObject = NULL; + int fds[16];
So there can be 16 FDs top? Isn't the actual value related to number of virtio queues?
The 16 is just a "big enough number". Overall, I think it is possible to dynamically figure out or allocate filedescriptors - I'll check it in the next patches.
+ int nfds = 0; + size_t i = 0; + qemuDomainNetworkPrivate *netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + + netpriv->libbpfRSSObject = NULL; + netpriv->ebpfrssfds = NULL;
This looks suspicious. When allocating private data, g_new0() made sure this is zeroed out.
+ + /* Add ebpf values */ + if (net->driver.virtio.rss == VIR_TRISTATE_SWITCH_ON + && net->driver.virtio.rss_hash_report != VIR_TRISTATE_SWITCH_ON + && virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_NET_EBPF_RSS_FDS)) { + ebpfRSSObject = virQEMUCapsGetEbpf(qemuCaps, "rss"); + nfds = qemuInterfaceLoadEbpf(ebpfRSSObject, &netpriv->libbpfRSSObject, fds, 16);
s/G_N_ELEMENTS(fds)/16/
Also, at this point if nfds == -1 we're not sure whether we're lacking libbpf, or libpf failed to load eBPF program. While the former should be skipped over, the latter is a fatal error, isn't it?
I'll check it.
+ + if (nfds > 0) {
To increase readability of this code (by decrasing level of nesting), I'd write this as:
if (nfds <= 0) return;
+ for (i = 0; i < nfds; ++i) { + g_autofree char *name = g_strdup_printf("ebpfrssfd-%s-%zu", net->info.alias, i); + + netpriv->ebpfrssfds = + g_slist_prepend(netpriv->ebpfrssfds, qemuFDPassDirectNew(name, fds + i)); + } + } + + netpriv->ebpfrssfds = g_slist_reverse(netpriv->ebpfrssfds); + } +} + +
Michal

Currently, there is no way to control that config through qemu.conf file. This optional is required for future eBPF tests. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_command.c | 8 +++++--- src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 77715cf6fe..0d41d34c3b 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -8917,9 +8917,11 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, qemuFDPassDirectTransferCommand(netpriv->slirpfd, cmd); qemuFDPassTransferCommand(netpriv->vdpafd, cmd); - qemuOpenEbpfRssFds(net, qemuCaps); - for (n = netpriv->ebpfrssfds; n; n = n->next) - qemuFDPassDirectTransferCommand(n->data, cmd); + if (cfg->allowEBPF) { + qemuOpenEbpfRssFds(net, qemuCaps); + for (n = netpriv->ebpfrssfds; n; n = n->next) + qemuFDPassDirectTransferCommand(n->data, cmd); + } if (!(hostnetprops = qemuBuildHostNetProps(vm, net))) goto cleanup; diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 4050a82341..79168c3e54 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -287,6 +287,8 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->deprecationBehavior = g_strdup("none"); cfg->storageUseNbdkit = USE_NBDKIT_DEFAULT; + cfg->allowEBPF = true; + return g_steal_pointer(&cfg); } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 36049b4bfa..778897bd40 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -233,6 +233,8 @@ struct _virQEMUDriverConfig { bool storageUseNbdkit; virQEMUSchedCore schedCore; + + bool allowEBPF; }; G_DEFINE_AUTOPTR_CLEANUP_FUNC(virQEMUDriverConfig, virObjectUnref); -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
Currently, there is no way to control that config through qemu.conf file. This optional is required for future eBPF tests.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_command.c | 8 +++++--- src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 77715cf6fe..0d41d34c3b 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -8917,9 +8917,11 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, qemuFDPassDirectTransferCommand(netpriv->slirpfd, cmd); qemuFDPassTransferCommand(netpriv->vdpafd, cmd);
- qemuOpenEbpfRssFds(net, qemuCaps); - for (n = netpriv->ebpfrssfds; n; n = n->next) - qemuFDPassDirectTransferCommand(n->data, cmd); + if (cfg->allowEBPF) { + qemuOpenEbpfRssFds(net, qemuCaps); + for (n = netpriv->ebpfrssfds; n; n = n->next) + qemuFDPassDirectTransferCommand(n->data, cmd); + }
if (!(hostnetprops = qemuBuildHostNetProps(vm, net))) goto cleanup; diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 4050a82341..79168c3e54 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -287,6 +287,8 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->deprecationBehavior = g_strdup("none"); cfg->storageUseNbdkit = USE_NBDKIT_DEFAULT;
+ cfg->allowEBPF = true; + return g_steal_pointer(&cfg); }
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 36049b4bfa..778897bd40 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -233,6 +233,8 @@ struct _virQEMUDriverConfig { bool storageUseNbdkit;
virQEMUSchedCore schedCore; + + bool allowEBPF; };
This structure should reflect knobs that are tunable in qemu.conf. If you need a temporary change of behaviour just for tests we use env vars for that. Michal

Hi all, On Fri, May 17, 2024 at 5:00 PM Michal Prívozník <mprivozn@redhat.com> wrote:
On 5/12/24 21:45, Andrew Melnychenko wrote:
Currently, there is no way to control that config through qemu.conf file. This optional is required for future eBPF tests.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- src/qemu/qemu_command.c | 8 +++++--- src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 77715cf6fe..0d41d34c3b 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -8917,9 +8917,11 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, qemuFDPassDirectTransferCommand(netpriv->slirpfd, cmd); qemuFDPassTransferCommand(netpriv->vdpafd, cmd);
- qemuOpenEbpfRssFds(net, qemuCaps); - for (n = netpriv->ebpfrssfds; n; n = n->next) - qemuFDPassDirectTransferCommand(n->data, cmd); + if (cfg->allowEBPF) { + qemuOpenEbpfRssFds(net, qemuCaps); + for (n = netpriv->ebpfrssfds; n; n = n->next) + qemuFDPassDirectTransferCommand(n->data, cmd); + }
if (!(hostnetprops = qemuBuildHostNetProps(vm, net))) goto cleanup; diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 4050a82341..79168c3e54 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -287,6 +287,8 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->deprecationBehavior = g_strdup("none"); cfg->storageUseNbdkit = USE_NBDKIT_DEFAULT;
+ cfg->allowEBPF = true; + return g_steal_pointer(&cfg); }
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 36049b4bfa..778897bd40 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -233,6 +233,8 @@ struct _virQEMUDriverConfig { bool storageUseNbdkit;
virQEMUSchedCore schedCore; + + bool allowEBPF; };
This structure should reflect knobs that are tunable in qemu.conf. If you need a temporary change of behaviour just for tests we use env vars for that.
Yes, I need it only for tests. I'll work on it in the next version of patches.
Michal

Added net-virtio-rss-bpf to qemuxmlconf's test. Synthetically modified caps-9.0.0 with a reply. Added mock functions for loading eBPF. Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- .../caps_9.0.0_x86_64.replies | 199 ++++++++++-------- .../caps_9.0.0_x86_64.xml | 4 + tests/qemuxml2argvmock.c | 21 ++ .../net-virtio-rss-bpf.x86_64-latest.args | 37 ++++ .../net-virtio-rss-bpf.x86_64-latest.xml | 46 ++++ tests/qemuxmlconfdata/net-virtio-rss-bpf.xml | 46 ++++ tests/qemuxmlconftest.c | 4 + 7 files changed, 265 insertions(+), 92 deletions(-) create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.xml diff --git a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies index 5d36853ce3..b94625904b 100644 --- a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies +++ b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies @@ -23654,21 +23654,36 @@ } { - "execute": "query-kvm", + "execute": "request-ebpf", + "arguments": { + "id": "rss" + }, + "id": "libvirt-5" +} + +{ + "return": { + "object": "Test data" + }, "id": "libvirt-5" } +{ + "execute": "query-kvm", + "id": "libvirt-6" +} + { "return": { "enabled": true, "present": true }, - "id": "libvirt-5" + "id": "libvirt-6" } { "execute": "qom-list-types", - "id": "libvirt-6" + "id": "libvirt-7" } { @@ -26378,7 +26393,7 @@ "parent": "x86_64-cpu" } ], - "id": "libvirt-6" + "id": "libvirt-7" } { @@ -26386,7 +26401,7 @@ "arguments": { "typename": "virtio-blk-pci" }, - "id": "libvirt-7" + "id": "libvirt-8" } { @@ -26801,7 +26816,7 @@ "type": "bool" } ], - "id": "libvirt-7" + "id": "libvirt-8" } { @@ -26809,7 +26824,7 @@ "arguments": { "typename": "virtio-net-pci" }, - "id": "libvirt-8" + "id": "libvirt-9" } { @@ -27268,7 +27283,7 @@ "type": "bool" } ], - "id": "libvirt-8" + "id": "libvirt-9" } { @@ -27276,7 +27291,7 @@ "arguments": { "typename": "virtio-scsi-pci" }, - "id": "libvirt-9" + "id": "libvirt-10" } { @@ -27542,7 +27557,7 @@ "type": "child<virtio-scsi-device>" } ], - "id": "libvirt-9" + "id": "libvirt-10" } { @@ -27550,11 +27565,11 @@ "arguments": { "typename": "virtio-net-ccw" }, - "id": "libvirt-10" + "id": "libvirt-11" } { - "id": "libvirt-10", + "id": "libvirt-11", "error": { "class": "DeviceNotFound", "desc": "Device 'virtio-net-ccw' not found" @@ -27566,11 +27581,11 @@ "arguments": { "typename": "virtio-scsi-ccw" }, - "id": "libvirt-11" + "id": "libvirt-12" } { - "id": "libvirt-11", + "id": "libvirt-12", "error": { "class": "DeviceNotFound", "desc": "Device 'virtio-scsi-ccw' not found" @@ -27582,7 +27597,7 @@ "arguments": { "typename": "vfio-pci" }, - "id": "libvirt-12" + "id": "libvirt-13" } { @@ -27800,7 +27815,7 @@ "type": "int32" } ], - "id": "libvirt-12" + "id": "libvirt-13" } { @@ -27808,7 +27823,7 @@ "arguments": { "typename": "scsi-hd" }, - "id": "libvirt-13" + "id": "libvirt-14" } { @@ -28009,7 +28024,7 @@ "type": "int32" } ], - "id": "libvirt-13" + "id": "libvirt-14" } { @@ -28017,7 +28032,7 @@ "arguments": { "typename": "ide-hd" }, - "id": "libvirt-14" + "id": "libvirt-15" } { @@ -28168,7 +28183,7 @@ "type": "int32" } ], - "id": "libvirt-14" + "id": "libvirt-15" } { @@ -28176,7 +28191,7 @@ "arguments": { "typename": "PIIX4_PM" }, - "id": "libvirt-15" + "id": "libvirt-16" } { @@ -28299,7 +28314,7 @@ "type": "link<irq>" } ], - "id": "libvirt-15" + "id": "libvirt-16" } { @@ -28307,7 +28322,7 @@ "arguments": { "typename": "usb-redir" }, - "id": "libvirt-16" + "id": "libvirt-17" } { @@ -28363,7 +28378,7 @@ "type": "bool" } ], - "id": "libvirt-16" + "id": "libvirt-17" } { @@ -28371,7 +28386,7 @@ "arguments": { "typename": "usb-storage" }, - "id": "libvirt-17" + "id": "libvirt-18" } { @@ -28486,7 +28501,7 @@ "type": "bool" } ], - "id": "libvirt-17" + "id": "libvirt-18" } { @@ -28494,7 +28509,7 @@ "arguments": { "typename": "kvm-pit" }, - "id": "libvirt-18" + "id": "libvirt-19" } { @@ -28510,7 +28525,7 @@ "type": "LostTickPolicy" } ], - "id": "libvirt-18" + "id": "libvirt-19" } { @@ -28518,7 +28533,7 @@ "arguments": { "typename": "VGA" }, - "id": "libvirt-19" + "id": "libvirt-20" } { @@ -28640,7 +28655,7 @@ "type": "uint32" } ], - "id": "libvirt-19" + "id": "libvirt-20" } { @@ -28648,7 +28663,7 @@ "arguments": { "typename": "vmware-svga" }, - "id": "libvirt-20" + "id": "libvirt-21" } { @@ -28723,7 +28738,7 @@ "type": "uint32" } ], - "id": "libvirt-20" + "id": "libvirt-21" } { @@ -28731,7 +28746,7 @@ "arguments": { "typename": "qxl" }, - "id": "libvirt-21" + "id": "libvirt-22" } { @@ -28871,7 +28886,7 @@ "type": "int32" } ], - "id": "libvirt-21" + "id": "libvirt-22" } { @@ -28879,7 +28894,7 @@ "arguments": { "typename": "virtio-gpu-pci" }, - "id": "libvirt-22" + "id": "libvirt-23" } { @@ -29141,7 +29156,7 @@ "type": "child<virtio-gpu-device>" } ], - "id": "libvirt-22" + "id": "libvirt-23" } { @@ -29149,7 +29164,7 @@ "arguments": { "typename": "virtio-gpu-device" }, - "id": "libvirt-23" + "id": "libvirt-24" } { @@ -29249,7 +29264,7 @@ "type": "size" } ], - "id": "libvirt-23" + "id": "libvirt-24" } { @@ -29257,7 +29272,7 @@ "arguments": { "typename": "ICH9-LPC" }, - "id": "libvirt-24" + "id": "libvirt-25" } { @@ -29515,7 +29530,7 @@ "type": "link<irq>" } ], - "id": "libvirt-24" + "id": "libvirt-25" } { @@ -29523,7 +29538,7 @@ "arguments": { "typename": "virtio-balloon-pci" }, - "id": "libvirt-25" + "id": "libvirt-26" } { @@ -29778,7 +29793,7 @@ "type": "child<virtio-balloon-device>" } ], - "id": "libvirt-25" + "id": "libvirt-26" } { @@ -29786,11 +29801,11 @@ "arguments": { "typename": "virtio-balloon-ccw" }, - "id": "libvirt-26" + "id": "libvirt-27" } { - "id": "libvirt-26", + "id": "libvirt-27", "error": { "class": "DeviceNotFound", "desc": "Device 'virtio-balloon-ccw' not found" @@ -29802,7 +29817,7 @@ "arguments": { "typename": "virtio-balloon-device" }, - "id": "libvirt-27" + "id": "libvirt-28" } { @@ -29906,7 +29921,7 @@ "type": "guest statistics" } ], - "id": "libvirt-27" + "id": "libvirt-28" } { @@ -29914,7 +29929,7 @@ "arguments": { "typename": "intel-iommu" }, - "id": "libvirt-28" + "id": "libvirt-29" } { @@ -29987,7 +30002,7 @@ "type": "bool" } ], - "id": "libvirt-28" + "id": "libvirt-29" } { @@ -29995,7 +30010,7 @@ "arguments": { "typename": "mch" }, - "id": "libvirt-29" + "id": "libvirt-30" } { @@ -30070,7 +30085,7 @@ "type": "uint16" } ], - "id": "libvirt-29" + "id": "libvirt-30" } { @@ -30078,7 +30093,7 @@ "arguments": { "typename": "nvdimm" }, - "id": "libvirt-30" + "id": "libvirt-31" } { @@ -30120,7 +30135,7 @@ "type": "uint64" } ], - "id": "libvirt-30" + "id": "libvirt-31" } { @@ -30128,7 +30143,7 @@ "arguments": { "typename": "pcie-root-port" }, - "id": "libvirt-31" + "id": "libvirt-32" } { @@ -30281,7 +30296,7 @@ "type": "size" } ], - "id": "libvirt-31" + "id": "libvirt-32" } { @@ -30289,7 +30304,7 @@ "arguments": { "typename": "usb-host" }, - "id": "libvirt-32" + "id": "libvirt-33" } { @@ -30385,7 +30400,7 @@ "type": "bool" } ], - "id": "libvirt-32" + "id": "libvirt-33" } { @@ -30393,7 +30408,7 @@ "arguments": { "typename": "vhost-user-fs-device" }, - "id": "libvirt-33" + "id": "libvirt-34" } { @@ -30479,7 +30494,7 @@ "type": "int32" } ], - "id": "libvirt-33" + "id": "libvirt-34" } { @@ -30487,7 +30502,7 @@ "arguments": { "typename": "virtio-mem-pci" }, - "id": "libvirt-34" + "id": "libvirt-35" } { @@ -30748,7 +30763,7 @@ "type": "child<virtio-mem>" } ], - "id": "libvirt-34" + "id": "libvirt-35" } { @@ -30756,7 +30771,7 @@ "arguments": { "typename": "virtio-iommu-pci" }, - "id": "libvirt-35" + "id": "libvirt-36" } { @@ -31000,7 +31015,7 @@ "type": "child<virtio-iommu-device>" } ], - "id": "libvirt-35" + "id": "libvirt-36" } { @@ -31008,7 +31023,7 @@ "arguments": { "typename": "memory-backend-file" }, - "id": "libvirt-36" + "id": "libvirt-37" } { @@ -31102,7 +31117,7 @@ "type": "bool" } ], - "id": "libvirt-36" + "id": "libvirt-37" } { @@ -31110,7 +31125,7 @@ "arguments": { "typename": "memory-backend-memfd" }, - "id": "libvirt-37" + "id": "libvirt-38" } { @@ -31189,7 +31204,7 @@ "type": "int" } ], - "id": "libvirt-37" + "id": "libvirt-38" } { @@ -31197,7 +31212,7 @@ "arguments": { "typename": "max-x86_64-cpu" }, - "id": "libvirt-38" + "id": "libvirt-39" } { @@ -33114,12 +33129,12 @@ "type": "bool" } ], - "id": "libvirt-38" + "id": "libvirt-39" } { "execute": "query-machines", - "id": "libvirt-39" + "id": "libvirt-40" } { @@ -33755,7 +33770,7 @@ "default-ram-id": "pc.ram" } ], - "id": "libvirt-39" + "id": "libvirt-40" } { @@ -33763,7 +33778,7 @@ "arguments": { "typename": "none-machine" }, - "id": "libvirt-40" + "id": "libvirt-41" } { @@ -33876,12 +33891,12 @@ "type": "child<container>" } ], - "id": "libvirt-40" + "id": "libvirt-41" } { "execute": "query-cpu-definitions", - "id": "libvirt-41" + "id": "libvirt-42" } { @@ -36263,12 +36278,12 @@ "deprecated": false } ], - "id": "libvirt-41" + "id": "libvirt-42" } { "execute": "query-tpm-models", - "id": "libvirt-42" + "id": "libvirt-43" } { @@ -36276,12 +36291,12 @@ "tpm-crb", "tpm-tis" ], - "id": "libvirt-42" + "id": "libvirt-43" } { "execute": "query-tpm-types", - "id": "libvirt-43" + "id": "libvirt-44" } { @@ -36289,12 +36304,12 @@ "passthrough", "emulator" ], - "id": "libvirt-43" + "id": "libvirt-44" } { "execute": "query-command-line-options", - "id": "libvirt-44" + "id": "libvirt-45" } { @@ -37769,12 +37784,12 @@ "option": "drive" } ], - "id": "libvirt-44" + "id": "libvirt-45" } { "execute": "query-migrate-capabilities", - "id": "libvirt-45" + "id": "libvirt-46" } { @@ -37876,16 +37891,16 @@ "capability": "mapped-ram" } ], - "id": "libvirt-45" + "id": "libvirt-46" } { "execute": "query-sev-capabilities", - "id": "libvirt-46" + "id": "libvirt-47" } { - "id": "libvirt-46", + "id": "libvirt-47", "error": { "class": "GenericError", "desc": "SEV: Failed to open /dev/sev: No such file or directory" @@ -37894,11 +37909,11 @@ { "execute": "query-sgx-capabilities", - "id": "libvirt-47" + "id": "libvirt-48" } { - "id": "libvirt-47", + "id": "libvirt-48", "error": { "class": "GenericError", "desc": "SGX is not enabled in KVM" @@ -37913,7 +37928,7 @@ "name": "host" } }, - "id": "libvirt-48" + "id": "libvirt-49" } { @@ -38281,7 +38296,7 @@ } } }, - "id": "libvirt-48" + "id": "libvirt-49" } { @@ -38295,7 +38310,7 @@ } } }, - "id": "libvirt-49" + "id": "libvirt-50" } { @@ -38663,7 +38678,7 @@ } } }, - "id": "libvirt-49" + "id": "libvirt-50" } { @@ -38678,7 +38693,7 @@ } } }, - "id": "libvirt-50" + "id": "libvirt-51" } { @@ -39152,7 +39167,7 @@ } } }, - "id": "libvirt-50" + "id": "libvirt-51" } { diff --git a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.xml b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.xml index 204d243247..eafe2c30f8 100644 --- a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.xml +++ b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.xml @@ -205,6 +205,7 @@ <flag name='display-reload'/> <flag name='usb-mtp'/> <flag name='virtio-sound'/> + <flag name='virtio-net.ebpf_rss_fds'/> <version>9000000</version> <microcodeVersion>43100245</microcodeVersion> <package>v9.0.0</package> @@ -3691,4 +3692,7 @@ <cap name='ipi'/> <cap name='avic'/> </hypervCapabilities> + <ebpf> + <object id='rss' data='Test data'/> + </ebpf> </qemuCaps> diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c index 9cc97199c4..0d1ebbe5b9 100644 --- a/tests/qemuxml2argvmock.c +++ b/tests/qemuxml2argvmock.c @@ -292,3 +292,24 @@ virNetDevSetMTU(const char *ifname G_GNUC_UNUSED, { return 0; } + +int +qemuInterfaceLoadEbpf(__attribute__((unused)) const char *ebpfObject, + __attribute__((unused)) void **retLibbpfObj, int *fds, size_t nfds) +{ + if (nfds >= 4) { + fds[0] = 0x100; + fds[1] = 0x101; + fds[2] = 0x102; + fds[3] = 0x103; + return 4; + } else { + return -1; + } +} + +void +qemuInterfaceCloseEbpf(__attribute__((unused)) void *libbpfObj) +{ + return; +} diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args new file mode 100644 index 0000000000..b9497e5a73 --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args @@ -0,0 +1,37 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \ +-netdev '{"type":"user","id":"hostnet0"}' \ +-device '{"driver":"virtio-net-pci","rss":true,"ebpf-rss-fds":["256","257","258","259"],"netdev":"hostnet0","id":"net0","mac":"00:11:22:33:44:55","bus":"pci.0","addr":"0x2"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x5"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 2842b44b3e..0f53ef0408 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -1785,7 +1785,11 @@ mymain(void) DO_TEST_CAPS_LATEST_FAILURE("net-hostdev-fail"); DO_TEST_CAPS_LATEST("net-vdpa"); DO_TEST_CAPS_LATEST("net-vdpa-multiqueue"); + + driver.config->allowEBPF = false; DO_TEST_CAPS_LATEST("net-virtio-rss"); + driver.config->allowEBPF = true; + DO_TEST_CAPS_LATEST("net-virtio-rss-bpf"); DO_TEST_CAPS_LATEST("hostdev-pci-multifunction"); -- 2.44.0

On 5/12/24 21:45, Andrew Melnychenko wrote:
Added net-virtio-rss-bpf to qemuxmlconf's test. Synthetically modified caps-9.0.0 with a reply. Added mock functions for loading eBPF.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- .../caps_9.0.0_x86_64.replies | 199 ++++++++++-------- .../caps_9.0.0_x86_64.xml | 4 + tests/qemuxml2argvmock.c | 21 ++ .../net-virtio-rss-bpf.x86_64-latest.args | 37 ++++ .../net-virtio-rss-bpf.x86_64-latest.xml | 46 ++++ tests/qemuxmlconfdata/net-virtio-rss-bpf.xml | 46 ++++ tests/qemuxmlconftest.c | 4 + 7 files changed, 265 insertions(+), 92 deletions(-) create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.xml
diff --git a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies index 5d36853ce3..b94625904b 100644 --- a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies +++ b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies
Changes to this file should be done earlier.
diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c index 9cc97199c4..0d1ebbe5b9 100644 --- a/tests/qemuxml2argvmock.c +++ b/tests/qemuxml2argvmock.c @@ -292,3 +292,24 @@ virNetDevSetMTU(const char *ifname G_GNUC_UNUSED, { return 0; } + +int +qemuInterfaceLoadEbpf(__attribute__((unused)) const char *ebpfObject, + __attribute__((unused)) void **retLibbpfObj, int *fds, size_t nfds) +{ + if (nfds >= 4) { + fds[0] = 0x100; + fds[1] = 0x101; + fds[2] = 0x102; + fds[3] = 0x103; + return 4; + } else { + return -1; + } +} + +void +qemuInterfaceCloseEbpf(__attribute__((unused)) void *libbpfObj) +{ + return; +} diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args new file mode 100644 index 0000000000..b9497e5a73 --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args @@ -0,0 +1,37 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \ +-netdev '{"type":"user","id":"hostnet0"}' \ +-device '{"driver":"virtio-net-pci","rss":true,"ebpf-rss-fds":["256","257","258","259"],"netdev":"hostnet0","id":"net0","mac":"00:11:22:33:44:55","bus":"pci.0","addr":"0x2"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x5"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 2842b44b3e..0f53ef0408 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -1785,7 +1785,11 @@ mymain(void) DO_TEST_CAPS_LATEST_FAILURE("net-hostdev-fail"); DO_TEST_CAPS_LATEST("net-vdpa"); DO_TEST_CAPS_LATEST("net-vdpa-multiqueue"); + + driver.config->allowEBPF = false; DO_TEST_CAPS_LATEST("net-virtio-rss"); + driver.config->allowEBPF = true; + DO_TEST_CAPS_LATEST("net-virtio-rss-bpf");
What we usually do in this case is something like: g_setenv("SOME_ENV_VAR", 0, true); DO_TEST_CAPS_LATEST(); unsetenv("SOME_ENV_VAR"); See TEST_TPM_ENV_VAR for example. And then the mocked version of qemuInterfaceLoadEbpf() would check for this envvar and if set/unset do the mocked code or just return 0 or something.
DO_TEST_CAPS_LATEST("hostdev-pci-multifunction");
Michal

Hi all, On Fri, May 17, 2024 at 5:00 PM Michal Prívozník <mprivozn@redhat.com> wrote:
On 5/12/24 21:45, Andrew Melnychenko wrote:
Added net-virtio-rss-bpf to qemuxmlconf's test. Synthetically modified caps-9.0.0 with a reply. Added mock functions for loading eBPF.
Signed-off-by: Andrew Melnychenko <andrew@daynix.com> --- .../caps_9.0.0_x86_64.replies | 199 ++++++++++-------- .../caps_9.0.0_x86_64.xml | 4 + tests/qemuxml2argvmock.c | 21 ++ .../net-virtio-rss-bpf.x86_64-latest.args | 37 ++++ .../net-virtio-rss-bpf.x86_64-latest.xml | 46 ++++ tests/qemuxmlconfdata/net-virtio-rss-bpf.xml | 46 ++++ tests/qemuxmlconftest.c | 4 + 7 files changed, 265 insertions(+), 92 deletions(-) create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.xml
diff --git a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies index 5d36853ce3..b94625904b 100644 --- a/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies +++ b/tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies
Changes to this file should be done earlier.
diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c index 9cc97199c4..0d1ebbe5b9 100644 --- a/tests/qemuxml2argvmock.c +++ b/tests/qemuxml2argvmock.c @@ -292,3 +292,24 @@ virNetDevSetMTU(const char *ifname G_GNUC_UNUSED, { return 0; } + +int +qemuInterfaceLoadEbpf(__attribute__((unused)) const char *ebpfObject, + __attribute__((unused)) void **retLibbpfObj, int *fds, size_t nfds) +{ + if (nfds >= 4) { + fds[0] = 0x100; + fds[1] = 0x101; + fds[2] = 0x102; + fds[3] = 0x103; + return 4; + } else { + return -1; + } +} + +void +qemuInterfaceCloseEbpf(__attribute__((unused)) void *libbpfObj) +{ + return; +} diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args new file mode 100644 index 0000000000..b9497e5a73 --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args @@ -0,0 +1,37 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \ +-netdev '{"type":"user","id":"hostnet0"}' \ +-device '{"driver":"virtio-net-pci","rss":true,"ebpf-rss-fds":["256","257","258","259"],"netdev":"hostnet0","id":"net0","mac":"00:11:22:33:44:55","bus":"pci.0","addr":"0x2"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x5"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml new file mode 100644 index 0000000000..198540380c --- /dev/null +++ b/tests/qemuxmlconfdata/net-virtio-rss-bpf.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <interface type='user'> + <mac address='00:11:22:33:44:55'/> + <model type='virtio'/> + <driver rss='on'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </interface> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 2842b44b3e..0f53ef0408 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -1785,7 +1785,11 @@ mymain(void) DO_TEST_CAPS_LATEST_FAILURE("net-hostdev-fail"); DO_TEST_CAPS_LATEST("net-vdpa"); DO_TEST_CAPS_LATEST("net-vdpa-multiqueue"); + + driver.config->allowEBPF = false; DO_TEST_CAPS_LATEST("net-virtio-rss"); + driver.config->allowEBPF = true; + DO_TEST_CAPS_LATEST("net-virtio-rss-bpf");
What we usually do in this case is something like:
g_setenv("SOME_ENV_VAR", 0, true); DO_TEST_CAPS_LATEST(); unsetenv("SOME_ENV_VAR");
See TEST_TPM_ENV_VAR for example.
And then the mocked version of qemuInterfaceLoadEbpf() would check for this envvar and if set/unset do the mocked code or just return 0 or something.
Thank you, I'll check what I can implement in the next version.
DO_TEST_CAPS_LATEST("hostdev-pci-multifunction");
Michal

On 5/12/24 21:45, Andrew Melnychenko wrote:
This series of rfc patches adds support for loading the RSS eBPF program and passing it to the QEMU. Comments and suggestions would be useful.
QEMU with vhost may work with RSS through eBPF. To load eBPF, the capabilities required that Libvirt may provide. eBPF program and maps may be unique for particular QEMU and Libvirt retrieves eBPF through qapi. For now, there is only "RSS" eBPF object in QEMU, in the future, there may be another one(g.e. network filters). That's why in Libvirt added logic to load and store any eBPF object that QEMU provides using qapi schema.
One of the reasons why this series of patches is in RFC are tests. To this series of patches, the tests were added. For now, the tests are synthetic, the proper "reply" file should be generated with a new "caps" file. Currently, there are changes in caps-9.0.0* files. There was added support for ebpf_rss_fds feature, and request-ebpf command. Also, there was added new config for qemuConfig - allowEBPF. This config allows to enable/disable eBPF blob loading explicitly. This is required for qemuxmlconf tests - where some test expects that RSS would not support eBPF. So, overall, the tests are required for review, comment, and discussion how we want them to be implemented in the future.
For virtio-net RSS, the document has not changed. ``` <interface type="network"> <model type="virtio"/> <driver queues="4" rss="on" rss_hash_report="off"/> <interface type="network"> ```
Simplified routine for RSS: * Libvirt retrieves eBPF "RSS" and load it. * Libvirt passes file descriptors to virtio-net with property "ebpf_rss_fds" ("rss" property should be "on" too). * if fds was provided - QEMU using eBPF RSS implementation. * if fds was not provided - QEMU tries to load eBPF RSS in own context and use it. * if eBPF RSS was not loaded - QEMU uses "in-qemu" RSS(vhost not supported).
Changes since RFC v2: * refactored and rebased. * applied changes according to the Qemu. * added basic test.
Changes since RFC v1: * changed eBPF format saved in the XML cache. * refactored and checked with syntax test. * refactored patch hunks.
Andrew Melnychenko (6): qemu_monitor: Added QEMU's "request-ebpf" support. qemu_capabilities: Added logic for retrieving eBPF objects from QEMU. qemu_interface: Added routine for loading the eBPF objects. qemu_command: Added "ebpf_rss_fds" support for virtio-net. qemu_conf: Added configuration to optionally disable eBPF loading. tests: Added tests for eBPF blob loading.
meson.build | 7 + meson_options.txt | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_capabilities.c | 126 +++++++++++ src/qemu/qemu_capabilities.h | 6 + src/qemu/qemu_command.c | 63 ++++++ src/qemu/qemu_conf.c | 2 + src/qemu/qemu_conf.h | 2 + src/qemu/qemu_domain.c | 4 + src/qemu/qemu_domain.h | 3 + src/qemu/qemu_interface.c | 83 ++++++++ src/qemu/qemu_interface.h | 4 + src/qemu/qemu_monitor.c | 13 ++ src/qemu/qemu_monitor.h | 3 + src/qemu/qemu_monitor_json.c | 26 +++ src/qemu/qemu_monitor_json.h | 3 + .../caps_9.0.0_x86_64.replies | 199 ++++++++++-------- .../caps_9.0.0_x86_64.xml | 4 + tests/qemuxml2argvmock.c | 21 ++ .../net-virtio-rss-bpf.x86_64-latest.args | 37 ++++ .../net-virtio-rss-bpf.x86_64-latest.xml | 46 ++++ tests/qemuxmlconfdata/net-virtio-rss-bpf.xml | 46 ++++ tests/qemuxmlconftest.c | 4 + 23 files changed, 612 insertions(+), 92 deletions(-) create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/net-virtio-rss-bpf.xml
Couple of thoughts: 1) we mandate that the code and test suite passes after each individual commit. In practice, this means that parts of the last patch should be amended to the second one. 2) linking with ebpf - is there a way to avoid that? I mean tried to avoid using libebpf when rewriting devices CGroupV1 controller for CGroupsV2. 3) trust issues - libvirt will load "random" eBPF program into kernel. Can it be trusted? Because on one hand libvirt does a lot to restrict QEMU (because it can't be trusted) but then it'd trust QEMU and load program it provided into kernel. But to be fair - before QEMU runs any guest code it can be trusted, probably. Or better - it is as vulnerable to malicious sysadmin attacks as any other binary on the system. Michal

On 5/12/24 21:45, Andrew Melnychenko wrote:
This series of rfc patches adds support for loading the RSS eBPF program and passing it to the QEMU. Comments and suggestions would be useful.
Hey, any plans on sending another version? I think we were pretty close to have this merged. Michal

Hello all, yes. Curently I have an issue with my workspace. As soon as I get it fixed, I'll send another patches. On Wednesday, June 26, 2024, Michal Prívozník <mprivozn@redhat.com> wrote:
On 5/12/24 21:45, Andrew Melnychenko wrote:
This series of rfc patches adds support for loading the RSS eBPF program and passing it to the QEMU. Comments and suggestions would be useful.
Hey, any plans on sending another version? I think we were pretty close to have this merged.
Michal
participants (3)
-
Andrew Melnichenko
-
Andrew Melnychenko
-
Michal Prívozník