From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> Helpers to start the qemu-rdp server and set it up. Signed-off-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> --- po/POTFILES | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_domain.c | 1 + src/qemu/qemu_domain.h | 2 + src/qemu/qemu_rdp.c | 427 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_rdp.h | 71 +++++++ 6 files changed, 503 insertions(+) create mode 100644 src/qemu/qemu_rdp.c create mode 100644 src/qemu/qemu_rdp.h diff --git a/po/POTFILES b/po/POTFILES index d4b3de781b..0c83affb44 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -195,6 +195,7 @@ src/qemu/qemu_passt.c src/qemu/qemu_postparse.c src/qemu/qemu_process.c src/qemu/qemu_qapi.c +src/qemu/qemu_rdp.c src/qemu/qemu_saveimage.c src/qemu/qemu_slirp.c src/qemu/qemu_snapshot.c diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 43a8ad7c3b..7a07d4f2c4 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -34,6 +34,7 @@ qemu_driver_sources = [ 'qemu_postparse.c', 'qemu_process.c', 'qemu_qapi.c', + 'qemu_rdp.c', 'qemu_saveimage.c', 'qemu_security.c', 'qemu_snapshot.c', diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c7d7ac26ce..7ad31c5844 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1038,6 +1038,7 @@ qemuDomainGraphicsPrivateDispose(void *obj) g_free(priv->tlsAlias); g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); + g_clear_pointer(&priv->rdp, qemuRdpFree); } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 03cf84695f..d3ccbcd63c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -35,6 +35,7 @@ #include "qemu_capabilities.h" #include "qemu_migration_params.h" #include "qemu_nbdkit.h" +#include "qemu_rdp.h" #include "qemu_slirp.h" #include "qemu_fd.h" #include "virchrdev.h" @@ -419,6 +420,7 @@ struct _qemuDomainGraphicsPrivate { char *tlsAlias; qemuDomainSecretInfo *secinfo; + qemuRdp *rdp; }; diff --git a/src/qemu/qemu_rdp.c b/src/qemu/qemu_rdp.c new file mode 100644 index 0000000000..b1b03ad803 --- /dev/null +++ b/src/qemu/qemu_rdp.c @@ -0,0 +1,427 @@ +/* + * qemu_rdp.c: QEMU Rdp support + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <gio/gio.h> + +#include "qemu_dbus.h" +#include "qemu_extdevice.h" +#include "qemu_security.h" +#include "qemu_rdp.h" +#include "virenum.h" +#include "virerror.h" +#include "virjson.h" +#include "virlog.h" +#include "virpidfile.h" +#include "virutil.h" +#include "virgdbus.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("qemu.rdp"); + +VIR_ENUM_IMPL(qemuRdpFeature, + QEMU_RDP_FEATURE_LAST, + "", + "dbus-address", + "remotefx" +); + +#define ORG_QEMUDISPLAY_RDP "org.QemuDisplay.RDP" +#define ORG_QEMUDISPLAY_RDP_PATH "/org/qemu_display/rdp" +#define ORG_QEMUDISPLAY_RDP_IFACE "org.QemuDisplay.RDP" + + +void +qemuRdpFree(qemuRdp *rdp) +{ + if (!rdp) + return; + + virBitmapFree(rdp->features); + g_free(rdp); +} + + +void +qemuRdpSetFeature(qemuRdp *rdp, + qemuRdpFeature feature) +{ + ignore_value(virBitmapSetBit(rdp->features, feature)); +} + + +bool +qemuRdpHasFeature(const qemuRdp *rdp, + qemuRdpFeature feature) +{ + return virBitmapIsBitSet(rdp->features, feature); +} + + +qemuRdp * +qemuRdpNew(void) +{ + g_autoptr(qemuRdp) rdp = g_new0(qemuRdp, 1); + + rdp->features = virBitmapNew(QEMU_RDP_FEATURE_LAST); + rdp->pid = -1; + + return g_steal_pointer(&rdp); +} + + +qemuRdp * +qemuRdpNewForHelper(const char *helper) +{ + g_autoptr(qemuRdp) rdp = NULL; + g_autoptr(virCommand) cmd = NULL; + g_autofree char *output = NULL; + g_autoptr(virJSONValue) doc = NULL; + virJSONValue *featuresJSON; + g_autofree char *helperPath = NULL; + size_t i, nfeatures; + + helperPath = virFindFileInPath(helper); + if (!helperPath) { + virReportSystemError(errno, + _("'%1$s' is not a suitable qemu-rdp helper name"), + helper); + return NULL; + } + + rdp = qemuRdpNew(); + cmd = virCommandNewArgList(helperPath, "--print-capabilities", NULL); + virCommandSetOutputBuffer(cmd, &output); + if (virCommandRun(cmd, NULL) < 0) + return NULL; + + if (!(doc = virJSONValueFromString(output)) || + !(featuresJSON = virJSONValueObjectGetArray(doc, "features"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to parse json capabilities '%1$s'"), + helper); + return NULL; + } + + nfeatures = virJSONValueArraySize(featuresJSON); + for (i = 0; i < nfeatures; i++) { + virJSONValue *item = virJSONValueArrayGet(featuresJSON, i); + const char *tmpStr = virJSONValueGetString(item); + int tmp; + + if ((tmp = qemuRdpFeatureTypeFromString(tmpStr)) <= 0) { + VIR_WARN("unknown qemu-rdp feature %s", tmpStr); + continue; + } + + qemuRdpSetFeature(rdp, tmp); + } + + return g_steal_pointer(&rdp); +} + + +static char * +qemuRdpCreatePidFilename(virDomainObj *vm) +{ + qemuDomainObjPrivate *priv = vm->privateData; + virQEMUDriver *driver = priv->driver; + g_autofree char *shortName = virDomainDefGetShortName(vm->def); + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + g_autofree char *name = NULL; + + name = g_strdup_printf("%s-rdp", shortName); + + return virPidFileBuildPath(cfg->rdpStateDir, name); +} + + +static char * +qemuRdpCreateLogFilename(virQEMUDriverConfig *cfg, + const virDomainDef *def) +{ + return virFileBuildPath(cfg->logDir, def->name, "-qemu-rdp.log"); +} + + +void +qemuRdpStop(virDomainObj *vm, virDomainGraphicsDef *gfx) +{ + qemuDomainObjPrivate *priv = vm->privateData; + qemuDomainGraphicsPrivate *gfxpriv = QEMU_DOMAIN_GRAPHICS_PRIVATE(gfx); + qemuRdp *rdp = gfxpriv->rdp; + g_autofree char *pidfile = qemuRdpCreatePidFilename(vm); + virErrorPtr orig_err; + + if (!rdp) + return; + + if (rdp->leaving_id) { + g_dbus_connection_signal_unsubscribe(priv->dbusConnection, rdp->leaving_id); + rdp->leaving_id = 0; + } + g_clear_handle_id(&rdp->name_watch, g_bus_unwatch_name); + + virErrorPreserveLast(&orig_err); + + if (virPidFileForceCleanupPath(pidfile) < 0) { + VIR_WARN("Unable to kill qemu-rdp process"); + } else { + rdp->pid = -1; + } + + virErrorRestore(&orig_err); +} + + +int +qemuRdpSetupCgroup(qemuRdp *rdp, + virCgroup *cgroup) +{ + return virCgroupAddProcess(cgroup, rdp->pid); +} + + +static void +on_leaving_signal(GDBusConnection *connection, + const gchar *sender_name G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name G_GNUC_UNUSED, + const gchar *signal_name G_GNUC_UNUSED, + GVariant *parameters, + gpointer user_data) +{ + qemuRdp *rdp = user_data; + const gchar *reason; + + g_variant_get(parameters, "(&s)", &reason); + VIR_DEBUG("%s.Leaving reason: '%s'", ORG_QEMUDISPLAY_RDP_IFACE, reason); + g_dbus_connection_signal_unsubscribe(connection, rdp->leaving_id); + rdp->leaving_id = 0; +} + + +static void +name_appeared_cb(GDBusConnection* connection, + const gchar* name G_GNUC_UNUSED, + const gchar* name_owner G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + qemuRdp *rdp = user_data; + + VIR_DEBUG("'%s' appeared", name); + rdp->name_appeared = true; + + if (!rdp->leaving_id) { + rdp->leaving_id = g_dbus_connection_signal_subscribe( + connection, + ORG_QEMUDISPLAY_RDP, + ORG_QEMUDISPLAY_RDP_IFACE, + "Leaving", + ORG_QEMUDISPLAY_RDP_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_leaving_signal, + rdp, + NULL); + } +} + + +static void +name_vanished_cb(GDBusConnection* connection G_GNUC_UNUSED, + const gchar* name G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + qemuRdp *rdp = user_data; + + if (rdp->name_appeared && rdp->leaving_id) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("'%1$s' vanished unexpectedly"), name); + } +} + + +int +qemuRdpStart(virDomainObj *vm, virDomainGraphicsDef *gfx) +{ + qemuDomainObjPrivate *priv = vm->privateData; + virQEMUDriver *driver = priv->driver; + qemuDomainGraphicsPrivate *gfxpriv = QEMU_DOMAIN_GRAPHICS_PRIVATE(gfx); + qemuRdp *rdp = gfxpriv->rdp; + virDomainGraphicsListenDef *glisten = NULL; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + g_autoptr(virCommand) cmd = NULL; + g_autofree char *pidfile = NULL; + g_autofree char *logpath = NULL; + g_autofree char *certpath = NULL; + g_autofree char *keypath = NULL; + g_autofree char *dbus_addr = qemuDBusGetAddress(driver, vm); + g_auto(virBuffer) bind_addr = VIR_BUFFER_INITIALIZER; + pid_t pid = -1; + VIR_AUTOCLOSE logfd = -1; + + if (rdp->pid != -1) { + return 0; + } + + if (!(glisten = virDomainGraphicsGetListen(gfx, 0))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing listen element")); + return -1; + } + + switch (glisten->type) { + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK: + if (glisten->address) { + bool escapeAddr = strchr(glisten->address, ':') != NULL; + if (escapeAddr) + virBufferAsprintf(&bind_addr, "[%s]", glisten->address); + else + virBufferAdd(&bind_addr, glisten->address, -1); + } + virBufferAsprintf(&bind_addr, ":%d", + gfx->data.rdp.port); + break; + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unsupported qemu-rdp listen type")); + return -1; + } + + if (!(pidfile = qemuRdpCreatePidFilename(vm))) + return -1; + + logpath = qemuRdpCreateLogFilename(cfg, vm->def); + + if (cfg->stdioLogD) { + g_autoptr(virLogManager) logManager = virLogManagerNew(driver->privileged); + + if (!logManager) + goto error; + + if ((logfd = virLogManagerDomainOpenLogFile(logManager, + "qemu", + vm->def->uuid, + vm->def->name, + logpath, + 0, + NULL, NULL)) < 0) + goto error; + } else { + if ((logfd = open(logpath, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to create logfile %1$s"), + logpath); + goto error; + } + if (virSetCloseExec(logfd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %1$s"), + logpath); + goto error; + } + } + + cmd = virCommandNew(cfg->qemuRdpName); + virCommandClearCaps(cmd); + virCommandSetPidFile(cmd, pidfile); + virCommandSetOutputFD(cmd, &logfd); + virCommandSetErrorFD(cmd, &logfd); + virCommandDaemonize(cmd); + if (dbus_addr) { + if (!qemuRdpHasFeature(rdp, QEMU_RDP_FEATURE_DBUS_ADDRESS)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu-rdp doesn't support custom D-Bus address")); + } + virCommandAddArgPair(cmd, "--dbus-address", dbus_addr); + } + virCommandAddArg(cmd, "serve"); + + virCommandAddArg(cmd, "--bind-address"); + virCommandAddArgBuffer(cmd, &bind_addr); + + certpath = g_build_filename(cfg->rdpTLSx509certdir, "server-cert.pem", NULL); + keypath = g_build_filename(cfg->rdpTLSx509certdir, "server-key.pem", NULL); + virCommandAddArgPair(cmd, "--cert", certpath); + virCommandAddArgPair(cmd, "--key", keypath); + + if (qemuExtDeviceLogCommand(driver, vm, cmd, "qemu-rdp") < 0) + return -1; + + rdp->name_watch = g_bus_watch_name_on_connection(priv->dbusConnection, + ORG_QEMUDISPLAY_RDP, + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_cb, + name_vanished_cb, + rdp, + NULL); + + if (qemuSecurityCommandRun(driver, vm, cmd, -1, -1, false, NULL) < 0) + goto error; + + if (virPidFileReadPath(pidfile, &pid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to read qemu-rdp pidfile '%1$s'"), + pidfile); + goto error; + } + + if (virProcessKill(pid, 0) != 0) { + virReportSystemError(errno, "%s", + _("qemu-rdp died unexpectedly")); + goto error; + } + + rdp->pid = pid; + + return 0; + + error: + g_clear_handle_id(&rdp->name_watch, g_bus_unwatch_name); + qemuRdpStop(vm, gfx); + return -1; +} + + +int +qemuRdpSetCredentials(virDomainObj *vm, + const char *username, + const char *password, + const char *domain) +{ + qemuDomainObjPrivate *priv = vm->privateData; + g_autoptr(GVariant) args = NULL; + + args = g_variant_new("(sss)", username, password, domain); + + return virGDBusCallMethod(priv->dbusConnection, + NULL, + G_VARIANT_TYPE("()"), + NULL, + ORG_QEMUDISPLAY_RDP, + ORG_QEMUDISPLAY_RDP_PATH, + ORG_QEMUDISPLAY_RDP_IFACE, + "SetCredentials", + args); +} diff --git a/src/qemu/qemu_rdp.h b/src/qemu/qemu_rdp.h new file mode 100644 index 0000000000..6af90b06d2 --- /dev/null +++ b/src/qemu/qemu_rdp.h @@ -0,0 +1,71 @@ +/* + * qemu_rdp.h: QEMU RDP support + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "qemu_conf.h" +#include "virbitmap.h" +#include "virenum.h" + +typedef enum { + QEMU_RDP_FEATURE_NONE = 0, + + QEMU_RDP_FEATURE_DBUS_ADDRESS, + QEMU_RDP_FEATURE_REMOTEFX, + QEMU_RDP_FEATURE_LAST, +} qemuRdpFeature; + +VIR_ENUM_DECL(qemuRdpFeature); + +typedef struct _qemuRdp qemuRdp; +struct _qemuRdp { + int fd[2]; + virBitmap *features; + pid_t pid; + guint name_watch; + bool name_appeared; + guint leaving_id; +}; + +qemuRdp *qemuRdpNew(void); + +qemuRdp *qemuRdpNewForHelper(const char *helper); + +void qemuRdpFree(qemuRdp *rdp); + +void qemuRdpSetFeature(qemuRdp *rdp, + qemuRdpFeature feature); + +bool qemuRdpHasFeature(const qemuRdp *rdp, + qemuRdpFeature feature); + +int qemuRdpStart(virDomainObj *vm, + virDomainGraphicsDef *gfx); + +void qemuRdpStop(virDomainObj *vm, + virDomainGraphicsDef *gfx); + +int qemuRdpSetupCgroup(qemuRdp *rdp, + virCgroup *cgroup); + +int qemuRdpSetCredentials(virDomainObj *vm, + const char *username, + const char *password, + const char *domain); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuRdp, qemuRdpFree); -- 2.47.0