This implements XML config to represent a subset of the features supported by 'passt' (https://passt.top), which is an alternative backend for emulated network devices that requires no elevated privileges (similar to slirp, but "better"). Along with setting the backend to use passt (via <backend type='passt'/> when the interface type='user'), we also support passt's --log-file and --interface options (via the <backend> subelement logFile and upstream attributes) and its --tcp-ports and --udp-ports options (which selectively forward incoming connections to the host on to the guest) via the new <portForward> subelement of <interface>. Here is an example of the config for a network interface that uses passt to connect: <interface type='user'> <mac address='52:54:00:a8:33:fc'/> <ip address='192.168.221.122' family='ipv4'/> <model type='virtio'/> <backend type='passt' logFile='/tmp/xyzzy.log' upstream='eth0'/> <portForward address='10.0.0.1' proto='tcp' dev='eth0'> <range start='2022' to='22'/> <range start='5000' end='5099' to='1000'/> <range start='5010' end='5029' exclude='yes'/> </portForward> <portForward proto='udp'> <range start='10101'/> </portForward> </interface> In this case: * the guest will be offered address 192.168.221.122 for its interface via DHCP * the passt process will write all log messages to /tmp/xyzzy.log * routes to the outside for the guest will be derived from the addresses and routes associated with the host interface "eth0". * incoming tcp port 2022 to the host will be forwarded to port 22 on the guest. * incoming tcp ports 5000-5099 (with the exception of ports 5010-5029) to the host will be forwarded to port 1000-1099 on the guest. * incoming udp packets on port 10101 will be forwarded (unchanged) to the guest. Signed-off-by: Laine Stump <laine(a)redhat.com> --- docs/formatdomain.rst | 95 +++++++- src/conf/domain_conf.c | 242 +++++++++++++++++++- src/conf/domain_conf.h | 40 ++++ src/conf/domain_validate.c | 32 ++- src/conf/virconftypes.h | 4 + src/libvirt_private.syms | 1 + tests/qemuxml2xmloutdata/net-user-passt.xml | 1 + tests/qemuxml2xmltest.c | 1 + 8 files changed, 401 insertions(+), 15 deletions(-) create mode 120000 tests/qemuxml2xmloutdata/net-user-passt.xml diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index d7fffc6e0b..cd8fd4483b 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -4767,19 +4767,25 @@ to the interface. </devices> ... -Userspace SLIRP stack -^^^^^^^^^^^^^^^^^^^^^ +Userspace (SLIRP or passt) connection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``user`` type connects the guest interface to the outside via a +transparent userspace proxy that doesn't require any special system +privileges, making it usable in cases when libvirt itself is running +with no privileges (e.g. libvirt's "session mode" daemon, or when +libvirt is run inside an unprivileged container). -Provides a virtual LAN with NAT to the outside world. The virtual network has -DHCP & DNS services and will give the guest VM addresses starting from -``10.0.2.15``. The default router will be ``10.0.2.2`` and the DNS server will -be ``10.0.2.3``. This networking is the only option for unprivileged users who -need their VMs to have outgoing access. :since:`Since 3.8.0` it is possible to -override the default network address by including an ``ip`` element specifying -an IPv4 address in its one mandatory attribute, ``address``. Optionally, a -second ``ip`` element with a ``family`` attribute set to "ipv6" can be specified -to add an IPv6 address to the interface. ``address``. Optionally, address -``prefix`` can be specified. +By default, this user proxy is done with QEMU's internal SLIRP driver +which has DHCP & DNS services that give the guest IP addresses +starting from ``10.0.2.15``, a default route of ``10.0.2.2`` and DNS +server of ``10.0.2.3``. :since:`Since 3.8.0` it is possible to override +the default network address by including an ``ip`` element specifying +an IPv4 address in its one mandatory attribute, +``address``. Optionally, a second ``ip`` element with a ``family`` +attribute set to "ipv6" can be specified to add an IPv6 address to the +interface. ``address``. Optionally, address ``prefix`` can be +specified. :: @@ -4795,6 +4801,71 @@ to add an IPv6 address to the interface. ``address``. Optionally, address </devices> ... +:since:`Since 9.0.0` an alternate backend implementation of the +``user`` interface type can be selected by setting the interface's +``<backend>`` subelement ``type`` attribute to ``passt``. In this +case, the passt transport (https://passt.top) is used. Similar to +SLIRP, passt has an internal DHCP server that provides a requesting +guest with one ipv4 and one ipv6 address; it then uses userspace +proxies and a separate network namespace to provide outgoing +UDP/TCP/ICMP sessions, and optionally redirect incoming traffic +destined for the host toward the guest instead. + +When the passt backend is used, the ``<backend>`` attribute +``logFile`` can be used to tell the passt process for this interface +where to write its message log, and the ``<backend>`` attribute +``upstream`` can tell it to restrict upstream traffic to a particular +host interface. + +Additionally, when passt is used, multiple ``<portForward>`` elements +can be added to forward incoming network traffic for the host to this +guest interface. Each ``<portForward>`` must have a ``proto`` +attribute (set to ``tcp`` or ``udp``) and optional original +``address`` (if not specified, then all incoming sessions to any host +IP for the given proto/port(s) will be forwarded to the guest). + +The decision of which ports to forward is described with zero or more +``<range>`` subelements of ``<portForward>`` (if there is no +``<range>`` then **all** ports for the given proto/address will be +forwarded). Each ``<range>`` has a ``start`` and optional ``end`` +attribute. If ``end`` is omitted then a single port will be forwarded, +otherwise all ports between ``start`` and ``end`` (inclusive) will be +forwarded. If the port number(s) should remain unmodified as the +session is forwarded, no further options are needed, but if the guest +is expecting the sessions on a different port, then this should be +specified with the ``to`` attribute of ``<range>`` - the port number +of each forwarded session in the range will be offeset by "``to`` - +``start``". A ``<range>`` element can also be used to specify a range +of ports that should **not** be forwarded. This is done by setting the +range's ``exclude`` attribute to ``yes``. This may not seem very +useful, but can be when it is desirable to forward a long range of +ports **with the exception of some subset**. + +:: + + ... + <devices> + ... + <interface type='user'> + <backend type='passt' logFile='/var/log/passt.log' upstream='eth0'/> + <mac address="00:11:22:33:44:55"/> + <ip family='ipv4' address='172.17.2.0' prefix='24'/> + <ip family='ipv6' address='2001:db8:ac10:fd01::' prefix='64'/> + <portForward proto='tcp' address='2001:db8:ac10:fd01::1:10' start='2022'> + <port start='22'/> + </portForward> + <portForward proto='udp' address='1.2.3.4' start='5000' end='5020'> + <port start='6000' end='6020'/> + </portForward> + <portForward exclude='yes' proto='tcp' address='1.2.3.4' start='5010' end='5015'/> + <portForward proto='tcp' start='80'/> + <portForward proto='tcp' start='443'> + <port start='344'/> + </portForward> + </interface> + </devices> + ... + Generic ethernet connection ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 9502f2ebab..19252b28c5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -632,6 +632,19 @@ VIR_ENUM_IMPL(virDomainNetInterfaceLinkState, "down", ); +VIR_ENUM_IMPL(virDomainNetBackend, + VIR_DOMAIN_NET_BACKEND_LAST, + "default", + "passt", +); + +VIR_ENUM_IMPL(virDomainNetProto, + VIR_DOMAIN_NET_PROTO_LAST, + "none", + "tcp", + "udp", +); + VIR_ENUM_IMPL(virDomainChrDeviceState, VIR_DOMAIN_CHR_DEVICE_STATE_LAST, "default", @@ -2602,10 +2615,25 @@ virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming) g_free(teaming); } +void +virDomainNetPortForwardFree(virDomainNetPortForward *pf) +{ + size_t i; + + if (pf) + g_free(pf->dev); + + for (i = 0; i < pf->nRanges; i++) + g_free(pf->ranges[i]); + + g_free(pf); +} void virDomainNetDefFree(virDomainNetDef *def) { + size_t i; + if (!def) return; @@ -2663,6 +2691,8 @@ virDomainNetDefFree(virDomainNetDef *def) g_free(def->backend.tap); g_free(def->backend.vhost); + g_free(def->backend.logFile); + g_free(def->backend.upstream); virDomainNetTeamingInfoFree(def->teaming); g_free(def->virtPortProfile); g_free(def->script); @@ -2684,6 +2714,10 @@ virDomainNetDefFree(virDomainNetDef *def) virNetDevBandwidthFree(def->bandwidth); virNetDevVlanClear(&def->vlan); + for (i = 0; i < def->nPortForwards; i++) + virDomainNetPortForwardFree(def->portForwards[i]); + g_free(def->portForwards); + virObjectUnref(def->privateData); g_free(def); } @@ -8977,6 +9011,14 @@ virDomainNetBackendParseXML(xmlNodePtr node, g_autofree char *tap = virXMLPropString(node, "tap"); g_autofree char *vhost = virXMLPropString(node, "vhost"); + if (virXMLPropEnum(node, "type", virDomainNetBackendTypeFromString, + VIR_XML_PROP_NONZERO, &def->backend.type) < 0) { + return -1; + } + + def->backend.logFile = virXMLPropString(node, "logFile"); + def->backend.upstream = virXMLPropString(node, "upstream"); + if (tap) def->backend.tap = virFileSanitizePath(tap); @@ -8990,6 +9032,122 @@ virDomainNetBackendParseXML(xmlNodePtr node, } +static virDomainNetPortForwardRange * +virDomainNetPortForwardRangeParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt) + g_autofree virDomainNetPortForwardRange *def = g_new0(virDomainNetPortForwardRange, 1); + + ctxt->node = node; + + if (virXMLPropUInt(node, "start", 10, + VIR_XML_PROP_NONZERO, &def->start) < 0) { + return NULL; + } + if (virXMLPropUInt(node, "end", 10, + VIR_XML_PROP_NONZERO, &def->end) < 0) { + return NULL; + } + if (virXMLPropUInt(node, "to", 10, + VIR_XML_PROP_NONZERO, &def->to) < 0) { + return NULL; + } + if (virXMLPropTristateBool(node, "exclude", VIR_XML_PROP_NONE, + &def->exclude) < 0) { + return NULL; + } + + return g_steal_pointer(&def); +} + + +static int +virDomainNetPortForwardRangesParseXML(virDomainNetPortForward *def, + xmlXPathContextPtr ctxt) +{ + int nRanges; + g_autofree xmlNodePtr *ranges = NULL; + size_t i; + + if ((nRanges = virXPathNodeSet("./range", + ctxt, &ranges)) <= 0) { + return nRanges; + } + + def->ranges = g_new0(virDomainNetPortForwardRange *, nRanges); + + for (i = 0; i < nRanges; i++) { + g_autofree virDomainNetPortForwardRange *range = NULL; + + if (!(range = virDomainNetPortForwardRangeParseXML(ranges[i], ctxt))) { + return -1; + } + def->ranges[def->nRanges++] = g_steal_pointer(&range); + } + return 0; +} + + +static virDomainNetPortForward * +virDomainNetPortForwardDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt) + g_autofree char *address = NULL; + g_autoptr(virDomainNetPortForward) def = g_new0(virDomainNetPortForward, 1); + + ctxt->node = node; + + if (virXMLPropEnum(node, "proto", virDomainNetProtoTypeFromString, + VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO, + &def->proto) < 0) { + return NULL; + } + + address = virXMLPropString(node, "address"); + if (address && virSocketAddrParse(&def->address, address, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid address '%s' in <portForward>"), address); + return NULL; + } + + def->dev = virXMLPropString(node, "dev"); + + if (virDomainNetPortForwardRangesParseXML(def, ctxt) < 0) + return NULL; + + return g_steal_pointer(&def); +} + + +static int +virDomainNetPortForwardsParseXML(virDomainNetDef *def, + xmlXPathContextPtr ctxt) +{ + int nPortForwards; + g_autofree xmlNodePtr *portForwards = NULL; + size_t i; + + if ((nPortForwards = virXPathNodeSet("./portForward", + ctxt, &portForwards)) <= 0) { + return nPortForwards; + } + + def->portForwards = g_new0(virDomainNetPortForward *, nPortForwards); + + for (i = 0; i < nPortForwards; i++) { + g_autoptr(virDomainNetPortForward) pf = NULL; + + if (!(pf = virDomainNetPortForwardDefParseXML(portForwards[i], ctxt))) { + return -1; + } + def->portForwards[def->nPortForwards++] = g_steal_pointer(&pf); + } + return 0; +} + + static int virDomainNetDefParseXMLRequireSource(virDomainNetDef *def, xmlNodePtr source_node) @@ -9381,6 +9539,9 @@ virDomainNetDefParseXML(virDomainXMLOption *xmlopt, ctxt, &def->guestIP) < 0) return NULL; + if (virDomainNetPortForwardsParseXML(def, ctxt) < 0) + return NULL; + if (def->managed_tap != VIR_TRISTATE_BOOL_NO && def->ifname && (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) && (STRPREFIX(def->ifname, VIR_NET_GENERATED_VNET_PREFIX) || @@ -23274,17 +23435,91 @@ static void virDomainNetBackendFormat(virBuffer *buf, virDomainNetBackend *backend) { - - if (!(backend->tap || backend->vhost)) + if (!(backend->type || backend->tap || backend->vhost + || backend->logFile || backend->upstream)) { return; + } virBufferAddLit(buf, "<backend"); + if (backend->type) + virBufferAsprintf(buf, " type='%s'", virDomainNetBackendTypeToString(backend->type)); virBufferEscapeString(buf, " tap='%s'", backend->tap); virBufferEscapeString(buf, " vhost='%s'", backend->vhost); + virBufferEscapeString(buf, " logFile='%s'", backend->logFile); + virBufferEscapeString(buf, " upstream='%s'", backend->upstream); virBufferAddLit(buf, "/>\n"); } +static void +virDomainNetPortForwardRangesFormat(virBuffer *buf, + virDomainNetPortForward *def) +{ + size_t i; + + for (i = 0; i < def->nRanges; i++) { + virDomainNetPortForwardRange *range = def->ranges[i]; + + virBufferAddLit(buf, "<range"); + + if (range->start) { + virBufferAsprintf(buf, " start='%u'", range->start); + if (range->end) + virBufferAsprintf(buf, " end='%u'", range->end); + if (range->to) + virBufferAsprintf(buf, " to='%u'", range->to); + } + + if (range->exclude) { + virBufferAsprintf(buf, " exclude='%s'", + virTristateBoolTypeToString(range->exclude)); + } + + virBufferAddLit(buf, "/>\n"); + } +} + + +static int +virDomainNetPortForwardsFormat(virBuffer *buf, + virDomainNetDef *def) +{ + size_t i; + + if (!def->nPortForwards) + return 0; + + for (i = 0; i < def->nPortForwards; i++) { + virDomainNetPortForward *pf = def->portForwards[i]; + + virBufferAddLit(buf, "<portForward"); + virBufferAsprintf(buf, " proto='%s'", + virDomainNetProtoTypeToString(pf->proto)); + if (VIR_SOCKET_ADDR_VALID(&pf->address)) { + g_autofree char *ipStr = virSocketAddrFormat(&pf->address); + + if (!ipStr) + return -1; + + virBufferAsprintf(buf, " address='%s'", ipStr); + } + virBufferEscapeString(buf, " dev='%s'", pf->dev); + + if (pf->nRanges == 0) { + virBufferAddLit(buf, "/>\n"); + } else { + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + virDomainNetPortForwardRangesFormat(buf, pf); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</portForward>\n"); + } + } + + return 0; +} + + int virDomainNetDefFormat(virBuffer *buf, virDomainNetDef *def, @@ -23533,6 +23768,9 @@ virDomainNetDefFormat(virBuffer *buf, if (virDomainNetIPInfoFormat(buf, &def->guestIP) < 0) return -1; + if (virDomainNetPortForwardsFormat(buf, def) < 0) + return -1; + virBufferEscapeString(buf, "<script path='%s'/>\n", def->script); virBufferEscapeString(buf, "<downscript path='%s'/>\n", diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index e57e70866a..65fa9cf6e4 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1023,6 +1023,21 @@ typedef enum { VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST } virDomainNetInterfaceLinkState; +typedef enum { + VIR_DOMAIN_NET_BACKEND_DEFAULT = 0, + VIR_DOMAIN_NET_BACKEND_PASST, + + VIR_DOMAIN_NET_BACKEND_LAST +} virDomainNetBackendType; + +typedef enum { + VIR_DOMAIN_NET_PROTO_NONE = 0, + VIR_DOMAIN_NET_PROTO_TCP, + VIR_DOMAIN_NET_PROTO_UDP, + + VIR_DOMAIN_NET_PROTO_LAST +} virDomainNetProto; + /* Config that was actually used to bring up interface, after * resolving network reference. This is private data, only used within * libvirt, but still must maintain backward compatibility, because @@ -1052,8 +1067,27 @@ struct _virDomainActualNetDef { }; 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 */ + char *upstream; /* host interface to use for traffic egress */ +}; + +struct _virDomainNetPortForwardRange { + unsigned int start; /* original dst port range start */ + unsigned int end; /* range end (0 for "single port") */ + unsigned int to; /* start of range to forward to (0 for "unchanged") */ + virTristateBool exclude; /* true if this is a range to *not* forward */ +}; + +struct _virDomainNetPortForward { + char *dev; /* host interface of incoming traffic */ + virDomainNetProto proto; /* tcp/udp */ + virSocketAddr address; /* original dst address (empty = wildcard) */ + size_t nRanges; + virDomainNetPortForwardRange **ranges; /* list of ranges to forward */ }; /* Stores the virtual network interface configuration */ @@ -1159,6 +1193,8 @@ struct _virDomainNetDef { char *ifname_guest_actual; char *ifname_guest; virNetDevIPInfo guestIP; + size_t nPortForwards; + virDomainNetPortForward **portForwards; virDomainDeviceInfo info; char *filter; GHashTable *filterparams; @@ -3440,6 +3476,8 @@ void virDomainVsockDefFree(virDomainVsockDef *vsock); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainVsockDef, virDomainVsockDefFree); void virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetTeamingInfo, virDomainNetTeamingInfoFree); +void virDomainNetPortForwardFree(virDomainNetPortForward *pf); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetPortForward, virDomainNetPortForwardFree); void virDomainNetDefFree(virDomainNetDef *def); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetDef, virDomainNetDefFree); void virDomainSmartcardDefFree(virDomainSmartcardDef *def); @@ -4027,6 +4065,8 @@ VIR_ENUM_DECL(virDomainNetVirtioTxMode); VIR_ENUM_DECL(virDomainNetMacType); VIR_ENUM_DECL(virDomainNetTeaming); VIR_ENUM_DECL(virDomainNetInterfaceLinkState); +VIR_ENUM_DECL(virDomainNetBackend); +VIR_ENUM_DECL(virDomainNetProto); VIR_ENUM_DECL(virDomainNetModel); VIR_ENUM_DECL(virDomainChrDevice); VIR_ENUM_DECL(virDomainChrChannelTarget); diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c index 95b8d9b419..48a701bf93 100644 --- a/src/conf/domain_validate.c +++ b/src/conf/domain_validate.c @@ -2134,6 +2134,14 @@ virDomainNetDefValidate(const virDomainNetDef *net) return -1; } + if (net->type != VIR_DOMAIN_NET_TYPE_USER) { + if (net->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("\"<backend type='passt'/>\" can only be used with \"<interface type='user'>\"")); + return -1; + } + } + switch (net->type) { case VIR_DOMAIN_NET_TYPE_VHOSTUSER: if (!virDomainNetIsVirtioModel(net)) { @@ -2150,6 +2158,29 @@ virDomainNetDefValidate(const virDomainNetDef *net) } break; + case VIR_DOMAIN_NET_TYPE_USER: + if (net->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) { + size_t p; + + for (p = 0; p < net->nPortForwards; p++) { + size_t r; + virDomainNetPortForward *pf = net->portForwards[p]; + + for (r = 0; r < pf->nRanges; r++) { + virDomainNetPortForwardRange *range = pf->ranges[r]; + + if (!range->start + && (range->end || range->to + || range->exclude != VIR_TRISTATE_BOOL_ABSENT)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("<portForward> <range> requires 'start' attribute if 'end', 'to', or 'exclude' is specified")); + return -1; + } + } + } + } + break; + case VIR_DOMAIN_NET_TYPE_NETWORK: case VIR_DOMAIN_NET_TYPE_VDPA: case VIR_DOMAIN_NET_TYPE_BRIDGE: @@ -2162,7 +2193,6 @@ virDomainNetDefValidate(const virDomainNetDef *net) case VIR_DOMAIN_NET_TYPE_HOSTDEV: case VIR_DOMAIN_NET_TYPE_VDS: case VIR_DOMAIN_NET_TYPE_ETHERNET: - case VIR_DOMAIN_NET_TYPE_USER: case VIR_DOMAIN_NET_TYPE_NULL: case VIR_DOMAIN_NET_TYPE_LAST: break; diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h index 7bd9aa8e0a..adb2496cba 100644 --- a/src/conf/virconftypes.h +++ b/src/conf/virconftypes.h @@ -174,6 +174,10 @@ typedef struct _virDomainNVRAMDef virDomainNVRAMDef; typedef struct _virDomainNetBackend virDomainNetBackend; +typedef struct _virDomainNetPortForwardRange virDomainNetPortForwardRange; + +typedef struct _virDomainNetPortForward virDomainNetPortForward; + typedef struct _virDomainNetDef virDomainNetDef; typedef struct _virDomainNetTeamingInfo virDomainNetTeamingInfo; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index ae746a2d51..6a0c1d0972 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -551,6 +551,7 @@ virDomainNetIsVirtioModel; virDomainNetModelTypeFromString; virDomainNetModelTypeToString; virDomainNetNotifyActualDevice; +virDomainNetPortForwardFree; virDomainNetReleaseActualDevice; virDomainNetRemove; virDomainNetRemoveByObj; diff --git a/tests/qemuxml2xmloutdata/net-user-passt.xml b/tests/qemuxml2xmloutdata/net-user-passt.xml new file mode 120000 index 0000000000..cfbc023ada --- /dev/null +++ b/tests/qemuxml2xmloutdata/net-user-passt.xml @@ -0,0 +1 @@ +../qemuxml2argvdata/net-user-passt.xml \ No newline at end of file diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index e13da8bd2c..c9cc3416d5 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -459,6 +459,7 @@ mymain(void) DO_TEST_NOCAPS("net-vhostuser"); DO_TEST_NOCAPS("net-user"); DO_TEST_NOCAPS("net-user-addr"); + DO_TEST_NOCAPS("net-user-passt"); DO_TEST_NOCAPS("net-virtio"); DO_TEST_NOCAPS("net-virtio-device"); DO_TEST_NOCAPS("net-virtio-disable-offloads"); -- 2.38.1