From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTP id 2602D5A0268 for ; Mon, 9 Jan 2023 05:11:17 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1673237476; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6dNqT/B3ggZCDTo9Rc8f0tzItpzAX9eqFH0C9/PCSGA=; b=SrTtoreS3sKhlFohq45qTmcMsh2hDEH6cuKIsSS1OArtUFu9B38SnwYrJ1tOiuUxDS8snl IKdu/kgk4VW+ZGh0PpxM1tp98/7bLOIGY7S2mUBoiCuydgeMbW0mVIaJNILRVfQoFo0udy sipx+0PcGUFQQIMKelMLonsgRrsn3cA= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-195-mcMaNaLFNWiWdy1FB1345A-1; Sun, 08 Jan 2023 23:11:14 -0500 X-MC-Unique: mcMaNaLFNWiWdy1FB1345A-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 5E05F857D0D for ; Mon, 9 Jan 2023 04:11:14 +0000 (UTC) Received: from vhost3.router.laine.org (unknown [10.2.16.67]) by smtp.corp.redhat.com (Postfix) with ESMTP id 3777D1121314; Mon, 9 Jan 2023 04:11:14 +0000 (UTC) From: Laine Stump To: libvir-list@redhat.com Subject: [libvirt PATCH 5/9] conf: parse/format passt-related XML additions Date: Sun, 8 Jan 2023 23:11:08 -0500 Message-Id: <20230109041112.368790-6-laine@redhat.com> In-Reply-To: <20230109041112.368790-1-laine@redhat.com> References: <20230109041112.368790-1-laine@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: TFEKY6IZVQO2PTXBOG5N4WFVENMIH555 X-Message-ID-Hash: TFEKY6IZVQO2PTXBOG5N4WFVENMIH555 X-MailFrom: laine@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: sbrivio@redhat.com, passt-dev@passt.top X-Mailman-Version: 3.3.3 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- 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. ... -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 ... +: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 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 "), 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, "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, "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, "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, "\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, "