From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> 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@xxxxxxxxxx> --- 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