From: Marc-André Lureau <marcandre.lureau(a)redhat.com>
Wire the external server RDP support with QEMU.
Check the configuration, allocate a port, start the process
and set the credentials.
Signed-off-by: Marc-André Lureau <marcandre.lureau(a)redhat.com>
---
docs/formatdomain.rst | 25 ++++--
src/conf/domain_conf.h | 1 +
src/qemu/qemu_extdevice.c | 46 +++++++++--
src/qemu/qemu_hotplug.c | 49 ++++++++++-
src/qemu/qemu_hotplug.h | 1 +
src/qemu/qemu_process.c | 167 ++++++++++++++++++++++++++++++++++----
6 files changed, 257 insertions(+), 32 deletions(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index 785b51bb4e..358feb2625 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -6410,7 +6410,7 @@ interaction with the admin.
<graphics type='vnc' port='5904'
sharePolicy='allow-exclusive'>
<listen type='address' address='1.2.3.4'/>
</graphics>
- <graphics type='rdp' autoport='yes' multiUser='yes'
/>
+ <graphics type='rdp' autoport='yes' multiUser='yes'/>
<graphics type='desktop' fullscreen='yes'/>
<graphics type='spice'>
<listen type='network' network='rednet'/>
@@ -6573,14 +6573,21 @@ interaction with the admin.
Starts a RDP server. The ``port`` attribute specifies the TCP port number
(with -1 as legacy syntax indicating that it should be auto-allocated).
The ``autoport`` attribute is the new preferred syntax for indicating
- auto-allocation of the TCP port to use. In the VirtualBox driver, the
- ``autoport`` will make the hypervisor pick available port from 3389-3689
- range when the VM is started. The chosen port will be reflected in the
- ``port`` attribute. The ``multiUser`` attribute is a boolean deciding
- whether multiple simultaneous connections to the VM are permitted. The
- ``replaceUser`` attribute is a boolean deciding whether the existing
- connection must be dropped and a new connection must be established by the
- VRDP server, when a new client connects in single connection mode.
+ auto-allocation of the TCP port to use.
+
+ A ``dbus`` graphics is also required to enable the QEMU RDP support, which
+ uses an external "qemu-rdp" helper process. The ``username`` and
+ ``passwd`` attributes set the credentials (when they are not set, the RDP
+ access may be disabled by the helper). :since:`Since 11.1.0`
+
+ In the VirtualBox driver, the ``autoport`` will make the hypervisor pick
+ available port from 3389-3689 range when the VM is started. The chosen
+ port will be reflected in the ``port`` attribute. The ``multiUser``
+ attribute is a boolean deciding whether multiple simultaneous connections
+ to the VM are permitted. The ``replaceUser`` attribute is a boolean
+ deciding whether the existing connection must be dropped and a new
+ connection must be established by the VRDP server, when a new client
+ connects in single connection mode.
``desktop``
This value is reserved for VirtualBox domains for the moment. It displays
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index b08e2549b7..ff05920030 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2025,6 +2025,7 @@ struct _virDomainGraphicsDef {
} sdl;
struct {
int port;
+ bool portReserved;
bool autoport;
bool replaceUser;
bool multiUser;
diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c
index 954cb323a4..e0c9cd1d91 100644
--- a/src/qemu/qemu_extdevice.c
+++ b/src/qemu/qemu_extdevice.c
@@ -236,14 +236,28 @@ qemuExtDevicesStart(virQEMUDriver *driver,
for (i = 0; i < def->ngraphics; i++) {
virDomainGraphicsDef *graphics = def->graphics[i];
- if (graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_DBUS)
+ switch (graphics->type) {
+ case VIR_DOMAIN_GRAPHICS_TYPE_DBUS: {
+ if (graphics->data.dbus.p2p || graphics->data.dbus.fromConfig)
+ continue;
+ if (qemuDBusStart(driver, vm) < 0)
+ return -1;
continue;
-
- if (graphics->data.dbus.p2p || graphics->data.dbus.fromConfig)
+ }
+ case VIR_DOMAIN_GRAPHICS_TYPE_RDP: {
+ if (qemuRdpStart(vm, graphics) < 0)
+ return -1;
continue;
-
- if (qemuDBusStart(driver, vm) < 0)
- return -1;
+ }
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+ case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+ case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+ case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+ case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+ case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+ default:
+ continue;
+ }
}
for (i = 0; i < def->ndisks; i++) {
@@ -309,6 +323,26 @@ qemuExtDevicesStop(virQEMUDriver *driver,
qemuVirtioFSStop(driver, vm, fs);
}
+ for (i = 0; i < def->ngraphics; i++) {
+ virDomainGraphicsDef *graphics = def->graphics[i];
+
+ switch (graphics->type) {
+ case VIR_DOMAIN_GRAPHICS_TYPE_RDP: {
+ qemuRdpStop(vm, graphics);
+ continue;
+ }
+ case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+ case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+ case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+ case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+ case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+ case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+ default:
+ continue;
+ }
+ }
+
for (i = 0; i < def->ndisks; i++) {
virDomainDiskDef *disk = def->disks[i];
qemuNbdkitStopStorageSource(disk->src, vm, true);
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 5a7e6c3b12..38d21642fe 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -4410,6 +4410,7 @@ int
qemuDomainChangeGraphicsPasswords(virDomainObj *vm,
int type,
virDomainGraphicsAuthDef *auth,
+ const char *defaultUsername,
const char *defaultPasswd,
int asyncJob)
{
@@ -4419,13 +4420,19 @@ qemuDomainChangeGraphicsPasswords(virDomainObj *vm,
g_autofree char *validTo = NULL;
const char *connected = NULL;
const char *password;
+ const char *username;
int ret = -1;
if (!auth->passwd && !defaultPasswd)
return 0;
+ username = auth->username ? auth->username : defaultUsername;
password = auth->passwd ? auth->passwd : defaultPasswd;
+ if (type == VIR_DOMAIN_GRAPHICS_TYPE_RDP) {
+ return qemuRdpSetCredentials(vm, username, password, "");
+ }
+
if (auth->connected)
connected = virDomainGraphicsAuthConnectedTypeToString(auth->connected);
@@ -4555,6 +4562,7 @@ qemuDomainChangeGraphics(virQEMUDriver *driver,
if (qemuDomainChangeGraphicsPasswords(vm,
VIR_DOMAIN_GRAPHICS_TYPE_VNC,
&dev->data.vnc.auth,
+ NULL,
cfg->vncPassword,
VIR_ASYNC_JOB_NONE) < 0)
return -1;
@@ -4602,6 +4610,7 @@ qemuDomainChangeGraphics(virQEMUDriver *driver,
if (qemuDomainChangeGraphicsPasswords(vm,
VIR_DOMAIN_GRAPHICS_TYPE_SPICE,
&dev->data.spice.auth,
+ NULL,
cfg->spicePassword,
VIR_ASYNC_JOB_NONE) < 0)
return -1;
@@ -4617,8 +4626,46 @@ qemuDomainChangeGraphics(virQEMUDriver *driver,
}
break;
- case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ if ((olddev->data.rdp.autoport != dev->data.rdp.autoport) ||
+ (!dev->data.rdp.autoport &&
+ (olddev->data.rdp.port != dev->data.rdp.port))) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("cannot change port settings on rdp graphics"));
+ return -1;
+ }
+
+ /* If a password lifetime was, or is set, or action if connected has
+ * changed, then we must always run, even if new password matches
+ * old password */
+ if (olddev->data.rdp.auth.expires ||
+ dev->data.rdp.auth.expires ||
+ olddev->data.rdp.auth.connected != dev->data.rdp.auth.connected ||
+ STRNEQ_NULLABLE(olddev->data.rdp.auth.username,
+ dev->data.rdp.auth.username) ||
+ STRNEQ_NULLABLE(olddev->data.rdp.auth.passwd,
+ dev->data.rdp.auth.passwd)) {
+ VIR_DEBUG("Updating password on RDP server %p %p",
+ dev->data.rdp.auth.passwd, cfg->rdpPassword);
+ if (qemuDomainChangeGraphicsPasswords(vm,
+ VIR_DOMAIN_GRAPHICS_TYPE_RDP,
+ &dev->data.rdp.auth,
+ cfg->rdpUsername,
+ cfg->rdpPassword,
+ VIR_ASYNC_JOB_NONE) < 0)
+ return -1;
+
+ /* Steal the new dev's char * reference */
+ VIR_FREE(olddev->data.rdp.auth.username);
+ olddev->data.rdp.auth.username =
g_steal_pointer(&dev->data.rdp.auth.username);
+ VIR_FREE(olddev->data.rdp.auth.passwd);
+ olddev->data.rdp.auth.passwd =
g_steal_pointer(&dev->data.rdp.auth.passwd);
+ olddev->data.rdp.auth.validTo = dev->data.rdp.auth.validTo;
+ olddev->data.rdp.auth.expires = dev->data.rdp.auth.expires;
+ olddev->data.rdp.auth.connected = dev->data.rdp.auth.connected;
+ }
+ break;
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h
index 4fe7f4923e..8108d96de9 100644
--- a/src/qemu/qemu_hotplug.h
+++ b/src/qemu/qemu_hotplug.h
@@ -51,6 +51,7 @@ int qemuDomainFindGraphicsIndex(virDomainDef *def,
int qemuDomainChangeGraphicsPasswords(virDomainObj *vm,
int type,
virDomainGraphicsAuthDef *auth,
+ const char *defaultUsername,
const char *defaultPasswd,
int asyncJob);
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 34a755a49a..c0dd9c0b35 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -2949,18 +2949,29 @@ qemuProcessInitPasswords(virQEMUDriver *driver,
for (i = 0; i < vm->def->ngraphics; ++i) {
virDomainGraphicsDef *graphics = vm->def->graphics[i];
- if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
- ret = qemuDomainChangeGraphicsPasswords(vm,
- VIR_DOMAIN_GRAPHICS_TYPE_VNC,
- &graphics->data.vnc.auth,
- cfg->vncPassword,
- asyncJob);
- } else if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
- ret = qemuDomainChangeGraphicsPasswords(vm,
- VIR_DOMAIN_GRAPHICS_TYPE_SPICE,
- &graphics->data.spice.auth,
- cfg->spicePassword,
- asyncJob);
+
+ switch (graphics->type) {
+ case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
+ ret = qemuDomainChangeGraphicsPasswords(
+ vm, VIR_DOMAIN_GRAPHICS_TYPE_VNC, &graphics->data.vnc.auth, NULL,
+ cfg->vncPassword, asyncJob);
+ break;
+ case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
+ ret = qemuDomainChangeGraphicsPasswords(
+ vm, VIR_DOMAIN_GRAPHICS_TYPE_SPICE, &graphics->data.spice.auth,
+ NULL, cfg->spicePassword, asyncJob);
+ break;
+ case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ ret = qemuDomainChangeGraphicsPasswords(
+ vm, VIR_DOMAIN_GRAPHICS_TYPE_RDP, &graphics->data.rdp.auth,
+ cfg->rdpUsername, cfg->rdpPassword, asyncJob);
+ break;
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
+ case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
+ case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
+ case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
+ case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
+ break;
}
if (ret < 0)
@@ -4239,6 +4250,30 @@ qemuProcessSPICEAllocatePorts(virQEMUDriver *driver,
return 0;
}
+static int
+qemuProcessRDPAllocatePorts(virQEMUDriver *driver,
+ virDomainGraphicsDef *graphics,
+ bool allocate)
+{
+ unsigned short port;
+
+ if (!allocate) {
+ if (graphics->data.rdp.autoport)
+ graphics->data.rdp.port = 3389;
+
+ return 0;
+ }
+
+ if (graphics->data.rdp.autoport) {
+ if (virPortAllocatorAcquire(driver->rdpPorts, &port) < 0)
+ return -1;
+ graphics->data.rdp.port = port;
+ graphics->data.rdp.portReserved = true;
+ }
+
+ return 0;
+}
+
static int
qemuProcessVerifyHypervFeatures(virDomainDef *def,
@@ -4943,8 +4978,16 @@ qemuProcessGraphicsReservePorts(virDomainGraphicsDef *graphics,
}
break;
- case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ if (!graphics->data.rdp.autoport ||
+ reconnect) {
+ if (virPortAllocatorSetUsed(graphics->data.rdp.port) < 0)
+ return -1;
+ graphics->data.rdp.portReserved = true;
+ }
+ break;
+
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
@@ -4983,8 +5026,12 @@ qemuProcessGraphicsAllocatePorts(virQEMUDriver *driver,
return -1;
break;
- case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ if (qemuProcessRDPAllocatePorts(driver, graphics, allocate) < 0)
+ return -1;
+ break;
+
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
@@ -5151,8 +5198,11 @@ qemuProcessGraphicsSetupListen(virQEMUDriver *driver,
listenAddr = cfg->spiceListen;
break;
- case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ listenAddr = cfg->rdpListen;
+ break;
+
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
@@ -5435,6 +5485,23 @@ qemuProcessStartWarnShmem(virDomainObj *vm)
}
+static bool
+virDomainDefHasDBus(const virDomainDef *def, bool p2p)
+{
+ size_t i = 0;
+
+ for (i = 0; i < def->ngraphics; i++) {
+ virDomainGraphicsDef *graphics = def->graphics[i];
+
+ if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_DBUS) {
+ return graphics->data.dbus.p2p == p2p;
+ }
+ }
+
+ return false;
+}
+
+
static int
qemuProcessStartValidateGraphics(virDomainObj *vm)
{
@@ -5453,8 +5520,30 @@ qemuProcessStartValidateGraphics(virDomainObj *vm)
}
break;
- case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
+ if (graphics->nListens > 1) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("qemu-rdp does not support multiple listens for one
graphics device."));
+ return -1;
+ }
+ if (graphics->data.rdp.multiUser) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("qemu-rdp doesn't support the
'multiUser' attribute."));
+ return -1;
+ }
+ if (graphics->data.rdp.replaceUser) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("qemu-rdp doesn't support the
'replaceUser' attribute."));
+ return -1;
+ }
+ if (!virDomainDefHasDBus(vm->def, false)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("qemu-rdp support requires a D-Bus bus graphics
device."));
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
case VIR_DOMAIN_GRAPHICS_TYPE_DBUS:
@@ -5939,6 +6028,42 @@ qemuProcessPrepareHostNetwork(virDomainObj *vm)
return 0;
}
+#include "qemu_rdp.h"
+
+static int
+qemuPrepareGraphicsRdp(virQEMUDriver *driver,
+ virDomainGraphicsDef *gfx)
+{
+ g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+ g_autoptr(qemuRdp) rdp = NULL;
+
+ if (!(rdp = qemuRdpNewForHelper(cfg->qemuRdpName)))
+ return -1;
+
+ QEMU_DOMAIN_GRAPHICS_PRIVATE(gfx)->rdp = g_steal_pointer(&rdp);
+
+ return 0;
+}
+
+
+static int
+qemuProcessPrepareGraphics(virDomainObj *vm)
+{
+ qemuDomainObjPrivate *priv = vm->privateData;
+ size_t i;
+
+ for (i = 0; i < vm->def->ngraphics; i++) {
+ virDomainGraphicsDef *gfx = vm->def->graphics[i];
+
+ if (gfx->type == VIR_DOMAIN_GRAPHICS_TYPE_RDP &&
+ qemuPrepareGraphicsRdp(priv->driver, gfx) < 0)
+ return -1;
+
+ }
+
+ return 0;
+}
+
struct qemuProcessSetupVcpuSchedCoreHelperData {
pid_t vcpupid;
@@ -7410,6 +7535,10 @@ qemuProcessPrepareHost(virQEMUDriver *driver,
if (qemuProcessPrepareHostNetwork(vm) < 0)
return -1;
+ VIR_DEBUG("Preparing graphics");
+ if (qemuProcessPrepareGraphics(vm) < 0)
+ return -1;
+
/* Must be run before security labelling */
VIR_DEBUG("Preparing host devices");
if (!cfg->relaxedACS)
@@ -8982,6 +9111,12 @@ void qemuProcessStop(virQEMUDriver *driver,
graphics->data.spice.tlsPortReserved = false;
}
}
+ if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_RDP) {
+ if (graphics->data.rdp.portReserved) {
+ virPortAllocatorRelease(graphics->data.rdp.port);
+ graphics->data.rdp.portReserved = false;
+ }
+ }
}
for (i = 0; i < vm->ndeprecations; i++)
--
2.47.0