[libvirt] [PATCH RFC] Allow a client to connect to QEMU's VNC by passing an FD via libvirt

This patch implements a new API which allows a libvirt client application to connect to QEMU's VNC server, by passing a FD from an anonymous socketpair, to libvirt, which then passes it onto QEMU via the monitor. This obviously only works if the client app is talking to a local libvirt over UNIX sockets, not for any remote apps using TCP, or SSH tunnel. typedef enum { VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH = (1 << 0), } virDomainConnectGraphicsFlags; int virDomainConnectGraphics(virDomainPtr dom, const char *devname, int fd, unsigned int flags); The QEMU monitor command I proposed actually allo2s conecting to VNC or SPICE or any character device, so I might rename this API to replace 'Graphics' with 'Client' or something. Or we could add a separate API for connecting to character devices. This does a semi-nasty hack to the remote protocol, defining a new client -> server message type: REMOTE_CALL_WITH_FD which is identical to REMOTE_CALL, but a UNIX FD is passed between the header & payload. The reason I call this nasty is that it only allows for a single FD to be passed per RPC call. This seems like a reasonable restriction and it makes the code utterly trivial now, but I'm afraid it might bite us later. * src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h, src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h, src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h: Add support for 'client_add' monitor command with FD passing * src/qemu/qemu_driver.c: Implement the new virDomainConnectGraphics API call * include/libvirt/libvirt.h.in, src/driver.h, src/libvirt_public.syms, src/libvirt.c: Define a new virDomainConnectGraphics API * daemon/dispatch.c, daemon/libvirtd.c, daemon/libvirtd.h, daemon/remote.c, src/remote/remote_driver.c, src/remote/remote_protocol.x: Support for FD passing and the virDomainConnectGraphics API --- daemon/dispatch.c | 14 +++++- daemon/libvirtd.c | 13 +++++ daemon/libvirtd.h | 4 ++ daemon/remote.c | 44 ++++++++++++++++++ include/libvirt/libvirt.h.in | 14 +++++- src/driver.h | 6 +++ src/libvirt.c | 50 ++++++++++++++++++++ src/libvirt_private.syms | 3 + src/libvirt_public.syms | 1 + src/qemu/qemu_driver.c | 59 ++++++++++++++++++++++++ src/qemu/qemu_monitor.c | 32 +++++++++++++ src/qemu/qemu_monitor.h | 6 +++ src/qemu/qemu_monitor_json.c | 31 ++++++++++++- src/qemu/qemu_monitor_json.h | 5 ++ src/qemu/qemu_monitor_text.c | 31 +++++++++++++ src/qemu/qemu_monitor_text.h | 5 ++ src/remote/remote_driver.c | 102 +++++++++++++++++++++++++++++++++++++++++- src/remote/remote_protocol.x | 23 +++++++++- 18 files changed, 434 insertions(+), 9 deletions(-) diff --git a/daemon/dispatch.c b/daemon/dispatch.c index 010be1e..723b1c7 100644 --- a/daemon/dispatch.c +++ b/daemon/dispatch.c @@ -29,9 +29,11 @@ #include "dispatch.h" #include "remote.h" - +#include "files.h" #include "memory.h" +virThreadLocal clientFD; + /* Convert a libvirt virError object into wire format */ static void remoteDispatchCopyError (remote_error *rerr, @@ -391,6 +393,7 @@ remoteDispatchClientRequest(struct qemud_server *server, switch (msg->hdr.type) { case REMOTE_CALL: + case REMOTE_CALL_WITH_FD: return remoteDispatchClientCall(server, client, msg, qemu_call); case REMOTE_STREAM: @@ -503,6 +506,10 @@ remoteDispatchClientCall (struct qemud_server *server, conn = client->conn; virMutexUnlock(&client->lock); + if (msg->hdr.type == REMOTE_CALL_WITH_FD) { + virThreadLocalSet(&clientFD, &msg->fd); + } + /* * When the RPC handler is called: * @@ -515,6 +522,11 @@ remoteDispatchClientCall (struct qemud_server *server, */ rv = (data->fn)(server, client, conn, &msg->hdr, &rerr, &args, &ret); + if (msg->hdr.type == REMOTE_CALL_WITH_FD) { + virThreadLocalSet(&clientFD, NULL); + VIR_FORCE_CLOSE(msg->fd); + } + virMutexLock(&server->lock); virMutexLock(&client->lock); virMutexUnlock(&server->lock); diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index 5f291ec..6d6a44b 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -67,6 +67,7 @@ #include "stream.h" #include "hooks.h" #include "virtaudit.h" +#include "passfd.h" #ifdef HAVE_AVAHI # include "mdns.h" #endif @@ -875,6 +876,12 @@ static struct qemud_server *qemudInitialize(void) { return NULL; } + if (virThreadLocalInit(&clientFD, NULL) < 0) { + VIR_ERROR(_("Failed to initialize thread local")); + VIR_FREE(server); + return NULL; + } + server->privileged = geteuid() == 0 ? 1 : 0; server->sigread = server->sigwrite = -1; @@ -1882,6 +1889,12 @@ readmore: qemudDispatchClientFailure(client); } + if (msg->hdr.type == REMOTE_CALL_WITH_FD) { + VIR_ERROR("Trying to get FD"); + msg->fd = recvfd(client->fd, O_CLOEXEC); + VIR_ERROR("Got %d", msg->fd); + } + /* Check if any filters match this message */ filter = client->filters; while (filter) { diff --git a/daemon/libvirtd.h b/daemon/libvirtd.h index ea00d5c..7866067 100644 --- a/daemon/libvirtd.h +++ b/daemon/libvirtd.h @@ -147,6 +147,8 @@ struct qemud_client_message { remote_message_header hdr; + int fd; + struct qemud_client_message *next; }; @@ -304,6 +306,8 @@ struct qemud_server { # endif }; +extern virThreadLocal clientFD; + void qemudLog(int priority, const char *fmt, ...) ATTRIBUTE_FMT_PRINTF(2,3); diff --git a/daemon/remote.c b/daemon/remote.c index 37fbed0..700e12c 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2778,6 +2778,50 @@ cleanup: } +static int +remoteDispatchDomainConnectGraphics(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_connect_graphics_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virDomainPtr dom = NULL; + int *fd; + int rv = -1; + + if (!conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(conn, args->dom))) + goto cleanup; + + fd = virThreadLocalGet(&clientFD); + if (!fd || *fd == -1) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing file descriptor")); + goto cleanup; + } + + if (virDomainConnectGraphics(dom, + args->devicestr, + *fd, + args->flags) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + remoteDispatchError(rerr); + if (dom) + virDomainFree(dom); + return rv; +} + + #include "remote_dispatch_bodies.h" #include "qemu_dispatch_bodies.h" diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 3f634e6..49b8fc1 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2654,6 +2654,7 @@ typedef enum { typedef enum { VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4, /* IPv4 address */ VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV6, /* IPv6 address */ + VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_UNIX, /* UNIX socket path */ } virDomainEventGraphicsAddressType; @@ -2665,8 +2666,8 @@ typedef enum { */ struct _virDomainEventGraphicsAddress { int family; /* Address family, virDomainEventGraphicsAddressType */ - const char *node; /* Address of node (eg IP address) */ - const char *service; /* Service name/number (eg TCP port) */ + const char *node; /* Address of node (eg IP address, or UNIX path) */ + const char *service; /* Service name/number (eg TCP port, or NULL) */ }; typedef struct _virDomainEventGraphicsAddress virDomainEventGraphicsAddress; typedef virDomainEventGraphicsAddress *virDomainEventGraphicsAddressPtr; @@ -2862,6 +2863,15 @@ int virDomainOpenConsole(virDomainPtr dom, virStreamPtr st, unsigned int flags); +typedef enum { + VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH = (1 << 0), +} virDomainConnectGraphicsFlags; + +int virDomainConnectGraphics(virDomainPtr dom, + const char *devname, + int fd, + unsigned int flags); + int virDomainInjectNMI(virDomainPtr domain, unsigned int flags); diff --git a/src/driver.h b/src/driver.h index 62bbc1d..cfa4a00 100644 --- a/src/driver.h +++ b/src/driver.h @@ -564,6 +564,11 @@ typedef int const char *devname, virStreamPtr st, unsigned int flags); +typedef int + (*virDrvDomainConnectGraphics)(virDomainPtr dom, + const char *devname, + int fd, + unsigned int flags); typedef int (*virDrvDomainInjectNMI)(virDomainPtr dom, unsigned int flags); @@ -797,6 +802,7 @@ struct _virDriver { virDrvDomainSnapshotDelete domainSnapshotDelete; virDrvDomainQemuMonitorCommand qemuDomainMonitorCommand; virDrvDomainOpenConsole domainOpenConsole; + virDrvDomainConnectGraphics domainConnectGraphics; virDrvDomainInjectNMI domainInjectNMI; virDrvDomainMigrateBegin3 domainMigrateBegin3; virDrvDomainMigratePrepare3 domainMigratePrepare3; diff --git a/src/libvirt.c b/src/libvirt.c index c57e0c3..bbd4157 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15548,3 +15548,53 @@ error: virDispatchError(dom->conn); return -1; } + +int virDomainConnectGraphics(virDomainPtr dom, + const char *devname, + int fd, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "devname=%s, fd=%d, flags=%u", + NULLSTR(devname), fd, flags); + + virResetLastError(); + + if (!VIR_IS_DOMAIN(dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = dom->conn; + + if (devname == NULL) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + if (fd < 0) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + if (dom->conn->flags & VIR_CONNECT_RO) { + virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (dom->conn->driver->domainConnectGraphics) { + int ret; + ret = dom->conn->driver->domainConnectGraphics(dom, devname, fd, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(dom->conn); + return -1; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 6f253ab..24f811b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -950,6 +950,9 @@ virThreadIsSelf; virThreadJoin; virThreadSelf; virThreadSelfID; +virThreadLocalGet; +virThreadLocalInit; +virThreadLocalSet; # usb.h diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 39d4cae..2f4aa0b 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -467,6 +467,7 @@ LIBVIRT_0.9.3 { virEventUpdateTimeout; virNodeGetCPUStats; virNodeGetMemoryStats; + virDomainConnectGraphics; } LIBVIRT_0.9.2; # .... define new API here using predicted next version number .... diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 01587e8..cdbd2e6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8314,6 +8314,64 @@ qemuDomainGetBlockPullInfo(virDomainPtr dom, const char *path, return qemuDomainBlockPullImpl(dom, path, info, BLOCK_PULL_MODE_INFO); } +static int +qemuDomainConnectGraphics(virDomainPtr dom, + const char *devname, + int fd, + unsigned int flags) +{ + struct qemud_driver *driver = dom->conn->privateData; + virDomainObjPtr vm = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + int ret = -1; + qemuDomainObjPrivatePtr priv; + + virCheckFlags(VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH, -1); + + qemuDriverLock(driver); + virUUIDFormat(dom->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + + priv = vm->privateData; + + if (STRNEQ(devname, "vnc") && + STRNEQ(devname, "spice")) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find graphics device %s"), + devname); + goto cleanup; + } + + if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0) + goto cleanup; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + ret = qemuMonitorConnectGraphics(priv->mon, devname, fd, "graphicsfd", + flags & VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (qemuDomainObjEndJob(vm) == 0) { + vm = NULL; + goto cleanup; + } + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = "QEMU", @@ -8429,6 +8487,7 @@ static virDriver qemuDriver = { .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */ .qemuDomainMonitorCommand = qemuDomainMonitorCommand, /* 0.8.3 */ .domainOpenConsole = qemuDomainOpenConsole, /* 0.8.6 */ + .domainConnectGraphics = qemuDomainConnectGraphics, /* 0.9.2 */ .domainInjectNMI = qemuDomainInjectNMI, /* 0.9.2 */ .domainMigrateBegin3 = qemuDomainMigrateBegin3, /* 0.9.2 */ .domainMigratePrepare3 = qemuDomainMigratePrepare3, /* 0.9.2 */ diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 89a3f64..8259b44 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2404,3 +2404,35 @@ int qemuMonitorBlockPull(qemuMonitorPtr mon, ret = qemuMonitorTextBlockPull(mon, path, info, mode); return ret; } + +int qemuMonitorConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + int fd, + const char *fdname, + bool skipauth) +{ + VIR_DEBUG("mon=%p device=%s fd=%d fdname=%s skipauth=%d", + mon, devicestr, fd, NULLSTR(fdname), skipauth); + int ret; + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (qemuMonitorSendFileHandle(mon, fdname, fd) < 0) + return -1; + + if (mon->json) + ret = qemuMonitorJSONConnectGraphics(mon, devicestr, fdname, skipauth); + else + ret = qemuMonitorTextConnectGraphics(mon, devicestr, fdname, skipauth); + + if (ret < 0) { + if (qemuMonitorCloseFileHandle(mon, fdname) < 0) + VIR_WARN("failed to close device handle '%s'", fdname); + } + + return ret; +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 3bb0269..3fc6ec6 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -462,6 +462,12 @@ int qemuMonitorBlockPull(qemuMonitorPtr mon, virDomainBlockPullInfoPtr info, int mode); +int qemuMonitorConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + int fd, + const char *fdname, + bool skipauth); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 56ec65b..f8bac98 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -615,8 +615,8 @@ static void qemuMonitorJSONHandleIOError(qemuMonitorPtr mon, virJSONValuePtr dat VIR_ENUM_DECL(qemuMonitorGraphicsAddressFamily) -VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily, VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV6 + 1, - "ipv4", "ipv6"); +VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily, VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_UNIX + 1, + "ipv4", "ipv6", "unix"); static void qemuMonitorJSONHandleVNC(qemuMonitorPtr mon, virJSONValuePtr data, int phase) { @@ -2814,3 +2814,30 @@ int qemuMonitorJSONBlockPull(qemuMonitorPtr mon, virJSONValueFree(reply); return ret; } + +int qemuMonitorJSONConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + const char *fdname, + bool skipauth) +{ + int ret; + virJSONValuePtr cmd, reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("add_client", + "s:protocol", devicestr, + "s:fdname", fdname, + "b:skipauth", skipauth, + NULL); + + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 393d8fc..468acb8 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -222,4 +222,9 @@ int qemuMonitorJSONBlockPull(qemuMonitorPtr mon, virDomainBlockPullInfoPtr info, int mode); +int qemuMonitorJSONConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + const char *fdname, + bool skipauth); + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index a16ea91..f11b58f 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2906,3 +2906,34 @@ cleanup: VIR_FREE(reply); return ret; } + +int qemuMonitorTextConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + const char *fdname, + bool skipauth) +{ + char *cmd = NULL; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "add_client %s %s %d", devicestr, fdname, skipauth ? 0 : 1) < 0){ + virReportOOMError(); + goto cleanup; + } + + if (qemuMonitorHMPCommand(mon, cmd, &reply) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("adding graphics client failed")); + goto cleanup; + } + + if (STRNEQ(reply, "")) + goto cleanup; + + ret = 0; + +cleanup: + VIR_FREE(reply); + VIR_FREE(cmd); + return ret; +} diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 4fa5064..12efe4f 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -215,4 +215,9 @@ int qemuMonitorTextBlockPull(qemuMonitorPtr mon, virDomainBlockPullInfoPtr info, int mode); +int qemuMonitorTextConnectGraphics(qemuMonitorPtr mon, + const char *devicestr, + const char *fdname, + bool skipauth); + #endif /* QEMU_MONITOR_TEXT_H */ diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index d7ce76e..688e65d 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -83,6 +83,7 @@ #include "ignore-value.h" #include "files.h" #include "command.h" +#include "passfd.h" #define VIR_FROM_THIS VIR_FROM_REMOTE @@ -128,6 +129,8 @@ struct remote_thread_call { unsigned int serial; unsigned int proc_nr; + int fd; + virCond cond; int want_reply; @@ -173,6 +176,7 @@ struct private_data { int errfd; /* File handle connected to remote stderr */ int watch; /* File handle watch */ pid_t pid; /* PID of tunnel process */ + int hasSendFD; /* Whether sendfd is possible */ int uses_tls; /* TLS enabled on socket? */ int is_secure; /* Secure if TLS or SASL or UNIX sockets */ gnutls_session_t session; /* GnuTLS session (if uses_tls != 0). */ @@ -240,6 +244,10 @@ static int call (virConnectPtr conn, struct private_data *priv, int flags, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret); +static int callWithFD(virConnectPtr conn, struct private_data *priv, + int flags, int fd, int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret); static int remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open, virConnectAuthPtr auth, const char *authtype); #if HAVE_SASL @@ -722,6 +730,7 @@ doRemoteOpen (virConnectPtr conn, addr.sun_path[0] = '\0'; autostart_retry: + priv->hasSendFD = 1; priv->is_secure = 1; priv->sock = socket (AF_UNIX, SOCK_STREAM, 0); if (priv->sock == -1) { @@ -4603,6 +4612,38 @@ done: return rv; } + +static int +remoteDomainConnectGraphics(virDomainPtr dom, + const char *devicestr, + int fd, + unsigned int flags) +{ + struct private_data *priv = dom->conn->privateData; + int rv = -1; + remote_domain_connect_graphics_args args; + + remoteDriverLock(priv); + + make_nonnull_domain (&args.dom, dom); + args.devicestr = (char*)devicestr; + args.flags = flags; + + if (callWithFD(dom->conn, priv, 0, fd, REMOTE_PROC_DOMAIN_CONNECT_GRAPHICS, + (xdrproc_t) xdr_remote_domain_connect_graphics_args, (char *) &args, + (xdrproc_t) xdr_void, NULL) == -1) { + goto done; + } + + rv = 0; + +done: + remoteDriverUnlock(priv); + + return rv; +} + + /*----------------------------------------------------------------------*/ static int @@ -4995,6 +5036,7 @@ done: static struct remote_thread_call * prepareCall(struct private_data *priv, int flags, + int fd, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret) @@ -5021,6 +5063,7 @@ prepareCall(struct private_data *priv, rv->ret_filter = ret_filter; rv->ret = ret; rv->want_reply = 1; + rv->fd = fd; if (flags & REMOTE_CALL_QEMU) { hdr.prog = QEMU_PROGRAM; @@ -5031,7 +5074,7 @@ prepareCall(struct private_data *priv, hdr.vers = REMOTE_PROTOCOL_VERSION; } hdr.proc = proc_nr; - hdr.type = REMOTE_CALL; + hdr.type = fd == -1 ? REMOTE_CALL : REMOTE_CALL_WITH_FD; hdr.serial = rv->serial; hdr.status = REMOTE_OK; @@ -5221,6 +5264,21 @@ remoteIOWriteMessage(struct private_data *priv, if (priv->saslEncodedOffset == priv->saslEncodedLength) { priv->saslEncoded = NULL; priv->saslEncodedOffset = priv->saslEncodedLength = 0; + + if (thecall->fd != -1) { + if (!priv->hasSendFD) { + remoteError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to send file descriptors over this connection")); + return -1; + } + if (sendfd(priv->sock, thecall->fd) < 0) { + virReportSystemError(errno, + _("Failed to send file descriptor %d"), + thecall->fd); + return -1; + } + } + if (thecall->want_reply) thecall->mode = REMOTE_MODE_WAIT_RX; else @@ -5238,6 +5296,21 @@ remoteIOWriteMessage(struct private_data *priv, if (thecall->bufferOffset == thecall->bufferLength) { thecall->bufferOffset = thecall->bufferLength = 0; + + if (thecall->fd != -1) { + if (!priv->hasSendFD) { + remoteError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to send file descriptors over this connection")); + return -1; + } + if (sendfd(priv->sock, thecall->fd) < 0) { + virReportSystemError(errno, + _("Failed to send file descriptor %d"), + thecall->fd); + return -1; + } + } + if (thecall->want_reply) thecall->mode = REMOTE_MODE_WAIT_RX; else @@ -6181,7 +6254,31 @@ call (virConnectPtr conn, struct private_data *priv, struct remote_thread_call *thiscall; int rv; - thiscall = prepareCall(priv, flags, proc_nr, args_filter, args, + thiscall = prepareCall(priv, flags, -1, proc_nr, args_filter, args, + ret_filter, ret); + + if (!thiscall) { + return -1; + } + + rv = remoteIO(conn, priv, flags, thiscall); + ignore_value(virCondDestroy(&thiscall->cond)); + VIR_FREE(thiscall); + return rv; +} + + +static int +callWithFD(virConnectPtr conn, struct private_data *priv, + int flags, int fd, + int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret) +{ + struct remote_thread_call *thiscall; + int rv; + + thiscall = prepareCall(priv, flags, fd, proc_nr, args_filter, args, ret_filter, ret); if (!thiscall) { @@ -6525,6 +6622,7 @@ static virDriver remote_driver = { .domainSnapshotDelete = remoteDomainSnapshotDelete, /* 0.8.0 */ .qemuDomainMonitorCommand = remoteQemuDomainMonitorCommand, /* 0.8.3 */ .domainOpenConsole = remoteDomainOpenConsole, /* 0.8.6 */ + .domainConnectGraphics = remoteDomainConnectGraphics, /* 0.9.2 */ .domainInjectNMI = remoteDomainInjectNMI, /* 0.9.2 */ .domainMigrateBegin3 = remoteDomainMigrateBegin3, /* 0.9.2 */ .domainMigratePrepare3 = remoteDomainMigratePrepare3, /* 0.9.2 */ diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 3f8f006..3f40b1c 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2038,6 +2038,12 @@ struct remote_domain_open_console_args { unsigned int flags; }; +struct remote_domain_connect_graphics_args { + remote_nonnull_domain dom; + remote_nonnull_string devicestr; + unsigned int flags; +}; + struct remote_storage_vol_upload_args { remote_nonnull_storage_vol vol; unsigned hyper offset; @@ -2425,7 +2431,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_BLOCK_PULL_ABORT = 231, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_BLOCK_PULL_INFO = 232, /* autogen autogen */ REMOTE_PROC_DOMAIN_EVENT_BLOCK_PULL = 233, /* skipgen skipgen */ - REMOTE_PROC_DOMAIN_GET_CONTROL_INFO = 234 /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_CONTROL_INFO = 234, /* autogen autogen */ + REMOTE_PROC_DOMAIN_CONNECT_GRAPHICS = 235 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? @@ -2453,6 +2460,7 @@ enum remote_procedure { * Length | int | Total number of bytes in message _including_ length. * Header | remote_message_header | Control information about procedure call * Payload | - | Variable payload data per procedure + * FD | SCM_CREDS | A file descriptor OOM * * In header, the 'serial' field varies according to: * @@ -2468,6 +2476,9 @@ enum remote_procedure { * - type == REMOTE_STREAM * * serial matches that from the corresponding REMOTE_CALL * + * - type == REMOTE_CALL_WITH_FD + * * As REMOTE_CALL, but a FD follows the payload + * * and the 'status' field varies according to: * * - type == REMOTE_CALL @@ -2485,6 +2496,9 @@ enum remote_procedure { * * REMOTE_OK if stream is complete * * REMOTE_ERROR if stream had an error * + * - type == REMOTE_CALL_WITH_FD + * * As with REMOTE_CALL + * * Payload varies according to type and status: * * - type == REMOTE_CALL @@ -2509,6 +2523,9 @@ enum remote_procedure { * remote_error error information * * status == REMOTE_OK * <empty> + * + * - type == REMOTE_CALL_WITH_FD + * * As with REMOTE_CALL, but SCM_CREDS data has a following FD */ enum remote_message_type { /* client -> server. args from a method call */ @@ -2518,7 +2535,9 @@ enum remote_message_type { /* either direction. async notification */ REMOTE_MESSAGE = 2, /* either direction. stream data packet */ - REMOTE_STREAM = 3 + REMOTE_STREAM = 3, + /* passing a file descriptor */ + REMOTE_CALL_WITH_FD = 4 }; enum remote_message_status { -- 1.7.4.4

On 06/23/2011 06:48 AM, Daniel P. Berrange wrote:
This patch implements a new API which allows a libvirt client application to connect to QEMU's VNC server, by passing a FD from an anonymous socketpair, to libvirt, which then passes it onto QEMU via the monitor. This obviously only works if the client app is talking to a local libvirt over UNIX sockets, not for any remote apps using TCP, or SSH tunnel.
In the remote case, would it be possible to reuse libvirt's rpc streams such that the libvirt local to the qemu sets up the fd, then passes the data from the local socket over the stream back to the remote client to feed into the client's fd (that is, similar to how we support remote consoles)? But that doesn't have to happen right away.
typedef enum { VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH = (1 << 0), } virDomainConnectGraphicsFlags;
int virDomainConnectGraphics(virDomainPtr dom, const char *devname, int fd, unsigned int flags);
The QEMU monitor command I proposed actually allo2s conecting to VNC or SPICE or any character device, so I might rename this API to replace 'Graphics' with 'Client' or something. Or we could add a separate API for connecting to character devices.
This does a semi-nasty hack to the remote protocol, defining a new client -> server message type: REMOTE_CALL_WITH_FD which is identical to REMOTE_CALL, but a UNIX FD is passed between the header & payload. The reason I call this nasty is that it only allows for a single FD to be passed per RPC call. This seems like a reasonable restriction and it makes the code utterly trivial now, but I'm afraid it might bite us later.
I'm not too worried. Look at how qemu's getfd monitor command works - all it does is pass a single fd and associate it with a name, then all subsequent commands that want an fd use the associated name. That way, you can pass as many fds as you need, one at a time, prior to the actual command that wants to use multiple fds. Of course, in the case of virDomainConnectGraphics, where we only need one fd, it is much nicer to pass the fd and act on it immediately, without having to go through separate naming of each passed fd followed by referring to the name.
+++ b/daemon/libvirtd.c @@ -67,6 +67,7 @@ #include "stream.h" #include "hooks.h" #include "virtaudit.h" +#include "passfd.h"
Yay gnulib for making this easy!
@@ -1882,6 +1889,12 @@ readmore: qemudDispatchClientFailure(client); }
+ if (msg->hdr.type == REMOTE_CALL_WITH_FD) { + VIR_ERROR("Trying to get FD"); + msg->fd = recvfd(client->fd, O_CLOEXEC); + VIR_ERROR("Got %d", msg->fd); + } + /* Check if any filters match this message */ filter = client->filters;
I'm guessing that this depends on your rpc rewrite series going in first, so that probably ought to get a better review before I spend too much time on this one. But the general idea seems useful.
+++ b/src/libvirt.c @@ -15548,3 +15548,53 @@ error: virDispatchError(dom->conn); return -1; } + +int virDomainConnectGraphics(virDomainPtr dom, + const char *devname, + int fd, + unsigned int flags)
No documentation? What shame...
+++ b/src/libvirt_private.syms @@ -950,6 +950,9 @@ virThreadIsSelf; virThreadJoin; virThreadSelf; virThreadSelfID; +virThreadLocalGet; +virThreadLocalInit; +virThreadLocalSet;
Sorting?
@@ -8429,6 +8487,7 @@ static virDriver qemuDriver = { .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */ .qemuDomainMonitorCommand = qemuDomainMonitorCommand, /* 0.8.3 */ .domainOpenConsole = qemuDomainOpenConsole, /* 0.8.6 */ + .domainConnectGraphics = qemuDomainConnectGraphics, /* 0.9.2 */
0.9.3 (maybe, but certainly not 0.9.2).
@@ -6525,6 +6622,7 @@ static virDriver remote_driver = { .domainSnapshotDelete = remoteDomainSnapshotDelete, /* 0.8.0 */ .qemuDomainMonitorCommand = remoteQemuDomainMonitorCommand, /* 0.8.3 */ .domainOpenConsole = remoteDomainOpenConsole, /* 0.8.6 */ + .domainConnectGraphics = remoteDomainConnectGraphics, /* 0.9.2 */
Likewise. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org
participants (2)
-
Daniel P. Berrange
-
Eric Blake