[PATCH 0/2] qemu: add support for vhost-net when using the mshv accelerator
Currently vhost-net is dropped for everything but KVM, this change enables it also for the MSHV accelerator (HYPERV). A unit test has been added to confirm the vhost param is generated in a QEMU cmdline for KVM and MSHV accelerators. Magnus Kulke (2): qemu: Consider vhost-net for mshv accelerator test: vhost-net qemu commandline generation src/qemu/qemu_interface.c | 6 +- tests/qemucommandutiltest.c | 128 ++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) -- 2.34.1
Currently vhost-net is dropped for everything but KVM, this change enables it also for the MSHV accelerator (HYPERV). Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> --- src/qemu/qemu_interface.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index edc53d5..61bcbc6 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -162,9 +162,9 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, if (!vhostnet_path) vhostnet_path = "/dev/vhost-net"; - /* If running a plain QEMU guest, or - * if the config says explicitly to not use vhost, return now */ - if (vm->def->virtType != VIR_DOMAIN_VIRT_KVM || + /* Only enable vhost for KVM and MSHV guests, unless explicitly disabled. */ + if ((vm->def->virtType != VIR_DOMAIN_VIRT_KVM && + vm->def->virtType != VIR_DOMAIN_VIRT_HYPERV) || net->driver.virtio.name == VIR_DOMAIN_NET_DRIVER_TYPE_QEMU) return 0; -- 2.34.1
On 10/12/26 13:20, Magnus Kulke wrote:
Currently vhost-net is dropped for everything but KVM, this change enables it also for the MSHV accelerator (HYPERV).
Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> --- src/qemu/qemu_interface.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index edc53d5..61bcbc6 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -162,9 +162,9 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, if (!vhostnet_path) vhostnet_path = "/dev/vhost-net";
- /* If running a plain QEMU guest, or - * if the config says explicitly to not use vhost, return now */ - if (vm->def->virtType != VIR_DOMAIN_VIRT_KVM || + /* Only enable vhost for KVM and MSHV guests, unless explicitly disabled. */ + if ((vm->def->virtType != VIR_DOMAIN_VIRT_KVM && + vm->def->virtType != VIR_DOMAIN_VIRT_HYPERV) || net->driver.virtio.name == VIR_DOMAIN_NET_DRIVER_TYPE_QEMU) return 0;
ACK to this patch. We ought to take the opportunity and wrap the if() body in curly braces per our coding standard. But that's a nit pick. Michal
This test asserts that the vhost-net commandline is generated for KVM and MSHV accelerator options. $ VIR_TEST_RANGE=2-10 VIR_TEST_DEBUG=1 meson test -C build \ --verbose qemucommandutiltest TEST: qemucommandutiltest 2) KVM vhost ... OK 3) KVM vhost multiqueue ... OK 4) MSHV vhost ... OK 5) MSHV vhost multiqueue ... OK 6) MSHV default ... OK 7) MSHV qemu backend ... OK 8) TCG vhost ... OK 9) MSHV vhost unavailable ... OK 10) MSHV default unavailable ... OK Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> --- tests/qemucommandutiltest.c | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/qemucommandutiltest.c b/tests/qemucommandutiltest.c index 58493c7..f499d42 100644 --- a/tests/qemucommandutiltest.c +++ b/tests/qemucommandutiltest.c @@ -19,9 +19,15 @@ #include <config.h> +#include <fcntl.h> + #include "util/virjson.h" #include "util/virqemu.h" #include "testutils.h" +#include "qemu/qemu_command.h" +#include "qemu/qemu_domain.h" +#include "qemu/qemu_interface.h" +#include "virfile.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -64,6 +70,111 @@ testQemuCommandBuildFromJSON(const void *opaque) return 0; } +struct testVhostNetInfo { + virDomainVirtType virtType; + virDomainNetDriverType driver; + unsigned int queues; + unsigned int expectedVhostFDs; + bool unavailable; + int expectedResult; +}; + +static int +testOpenVhostNet(const void *opaque) +{ + const struct testVhostNetInfo *info = opaque; + g_autoptr(virDomainXMLOption) xmlopt = NULL; + g_autoptr(virDomainDef) def = virDomainDefNew(NULL); + virDomainObj vm = { .def = def }; + g_autoptr(virDomainNetDef) net = NULL; + g_autoptr(virJSONValue) props = NULL; + g_autoptr(virCommand) cmd = virCommandNew("/bin/true"); + g_auto(virBuffer) expected = VIR_BUFFER_INITIALIZER; + g_autofree char *expectedFDs = NULL; + qemuDomainNetworkPrivate *netpriv; + GSList *n; + bool vhost = false; + unsigned int queues = info->queues ? info->queues : 1; + size_t i; + int rc; + + if (!def || + !(xmlopt = virDomainXMLOptionNew(NULL, + &virQEMUDriverPrivateDataCallbacks, + NULL, NULL, NULL, NULL)) || + !(net = virDomainNetDefNew(xmlopt))) + return -1; + + def->name = g_strdup("vhost-test"); + def->virtType = info->virtType; + def->os.arch = VIR_ARCH_X86_64; + net->type = VIR_DOMAIN_NET_TYPE_ETHERNET; + net->model = VIR_DOMAIN_NET_MODEL_VIRTIO; + net->info.alias = g_strdup("net0"); + net->driver.virtio.name = info->driver; + net->driver.virtio.queues = info->queues; + /* Exercise the real device-opening code without requiring vhost support. */ + net->backend.vhost = g_strdup(info->unavailable ? + abs_builddir "/missing-vhost-net" : "/dev/null"); + netpriv = QEMU_DOMAIN_NETWORK_PRIVATE(net); + + rc = qemuInterfaceOpenVhostNet(&vm, net); + if (virTestCompareToULL(info->expectedResult != 0, rc != 0) < 0 || + virTestCompareToULL(info->expectedVhostFDs, + g_slist_length(netpriv->vhostfds)) < 0) + return -1; + + if (rc < 0) { + virResetLastError(); + return 0; + } + + for (i = 0; i < queues; i++) { + VIR_AUTOCLOSE fd = open("/dev/null", O_RDWR); + g_autofree char *name = g_strdup_printf("tapfd-net0%zu", i); + + if (fd < 0) { + virReportSystemError(errno, "%s", "Unable to open /dev/null"); + return -1; + } + netpriv->tapfds = g_slist_append(netpriv->tapfds, + qemuFDPassDirectNew(name, &fd)); + } + + for (n = netpriv->tapfds; n; n = n->next) + qemuFDPassDirectTransferCommand(n->data, cmd); + + for (n = netpriv->vhostfds; n; n = n->next) { + qemuFDPassDirectTransferCommand(n->data, cmd); + virBufferAsprintf(&expected, "%s:", qemuFDPassDirectGetPath(n->data)); + } + virBufferTrim(&expected, ":"); + expectedFDs = virBufferContentAndReset(&expected); + + if (!(props = qemuBuildHostNetProps(&vm, net))) + return -1; + + if (!info->expectedVhostFDs) { + if (virJSONValueObjectHasKey(props, "vhost") || + virJSONValueObjectHasKey(props, "vhostfd") || + virJSONValueObjectHasKey(props, "vhostfds")) { + fprintf(stderr, "Unexpected vhost properties\n"); + return -1; + } + return 0; + } + + if (virJSONValueObjectGetBoolean(props, "vhost", &vhost) < 0 || !vhost) { + fprintf(stderr, "Missing vhost=true\n"); + return -1; + } + + return virTestCompareToString(expectedFDs, + virJSONValueObjectGetString(props, + queues > 1 ? + "vhostfds" : "vhostfd")); +} + static int mymain(void) { @@ -110,6 +221,23 @@ mymain(void) "server.2.type=tcp," "server.2.host=example.com"); +#define DO_TEST_VHOST_NET(name, virt, driver, queues, nfds, unavailable, result) \ + do { \ + const struct testVhostNetInfo info = { virt, driver, queues, nfds, unavailable, result }; \ + if (virTestRun(name, testOpenVhostNet, &info) < 0) \ + ret = -1; \ + } while (0) + + DO_TEST_VHOST_NET("KVM vhost", VIR_DOMAIN_VIRT_KVM, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 0, 1, false, 0); + DO_TEST_VHOST_NET("KVM vhost multiqueue", VIR_DOMAIN_VIRT_KVM, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 8, 8, false, 0); + DO_TEST_VHOST_NET("MSHV vhost", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 0, 1, false, 0); + DO_TEST_VHOST_NET("MSHV vhost multiqueue", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 8, 8, false, 0); + DO_TEST_VHOST_NET("MSHV default", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_DEFAULT, 8, 8, false, 0); + DO_TEST_VHOST_NET("MSHV qemu backend", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_QEMU, 8, 0, false, 0); + DO_TEST_VHOST_NET("TCG vhost", VIR_DOMAIN_VIRT_QEMU, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 8, 0, false, 0); + DO_TEST_VHOST_NET("MSHV vhost unavailable", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_VHOST, 8, 0, true, -1); + DO_TEST_VHOST_NET("MSHV default unavailable", VIR_DOMAIN_VIRT_HYPERV, VIR_DOMAIN_NET_DRIVER_TYPE_DEFAULT, 8, 0, true, 0); + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 2.34.1
On 10/12/26 13:20, Magnus Kulke wrote:
This test asserts that the vhost-net commandline is generated for KVM and MSHV accelerator options.
$ VIR_TEST_RANGE=2-10 VIR_TEST_DEBUG=1 meson test -C build \ --verbose qemucommandutiltest
TEST: qemucommandutiltest 2) KVM vhost ... OK 3) KVM vhost multiqueue ... OK 4) MSHV vhost ... OK 5) MSHV vhost multiqueue ... OK 6) MSHV default ... OK 7) MSHV qemu backend ... OK 8) TCG vhost ... OK 9) MSHV vhost unavailable ... OK 10) MSHV default unavailable ... OK
Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> --- tests/qemucommandutiltest.c | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+)
I see where you're coming from, but this is not the correct test. This should be another test case (or an existing one modified) to qemuxmlconftest. There, mocking is used, i.e. there's an alternative implementation of qemuInterfaceOpenVhostNet() that's used from the test binary. Unfortunately, the alternative implementation does not honor the check from 1/2. We have two options here: 1) I'll merge just your first patch, or 2) I'll post patches to make test suite have the same check as the production code, and you'll rebase your patches on top. NB: In my review, I did the following change to demonstrate 1/2 works as expected: diff --git i/tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml w/tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml index d8fc60a212..f8e46c30c7 100644 --- i/tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml +++ w/tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml @@ -27,6 +27,11 @@ <source file='/var/lib/libvirt/images/test.qcow2'/> <target dev='vda' bus='virtio'/> </disk> + <interface type='ethernet'> + <mac address='52:54:00:e5:48:5a'/> + <model type='virtio'/> + <driver name='vhost' ioeventfd='on' queues='5'/> + </interface> <console type='pty'/> <rng model='virtio'> <backend model='random'>/dev/urandom</backend> Michal
On Thu, Oct 15, 2026 at 01:57:05PM +0200, Michal Prívozník wrote:
On 10/12/26 13:20, Magnus Kulke wrote: I see where you're coming from, but this is not the correct test. This should be another test case (or an existing one modified) to qemuxmlconftest. There, mocking is used, i.e. there's an alternative implementation of qemuInterfaceOpenVhostNet() that's used from the test binary. Unfortunately, the alternative implementation does not honor the check from 1/2.
We have two options here:
1) I'll merge just your first patch, or 2) I'll post patches to make test suite have the same check as the production code, and you'll rebase your patches on top.
thanks for the review. yes, I figured that there was a proper place to add the test, but I didn't find it at first glance. I'm fine with dropping the test commit and merging 1/2 (i.e. option 1). If I should add a test case later once qemuxmlconftest is able to cover it, you can cc me. best, magnus
On 10/15/26 17:07, Magnus Kulke wrote:
On Thu, Oct 15, 2026 at 01:57:05PM +0200, Michal Prívozník wrote:
On 10/12/26 13:20, Magnus Kulke wrote: I see where you're coming from, but this is not the correct test. This should be another test case (or an existing one modified) to qemuxmlconftest. There, mocking is used, i.e. there's an alternative implementation of qemuInterfaceOpenVhostNet() that's used from the test binary. Unfortunately, the alternative implementation does not honor the check from 1/2.
We have two options here:
1) I'll merge just your first patch, or 2) I'll post patches to make test suite have the same check as the production code, and you'll rebase your patches on top.
thanks for the review. yes, I figured that there was a proper place to add the test, but I didn't find it at first glance. I'm fine with dropping the test commit and merging 1/2 (i.e. option 1). If I should add a test case later once qemuxmlconftest is able to cover it, you can cc me.
best,
magnus
Alright, I've just merged the necessary change to the mock [1]. What you can do now is: 1) have a patch before 1/2 that adds an <interface/> to tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml. For instance the following XML seems okay: <interface type='ethernet'> <mac address='52:54:00:e5:48:5a'/> <model type='virtio'/> <driver name='vhost' ioeventfd='on' queues='5'/> </interface> Do not forget to regenerate other files after that: VIR_TEST_REGENERATE_OUTPUT=1 ./qemuxmlconftest 2) For your original patch 1/2, you'll need to change now duplicate if() statement inside of qemuInterfaceOpenVhostNet() in tests/qemuxml2argvmock.c. After this, you'll too need to regenerate the file, BUT the diff will show the original change in action. Hope this helps. Michal 1: https://gitlab.com/libvirt/libvirt/-/commit/6e1a9eae6ed34cc3b902f5c6e7379a2a...
On Fri, Oct 16, 2026 at 10:15:05AM +0200, Michal Prívozník wrote:
On 10/15/26 17:07, Magnus Kulke wrote:
On Thu, Oct 15, 2026 at 01:57:05PM +0200, Michal Prívozník wrote:
On 10/12/26 13:20, Magnus Kulke wrote: I see where you're coming from, but this is not the correct test. This should be another test case (or an existing one modified) to qemuxmlconftest. There, mocking is used, i.e. there's an alternative implementation of qemuInterfaceOpenVhostNet() that's used from the test binary. Unfortunately, the alternative implementation does not honor the check from 1/2.
We have two options here:
1) I'll merge just your first patch, or 2) I'll post patches to make test suite have the same check as the production code, and you'll rebase your patches on top.
thanks for the review. yes, I figured that there was a proper place to add the test, but I didn't find it at first glance. I'm fine with dropping the test commit and merging 1/2 (i.e. option 1). If I should add a test case later once qemuxmlconftest is able to cover it, you can cc me.
best,
magnus
Alright, I've just merged the necessary change to the mock [1]. What you can do now is:
1) have a patch before 1/2 that adds an <interface/> to tests/qemuxmlconfdata/mshv-x86_64-q35-headless.xml. For instance the following XML seems okay:
<interface type='ethernet'> <mac address='52:54:00:e5:48:5a'/> <model type='virtio'/> <driver name='vhost' ioeventfd='on' queues='5'/> </interface>
Do not forget to regenerate other files after that:
VIR_TEST_REGENERATE_OUTPUT=1 ./qemuxmlconftest
2) For your original patch 1/2, you'll need to change now duplicate if() statement inside of qemuInterfaceOpenVhostNet() in tests/qemuxml2argvmock.c. After this, you'll too need to regenerate the file, BUT the diff will show the original change in action.
Hope this helps.
Michal
1: https://gitlab.com/libvirt/libvirt/-/commit/6e1a9eae6ed34cc3b902f5c6e7379a2a...
ok, thanks. I think I understood, let me issue a v2.
participants (2)
-
Magnus Kulke -
Michal Prívozník