</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.
::
</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
^^^^^^^^^^^^^^^^^^^^^^^^^^^
"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",
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
virDomainNetDefFree(virDomainNetDef *def)
{
+ size_t i;
+
if (!def)
return;
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);
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);
}
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);
}
+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)
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) ||
virDomainNetBackendFormat(virBuffer *buf,
virDomainNetBackend *backend)
{
+ g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
- if (!(backend->tap || backend->vhost))
- return;
+ if (backend->type) {
+ 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);
- virBufferEscapeString(buf, " vhost='%s'", backend->vhost);
- 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];
+ 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;
}
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",
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
};
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 */
char *ifname_guest_actual;
char *ifname_guest;
virNetDevIPInfo guestIP;
+ size_t nPortForwards;
+ virDomainNetPortForward **portForwards;
virDomainDeviceInfo info;
char *filter;
GHashTable *filterparams;
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);
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);
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) {
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
if (!virDomainNetIsVirtioModel(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",
+ _("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_VDPA:
case VIR_DOMAIN_NET_TYPE_BRIDGE:
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;
typedef struct _virDomainNetBackend virDomainNetBackend;
+typedef struct _virDomainNetPortForwardRange virDomainNetPortForwardRange;
+
+typedef struct _virDomainNetPortForward virDomainNetPortForward;
+
typedef struct _virDomainNetDef virDomainNetDef;
typedef struct _virDomainNetTeamingInfo virDomainNetTeamingInfo;
virDomainNetModelTypeFromString;
virDomainNetModelTypeToString;
virDomainNetNotifyActualDevice;
+virDomainNetPortForwardFree;
virDomainNetReleaseActualDevice;
virDomainNetRemove;
virDomainNetRemoveByObj;
--- /dev/null
+../qemuxml2argvdata/net-user-passt.xml
\ No newline at end of file
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");