From e653f9b3ed1b60037e3bc661d53b3f9407243fc2 Mon Sep 17 00:00:00 2001 From: Stefano Brivio Date: Sat, 20 Mar 2021 07:22:09 +0100 Subject: [PATCH] passt: Add libvirt patch for qemu UNIX socket domain back-end ...and mention it in the README. While at it, remove useless escaping in the README, and fix indentation in the syslog message with the qemu command line example. Signed-off-by: Stefano Brivio --- README.md | 15 +- ...upport-for-UNIX-domain-socket-as-qem.patch | 423 ++++++++++++++++++ passt.c | 2 +- 3 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch diff --git a/README.md b/README.md index 48f4f6d..8ae54ec 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ and native Layer-4 sockets (TCP, UDP, ICMP/ICMPv6 echo) on a host. It doesn't require any capabilities or privileges, and it can be used as a simple replacement for Slirp. +![overview diagram](/builds/passt_overview.png) + - [General idea](#general-idea) - [Non-functional Targets](#non-functional-targets) - [Interfaces and Environment](#interfaces-and-environment) @@ -50,7 +52,7 @@ solution comes with a number of downsides: _passt_ implements a thinner layer between guest and host, that only implements what's strictly needed to pretend processes are running locally. A further, full TCP/IP stack is not necessarily needed. Some sort of TCP adaptation is needed, -however, as this layer runs without the `CAP\_NET\_RAW` capability: we can't +however, as this layer runs without the `CAP_NET_RAW` capability: we can't create raw IP sockets on the pod, and therefore need to map packets at Layer-2 to Layer-4 sockets offered by the host kernel. @@ -163,6 +165,17 @@ before _passt_ starts. qemu-system-x86_64 ... -net socket,connect=/tmp/passt.socket -net nic,model=virtio +* alternatively, you can use libvirt, with [this patch](https://passt.top/passt/tree/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch), + to start qemu (with the patch mentioned above), with this kind of network + interface configuration: + + + + + +
+ + * and that's it, you should now have TCP connections, UDP, and ICMP/ICMPv6 echo working from/to the guest for IPv4 and IPv6 diff --git a/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch b/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch new file mode 100644 index 0000000..c0e3337 --- /dev/null +++ b/libvirt/0001-conf-Introduce-support-for-UNIX-domain-socket-as-qem.patch @@ -0,0 +1,423 @@ +From 62e95cb4b5708eca7860139dffef22e65b29514c Mon Sep 17 00:00:00 2001 +From: Stefano Brivio +Date: Sat, 20 Mar 2021 07:16:15 +0100 +Subject: [PATCH] conf: Introduce support for UNIX domain socket as qemu netdev + back-end + +Since qemu [TODO], named UNIX domain sockets can be used instead of +TCP to establish a virtual network between VMs. + +The obvious difference compared with TCP is that we need pass a path +instead of address and port. + +Signed-off-by: Stefano Brivio +--- + docs/formatdomain.rst | 41 +++++++++++++++++++------ + docs/schemas/domaincommon.rng | 50 +++++++++++++++++++++++------- + src/conf/domain_conf.c | 58 +++++++++++++++++++++++++++-------- + src/conf/domain_conf.h | 13 +++++--- + src/qemu/qemu_command.c | 46 ++++++++++++++++++--------- + src/qemu/qemu_hotplug.c | 8 +++-- + 6 files changed, 160 insertions(+), 56 deletions(-) + +diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst +index 9392c80113..b5b642e91a 100644 +--- a/docs/formatdomain.rst ++++ b/docs/formatdomain.rst +@@ -4995,18 +4995,20 @@ must be from the multicast address block. + + ... + +-:anchor:`` ++:anchor:`` + +-TCP tunnel +-^^^^^^^^^^ ++TCP or UNIX domain socket tunnel ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++A stream-oriented client/server architecture provides a virtual network. One VM ++provides the server end of the network, all other VMS are configured as clients. ++All network traffic is routed between the VMs via the server. This mode is also ++available to unprivileged users. There is no default DNS or DHCP support and no ++outgoing network access. To provide outgoing network access, one of the VMs ++should have a 2nd NIC which is connected to one of the first 4 network types and ++do the appropriate routing. + +-A TCP client/server architecture provides a virtual network. One VM provides the +-server end of the network, all other VMS are configured as clients. All network +-traffic is routed between the VMs via the server. This mode is also available to +-unprivileged users. There is no default DNS or DHCP support and no outgoing +-network access. To provide outgoing network access, one of the VMs should have a +-2nd NIC which is connected to one of the first 4 network types and do the +-appropriate routing. ++TCP endpoints can be specified as follows: + + :: + +@@ -5024,6 +5026,25 @@ appropriate routing. + + ... + ++Named UNIX domain sockets can be specified as follows: ++:since:`Since 7.2.0, qemu` ++ ++:: ++ ++ ... ++ ++ ++ ++ ++ ++ ... ++ ++ ++ ++ ++ ++ ... ++ + :anchor:`` + + UDP unicast tunnel +diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng +index 1dbfc68f18..350d6969c0 100644 +--- a/docs/schemas/domaincommon.rng ++++ b/docs/schemas/domaincommon.rng +@@ -3125,10 +3125,7 @@ + + + +- +- mcast +- client +- ++ mcast + + + +@@ -3143,6 +3140,30 @@ + + + ++ ++ ++ client ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + udp +@@ -3174,14 +3195,21 @@ + + + +- +- +- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + +- +- +- +- ++ + + + +diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c +index 7671050134..55543c47ce 100644 +--- a/src/conf/domain_conf.c ++++ b/src/conf/domain_conf.c +@@ -2569,8 +2569,9 @@ virDomainNetDefFree(virDomainNetDefPtr def) + case VIR_DOMAIN_NET_TYPE_CLIENT: + case VIR_DOMAIN_NET_TYPE_MCAST: + case VIR_DOMAIN_NET_TYPE_UDP: +- g_free(def->data.socket.address); +- g_free(def->data.socket.localaddr); ++ g_free(def->data.socket.net.address); ++ g_free(def->data.socket.net.localaddr); ++ g_free(def->data.socket.path); + break; + + case VIR_DOMAIN_NET_TYPE_NETWORK: +@@ -10792,6 +10793,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + g_autofree char *downscript = NULL; + g_autofree char *address = NULL; + g_autofree char *port = NULL; ++ g_autofree char *path = NULL; + g_autofree char *localaddr = NULL; + g_autofree char *localport = NULL; + g_autofree char *model = NULL; +@@ -10935,7 +10937,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + " "), type); + goto error; + } +- } else if (!address && ++ } else if (!address && !path && + (def->type == VIR_DOMAIN_NET_TYPE_SERVER || + def->type == VIR_DOMAIN_NET_TYPE_CLIENT || + def->type == VIR_DOMAIN_NET_TYPE_MCAST || +@@ -10943,6 +10945,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + virXMLNodeNameEqual(cur, "source")) { + address = virXMLPropString(cur, "address"); + port = virXMLPropString(cur, "port"); ++ path = virXMLPropString(cur, "path"); + if (!localaddr && def->type == VIR_DOMAIN_NET_TYPE_UDP) { + xmlNodePtr tmpnode = ctxt->node; + ctxt->node = cur; +@@ -11186,6 +11189,27 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + + case VIR_DOMAIN_NET_TYPE_CLIENT: + case VIR_DOMAIN_NET_TYPE_SERVER: ++ if (path != NULL) { ++ if (port != NULL || address != NULL || ++ localport != NULL || localaddr != NULL) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _(" 'path' attribute " ++ "for socket interface cannot be specified " ++ "together with other attributes")); ++ goto error; ++ } ++ def->data.socket.path = g_steal_pointer(&path); ++ break; ++ } ++ ++ if (port == NULL) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("Neither 'port' nor 'path' attribute " ++ "specified with socket interface")); ++ goto error; ++ } ++ ++ G_GNUC_FALLTHROUGH; + case VIR_DOMAIN_NET_TYPE_MCAST: + case VIR_DOMAIN_NET_TYPE_UDP: + if (port == NULL) { +@@ -11194,7 +11218,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + "specified with socket interface")); + goto error; + } +- if (virStrToLong_i(port, NULL, 10, &def->data.socket.port) < 0) { ++ if (virStrToLong_i(port, NULL, 10, &def->data.socket.net.port) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot parse 'port' attribute " + "with socket interface")); +@@ -11211,7 +11235,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + goto error; + } + } else { +- def->data.socket.address = g_steal_pointer(&address); ++ def->data.socket.net.address = g_steal_pointer(&address); + } + + if (def->type != VIR_DOMAIN_NET_TYPE_UDP) +@@ -11223,7 +11247,8 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + "specified with socket interface")); + goto error; + } +- if (virStrToLong_i(localport, NULL, 10, &def->data.socket.localport) < 0) { ++ if (virStrToLong_i(localport, NULL, 10, ++ &def->data.socket.net.localport) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot parse 'port' attribute " + "with socket interface")); +@@ -11236,7 +11261,7 @@ virDomainNetDefParseXML(virDomainXMLOptionPtr xmlopt, + "specified with socket interface")); + goto error; + } else { +- def->data.socket.localaddr = g_steal_pointer(&localaddr); ++ def->data.socket.net.localaddr = g_steal_pointer(&localaddr); + } + break; + +@@ -26219,15 +26244,22 @@ virDomainNetDefFormat(virBufferPtr buf, + + case VIR_DOMAIN_NET_TYPE_SERVER: + case VIR_DOMAIN_NET_TYPE_CLIENT: ++ if (def->data.socket.path) { ++ virBufferAsprintf(buf, "data.socket.path); ++ sourceLines++; ++ break; ++ } ++ G_GNUC_FALLTHROUGH; + case VIR_DOMAIN_NET_TYPE_MCAST: + case VIR_DOMAIN_NET_TYPE_UDP: +- if (def->data.socket.address) { ++ if (def->data.socket.net.address) { + virBufferAsprintf(buf, "data.socket.address, +- def->data.socket.port); ++ def->data.socket.net.address, ++ def->data.socket.net.port); + } else { + virBufferAsprintf(buf, "data.socket.port); ++ def->data.socket.net.port); + } + sourceLines++; + +@@ -26239,8 +26271,8 @@ virDomainNetDefFormat(virBufferPtr buf, + virBufferAdjustIndent(buf, 2); + + virBufferAsprintf(buf, "\n", +- def->data.socket.localaddr, +- def->data.socket.localport); ++ def->data.socket.net.localaddr, ++ def->data.socket.net.localport); + virBufferAdjustIndent(buf, -2); + break; + +diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h +index 87bc7e8625..3cc0842eed 100644 +--- a/src/conf/domain_conf.h ++++ b/src/conf/domain_conf.h +@@ -1042,11 +1042,14 @@ struct _virDomainNetDef { + virDomainNetTeamingInfoPtr teaming; + union { + virDomainChrSourceDefPtr vhostuser; +- struct { +- char *address; +- int port; +- char *localaddr; +- int localport; ++ union { ++ struct { ++ char *address; ++ int port; ++ char *localaddr; ++ int localport; ++ } net; ++ char *path; + } socket; /* any of NET_CLIENT or NET_SERVER or NET_MCAST */ + struct { + char *name; +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 5717f7b98d..c1687e582e 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -3639,37 +3639,55 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + break; + + case VIR_DOMAIN_NET_TYPE_CLIENT: +- if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || +- virJSONValueObjectAppendStringPrintf(netprops, "connect", "%s:%d", +- net->data.socket.address, +- net->data.socket.port) < 0) ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0) ++ return NULL; ++ ++ if (net->data.socket.path != NULL) { ++ if (virJSONValueObjectAppendStringPrintf(netprops, "connect", "%s", ++ net->data.socket.path) < 0) ++ return NULL; ++ break; ++ } ++ ++ if (virJSONValueObjectAppendStringPrintf(netprops, "connect", "%s:%d", ++ net->data.socket.net.address, ++ net->data.socket.net.port) < 0) + return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_SERVER: +- if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || +- virJSONValueObjectAppendStringPrintf(netprops, "listen", "%s:%d", +- NULLSTR_EMPTY(net->data.socket.address), +- net->data.socket.port) < 0) ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0) ++ return NULL; ++ ++ if (net->data.socket.path != NULL) { ++ if (virJSONValueObjectAppendStringPrintf(netprops, "listen", "%s", ++ net->data.socket.path) < 0) ++ return NULL; ++ break; ++ } ++ ++ if (virJSONValueObjectAppendStringPrintf(netprops, "listen", "%s:%d", ++ NULLSTR_EMPTY(net->data.socket.net.address), ++ net->data.socket.net.port) < 0) + return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_MCAST: + if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || + virJSONValueObjectAppendStringPrintf(netprops, "mcast", "%s:%d", +- net->data.socket.address, +- net->data.socket.port) < 0) ++ net->data.socket.net.address, ++ net->data.socket.net.port) < 0) + return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_UDP: + if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || + virJSONValueObjectAppendStringPrintf(netprops, "udp", "%s:%d", +- net->data.socket.address, +- net->data.socket.port) < 0 || ++ net->data.socket.net.address, ++ net->data.socket.net.port) < 0 || + virJSONValueObjectAppendStringPrintf(netprops, "localaddr", "%s:%d", +- net->data.socket.localaddr, +- net->data.socket.localport) < 0) ++ net->data.socket.net.localaddr, ++ net->data.socket.net.localport) < 0) + return NULL; + break; + +diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c +index a66354426d..8ca86f9c53 100644 +--- a/src/qemu/qemu_hotplug.c ++++ b/src/qemu/qemu_hotplug.c +@@ -3752,9 +3752,11 @@ qemuDomainChangeNet(virQEMUDriverPtr driver, + case VIR_DOMAIN_NET_TYPE_CLIENT: + case VIR_DOMAIN_NET_TYPE_MCAST: + case VIR_DOMAIN_NET_TYPE_UDP: +- if (STRNEQ_NULLABLE(olddev->data.socket.address, +- newdev->data.socket.address) || +- olddev->data.socket.port != newdev->data.socket.port) { ++ if (STRNEQ_NULLABLE(olddev->data.socket.path, ++ newdev->data.socket.path) || ++ STRNEQ_NULLABLE(olddev->data.socket.net.address, ++ newdev->data.socket.net.address) || ++ olddev->data.socket.net.port != newdev->data.socket.net.port) { + needReconnect = true; + } + break; +-- +2.28.0 + diff --git a/passt.c b/passt.c index 51b76c9..8bc7098 100644 --- a/passt.c +++ b/passt.c @@ -591,7 +591,7 @@ listen: info("or directly qemu, patched with:"); info(" qemu/0001-net-Allow-also-UNIX-domain-sockets-to-be-used-as-net.patch"); info("as follows:"); - info("kvm ... -net socket,connect=" + info(" kvm ... -net socket,connect=" UNIX_SOCK_PATH " -net nic,model=virtio"); c.fd_unix = accept(fd_unix, NULL, NULL);