[libvirt PATCH 5/9] conf: parse/format passt-related XML additions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>
---
 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




[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux