This adds support for custom command line arguments for the passt
backend, similar to qemu:commandline. The feature allows passing
additional arguments to the passt process for development and testing
purposes.

The implementation:
- Adds a passt XML namespace for custom arguments
- Properly taints the domain when custom args are used
- Includes comprehensive test coverage
- Adds documentation for the new feature

Usage example:
  <interface type='user'>
    <backend type='passt' 
xmlns:passt='http://libvirt.org/schemas/domain/passt/1.0'>
      <passt:commandline>
        <passt:arg value='--debug'/>
        <passt:arg value='--verbose'/>
      </passt:commandline>
    </backend>
  </interface>

Signed-off-by: Enrique Llorente <ellor...@redhat.com>
---
 docs/formatdomain.rst                         | 40 +++++++++
 src/conf/domain_conf.c                        | 84 ++++++++++++++++++-
 src/conf/domain_conf.h                        |  6 ++
 src/qemu/qemu_passt.c                         |  9 ++
 ...-user-passt-custom-args.x86_64-latest.args | 39 +++++++++
 ...t-user-passt-custom-args.x86_64-latest.xml | 57 +++++++++++++
 .../net-user-passt-custom-args.xml            | 52 ++++++++++++
 tests/qemuxmlconftest.c                       |  1 +
 8 files changed, 287 insertions(+), 1 deletion(-)
 create mode 100644 
tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.args
 create mode 100644 
tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.xml
 create mode 100644 tests/qemuxmlconfdata/net-user-passt-custom-args.xml

diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index 9a2f065590..59899aaa4a 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -5464,6 +5464,46 @@ ports **with the exception of some subset**.
    </devices>
    ...
 
+Custom passt commandline arguments
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:since:`Since 11.7.0` For development and testing purposes, it is
+sometimes useful to be able to pass additional command-line arguments
+directly to the passt process. This can be accomplished using a
+special passt namespace in the domain XML that is similar to the qemu
+commandline namespace:
+
+::
+
+   ...
+   <devices>
+     ...
+     <interface type='user'>
+       <backend type='passt' 
xmlns:passt='http://libvirt.org/schemas/domain/passt/1.0'>
+         <passt:commandline>
+           <passt:arg value='--debug'/>
+           <passt:arg value='--verbose'/>
+         </passt:commandline>
+       </backend>
+     </interface>
+   </devices>
+   ...
+
+Any arguments provided using this method will be appended to the passt
+command line, and will therefore override any default options set by
+libvirt in the case of conflicts.
+
+**This feature is intended for development and testing only.**
+Arguments are used as specified in the XML, and no validation beyond
+basic libvirt XML schema validation is performed. It is expected that
+this feature will primarily be used for testing new passt options, so
+that they can be evaluated for inclusion in libvirt's schema in a
+future release. It should not be used as a substitute for configuring
+passt with the proper libvirt XML elements and attributes. **When a
+domain uses this feature, it will be marked as "tainted" in the logs and
+the ``virsh dominfo`` output will include "custom-argv" in the list
+of reasons.**
+
 Generic ethernet connection
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 1e24e41a48..9d39bb39bd 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -2918,6 +2918,10 @@ virDomainNetDefFree(virDomainNetDef *def)
     g_free(def->backend.tap);
     g_free(def->backend.vhost);
     g_free(def->backend.logFile);
+    if (def->backend.passtCommandline) {
+        g_strfreev(def->backend.passtCommandline->args);
+        g_free(def->backend.passtCommandline);
+    }
     virDomainNetTeamingInfoFree(def->teaming);
     g_free(def->virtPortProfile);
     g_free(def->script);
@@ -9772,6 +9776,7 @@ virDomainNetBackendParseXML(xmlNodePtr node,
 {
     g_autofree char *tap = virXMLPropString(node, "tap");
     g_autofree char *vhost = virXMLPropString(node, "vhost");
+    xmlNodePtr cur;
 
     /* In the case of NET_TYPE_USER, backend type can be unspecified
      * (i.e. VIR_DOMAIN_NET_BACKEND_DEFAULT) and that means 'use
@@ -9808,6 +9813,40 @@ virDomainNetBackendParseXML(xmlNodePtr node,
         def->backend.vhost = virFileSanitizePath(vhost);
     }
 
+    /* Parse passt namespace commandline */
+    cur = node->children;
+    while (cur != NULL) {
+        if (cur->type == XML_ELEMENT_NODE) {
+            if (cur->ns && 
+                STREQ((const char *)cur->ns->href, 
"http://libvirt.org/schemas/domain/passt/1.0";) &&
+                STREQ((const char *)cur->name, "commandline")) {
+                xmlNodePtr arg_node = cur->children;
+                GPtrArray *args = g_ptr_array_new();
+                
+                while (arg_node != NULL) {
+                    if (arg_node->type == XML_ELEMENT_NODE &&
+                        arg_node->ns &&
+                        STREQ((const char *)arg_node->ns->href, 
"http://libvirt.org/schemas/domain/passt/1.0";) &&
+                        STREQ((const char *)arg_node->name, "arg")) {
+                        g_autofree char *value = virXMLPropString(arg_node, 
"value");
+                        if (value)
+                            g_ptr_array_add(args, g_strdup(value));
+                    }
+                    arg_node = arg_node->next;
+                }
+                
+                if (args->len > 0) {
+                    def->backend.passtCommandline = 
g_new0(virDomainNetBackendPasstCommandline, 1);
+                    g_ptr_array_add(args, NULL); /* NULL-terminate */
+                    def->backend.passtCommandline->args = (char 
**)g_ptr_array_free(args, FALSE);
+                } else {
+                    g_ptr_array_unref(args);
+                }
+            }
+        }
+        cur = cur->next;
+    }
+
     return 0;
 }
 
@@ -20802,6 +20841,33 @@ virDomainNetBackendIsEqual(virDomainNetBackend *src,
         STRNEQ_NULLABLE(src->logFile, dst->logFile)) {
         return false;
     }
+
+    /* Compare passt commandline */
+    if ((src->passtCommandline && dst->passtCommandline) ||
+        (!src->passtCommandline && !dst->passtCommandline)) {
+        if (src->passtCommandline && dst->passtCommandline) {
+            /* Compare argument arrays */
+            char **srcArgs = src->passtCommandline->args;
+            char **dstArgs = dst->passtCommandline->args;
+            
+            if (!srcArgs && !dstArgs) {
+                /* Both are NULL, continue */
+            } else if (!srcArgs || !dstArgs) {
+                return false; /* One is NULL, other is not */
+            } else {
+                /* Compare each argument */
+                int i = 0;
+                while (srcArgs[i] || dstArgs[i]) {
+                    if (!srcArgs[i] || !dstArgs[i] || STRNEQ(srcArgs[i], 
dstArgs[i]))
+                        return false;
+                    i++;
+                }
+            }
+        }
+    } else {
+        return false;
+    }
+
     return true;
 }
 
@@ -24926,6 +24992,7 @@ virDomainNetBackendFormat(virBuffer *buf,
                           virDomainNetBackend *backend)
 {
     g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
 
     if (backend->type) {
         virBufferAsprintf(&attrBuf, " type='%s'",
@@ -24934,7 +25001,22 @@ virDomainNetBackendFormat(virBuffer *buf,
     virBufferEscapeString(&attrBuf, " tap='%s'", backend->tap);
     virBufferEscapeString(&attrBuf, " vhost='%s'", backend->vhost);
     virBufferEscapeString(&attrBuf, " logFile='%s'", backend->logFile);
-    virXMLFormatElement(buf, "backend", &attrBuf, NULL);
+
+    /* Format passt commandline with namespace */
+    if (backend->passtCommandline && backend->passtCommandline->args) {
+        virBufferAddLit(&childBuf, "<passt:commandline 
xmlns:passt='http://libvirt.org/schemas/domain/passt/1.0'>\n");
+        virBufferAdjustIndent(&childBuf, 2);
+        
+        for (int i = 0; backend->passtCommandline->args[i]; i++) {
+            virBufferAsprintf(&childBuf, "<passt:arg value='%s'/>\n",
+                             backend->passtCommandline->args[i]);
+        }
+        
+        virBufferAdjustIndent(&childBuf, -2);
+        virBufferAddLit(&childBuf, "</passt:commandline>\n");
+    }
+
+    virXMLFormatElement(buf, "backend", &attrBuf, &childBuf);
 }
 
 
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 6997cf7c09..1f51bad546 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1070,12 +1070,18 @@ struct _virDomainActualNetDef {
     unsigned int class_id; /* class ID for bandwidth 'floor' */
 };
 
+typedef struct _virDomainNetBackendPasstCommandline 
virDomainNetBackendPasstCommandline;
+struct _virDomainNetBackendPasstCommandline {
+    char **args;  /* NULL-terminated array of arguments */
+};
+
 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 */
+    virDomainNetBackendPasstCommandline *passtCommandline;  /* for passt 
overrides */
 };
 
 struct _virDomainNetPortForwardRange {
diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c
index fcc34de384..e64ccfa5aa 100644
--- a/src/qemu/qemu_passt.c
+++ b/src/qemu/qemu_passt.c
@@ -317,6 +317,15 @@ qemuPasstStart(virDomainObj *vm,
         virCommandAddArg(cmd, virBufferCurrentContent(&buf));
     }
 
+    /* Add custom passt arguments from namespace */
+    if (net->backend.passtCommandline && net->backend.passtCommandline->args) {
+        for (i = 0; net->backend.passtCommandline->args[i]; i++) {
+            virCommandAddArg(cmd, net->backend.passtCommandline->args[i]);
+        }
+        
+        /* Taint the domain when using custom passt arguments */
+        qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_CUSTOM_ARGV, NULL);
+    }
 
     if (qemuExtDeviceLogCommand(driver, vm, cmd, "passt") < 0)
         return -1;
diff --git 
a/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.args 
b/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.args
new file mode 100644
index 0000000000..1715d4253c
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.args
@@ -0,0 +1,39 @@
+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-i386 \
+-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 qemu32 \
+-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"}' \
+-drive file=/dev/HostVG/QEMUGuest1,format=raw,if=none,id=drive-ide0-0-0 \
+-device 
'{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"drive-ide0-0-0","id":"ide0-0-0","bootindex":1}'
 \
+-netdev 
'{"type":"stream","addr":{"type":"unix","path":"/var/run/libvirt/qemu/passt/-1-QEMUGuest1-net0.socket"},"server":false,"reconnect-ms":5000,"id":"hostnet0"}'
 \
+-device 
'{"driver":"rtl8139","netdev":"hostnet0","id":"net0","mac":"52:54:00:12:34:56","bus":"pci.0","addr":"0x3"}'
 \
+-vnc 127.0.0.1:0 \
+-device '{"driver":"cirrus-vga","id":"video0","bus":"pci.0","addr":"0x2"}' \
+-device 
'{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x4"}' \
+-audiodev '{"id":"audio1","driver":"none"}' \
+-sandbox 
on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.xml 
b/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.xml
new file mode 100644
index 0000000000..310ff6c39e
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-custom-args.x86_64-latest.xml
@@ -0,0 +1,57 @@
+<domain type='qemu'>
+  <name>QEMUGuest1</name>
+  <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+  <memory unit='KiB'>219136</memory>
+  <currentMemory unit='KiB'>219136</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'>qemu32</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-i386</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='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' 
function='0x1'/>
+    </controller>
+    <controller type='pci' index='0' model='pci-root'/>
+    <interface type='user'>
+      <mac address='52:54:00:12:34:56'/>
+      <model type='rtl8139'/>
+      <backend type='passt' logFile='/tmp/passt.log'>
+        <passt:commandline 
xmlns:passt='http://libvirt.org/schemas/domain/passt/1.0'>
+          <passt:arg value='--debug'/>
+          <passt:arg value='--verbose'/>
+          <passt:arg value='--no-dhcp'/>
+        </passt:commandline>
+      </backend>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' 
function='0x0'/>
+    </interface>
+    <input type='mouse' bus='ps2'/>
+    <input type='keyboard' bus='ps2'/>
+    <graphics type='vnc' port='-1' autoport='yes'/>
+    <video>
+      <model type='cirrus' vram='16384' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' 
function='0x0'/>
+    </video>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' 
function='0x0'/>
+    </memballoon>
+    <audio id='1' type='none'/>
+  </devices>
+</domain>
diff --git a/tests/qemuxmlconfdata/net-user-passt-custom-args.xml 
b/tests/qemuxmlconfdata/net-user-passt-custom-args.xml
new file mode 100644
index 0000000000..15eca80b5e
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-custom-args.xml
@@ -0,0 +1,52 @@
+<domain type='qemu'>
+  <name>QEMUGuest1</name>
+  <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+  <memory unit='KiB'>219136</memory>
+  <currentMemory unit='KiB'>219136</currentMemory>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='i686' machine='pc'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>destroy</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-i386</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='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' 
function='0x1'/>
+    </controller>
+    <controller type='pci' index='0' model='pci-root'/>
+    <interface type='user'>
+      <mac address='52:54:00:12:34:56'/>
+      <backend type='passt' logFile='/tmp/passt.log'>
+        <passt:commandline 
xmlns:passt='http://libvirt.org/schemas/domain/passt/1.0'>
+          <passt:arg value='--debug'/>
+          <passt:arg value='--verbose'/>
+          <passt:arg value='--no-dhcp'/>
+        </passt:commandline>
+      </backend>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' 
function='0x0'/>
+    </interface>
+    <input type='mouse' bus='ps2'/>
+    <input type='keyboard' bus='ps2'/>
+    <graphics type='vnc' port='-1' autoport='yes'/>
+    <video>
+      <model type='cirrus' vram='16384' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' 
function='0x0'/>
+    </video>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' 
function='0x0'/>
+    </memballoon>
+  </devices>
+</domain> 
\ No newline at end of file
diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c
index 9fba984290..839ae49ed4 100644
--- a/tests/qemuxmlconftest.c
+++ b/tests/qemuxmlconftest.c
@@ -1805,6 +1805,7 @@ mymain(void)
     DO_TEST_CAPS_LATEST("net-user-addr");
     DO_TEST_CAPS_LATEST("net-user-passt");
     DO_TEST_CAPS_VER("net-user-passt", "7.2.0");
+    DO_TEST_CAPS_LATEST("net-user-passt-custom-args");
     DO_TEST_CAPS_LATEST_PARSE_ERROR("net-user-slirp-portforward");
     DO_TEST_CAPS_LATEST("net-vhostuser-passt");
     DO_TEST_CAPS_LATEST_PARSE_ERROR("net-vhostuser-passt-no-shmem");
-- 
2.50.0

Reply via email to