From a8ee7ae301d432609718b97eb9bacc077bea2966 Mon Sep 17 00:00:00 2001 From: Laine Stump Date: Fri, 11 Nov 2022 14:43:45 -0500 Subject: [PATCH] conf: parse/format passt-related XML additions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 when the interface type='user'), we also support passt's --log-file and --interface options (via the 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 subelement of . Here is an example of the config for a network interface that uses passt to connect: 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 Reviewed-by: Ján Tomko --- docs/formatdomain.rst | 95 +++++++- src/conf/domain_conf.c | 239 +++++++++++++++++++- 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, 394 insertions(+), 19 deletions(-) create mode 120000 tests/qemuxml2xmloutdata/net-user-passt.xml diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 109a2ac45a..2c44f77ab6 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -4775,19 +4775,25 @@ to the interface. ... -Userspace SLIRP stack -^^^^^^^^^^^^^^^^^^^^^ +Userspace (SLIRP or passt) connection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -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. +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). + +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. :: @@ -4803,6 +4809,71 @@ to add an IPv6 address to the interface. ``address``. Optionally, address ... +:since:`Since 9.0.0` an alternate backend implementation of the +``user`` interface type can be selected by setting the interface's +```` 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 ```` attribute +``logFile`` can be used to tell the passt process for this interface +where to write its message log, and the ```` attribute +``upstream`` can tell it to restrict upstream traffic to a particular +host interface. + +Additionally, when passt is used, multiple ```` elements +can be added to forward incoming network traffic for the host to this +guest interface. Each ```` 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 +```` subelements of ```` (if there is no +```` then **all** ports for the given proto/address will be +forwarded). Each ```` 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 ```` - the port number +of each forwarded session in the range will be offeset by "``to`` - +``start``". A ```` 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**. + +:: + + ... + + ... + + + + + + + + + + + + + + + + + + + ... + Generic ethernet connection ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 2ebbfda676..598e23b005 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", @@ -2611,10 +2624,26 @@ 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->ranges); + g_free(pf); +} void virDomainNetDefFree(virDomainNetDef *def) { + size_t i; + if (!def) return; @@ -2672,6 +2701,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); @@ -2693,6 +2724,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); } @@ -8995,6 +9030,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); @@ -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 "), 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) @@ -9398,6 +9555,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) || @@ -23316,14 +23476,78 @@ static void 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, "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; } @@ -23575,6 +23799,9 @@ virDomainNetDefFormat(virBuffer *buf, if (virDomainNetIPInfoFormat(buf, &def->guestIP) < 0) return -1; + if (virDomainNetPortForwardsFormat(buf, def) < 0) + return -1; + virBufferEscapeString(buf, "