conf: parse/format passt-related XML additions

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@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
This commit is contained in:
Laine Stump 2022-11-11 14:43:45 -05:00
parent 63fbe529fc
commit a8ee7ae301
8 changed files with 394 additions and 19 deletions

View File

@ -4775,19 +4775,25 @@ to the interface.
</devices> </devices>
... ...
Userspace SLIRP stack Userspace (SLIRP or passt) connection
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Provides a virtual LAN with NAT to the outside world. The virtual network has The ``user`` type connects the guest interface to the outside via a
DHCP & DNS services and will give the guest VM addresses starting from transparent userspace proxy that doesn't require any special system
``10.0.2.15``. The default router will be ``10.0.2.2`` and the DNS server will privileges, making it usable in cases when libvirt itself is running
be ``10.0.2.3``. This networking is the only option for unprivileged users who with no privileges (e.g. libvirt's "session mode" daemon, or when
need their VMs to have outgoing access. :since:`Since 3.8.0` it is possible to libvirt is run inside an unprivileged container).
override the default network address by including an ``ip`` element specifying
an IPv4 address in its one mandatory attribute, ``address``. Optionally, a By default, this user proxy is done with QEMU's internal SLIRP driver
second ``ip`` element with a ``family`` attribute set to "ipv6" can be specified which has DHCP & DNS services that give the guest IP addresses
to add an IPv6 address to the interface. ``address``. Optionally, address starting from ``10.0.2.15``, a default route of ``10.0.2.2`` and DNS
``prefix`` can be specified. 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.
:: ::
@ -4803,6 +4809,71 @@ to add an IPv6 address to the interface. ``address``. Optionally, address
</devices> </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 Generic ethernet connection
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -632,6 +632,19 @@ VIR_ENUM_IMPL(virDomainNetInterfaceLinkState,
"down", "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_ENUM_IMPL(virDomainChrDeviceState,
VIR_DOMAIN_CHR_DEVICE_STATE_LAST, VIR_DOMAIN_CHR_DEVICE_STATE_LAST,
"default", "default",
@ -2611,10 +2624,26 @@ virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming)
g_free(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->ranges);
g_free(pf);
}
void void
virDomainNetDefFree(virDomainNetDef *def) virDomainNetDefFree(virDomainNetDef *def)
{ {
size_t i;
if (!def) if (!def)
return; return;
@ -2672,6 +2701,8 @@ virDomainNetDefFree(virDomainNetDef *def)
g_free(def->backend.tap); g_free(def->backend.tap);
g_free(def->backend.vhost); g_free(def->backend.vhost);
g_free(def->backend.logFile);
g_free(def->backend.upstream);
virDomainNetTeamingInfoFree(def->teaming); virDomainNetTeamingInfoFree(def->teaming);
g_free(def->virtPortProfile); g_free(def->virtPortProfile);
g_free(def->script); g_free(def->script);
@ -2693,6 +2724,10 @@ virDomainNetDefFree(virDomainNetDef *def)
virNetDevBandwidthFree(def->bandwidth); virNetDevBandwidthFree(def->bandwidth);
virNetDevVlanClear(&def->vlan); virNetDevVlanClear(&def->vlan);
for (i = 0; i < def->nPortForwards; i++)
virDomainNetPortForwardFree(def->portForwards[i]);
g_free(def->portForwards);
virObjectUnref(def->privateData); virObjectUnref(def->privateData);
g_free(def); g_free(def);
} }
@ -8995,6 +9030,14 @@ virDomainNetBackendParseXML(xmlNodePtr node,
g_autofree char *tap = virXMLPropString(node, "tap"); g_autofree char *tap = virXMLPropString(node, "tap");
g_autofree char *vhost = virXMLPropString(node, "vhost"); 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) if (tap)
def->backend.tap = virFileSanitizePath(tap); def->backend.tap = virFileSanitizePath(tap);
@ -9008,6 +9051,120 @@ 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 static int
virDomainNetDefParseXMLRequireSource(virDomainNetDef *def, virDomainNetDefParseXMLRequireSource(virDomainNetDef *def,
xmlNodePtr source_node) xmlNodePtr source_node)
@ -9398,6 +9555,9 @@ virDomainNetDefParseXML(virDomainXMLOption *xmlopt,
ctxt, &def->guestIP) < 0) ctxt, &def->guestIP) < 0)
return NULL; return NULL;
if (virDomainNetPortForwardsParseXML(def, ctxt) < 0)
return NULL;
if (def->managed_tap != VIR_TRISTATE_BOOL_NO && def->ifname && if (def->managed_tap != VIR_TRISTATE_BOOL_NO && def->ifname &&
(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) && (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) &&
(STRPREFIX(def->ifname, VIR_NET_GENERATED_VNET_PREFIX) || (STRPREFIX(def->ifname, VIR_NET_GENERATED_VNET_PREFIX) ||
@ -23316,14 +23476,78 @@ static void
virDomainNetBackendFormat(virBuffer *buf, virDomainNetBackendFormat(virBuffer *buf,
virDomainNetBackend *backend) virDomainNetBackend *backend)
{ {
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
if (!(backend->tap || backend->vhost)) if (backend->type) {
return; virBufferAsprintf(&attrBuf, " type='%s'",
virDomainNetBackendTypeToString(backend->type));
}
virBufferEscapeString(&attrBuf, " tap='%s'", backend->tap);
virBufferEscapeString(&attrBuf, " vhost='%s'", backend->vhost);
virBufferEscapeString(&attrBuf, " logFile='%s'", backend->logFile);
virBufferEscapeString(&attrBuf, " upstream='%s'", backend->upstream);
virXMLFormatElement(buf, "backend", &attrBuf, NULL);
}
virBufferAddLit(buf, "<backend");
virBufferEscapeString(buf, " tap='%s'", backend->tap); static void
virBufferEscapeString(buf, " vhost='%s'", backend->vhost); virDomainNetPortForwardRangesFormat(virBuffer *buf,
virBufferAddLit(buf, "/>\n"); virDomainNetPortForward *def)
{
size_t i;
for (i = 0; i < def->nRanges; i++) {
virDomainNetPortForwardRange *range = def->ranges[i];
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
if (range->start) {
virBufferAsprintf(&attrBuf, " start='%u'", range->start);
if (range->end)
virBufferAsprintf(&attrBuf, " end='%u'", range->end);
if (range->to)
virBufferAsprintf(&attrBuf, " to='%u'", range->to);
}
if (range->exclude) {
virBufferAsprintf(&attrBuf, " exclude='%s'",
virTristateBoolTypeToString(range->exclude));
}
virXMLFormatElement(buf, "range", &attrBuf, NULL);
}
}
static int
virDomainNetPortForwardsFormat(virBuffer *buf,
virDomainNetDef *def)
{
size_t i;
if (!def->nPortForwards)
return 0;
for (i = 0; i < def->nPortForwards; i++) {
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
virDomainNetPortForward *pf = def->portForwards[i];
virBufferAsprintf(&attrBuf, " proto='%s'",
virDomainNetProtoTypeToString(pf->proto));
if (VIR_SOCKET_ADDR_VALID(&pf->address)) {
g_autofree char *ipStr = virSocketAddrFormat(&pf->address);
if (!ipStr)
return -1;
virBufferAsprintf(&attrBuf, " address='%s'", ipStr);
}
virBufferEscapeString(&attrBuf, " dev='%s'", pf->dev);
virDomainNetPortForwardRangesFormat(&childBuf, pf);
virXMLFormatElementEmpty(buf, "portForward", &attrBuf, &childBuf);
}
return 0;
} }
@ -23575,6 +23799,9 @@ virDomainNetDefFormat(virBuffer *buf,
if (virDomainNetIPInfoFormat(buf, &def->guestIP) < 0) if (virDomainNetIPInfoFormat(buf, &def->guestIP) < 0)
return -1; return -1;
if (virDomainNetPortForwardsFormat(buf, def) < 0)
return -1;
virBufferEscapeString(buf, "<script path='%s'/>\n", virBufferEscapeString(buf, "<script path='%s'/>\n",
def->script); def->script);
virBufferEscapeString(buf, "<downscript path='%s'/>\n", virBufferEscapeString(buf, "<downscript path='%s'/>\n",

View File

@ -1023,6 +1023,21 @@ typedef enum {
VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST
} virDomainNetInterfaceLinkState; } 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 /* Config that was actually used to bring up interface, after
* resolving network reference. This is private data, only used within * resolving network reference. This is private data, only used within
* libvirt, but still must maintain backward compatibility, because * libvirt, but still must maintain backward compatibility, because
@ -1052,8 +1067,27 @@ struct _virDomainActualNetDef {
}; };
struct _virDomainNetBackend { struct _virDomainNetBackend {
virDomainNetBackendType type;
char *tap; char *tap;
char *vhost; 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 */ /* Stores the virtual network interface configuration */
@ -1159,6 +1193,8 @@ struct _virDomainNetDef {
char *ifname_guest_actual; char *ifname_guest_actual;
char *ifname_guest; char *ifname_guest;
virNetDevIPInfo guestIP; virNetDevIPInfo guestIP;
size_t nPortForwards;
virDomainNetPortForward **portForwards;
virDomainDeviceInfo info; virDomainDeviceInfo info;
char *filter; char *filter;
GHashTable *filterparams; GHashTable *filterparams;
@ -3470,6 +3506,8 @@ void virDomainVsockDefFree(virDomainVsockDef *vsock);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainVsockDef, virDomainVsockDefFree); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainVsockDef, virDomainVsockDefFree);
void virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming); void virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetTeamingInfo, virDomainNetTeamingInfoFree); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetTeamingInfo, virDomainNetTeamingInfoFree);
void virDomainNetPortForwardFree(virDomainNetPortForward *pf);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetPortForward, virDomainNetPortForwardFree);
void virDomainNetDefFree(virDomainNetDef *def); void virDomainNetDefFree(virDomainNetDef *def);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetDef, virDomainNetDefFree); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetDef, virDomainNetDefFree);
void virDomainSmartcardDefFree(virDomainSmartcardDef *def); void virDomainSmartcardDefFree(virDomainSmartcardDef *def);
@ -4058,6 +4096,8 @@ VIR_ENUM_DECL(virDomainNetVirtioTxMode);
VIR_ENUM_DECL(virDomainNetMacType); VIR_ENUM_DECL(virDomainNetMacType);
VIR_ENUM_DECL(virDomainNetTeaming); VIR_ENUM_DECL(virDomainNetTeaming);
VIR_ENUM_DECL(virDomainNetInterfaceLinkState); VIR_ENUM_DECL(virDomainNetInterfaceLinkState);
VIR_ENUM_DECL(virDomainNetBackend);
VIR_ENUM_DECL(virDomainNetProto);
VIR_ENUM_DECL(virDomainNetModel); VIR_ENUM_DECL(virDomainNetModel);
VIR_ENUM_DECL(virDomainChrDevice); VIR_ENUM_DECL(virDomainChrDevice);
VIR_ENUM_DECL(virDomainChrChannelTarget); VIR_ENUM_DECL(virDomainChrChannelTarget);

View File

@ -2134,6 +2134,14 @@ virDomainNetDefValidate(const virDomainNetDef *net)
return -1; 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",
_("The 'passt' backend can only be used with interface type='user'"));
return -1;
}
}
switch (net->type) { switch (net->type) {
case VIR_DOMAIN_NET_TYPE_VHOSTUSER: case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
if (!virDomainNetIsVirtioModel(net)) { if (!virDomainNetIsVirtioModel(net)) {
@ -2150,6 +2158,29 @@ virDomainNetDefValidate(const virDomainNetDef *net)
} }
break; 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",
_("The 'range' of a 'portForward' requires 'start' attribute if 'end', 'to', or 'exclude' is specified"));
return -1;
}
}
}
}
break;
case VIR_DOMAIN_NET_TYPE_NETWORK: case VIR_DOMAIN_NET_TYPE_NETWORK:
case VIR_DOMAIN_NET_TYPE_VDPA: case VIR_DOMAIN_NET_TYPE_VDPA:
case VIR_DOMAIN_NET_TYPE_BRIDGE: 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_HOSTDEV:
case VIR_DOMAIN_NET_TYPE_VDS: case VIR_DOMAIN_NET_TYPE_VDS:
case VIR_DOMAIN_NET_TYPE_ETHERNET: case VIR_DOMAIN_NET_TYPE_ETHERNET:
case VIR_DOMAIN_NET_TYPE_USER:
case VIR_DOMAIN_NET_TYPE_NULL: case VIR_DOMAIN_NET_TYPE_NULL:
case VIR_DOMAIN_NET_TYPE_LAST: case VIR_DOMAIN_NET_TYPE_LAST:
break; break;

View File

@ -174,6 +174,10 @@ typedef struct _virDomainNVRAMDef virDomainNVRAMDef;
typedef struct _virDomainNetBackend virDomainNetBackend; typedef struct _virDomainNetBackend virDomainNetBackend;
typedef struct _virDomainNetPortForwardRange virDomainNetPortForwardRange;
typedef struct _virDomainNetPortForward virDomainNetPortForward;
typedef struct _virDomainNetDef virDomainNetDef; typedef struct _virDomainNetDef virDomainNetDef;
typedef struct _virDomainNetTeamingInfo virDomainNetTeamingInfo; typedef struct _virDomainNetTeamingInfo virDomainNetTeamingInfo;

View File

@ -551,6 +551,7 @@ virDomainNetIsVirtioModel;
virDomainNetModelTypeFromString; virDomainNetModelTypeFromString;
virDomainNetModelTypeToString; virDomainNetModelTypeToString;
virDomainNetNotifyActualDevice; virDomainNetNotifyActualDevice;
virDomainNetPortForwardFree;
virDomainNetReleaseActualDevice; virDomainNetReleaseActualDevice;
virDomainNetRemove; virDomainNetRemove;
virDomainNetRemoveByObj; virDomainNetRemoveByObj;

View File

@ -0,0 +1 @@
../qemuxml2argvdata/net-user-passt.xml

View File

@ -459,6 +459,7 @@ mymain(void)
DO_TEST_NOCAPS("net-vhostuser"); DO_TEST_NOCAPS("net-vhostuser");
DO_TEST_NOCAPS("net-user"); DO_TEST_NOCAPS("net-user");
DO_TEST_NOCAPS("net-user-addr"); DO_TEST_NOCAPS("net-user-addr");
DO_TEST_NOCAPS("net-user-passt");
DO_TEST_NOCAPS("net-virtio"); DO_TEST_NOCAPS("net-virtio");
DO_TEST_NOCAPS("net-virtio-device"); DO_TEST_NOCAPS("net-virtio-device");
DO_TEST_NOCAPS("net-virtio-disable-offloads"); DO_TEST_NOCAPS("net-virtio-disable-offloads");