Add support for passing extra arguments to the passt binary through
the domain XML configuration. This allows users to specify additional
command-line arguments for passt that are not covered by existing
structured fields.

The new extraArgs attribute is added to the backend element:
<backend type='passt' extraArgs='--debug --no-dhcp -v'/>

The extraArgs string is parsed using g_shell_parse_argv() to split
it into individual arguments before passing them to the passt command.

This change includes:
- New field in virDomainNetBackend structure
- XML schema update to allow extraArgs attribute
- Parsing and formatting support in domain_conf.c
- Backend comparison function update
- Memory cleanup for the new field
- QEMU passt integration to use the extra arguments
- Comprehensive tests for both user and vhostuser interfaces

This is an RFE to gather feedback on the approach. I have a few questions
for the community:

1. Is this general approach of adding extraArgs reasonable, or should we
   instead focus on adding specific structured fields for each passt option?

2. Should extraArgs be marked as unsupported/unstable in the documentation,
   with a clear indication that it's primarily intended for development and
   testing purposes?

3. Are there any security concerns with allowing arbitrary arguments to be
   passed to the passt binary via XML configuration?

4. Would it be better to validate the arguments against a known allowlist
   rather than allowing any argument string?

The current implementation uses g_shell_parse_argv() to safely parse the
argument string, but I'm open to feedback on whether additional validation
or restrictions should be applied.

Signed-off-by: Enrique Llorente <ellor...@redhat.com>
---
 src/conf/domain_conf.c                        |  6 ++-
 src/conf/domain_conf.h                        |  1 +
 src/conf/schemas/domaincommon.rng             |  3 ++
 src/qemu/qemu_passt.c                         | 16 +++++++
 ...t-user-passt-extra-args.x86_64-latest.args | 35 ++++++++++++++
 ...et-user-passt-extra-args.x86_64-latest.xml | 44 +++++++++++++++++
 .../net-user-passt-extra-args.xml             | 41 ++++++++++++++++
 ...stuser-passt-extra-args.x86_64-latest.args | 36 ++++++++++++++
 ...ostuser-passt-extra-args.x86_64-latest.xml | 47 +++++++++++++++++++
 .../net-vhostuser-passt-extra-args.xml        | 44 +++++++++++++++++
 tests/qemuxmlconftest.c                       |  2 +
 11 files changed, 274 insertions(+), 1 deletion(-)
 create mode 100644 
tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.args
 create mode 100644 
tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.xml
 create mode 100644 tests/qemuxmlconfdata/net-user-passt-extra-args.xml
 create mode 100644 
tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.args
 create mode 100644 
tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.xml
 create mode 100644 tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.xml

diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 1e24e41a48..a83b9002d0 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -2918,6 +2918,7 @@ virDomainNetDefFree(virDomainNetDef *def)
     g_free(def->backend.tap);
     g_free(def->backend.vhost);
     g_free(def->backend.logFile);
+    g_free(def->backend.extraArgs);
     virDomainNetTeamingInfoFree(def->teaming);
     g_free(def->virtPortProfile);
     g_free(def->script);
@@ -9798,6 +9799,7 @@ virDomainNetBackendParseXML(xmlNodePtr node,
     }
 
     def->backend.logFile = virXMLPropString(node, "logFile");
+    def->backend.extraArgs = virXMLPropString(node, "extraArgs");
 
     if (tap)
         def->backend.tap = virFileSanitizePath(tap);
@@ -20799,7 +20801,8 @@ 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->logFile, dst->logFile) ||
+        STRNEQ_NULLABLE(src->extraArgs, dst->extraArgs)) {
         return false;
     }
     return true;
@@ -24934,6 +24937,7 @@ virDomainNetBackendFormat(virBuffer *buf,
     virBufferEscapeString(&attrBuf, " tap='%s'", backend->tap);
     virBufferEscapeString(&attrBuf, " vhost='%s'", backend->vhost);
     virBufferEscapeString(&attrBuf, " logFile='%s'", backend->logFile);
+    virBufferEscapeString(&attrBuf, " extraArgs='%s'", backend->extraArgs);
     virXMLFormatElement(buf, "backend", &attrBuf, NULL);
 }
 
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 6997cf7c09..d85e6e5c8e 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1076,6 +1076,7 @@ struct _virDomainNetBackend {
     char *vhost;
     /* The following are currently only valid/used when backend type='passt' */
     char *logFile;  /* path to logfile used by passt process */
+    char *extraArgs; /* extra arguments to pass to passt process */
 };
 
 struct _virDomainNetPortForwardRange {
diff --git a/src/conf/schemas/domaincommon.rng 
b/src/conf/schemas/domaincommon.rng
index 183dd5db5e..58946b6873 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -3931,6 +3931,9 @@
               <ref name="absFilePath"/>
             </attribute>
           </optional>
+          <optional>
+            <attribute name="extraArgs"/>
+          </optional>
         </element>
       </optional>
       <optional>
diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c
index fcc34de384..47efbc4c80 100644
--- a/src/qemu/qemu_passt.c
+++ b/src/qemu/qemu_passt.c
@@ -229,6 +229,22 @@ qemuPasstStart(virDomainObj *vm,
     if (net->backend.logFile)
         virCommandAddArgList(cmd, "--log-file", net->backend.logFile, NULL);
 
+    /* Add extra arguments */
+    if (net->backend.extraArgs) {
+        g_auto(GStrv) extraArgv = NULL;
+        gint argc = 0;
+        
+        if (!g_shell_parse_argv(net->backend.extraArgs, &argc, &extraArgv, 
NULL)) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("Failed to parse extraArgs: %1$s"), 
net->backend.extraArgs);
+            return -1;
+        }
+
+        for (gint j = 0; j < argc; j++) {
+            virCommandAddArg(cmd, extraArgv[j]);
+        }
+    }
+
     /* Add IP address info */
     for (i = 0; i < net->guestIP.nips; i++) {
         const virNetDevIPAddr *ip = net->guestIP.ips[i];
diff --git a/tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.args 
b/tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.args
new file mode 100644
index 0000000000..48d2596594
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.args
@@ -0,0 +1,35 @@
+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 \
+-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":"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":"00:11:22:33:44:55","bus":"pci.0","addr":"0x2"}'
 \
+-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-extra-args.x86_64-latest.xml 
b/tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.xml
new file mode 100644
index 0000000000..e01dc55894
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-extra-args.x86_64-latest.xml
@@ -0,0 +1,44 @@
+<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='x86_64' 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='none'/>
+    <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='00:11:22:33:44:55'/>
+      <source dev='eth42'/>
+      <ip address='172.17.2.0' family='ipv4' prefix='24'/>
+      <model type='rtl8139'/>
+      <backend type='passt' logFile='/var/log/passt.log' extraArgs='--debug 
--no-dhcp -v'/>
+      <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='none'/>
+  </devices>
+</domain>
diff --git a/tests/qemuxmlconfdata/net-user-passt-extra-args.xml 
b/tests/qemuxmlconfdata/net-user-passt-extra-args.xml
new file mode 100644
index 0000000000..2967331796
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-user-passt-extra-args.xml
@@ -0,0 +1,41 @@
+<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='x86_64' 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-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='none'/>
+    <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='00:11:22:33:44:55'/>
+      <source dev='eth42'/>
+      <ip address='172.17.2.0' family='ipv4' prefix='24'/>
+      <model type='rtl8139'/>
+      <backend type='passt' logFile='/var/log/passt.log' extraArgs='--debug 
--no-dhcp -v'/>
+      <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='none'/>
+  </devices>
+</domain> 
\ No newline at end of file
diff --git 
a/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.args 
b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.args
new file mode 100644
index 0000000000..939905793d
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.args
@@ -0,0 +1,36 @@
+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-file","id":"pc.ram","mem-path":"/var/lib/libvirt/qemu/ram/-1-QEMUGuest1/pc.ram","share":true,"x-use-canonical-path-for-ramblock-id":false,"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 \
+-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}'
 \
+-chardev 
socket,id=charnet0,path=/var/run/libvirt/qemu/passt/-1-QEMUGuest1-net0.socket,reconnect-ms=5000
 \
+-netdev '{"type":"vhost-user","chardev":"charnet0","id":"hostnet0"}' \
+-device 
'{"driver":"virtio-net-pci","netdev":"hostnet0","id":"net0","mac":"00:11:22:33:44:55","bus":"pci.0","addr":"0x2"}'
 \
+-audiodev '{"id":"audio1","driver":"none"}' \
+-sandbox 
on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git 
a/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.xml 
b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.xml
new file mode 100644
index 0000000000..2673c6f6d1
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.x86_64-latest.xml
@@ -0,0 +1,47 @@
+<domain type='qemu'>
+  <name>QEMUGuest1</name>
+  <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+  <memory unit='KiB'>219136</memory>
+  <currentMemory unit='KiB'>219136</currentMemory>
+  <memoryBacking>
+    <access mode='shared'/>
+  </memoryBacking>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='x86_64' 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='none'/>
+    <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='vhostuser'>
+      <mac address='00:11:22:33:44:55'/>
+      <source dev='eth42'/>
+      <ip address='172.17.2.0' family='ipv4' prefix='24'/>
+      <model type='virtio'/>
+      <backend type='passt' logFile='/var/log/passt.log' extraArgs='--debug 
--no-dhcp --verbose'/>
+      <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='none'/>
+  </devices>
+</domain>
diff --git a/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.xml 
b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.xml
new file mode 100644
index 0000000000..1875e24b5a
--- /dev/null
+++ b/tests/qemuxmlconfdata/net-vhostuser-passt-extra-args.xml
@@ -0,0 +1,44 @@
+<domain type='qemu'>
+  <name>QEMUGuest1</name>
+  <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+  <memory unit='KiB'>219136</memory>
+  <currentMemory unit='KiB'>219136</currentMemory>
+  <memoryBacking>
+    <access mode='shared'/>
+  </memoryBacking>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='x86_64' 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-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='none'/>
+    <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='vhostuser'>
+      <mac address='00:11:22:33:44:55'/>
+      <source dev='eth42'/>
+      <ip address='172.17.2.0' family='ipv4' prefix='24'/>
+      <model type='virtio'/>
+      <backend type='passt' logFile='/var/log/passt.log' extraArgs='--debug 
--no-dhcp --verbose'/>
+      <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='none'/>
+  </devices>
+</domain> 
\ No newline at end of file
diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c
index 6ad4d90934..843d7efadb 100644
--- a/tests/qemuxmlconftest.c
+++ b/tests/qemuxmlconftest.c
@@ -1799,8 +1799,10 @@ 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-extra-args");
     DO_TEST_CAPS_LATEST_PARSE_ERROR("net-user-slirp-portforward");
     DO_TEST_CAPS_LATEST("net-vhostuser-passt");
+    DO_TEST_CAPS_LATEST("net-vhostuser-passt-extra-args");
     DO_TEST_CAPS_LATEST_PARSE_ERROR("net-vhostuser-passt-no-shmem");
     DO_TEST_CAPS_LATEST("net-virtio");
     DO_TEST_CAPS_LATEST("net-virtio-device");
-- 
2.49.0

Reply via email to