qemu: add RDP support

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@redhat.com>
Reviewed-by: Martin Kletzander <mkletzan@redhat.com>
This commit is contained in:
Marc-André Lureau authored and Martin Kletzander committed 2025-03-18 14:15:56 +01:00
1 parent f5e5a9bec9
commit d80f1f8e63
6 files changed
+258 -32

No files matched your search

+16 -9
View File
@@ -6552,7 +6552,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'/>
@@ -6715,14 +6715,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 non-p2p ``dbus`` graphics is required to enable the QEMU RDP support (it
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
+1
View File
@@ -2025,6 +2025,7 @@ struct _virDomainGraphicsDef {
} sdl;
struct {
int port;
bool portReserved;
bool autoport;
bool replaceUser;
bool multiUser;
+40 -6
View File
@@ -238,14 +238,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++) {
@@ -311,6 +325,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);
+50 -1
View File
@@ -4423,6 +4423,7 @@ int
qemuDomainChangeGraphicsPasswords(virDomainObj *vm,
int type,
virDomainGraphicsAuthDef *auth,
const char *defaultUsername,
const char *defaultPasswd,
int asyncJob)
{
@@ -4432,13 +4433,21 @@ 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) {
if (!password)
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing password"));
return qemuRdpSetCredentials(vm, username, password, "");
}
if (auth->connected)
connected = virDomainGraphicsAuthConnectedTypeToString(auth->connected);
@@ -4568,6 +4577,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;
@@ -4615,6 +4625,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;
@@ -4630,8 +4641,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:
+1
View File
@@ -57,6 +57,7 @@ int
qemuDomainChangeGraphicsPasswords(virDomainObj *vm,
int type,
virDomainGraphicsAuthDef *auth,
const char *defaultUsername,
const char *defaultPasswd,
int asyncJob);
+150 -16
View File
@@ -2969,18 +2969,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)
@@ -4259,6 +4270,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,
@@ -4967,8 +5002,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:
@@ -5007,8 +5050,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:
@@ -5175,8 +5222,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:
@@ -5409,6 +5459,23 @@ qemuProcessMakeDir(virQEMUDriver *driver,
}
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)
{
@@ -5427,8 +5494,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:
@@ -5938,6 +6027,41 @@ qemuProcessPrepareHostNetwork(virDomainObj *vm)
}
static int
qemuPrepareGraphicsRdp(virQEMUDriver *driver,
virDomainGraphicsDef *gfx)
{
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
qemuRdp *rdp;
if (!(rdp = qemuRdpNewForHelper(cfg->qemuRdpName)))
return -1;
QEMU_DOMAIN_GRAPHICS_PRIVATE(gfx)->rdp = 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;
pid_t dummypid;
@@ -7409,6 +7533,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)
@@ -8981,6 +9109,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++)