From 62e95cb4b5708eca7860139dffef22e65b29514c Mon Sep 17 00:00:00 2001
From: Stefano Brivio <sbrivio@redhat.com>
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 <sbrivio@redhat.com>
---
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.
</devices>
...
-:anchor:`<a id="elementsNICSTCP"/>`
+:anchor:`<a id="elementsNICSStream"/>`
-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.
</devices>
...
+Named UNIX domain sockets can be specified as follows:
+:since:`Since 7.2.0, qemu`
+
+::
+
+ ...
+ <devices>
+ <interface type='server'>
+ <mac address='52:54:00:22:c9:42'/>
+ <source path='/tmp/qemu.socket'/>
+ </interface>
+ ...
+ <interface type='client'>
+ <mac address='52:54:00:8b:c9:51'/>
+ <source path='/tmp/qemu.socket'/>
+ </interface>
+ </devices>
+ ...
+
:anchor:`<a id="elementsNICSUDP"/>`
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 @@
</group>
<group>
<attribute name="type">
- <choice>
- <value>mcast</value>
- <value>client</value>
- </choice>
+ <value>mcast</value>
</attribute>
<interleave>
<element name="source">
@@ -3143,6 +3140,30 @@
<ref name="interface-options"/>
</interleave>
</group>
+ <group>
+ <attribute name="type">
+ <value>client</value>
+ </attribute>
+ <interleave>
+ <element name="source">
+ <choice>
+ <group>
+ <attribute name="address">
+ <ref name="ipv4Addr"/>
+ </attribute>
+ <attribute name="port">
+ <ref name="PortNumber"/>
+ </attribute>
+ </group>
+ <attribute name="path">
+ <ref name="absFilePath"/>
+ </attribute>
+ </choice>
+ <empty/>
+ </element>
+ <ref name="interface-options"/>
+ </interleave>
+ </group>
<group>
<attribute name="type">
<value>udp</value>
@@ -3174,14 +3195,21 @@
</attribute>
<interleave>
<element name="source">
- <optional>
- <attribute name="address">
- <ref name="ipv4Addr"/>
+ <choice>
+ <group>
+ <optional>
+ <attribute name="address">
+ <ref name="ipv4Addr"/>
+ </attribute>
+ </optional>
+ <attribute name="port">
+ <ref name="PortNumber"/>
+ </attribute>
+ </group>
+ <attribute name="path">
+ <ref name="absFilePath"/>
</attribute>
- </optional>
- <attribute name="port">
- <ref name="PortNumber"/>
- </attribute>
+ </choice>
<empty/>
</element>
<ref name="interface-options"/>
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,
" <interface type='%s'>"), 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",
+ _("<source> '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 <source> '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 <source> '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 <local> '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, "<source path='%s'",
+ def->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, "<source address='%s' port='%d'",
- def->data.socket.address,
- def->data.socket.port);
+ def->data.socket.net.address,
+ def->data.socket.net.port);
} else {
virBufferAsprintf(buf, "<source port='%d'",
- def->data.socket.port);
+ def->data.socket.net.port);
}
sourceLines++;
@@ -26239,8 +26271,8 @@ virDomainNetDefFormat(virBufferPtr buf,
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<local address='%s' port='%d'/>\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