[RFC 0/4] Implement automatic attachment of the vhostuser port

From: Hyman Huang <yong.huang@smartx.com> This series offer an automated method to configure a vhostuser interface in server mode, simplifying integration with DPDK-enabled Open vSwitch bridges. To ensure simplicity and forward compatibility, we introduce openvswitch backend support for vhostuser interfaces in XML configuration, with an optional 'autoiface' attribute. Here is an example of the config for a vhostuser interface that attached to bridge automatically: <interface type='vhostuser'> <mac address='52:54:00:3b:83:1b'/> <source type='unix' path='/tmp/vhost2.sock' mode='server'/> <virtualport type='openvswitch'> <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> </virtualport> <backend type='openvswitch' autoiface='yes'> <parameters bridge='ovsbr-ddpbxnhaq'/> </backend> <model type='virtio'/> <driver queues='5'/> </interface> The backend element specifies the backend implementation of the vhostuser interface type. The type attribute is required and currently supports supports openvswitch and passt. If type is openvswitch, the autoiface attribute may be specified (yes or no), if autoiface is yes, the parameters element bridge attribute is mandatory. Libvirt will derive the interface name by extracting the substring after the '/' character in the vhostuser server path, and attach it to the bridge specified by the bridge attribute. Please review. Thanks. Yong Hyman Huang (4): qemu_passt: Make logFile backend-specific conf: Introduce autoiface attribute for vhostuser interface util: Add iface argument to virNetDevOpenvswitchAddPort qemu: Implement automatic attachment of the vhostuser port docs/formatdomain.rst | 37 ++++++++- src/conf/domain_conf.c | 75 +++++++++++++++++-- src/conf/domain_conf.h | 13 +++- src/conf/domain_postparse.c | 46 ++++++++++++ src/conf/domain_validate.c | 24 ++++++ src/conf/schemas/domaincommon.rng | 20 +++++ src/lxc/lxc_process.c | 3 +- src/qemu/qemu_command.c | 3 + src/qemu/qemu_hotplug.c | 4 + src/qemu/qemu_interface.c | 46 ++++++++++++ src/qemu/qemu_interface.h | 3 + src/qemu/qemu_passt.c | 4 +- src/qemu/qemu_process.c | 4 + src/util/virnetdevopenvswitch.c | 36 ++++++++- src/util/virnetdevopenvswitch.h | 10 ++- src/util/virnetdevtap.c | 3 +- .../net-vhostuser.x86_64-latest.xml | 10 +++ tests/qemuxmlconfdata/net-vhostuser.xml | 10 +++ 18 files changed, 336 insertions(+), 15 deletions(-) -- 2.27.0

From: Hyman Huang <yong.huang@smartx.com> The 'logFile' field within the virDomainNetBackend struct is backend-specific. Refactor the code by introducing a union struct to encapsulate backend-specific fields, rather than using a plain field. This refactoring is also a prerequisite for implementing the 'openvswitch' backend of vhostuser interface in upcoming commits. --- src/conf/domain_conf.c | 18 +++++++++++++----- src/conf/domain_conf.h | 7 +++++-- src/qemu/qemu_passt.c | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 1e24e41a48..9862a76023 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2917,7 +2917,8 @@ virDomainNetDefFree(virDomainNetDef *def) g_free(def->backend.tap); g_free(def->backend.vhost); - g_free(def->backend.logFile); + if (def->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) + g_free(def->backend.data.passt.logFile); virDomainNetTeamingInfoFree(def->teaming); g_free(def->virtPortProfile); g_free(def->script); @@ -9797,7 +9798,8 @@ virDomainNetBackendParseXML(xmlNodePtr node, return -1; } - def->backend.logFile = virXMLPropString(node, "logFile"); + if (def->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) + def->backend.data.passt.logFile = virXMLPropString(node, "logFile"); if (tap) def->backend.tap = virFileSanitizePath(tap); @@ -20798,10 +20800,14 @@ virDomainNetBackendIsEqual(virDomainNetBackend *src, { if (src->type != dst->type || STRNEQ_NULLABLE(src->tap, dst->tap) || - STRNEQ_NULLABLE(src->vhost, dst->vhost) || - STRNEQ_NULLABLE(src->logFile, dst->logFile)) { + STRNEQ_NULLABLE(src->vhost, dst->vhost)) { return false; } + + if (src->type == VIR_DOMAIN_NET_BACKEND_PASST) { + return !(STRNEQ_NULLABLE(src->data.passt.logFile, dst->data.passt.logFile)); + } + return true; } @@ -24933,7 +24939,9 @@ virDomainNetBackendFormat(virBuffer *buf, } virBufferEscapeString(&attrBuf, " tap='%s'", backend->tap); virBufferEscapeString(&attrBuf, " vhost='%s'", backend->vhost); - virBufferEscapeString(&attrBuf, " logFile='%s'", backend->logFile); + if (backend->type == VIR_DOMAIN_NET_BACKEND_PASST) { + virBufferEscapeString(&attrBuf, " logFile='%s'", backend->data.passt.logFile); + } virXMLFormatElement(buf, "backend", &attrBuf, NULL); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 6997cf7c09..6d43654f0a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1074,8 +1074,11 @@ struct _virDomainNetBackend { virDomainNetBackendType type; char *tap; char *vhost; - /* The following are currently only valid/used when backend type='passt' */ - char *logFile; /* path to logfile used by passt process */ + union { + struct { + char *logFile; /* path to logfile used by passt process */ + } passt; + } data; }; struct _virDomainNetPortForwardRange { diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c index fcc34de384..d74fc2ee47 100644 --- a/src/qemu/qemu_passt.c +++ b/src/qemu/qemu_passt.c @@ -226,8 +226,8 @@ qemuPasstStart(virDomainObj *vm, if (net->sourceDev) virCommandAddArgList(cmd, "--interface", net->sourceDev, NULL); - if (net->backend.logFile) - virCommandAddArgList(cmd, "--log-file", net->backend.logFile, NULL); + if (net->backend.data.passt.logFile) + virCommandAddArgList(cmd, "--log-file", net->backend.data.passt.logFile, NULL); /* Add IP address info */ for (i = 0; i < net->guestIP.nips; i++) { -- 2.27.0

From: Hyman Huang <yong.huang@smartx.com> This implements XML support for automatically attaching a vhostuser port to an Open vSwitch bridge. Here is an example of the config for a vhostuser interface that attached to bridge automatically: <interface type='vhostuser'> <mac address='52:54:00:3b:83:1b'/> <source type='unix' path='/tmp/vhostifname' mode='server'/> <virtualport type='openvswitch'> <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> </virtualport> <backend type='openvswitch' autoiface='yes'> <parameters bridge='ovsbr-ddpbxnhaq'/> </backend> <model type='virtio'/> <driver queues='5'/> </interface> This setup offers an automated method to configure a vhostuser interface in server mode, simplifying integration with DPDK-enabled Open vSwitch bridges. --- docs/formatdomain.rst | 37 +++++++++++- src/conf/domain_conf.c | 59 ++++++++++++++++++- src/conf/domain_conf.h | 6 ++ src/conf/domain_postparse.c | 46 +++++++++++++++ src/conf/domain_validate.c | 24 ++++++++ src/conf/schemas/domaincommon.rng | 20 +++++++ .../net-vhostuser.x86_64-latest.xml | 10 ++++ tests/qemuxmlconfdata/net-vhostuser.xml | 10 ++++ 8 files changed, 209 insertions(+), 3 deletions(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 9a2f065590..d7051618a7 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -6533,6 +6533,12 @@ plane is based on shared memory. <source type='unix' path='/tmp/vhost2.sock' mode='client'> <reconnect enabled='yes' timeout='10'/> </source> + <virtualport type='openvswitch'> + <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> + </virtualport> + <backend type='openvswitch' autoiface='yes'> + <parameters bridge='ovsbr-ddpbxnhaq'/> + </backend> <model type='virtio'/> <driver queues='5'/> </interface> @@ -6557,11 +6563,40 @@ two attributes ``enabled`` (which accepts ``yes`` and ``no``) and ``timeout`` which specifies the amount of seconds after which hypervisor tries to reconnect. +The ``<backend>`` element specifies the backend implementation of +the ``vhostuser`` interface type. The ``type`` attribute is required +and currently supports ``openvswitch`` and ``passt``. + +If ``type='openvswitch'``: + + * The ``autoiface`` attribute may be specified (``yes`` or ``no``). + * If ``autoiface='yes'``, the ``<parameters>`` element ``bridge`` + attribute is mandatory. Libvirt will derive the interface name + by extracting the substring after the ``/`` character in the + vhostuser server path, and attach it to the bridge specified by + the ``bridge`` attribute. + +If ``type='passt'``: + + * See the `here + <formatdomain.html#vhost-user-connection-with-passt-backend>`__ + section for detailed information. + +The optional ``<virtualport>`` element applies only to the +``openvswitch`` backend (which is both the default and only supported +type currently). See the `here +<formatdomain.html#bridge-to-lan>`__ section for detailed information +about ``<virtualport type='openvswitch'/>``. + Note that when ``mode='server'`` is used, the hypervisor will wait for the incoming connection to be established prior to actually running the VM. This is not possible when hotplugging an interface with such config so the VM will continue to run even when no connection is made. It's advised to use -``mode='client'`` instead. +``mode='client'`` instead. :since:`Since 11.6.0`, Libvirt can automatically +attach the vhostuser port to the Open vSwitch bridge (i.e., the ``backend`` +element has ``type='openvswitch'`` and ``autoiface='yes'``). This setup +offers an automated method to configure a vhostuser interface in server +mode, simplifying integration with DPDK-enabled Open vSwitch bridges. vhost-user connection with passt backend ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 9862a76023..2b0b840819 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -650,6 +650,7 @@ VIR_ENUM_IMPL(virDomainNetBackend, VIR_DOMAIN_NET_BACKEND_LAST, "default", "passt", + "openvswitch", ); VIR_ENUM_IMPL(virDomainNetProto, @@ -9773,6 +9774,8 @@ virDomainNetBackendParseXML(xmlNodePtr node, { g_autofree char *tap = virXMLPropString(node, "tap"); g_autofree char *vhost = virXMLPropString(node, "vhost"); + virTristateBool autoiface; + xmlNodePtr parameters = NULL; /* In the case of NET_TYPE_USER, backend type can be unspecified * (i.e. VIR_DOMAIN_NET_BACKEND_DEFAULT) and that means 'use @@ -9798,8 +9801,33 @@ virDomainNetBackendParseXML(xmlNodePtr node, return -1; } - if (def->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) + switch (def->backend.type) { + case VIR_DOMAIN_NET_BACKEND_DEFAULT: + break; + + case VIR_DOMAIN_NET_BACKEND_PASST: def->backend.data.passt.logFile = virXMLPropString(node, "logFile"); + break; + + case VIR_DOMAIN_NET_BACKEND_OPENVSWITCH: + if (virXMLPropTristateBool(node, "autoiface", VIR_XML_PROP_NONE, + &autoiface) < 0) + return -1; + virTristateBoolToBool(autoiface, &def->backend.data.openvswitch.autoiface); + + if (def->backend.data.openvswitch.autoiface) { + if ((parameters = virXMLNodeGetSubelement(node, "parameters"))) { + def->backend.data.openvswitch.iface = virXMLPropString(parameters, "iface"); + def->backend.data.openvswitch.bridge = virXMLPropString(parameters, "bridge"); + } + } + break; + + case VIR_DOMAIN_NET_BACKEND_LAST: + default: + virReportEnumRangeError(virDomainNetBackendType, def->backend.type); + break; + } if (tap) def->backend.tap = virFileSanitizePath(tap); @@ -10142,6 +10170,10 @@ virDomainNetDefParseXML(virDomainXMLOption *xmlopt, source_node, ctxt) < 0) return NULL; } + + virtualport_flags = VIR_VPORT_XML_GENERATE_MISSING_DEFAULTS | + VIR_VPORT_XML_REQUIRE_ALL_ATTRIBUTES | + VIR_VPORT_XML_REQUIRE_TYPE; } break; @@ -24927,11 +24959,26 @@ virDomainNetTeamingInfoFormat(virDomainNetTeamingInfo *teaming, } +static void +virDomainNetBackendOpenvswitchIfaceFormat(virDomainNetBackend *backend, + virBuffer *buf) +{ + if (backend->data.openvswitch.autoiface) { + virBufferEscapeString(buf, "<parameters iface='%s'", + backend->data.openvswitch.iface); + virBufferEscapeString(buf, " bridge='%s'", + backend->data.openvswitch.bridge); + virBufferAddLit(buf, "/>\n"); + } +} + + static void virDomainNetBackendFormat(virBuffer *buf, virDomainNetBackend *backend) { g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); if (backend->type) { virBufferAsprintf(&attrBuf, " type='%s'", @@ -24941,8 +24988,12 @@ virDomainNetBackendFormat(virBuffer *buf, virBufferEscapeString(&attrBuf, " vhost='%s'", backend->vhost); if (backend->type == VIR_DOMAIN_NET_BACKEND_PASST) { virBufferEscapeString(&attrBuf, " logFile='%s'", backend->data.passt.logFile); + } else if (backend->type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH) { + virBufferAsprintf(&attrBuf, " autoiface='%s'", + backend->data.openvswitch.autoiface ? "yes" : "no"); + virDomainNetBackendOpenvswitchIfaceFormat(backend, &childBuf); } - virXMLFormatElement(buf, "backend", &attrBuf, NULL); + virXMLFormatElement(buf, "backend", &attrBuf, &childBuf); } @@ -30141,6 +30192,10 @@ virDomainNetGetActualBridgeName(const virDomainNetDef *iface) (iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE || iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_NETWORK)) return iface->data.network.actual->data.bridge.brname; + if (iface->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER && + iface->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH && + iface->backend.data.openvswitch.autoiface) + return iface->backend.data.openvswitch.bridge; return NULL; } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 6d43654f0a..c19a56e375 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1030,6 +1030,7 @@ typedef enum { typedef enum { VIR_DOMAIN_NET_BACKEND_DEFAULT = 0, VIR_DOMAIN_NET_BACKEND_PASST, + VIR_DOMAIN_NET_BACKEND_OPENVSWITCH, VIR_DOMAIN_NET_BACKEND_LAST } virDomainNetBackendType; @@ -1078,6 +1079,11 @@ struct _virDomainNetBackend { struct { char *logFile; /* path to logfile used by passt process */ } passt; + struct { + bool autoiface; /* create openvswitch interface automatically */ + char *iface; /* interface name */ + char *bridge; /* bridge name that interface attached to */ + } openvswitch; } data; }; diff --git a/src/conf/domain_postparse.c b/src/conf/domain_postparse.c index a07ec8d94e..f618ea3165 100644 --- a/src/conf/domain_postparse.c +++ b/src/conf/domain_postparse.c @@ -1235,6 +1235,49 @@ virDomainDefRejectDuplicatePanics(virDomainDef *def) } +static int +virDomainPostParseInterfaceAutoGenerate(virDomainNetDef *net) +{ + if (net->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER && + net->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH && + net->backend.data.openvswitch.autoiface) { + if (!net->virtPortProfile) { + virNetDevVPortProfile *virtPort = g_new0(virNetDevVPortProfile, 1); + virtPort->virtPortType = VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH; + if (virUUIDGenerate(virtPort->instanceID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot generate a random uuid for instanceid")); + return -1; + } + virtPort->instanceID_specified = true; + net->virtPortProfile = virtPort; + } + + if (!net->backend.data.openvswitch.iface) { + virDomainChrSourceDef *src = net->data.vhostuser; + char *ifname = strrchr(src->data.nix.path, '/'); + if (++ifname) { + net->backend.data.openvswitch.iface = g_strdup(ifname); + } + } + } + + return 0; +} + + +static int +virDomainDefPostParseInterface(virDomainDef *def) +{ + size_t i; + for (i = 0; i < def->nnets; i++) { + if (virDomainPostParseInterfaceAutoGenerate(def->nets[i]) < 0) + return -1; + } + return 0; +} + + static int virDomainDefPostParseCommon(virDomainDef *def, struct virDomainDefPostParseDeviceIteratorData *data, @@ -1315,6 +1358,9 @@ virDomainDefPostParseCommon(virDomainDef *def, if (virDomainDefPostParseCPU(def) < 0) return -1; + if (virDomainDefPostParseInterface(def) < 0) + return -1; + return 0; } diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index b28af7fa56..03ebce93a1 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -2262,6 +2262,30 @@ virDomainNetDefValidate(const virDomainNetDef *net) } } + if (net->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH) { + if (net->type != VIR_DOMAIN_NET_TYPE_VHOSTUSER) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("The 'openvswitch' backend can only be used with interface type='vhostuser'")); + return -1; + } + + if (net->backend.data.openvswitch.autoiface) { + if (!net->backend.data.openvswitch.iface || + !net->backend.data.openvswitch.bridge) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No interface or bridge name provided")); + return -1; + } + + if ((!net->virtPortProfile) || + (net->virtPortProfile->virtPortType != VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid virtualport element encountered")); + return -1; + } + } + } + if (net->sourceDev && net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("The 'dev' attribute of the <source> element can only be used with <interface> type='user' or type='vhostuser' if the <backend> type='passt'")); diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 183dd5db5e..50a37fce43 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -3534,6 +3534,9 @@ </optional> <empty/> </element> + <optional> + <ref name="virtualPortProfile"/> + </optional> </optional> <ref name="interface-options"/> </interleave> @@ -3913,6 +3916,7 @@ <choice> <value>default</value> <value>passt</value> + <value>openvswitch</value> </choice> </attribute> </optional> @@ -3931,6 +3935,22 @@ <ref name="absFilePath"/> </attribute> </optional> + <optional> + <attribute name="autoiface"> + <ref name="virYesNo"/> + </attribute> + </optional> + <optional> + <element name="parameters"> + <optional> + <attribute name="bridge"> + <data type="string"> + <param name='pattern'>[a-zA-Z0-9\-_]+</param> + </data> + </attribute> + </optional> + </element> + </optional> </element> </optional> <optional> diff --git a/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml b/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml index 44bebef2c8..05b85a6df0 100644 --- a/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml +++ b/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml @@ -30,12 +30,22 @@ <interface type='vhostuser'> <mac address='52:54:00:ee:96:6b'/> <source type='unix' path='/tmp/vhost0.sock' mode='server'/> + <virtualport type='openvswitch'> + <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> + </virtualport> + <backend type='openvswitch' autoiface='no'/> <model type='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> </interface> <interface type='vhostuser'> <mac address='52:54:00:ee:96:6c'/> <source type='unix' path='/tmp/vhost1.sock' mode='client'/> + <virtualport type='openvswitch'> + <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> + </virtualport> + <backend type='openvswitch' autoiface='yes'> + <parameters bridge='ovsbr-ddpbxnhaq'/> + </backend> <model type='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> </interface> diff --git a/tests/qemuxmlconfdata/net-vhostuser.xml b/tests/qemuxmlconfdata/net-vhostuser.xml index 91d1abc027..dc4d69ba2b 100644 --- a/tests/qemuxmlconfdata/net-vhostuser.xml +++ b/tests/qemuxmlconfdata/net-vhostuser.xml @@ -23,11 +23,21 @@ <interface type='vhostuser'> <mac address='52:54:00:ee:96:6b'/> <source type='unix' path='/tmp/vhost0.sock' mode='server'/> + <virtualport type='openvswitch'> + <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> + </virtualport> + <backend type='openvswitch' autoiface='no'/> <model type='virtio'/> </interface> <interface type='vhostuser'> <mac address='52:54:00:ee:96:6c'/> <source type='unix' path='/tmp/vhost1.sock' mode='client'/> + <virtualport type='openvswitch'> + <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> + </virtualport> + <backend type='openvswitch' autoiface='yes'> + <parameters bridge='ovsbr-ddpbxnhaq'/> + </backend> <model type='virtio'/> </interface> <interface type='server'> -- 2.27.0

From: Hyman Huang <yong.huang@smartx.com> Introduce the virNetDevVhostUserIface struct to encapsulate the necessary configuration for attaching a vhostuser port to a bridge. Update the virNetDevOpenvswitchAddPort signature to accept an additional iface argument. Extend the implementation of virNetDevOpenvswitchAddPort to fully support automatic attachment of the vhostuser port. --- src/lxc/lxc_process.c | 3 ++- src/util/virnetdevopenvswitch.c | 36 ++++++++++++++++++++++++++++++++- src/util/virnetdevopenvswitch.h | 10 ++++++++- src/util/virnetdevtap.c | 3 ++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index 1bca9e8dae..ad1554fa56 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -286,7 +286,8 @@ virLXCProcessSetupInterfaceTap(virDomainDef *vm, if (brname) { if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) { if (virNetDevOpenvswitchAddPort(brname, parentVeth, &net->mac, vm->uuid, - vport, virDomainNetGetActualVlan(net)) < 0) + vport, virDomainNetGetActualVlan(net), + NULL) < 0) return NULL; } else { if (virNetDevBridgeAddPort(brname, parentVeth, virDomainNetGetActualVlan(net)) < 0) diff --git a/src/util/virnetdevopenvswitch.c b/src/util/virnetdevopenvswitch.c index 868d6d26ba..6f6b995fe2 100644 --- a/src/util/virnetdevopenvswitch.c +++ b/src/util/virnetdevopenvswitch.c @@ -119,6 +119,36 @@ virNetDevOpenvswitchConstructVlans(virCommand *cmd, const virNetDevVlan *virtVla } } +/** + * virNetDevOpenvswitchConstructIface: + * @cmd: command to construct + * @ifname: the ovs interface name + * @iface: the configuration of ovs vhost user interface + * + * Construct the ovs vhost user interface configuration + * parameters to be passed to ovs-vsctl command. + * + */ +static void +virNetDevOpenvswitchConstructIface(virCommand *cmd, const char *ifname, + const virNetDevVhostUserIface *iface) +{ + g_autofree char *type_info = NULL; + g_autofree char *server_path = NULL; + + if (!ifname || !iface) + return; + + type_info = g_strdup_printf("type=\"%s\"", + iface->client ? "dpdkvhostuserclient" : "dpdkvhostuser"); + server_path = g_strdup_printf("options:vhost-server-path=\"%s\"", iface->path); + + virCommandAddArgList(cmd, + "--", "set", "Interface", ifname, type_info, + "--", "set", "Interface", ifname, server_path, + NULL); +} + /** * virNetDevOpenvswitchAddPort: * @brname: the bridge name @@ -126,6 +156,7 @@ virNetDevOpenvswitchConstructVlans(virCommand *cmd, const virNetDevVlan *virtVla * @macaddr: the mac address of the virtual interface * @vmuuid: the Domain UUID that has this interface * @ovsport: the ovs specific fields + * @ovsiface: the configuration of ovs vhost user interface * * Add an interface to the OVS bridge * @@ -135,7 +166,8 @@ int virNetDevOpenvswitchAddPort(const char *brname, const char *ifname, const virMacAddr *macaddr, const unsigned char *vmuuid, const virNetDevVPortProfile *ovsport, - const virNetDevVlan *virtVlan) + const virNetDevVlan *virtVlan, + const virNetDevVhostUserIface *ovsiface) { char macaddrstr[VIR_MAC_STRING_BUFLEN]; char ifuuidstr[VIR_UUID_STRING_BUFLEN]; @@ -170,6 +202,8 @@ int virNetDevOpenvswitchAddPort(const char *brname, const char *ifname, virNetDevOpenvswitchConstructVlans(cmd, virtVlan); + virNetDevOpenvswitchConstructIface(cmd, ifname, ovsiface); + if (ovsport->profileID[0] == '\0') { virCommandAddArgList(cmd, "--", "set", "Interface", ifname, attachedmac_ex_id, diff --git a/src/util/virnetdevopenvswitch.h b/src/util/virnetdevopenvswitch.h index 3b4a1e509b..4d5300f7d8 100644 --- a/src/util/virnetdevopenvswitch.h +++ b/src/util/virnetdevopenvswitch.h @@ -27,6 +27,13 @@ #define VIR_NETDEV_OVS_DEFAULT_TIMEOUT 5 +typedef struct _virNetDevVhostUserIface virNetDevVhostUserIface; +typedef virNetDevVhostUserIface *virNetDevVhostUserIfacePtr; +struct _virNetDevVhostUserIface { + bool client; + char *path; /* vhost user path listened by server */ +}; + void virNetDevOpenvswitchSetTimeout(unsigned int timeout); int virNetDevOpenvswitchAddPort(const char *brname, @@ -34,7 +41,8 @@ int virNetDevOpenvswitchAddPort(const char *brname, const virMacAddr *macaddr, const unsigned char *vmuuid, const virNetDevVPortProfile *ovsport, - const virNetDevVlan *virtVlan) + const virNetDevVlan *virtVlan, + const virNetDevVhostUserIface *ovsiface) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) G_GNUC_WARN_UNUSED_RESULT; diff --git a/src/util/virnetdevtap.c b/src/util/virnetdevtap.c index b709d76cc7..f0e0c3c16b 100644 --- a/src/util/virnetdevtap.c +++ b/src/util/virnetdevtap.c @@ -479,7 +479,8 @@ virNetDevTapAttachBridge(const char *tapname, return -1; } else if (virtPortProfile->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) { if (virNetDevOpenvswitchAddPort(brname, tapname, macaddr, vmuuid, - virtPortProfile, virtVlan) < 0) + virtPortProfile, virtVlan, + NULL) < 0) return -1; } } else { -- 2.27.0

From: Hyman Huang <yong.huang@smartx.com> Add logic for automatic attachment and detachment of the vhostuser port within the vhostuser interface lifecycle. --- src/qemu/qemu_command.c | 3 +++ src/qemu/qemu_hotplug.c | 4 ++++ src/qemu/qemu_interface.c | 46 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_interface.h | 3 +++ src/qemu/qemu_process.c | 4 ++++ 5 files changed, 60 insertions(+) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 202f2dfaca..0370749fc7 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -8883,6 +8883,9 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver, if (qemuInterfaceVhostuserConnect(cmd, net, qemuCaps) < 0) goto cleanup; + if (qemuInterfaceVhostUserAddPort(def, net) < 0) + goto cleanup; + if (net->backend.type != VIR_DOMAIN_NET_BACKEND_PASST && virNetDevOpenvswitchGetVhostuserIfname(net->data.vhostuser->data.nix.path, net->data.vhostuser->data.nix.listen, diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 67a2464ce4..b6a028700c 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1305,6 +1305,10 @@ qemuDomainAttachNetDevice(virQEMUDriver *driver, if (qemuSecuritySetNetdevLabel(driver, vm, net) < 0) goto cleanup; teardownlabel = true; + + if (qemuInterfaceVhostUserAddPort(vm->def, net) < 0) + goto cleanup; + break; case VIR_DOMAIN_NET_TYPE_USER: diff --git a/src/qemu/qemu_interface.c b/src/qemu/qemu_interface.c index 23a23d201a..8518ddd8bd 100644 --- a/src/qemu/qemu_interface.c +++ b/src/qemu/qemu_interface.c @@ -215,3 +215,49 @@ qemuInterfaceOpenVhostNet(virDomainObj *vm, virDomainAuditNetDevice(vm->def, net, vhostnet_path, vhostfdSize); return 0; } + +/** + * qemuInterfaceVhostUserAddPort: + * @def: domain definition + * @net: network definition + * + * Called *only* called if actualType is VIR_DOMAIN_NET_TYPE_VHOSTUSER + * + * Returns 0 in case of success or -1 on failure + */ +int +qemuInterfaceVhostUserAddPort(virDomainDefPtr def, + virDomainNetDefPtr net) +{ + virDomainChrSourceDefPtr src = net->data.vhostuser; + virNetDevVhostUserIface iface = { + .client = src->data.nix.listen ? true : false, + .path = src->data.nix.path, + }; + + if (net->backend.type != VIR_DOMAIN_NET_BACKEND_OPENVSWITCH || + !net->backend.data.openvswitch.autoiface) + return 0; + + if (!net->backend.data.openvswitch.bridge || + !net->backend.data.openvswitch.iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No interface or bridge name found")); + return -1; + } + + if (virNetDevOpenvswitchAddPort(net->backend.data.openvswitch.bridge, + net->backend.data.openvswitch.iface, + &net->mac, def->uuid, + virDomainNetGetActualVirtPortProfile(net), + virDomainNetGetActualVlan(net), + &iface) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to attach vhost user port '%1$s' to bridge '%2$s'"), + net->backend.data.openvswitch.iface, + net->backend.data.openvswitch.bridge); + return -1; + } + + return 0; +} diff --git a/src/qemu/qemu_interface.h b/src/qemu/qemu_interface.h index d9deca0094..2f8e93b56e 100644 --- a/src/qemu/qemu_interface.h +++ b/src/qemu/qemu_interface.h @@ -37,3 +37,6 @@ int qemuInterfaceOpenVhostNet(virDomainObj *def, int qemuInterfacePrepareSlirp(virQEMUDriver *driver, virDomainNetDef *net); + +int qemuInterfaceVhostUserAddPort(virDomainDefPtr def, + virDomainNetDefPtr net); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 82cab3f76e..c17108fafc 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -5885,6 +5885,10 @@ qemuProcessPrepareDomainNetwork(virDomainObj *vm) /* some extra setup of internal data for passt vhostuser mode */ qemuPasstPrepareVhostUser(vm, net); } + + if (qemuInterfaceVhostUserAddPort(def, net) < 0) + return -1; + break; case VIR_DOMAIN_NET_TYPE_DIRECT: -- 2.27.0

Ping On Fri, Jul 4, 2025 at 11:20 PM <yong.huang@smartx.com> wrote:
From: Hyman Huang <yong.huang@smartx.com>
This series offer an automated method to configure a vhostuser interface in server mode, simplifying integration with DPDK-enabled Open vSwitch bridges.
To ensure simplicity and forward compatibility, we introduce openvswitch backend support for vhostuser interfaces in XML configuration, with an optional 'autoiface' attribute.
Here is an example of the config for a vhostuser interface that attached to bridge automatically:
<interface type='vhostuser'> <mac address='52:54:00:3b:83:1b'/> <source type='unix' path='/tmp/vhost2.sock' mode='server'/> <virtualport type='openvswitch'> <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> </virtualport> <backend type='openvswitch' autoiface='yes'> <parameters bridge='ovsbr-ddpbxnhaq'/> </backend> <model type='virtio'/> <driver queues='5'/> </interface>
The backend element specifies the backend implementation of the vhostuser interface type. The type attribute is required and currently supports supports openvswitch and passt.
If type is openvswitch, the autoiface attribute may be specified (yes or no), if autoiface is yes, the parameters element bridge attribute is mandatory. Libvirt will derive the interface name by extracting the substring after the '/' character in the vhostuser server path, and attach it to the bridge specified by the bridge attribute.
Please review. Thanks.
Yong
Hyman Huang (4): qemu_passt: Make logFile backend-specific conf: Introduce autoiface attribute for vhostuser interface util: Add iface argument to virNetDevOpenvswitchAddPort qemu: Implement automatic attachment of the vhostuser port
docs/formatdomain.rst | 37 ++++++++- src/conf/domain_conf.c | 75 +++++++++++++++++-- src/conf/domain_conf.h | 13 +++- src/conf/domain_postparse.c | 46 ++++++++++++ src/conf/domain_validate.c | 24 ++++++ src/conf/schemas/domaincommon.rng | 20 +++++ src/lxc/lxc_process.c | 3 +- src/qemu/qemu_command.c | 3 + src/qemu/qemu_hotplug.c | 4 + src/qemu/qemu_interface.c | 46 ++++++++++++ src/qemu/qemu_interface.h | 3 + src/qemu/qemu_passt.c | 4 +- src/qemu/qemu_process.c | 4 + src/util/virnetdevopenvswitch.c | 36 ++++++++- src/util/virnetdevopenvswitch.h | 10 ++- src/util/virnetdevtap.c | 3 +- .../net-vhostuser.x86_64-latest.xml | 10 +++ tests/qemuxmlconfdata/net-vhostuser.xml | 10 +++ 18 files changed, 336 insertions(+), 15 deletions(-)
-- 2.27.0
-- Best regards

Ping On Fri, Jul 4, 2025 at 11:20 PM <yong.huang@smartx.com> wrote:
From: Hyman Huang <yong.huang@smartx.com>
This series offer an automated method to configure a vhostuser interface in server mode, simplifying integration with DPDK-enabled Open vSwitch bridges.
To ensure simplicity and forward compatibility, we introduce openvswitch backend support for vhostuser interfaces in XML configuration, with an optional 'autoiface' attribute.
Here is an example of the config for a vhostuser interface that attached to bridge automatically:
<interface type='vhostuser'> <mac address='52:54:00:3b:83:1b'/> <source type='unix' path='/tmp/vhost2.sock' mode='server'/> <virtualport type='openvswitch'> <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/> </virtualport> <backend type='openvswitch' autoiface='yes'> <parameters bridge='ovsbr-ddpbxnhaq'/> </backend> <model type='virtio'/> <driver queues='5'/> </interface>
The backend element specifies the backend implementation of the vhostuser interface type. The type attribute is required and currently supports supports openvswitch and passt.
If type is openvswitch, the autoiface attribute may be specified (yes or no), if autoiface is yes, the parameters element bridge attribute is mandatory. Libvirt will derive the interface name by extracting the substring after the '/' character in the vhostuser server path, and attach it to the bridge specified by the bridge attribute.
Please review. Thanks.
Yong
Hyman Huang (4): qemu_passt: Make logFile backend-specific conf: Introduce autoiface attribute for vhostuser interface util: Add iface argument to virNetDevOpenvswitchAddPort qemu: Implement automatic attachment of the vhostuser port
docs/formatdomain.rst | 37 ++++++++- src/conf/domain_conf.c | 75 +++++++++++++++++-- src/conf/domain_conf.h | 13 +++- src/conf/domain_postparse.c | 46 ++++++++++++ src/conf/domain_validate.c | 24 ++++++ src/conf/schemas/domaincommon.rng | 20 +++++ src/lxc/lxc_process.c | 3 +- src/qemu/qemu_command.c | 3 + src/qemu/qemu_hotplug.c | 4 + src/qemu/qemu_interface.c | 46 ++++++++++++ src/qemu/qemu_interface.h | 3 + src/qemu/qemu_passt.c | 4 +- src/qemu/qemu_process.c | 4 + src/util/virnetdevopenvswitch.c | 36 ++++++++- src/util/virnetdevopenvswitch.h | 10 ++- src/util/virnetdevtap.c | 3 +- .../net-vhostuser.x86_64-latest.xml | 10 +++ tests/qemuxmlconfdata/net-vhostuser.xml | 10 +++ 18 files changed, 336 insertions(+), 15 deletions(-)
-- 2.27.0
-- Best regards
participants (2)
-
Yong Huang
-
yong.huang@smartx.com