/* * Virt Viewer: A virtual machine console viewer * * Copyright (C) 2007-2012 Red Hat, Inc. * Copyright (C) 2009-2012 Daniel P. Berrange * Copyright (C) 2010 Marc-André Lureau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_SOCKETPAIR) #include #endif #include "virt-viewer.h" #include "virt-viewer-app.h" #include "virt-viewer-events.h" #include "virt-viewer-auth.h" struct _VirtViewerPrivate { char *uri; virConnectPtr conn; virDomainPtr dom; char *domkey; gboolean withEvents; gboolean waitvm; gboolean reconnect; }; G_DEFINE_TYPE (VirtViewer, virt_viewer, VIRT_VIEWER_TYPE_APP) #define GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), VIRT_VIEWER_TYPE, VirtViewerPrivate)) static int virt_viewer_initial_connect(VirtViewerApp *self); static gboolean virt_viewer_open_connection(VirtViewerApp *self, int *fd); static void virt_viewer_deactivated(VirtViewerApp *self); static gboolean virt_viewer_start(VirtViewerApp *self); static void virt_viewer_get_property (GObject *object, guint property_id, GValue *value G_GNUC_UNUSED, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void virt_viewer_set_property (GObject *object, guint property_id, const GValue *value G_GNUC_UNUSED, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void virt_viewer_dispose (GObject *object) { VirtViewer *self = VIRT_VIEWER(object); VirtViewerPrivate *priv = self->priv; if (priv->dom) virDomainFree(priv->dom); if (priv->conn) virConnectClose(priv->conn); G_OBJECT_CLASS(virt_viewer_parent_class)->dispose (object); } static void virt_viewer_class_init (VirtViewerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); VirtViewerAppClass *app_class = VIRT_VIEWER_APP_CLASS (klass); g_type_class_add_private (klass, sizeof (VirtViewerPrivate)); object_class->get_property = virt_viewer_get_property; object_class->set_property = virt_viewer_set_property; object_class->dispose = virt_viewer_dispose; app_class->initial_connect = virt_viewer_initial_connect; app_class->deactivated = virt_viewer_deactivated; app_class->open_connection = virt_viewer_open_connection; app_class->start = virt_viewer_start; } static void virt_viewer_init(VirtViewer *self) { self->priv = GET_PRIVATE(self); } static void virt_viewer_deactivated(VirtViewerApp *app) { VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; if (priv->dom) { virDomainFree(priv->dom); priv->dom = NULL; } if (priv->reconnect) { if (!priv->withEvents) { DEBUG_LOG("No domain events, falling back to polling"); virt_viewer_app_start_reconnect_poll(app); } virt_viewer_app_show_status(app, _("Waiting for guest domain to re-start")); virt_viewer_app_trace(app, "Guest %s display has disconnected, waiting to reconnect", priv->domkey); } else { VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->deactivated(app); } } static int virt_viewer_parse_uuid(const char *name, unsigned char *uuid) { int i; const char *cur = name; for (i = 0;i < 16;) { uuid[i] = 0; if (*cur == 0) return -1; if ((*cur == '-') || (*cur == ' ')) { cur++; continue; } if ((*cur >= '0') && (*cur <= '9')) uuid[i] = *cur - '0'; else if ((*cur >= 'a') && (*cur <= 'f')) uuid[i] = *cur - 'a' + 10; else if ((*cur >= 'A') && (*cur <= 'F')) uuid[i] = *cur - 'A' + 10; else return -1; uuid[i] *= 16; cur++; if (*cur == 0) return -1; if ((*cur >= '0') && (*cur <= '9')) uuid[i] += *cur - '0'; else if ((*cur >= 'a') && (*cur <= 'f')) uuid[i] += *cur - 'a' + 10; else if ((*cur >= 'A') && (*cur <= 'F')) uuid[i] += *cur - 'A' + 10; else return -1; i++; cur++; } return 0; } static virDomainPtr virt_viewer_lookup_domain(VirtViewer *self) { char *end; VirtViewerPrivate *priv = self->priv; int id = strtol(priv->domkey, &end, 10); virDomainPtr dom = NULL; unsigned char uuid[16]; if (id >= 0 && end && !*end) { dom = virDomainLookupByID(priv->conn, id); } if (!dom && virt_viewer_parse_uuid(priv->domkey, uuid) == 0) { dom = virDomainLookupByUUID(priv->conn, uuid); } if (!dom) { dom = virDomainLookupByName(priv->conn, priv->domkey); } return dom; } static int virt_viewer_matches_domain(VirtViewer *self, virDomainPtr dom) { char *end; const char *name; VirtViewerPrivate *priv = self->priv; int id = strtol(priv->domkey, &end, 10); unsigned char wantuuid[16]; unsigned char domuuid[16]; if (id >= 0 && end && !*end) { if (virDomainGetID(dom) == id) return 1; } if (virt_viewer_parse_uuid(priv->domkey, wantuuid) == 0) { virDomainGetUUID(dom, domuuid); if (memcmp(wantuuid, domuuid, VIR_UUID_BUFLEN) == 0) return 1; } name = virDomainGetName(dom); if (strcmp(name, priv->domkey) == 0) return 1; return 0; } static char * virt_viewer_extract_xpath_string(const gchar *xmldesc, const gchar *xpath) { xmlDocPtr xml = NULL; xmlParserCtxtPtr pctxt = NULL; xmlXPathContextPtr ctxt = NULL; xmlXPathObjectPtr obj = NULL; char *port = NULL; pctxt = xmlNewParserCtxt(); if (!pctxt || !pctxt->sax) goto error; xml = xmlCtxtReadDoc(pctxt, (const xmlChar *)xmldesc, "domain.xml", NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOWARNING); if (!xml) goto error; ctxt = xmlXPathNewContext(xml); if (!ctxt) goto error; obj = xmlXPathEval((const xmlChar *)xpath, ctxt); if (!obj || obj->type != XPATH_STRING || !obj->stringval || !obj->stringval[0]) goto error; if (!strcmp((const char*)obj->stringval, "-1")) goto error; port = g_strdup((const char*)obj->stringval); xmlXPathFreeObject(obj); obj = NULL; error: xmlXPathFreeObject(obj); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); xmlFreeParserCtxt(pctxt); return port; } static gboolean virt_viewer_replace_host(const gchar *host) { GInetAddress *addr; gboolean ret; if (!host) return TRUE; addr = g_inet_address_new_from_string(host); if (!addr) /* Parsing error means it was probably a hostname */ return FALSE; ret = g_inet_address_get_is_any(addr); g_object_unref(addr); return ret; } static gboolean virt_viewer_extract_connect_info(VirtViewer *self, virDomainPtr dom) { char *type = NULL; char *xpath = NULL; gboolean retval = FALSE; char *xmldesc = virDomainGetXMLDesc(dom, 0); VirtViewerPrivate *priv = self->priv; VirtViewerApp *app = VIRT_VIEWER_APP(self); gchar *gport = NULL; gchar *gtlsport = NULL; gchar *ghost = NULL; gchar *unixsock = NULL; gchar *host = NULL; gchar *transport = NULL; gchar *user = NULL; gint port = 0; gchar *uri = NULL; virt_viewer_app_free_connect_info(app); if ((type = virt_viewer_extract_xpath_string(xmldesc, "string(/domain/devices/graphics/@type)")) == NULL) { virt_viewer_app_simple_message_dialog(app, _("Cannot determine the graphic type for the guest %s"), priv->domkey); goto cleanup; } if (virt_viewer_app_create_session(app, type) < 0) goto cleanup; xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@port)", type); if ((gport = virt_viewer_extract_xpath_string(xmldesc, xpath)) == NULL) { free(xpath); xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@socket)", type); if ((unixsock = virt_viewer_extract_xpath_string(xmldesc, xpath)) == NULL) { virt_viewer_app_simple_message_dialog(app, _("Cannot determine the graphic address for the guest %s"), priv->domkey); goto cleanup; } } else { if (g_str_equal(type, "spice")) { free(xpath); xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@tlsPort)", type); gtlsport = virt_viewer_extract_xpath_string(xmldesc, xpath); } free(xpath); xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@listen)", type); ghost = virt_viewer_extract_xpath_string(xmldesc, xpath); } if (ghost && gport) DEBUG_LOG("Guest graphics address is %s:%s", ghost, gport); else if (unixsock) DEBUG_LOG("Guest graphics address is %s", unixsock); uri = virConnectGetURI(priv->conn); if (virt_viewer_util_extract_host(uri, NULL, &host, &transport, &user, &port) < 0) { virt_viewer_app_simple_message_dialog(app, _("Cannot determine the host for the guest %s"), priv->domkey); goto cleanup; } /* If the XML listen attribute shows a wildcard address, we need to * throw that away since you obviously can't 'connect(2)' to that * from a remote host. Instead we fallback to the hostname used in * the libvirt URI. This isn't perfect but it is better than nothing. * If the transport is SSH, fallback to localhost as the connection * will be made from the remote end of the ssh connection. */ if (virt_viewer_replace_host(ghost)) { gchar *replacement_host = NULL; if (g_strcmp0(transport, "ssh") == 0) { replacement_host = g_strdup("localhost"); } else { replacement_host = g_strdup(host); } DEBUG_LOG("Guest graphics listen '%s' is NULL or a wildcard, replacing with '%s'", ghost ? ghost : "", replacement_host); g_free(ghost); ghost = replacement_host; } virt_viewer_app_set_connect_info(app, host, ghost, gport, gtlsport,transport, unixsock, user, port, NULL); retval = TRUE; cleanup: g_free(gport); g_free(gtlsport); g_free(ghost); g_free(unixsock); g_free(host); g_free(transport); g_free(user); g_free(type); g_free(xpath); g_free(xmldesc); g_free(uri); return retval; } static int virt_viewer_update_display(VirtViewer *self, virDomainPtr dom) { VirtViewerPrivate *priv = self->priv; VirtViewerApp *app = VIRT_VIEWER_APP(self); if (priv->dom) virDomainFree(priv->dom); priv->dom = dom; virDomainRef(priv->dom); virt_viewer_app_trace(app, "Guest %s is running, determining display", priv->domkey); g_object_set(app, "title", virDomainGetName(dom), NULL); if (!virt_viewer_app_has_session(app)) { if (!virt_viewer_extract_connect_info(self, dom)) return -1; } return 0; } static gboolean virt_viewer_open_connection(VirtViewerApp *self G_GNUC_UNUSED, int *fd) { #if defined(HAVE_SOCKETPAIR) VirtViewer *viewer = VIRT_VIEWER(self); VirtViewerPrivate *priv = viewer->priv; int pair[2]; #endif *fd = -1; #if defined(HAVE_SOCKETPAIR) if (!priv->dom) return TRUE; if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) < 0) return FALSE; if (virDomainOpenGraphics(priv->dom, 0, pair[0], VIR_DOMAIN_OPEN_GRAPHICS_SKIPAUTH) < 0) { virErrorPtr err = virGetLastError(); DEBUG_LOG("Error %s", err && err->message ? err->message : "Unknown"); close(pair[0]); close(pair[1]); return TRUE; } close(pair[0]); *fd = pair[1]; #endif return TRUE; } static int virt_viewer_domain_event(virConnectPtr conn G_GNUC_UNUSED, virDomainPtr dom, int event, int detail G_GNUC_UNUSED, void *opaque) { VirtViewer *self = opaque; VirtViewerApp *app = VIRT_VIEWER_APP(self); DEBUG_LOG("Got domain event %d %d", event, detail); if (!virt_viewer_matches_domain(self, dom)) return 0; switch (event) { case VIR_DOMAIN_EVENT_STOPPED: //virt_viewer_deactivate(self); break; case VIR_DOMAIN_EVENT_STARTED: virt_viewer_update_display(self, dom); virt_viewer_app_activate(app); break; } return 0; } static void virt_viewer_conn_event(virConnectPtr conn G_GNUC_UNUSED, int reason, void *opaque) { VirtViewer *self = opaque; VirtViewerApp *app = VIRT_VIEWER_APP(self); VirtViewerPrivate *priv = self->priv; DEBUG_LOG("Got connection event %d", reason); virConnectClose(priv->conn); priv->conn = NULL; virt_viewer_app_start_reconnect_poll(app); } static int virt_viewer_connect(VirtViewerApp *app); static int virt_viewer_initial_connect(VirtViewerApp *app) { virDomainPtr dom = NULL; virDomainInfo info; int ret = -1; VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; DEBUG_LOG("initial connect"); if (!priv->conn && virt_viewer_connect(app) < 0) { virt_viewer_app_show_status(app, _("Waiting for libvirt to start")); goto done; } virt_viewer_app_show_status(app, _("Finding guest domain")); dom = virt_viewer_lookup_domain(self); if (!dom) { if (priv->waitvm) { virt_viewer_app_show_status(app, _("Waiting for guest domain to be created")); virt_viewer_app_trace(app, "Guest %s does not yet exist, waiting for it to be created", priv->domkey); goto done; } else { virt_viewer_app_simple_message_dialog(app, _("Cannot find guest domain %s"), priv->domkey); DEBUG_LOG("Cannot find guest %s", priv->domkey); goto cleanup; } } virt_viewer_app_show_status(app, _("Checking guest domain status")); if (virDomainGetInfo(dom, &info) < 0) { DEBUG_LOG("Cannot get guest state"); goto cleanup; } if (info.state == VIR_DOMAIN_SHUTOFF) { virt_viewer_app_show_status(app, _("Waiting for guest domain to start")); } else { ret = virt_viewer_update_display(self, dom); if (ret >= 0) ret = VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->initial_connect(app); if (ret < 0) { if (priv->waitvm) { virt_viewer_app_show_status(app, _("Waiting for guest domain to start server")); virt_viewer_app_trace(app, "Guest %s has not activated its display yet, waiting for it to start", priv->domkey); } else { DEBUG_LOG("Failed to activate viewer"); goto cleanup; } } else if (ret == 0) { DEBUG_LOG("Failed to activate viewer"); ret = -1; goto cleanup; } } done: ret = 0; cleanup: if (dom) virDomainFree(dom); return ret; } static void virt_viewer_error_func (void *data G_GNUC_UNUSED, virErrorPtr error G_GNUC_UNUSED) { /* nada */ } static int virt_viewer_auth_libvirt_credentials(virConnectCredentialPtr cred, unsigned int ncred, void *cbdata) { char **username = NULL, **password = NULL; VirtViewer *app = cbdata; int i; int ret = -1; DEBUG_LOG("Got libvirt credential request for %d credential(s)", ncred); for (i = 0 ; i < ncred ; i++) { switch (cred[i].type) { case VIR_CRED_USERNAME: case VIR_CRED_AUTHNAME: username = &cred[i].result; break; case VIR_CRED_PASSPHRASE: password = &cred[i].result; break; default: DEBUG_LOG("Unsupported libvirt credential %d", cred[i].type); return -1; } } if (username || password) { VirtViewerWindow *vwin = virt_viewer_app_get_main_window(VIRT_VIEWER_APP(app)); GtkWindow *win = virt_viewer_window_get_window(vwin); ret = virt_viewer_auth_collect_credentials(win, "libvirt", app->priv->uri, username, password); if (ret < 0) goto cleanup; } else { ret = 0; } for (i = 0 ; i < ncred ; i++) { switch (cred[i].type) { case VIR_CRED_AUTHNAME: case VIR_CRED_USERNAME: case VIR_CRED_PASSPHRASE: if (cred[i].result) cred[i].resultlen = strlen(cred[i].result); else cred[i].resultlen = 0; DEBUG_LOG("Got '%s' %d %d", cred[i].result, cred[i].resultlen, cred[i].type); break; } } cleanup: DEBUG_LOG("Return %d", ret); return ret; } static int virt_viewer_connect(VirtViewerApp *app) { VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; int cred_types[] = { VIR_CRED_AUTHNAME, VIR_CRED_PASSPHRASE }; virConnectAuth auth_libvirt = { .credtype = cred_types, .ncredtype = ARRAY_CARDINALITY(cred_types), .cb = virt_viewer_auth_libvirt_credentials, .cbdata = app, }; int oflags = 0; if (!virt_viewer_app_get_attach(app)) oflags |= VIR_CONNECT_RO; DEBUG_LOG("connecting ..."); virt_viewer_app_trace(app, "Opening connection to libvirt with URI %s", priv->uri ? priv->uri : ""); priv->conn = virConnectOpenAuth(priv->uri, //virConnectAuthPtrDefault, &auth_libvirt, oflags); if (!priv->conn) { virt_viewer_app_simple_message_dialog(app, _("Unable to connect to libvirt with URI %s"), priv->uri ? priv->uri : _("[none]")); return -1; } if (virt_viewer_app_initial_connect(app) < 0) return -1; if (virConnectDomainEventRegister(priv->conn, virt_viewer_domain_event, self, NULL) < 0) priv->withEvents = FALSE; else priv->withEvents = TRUE; if (!priv->withEvents && !virt_viewer_app_is_active(app)) { DEBUG_LOG("No domain events, falling back to polling"); virt_viewer_app_start_reconnect_poll(app); } if (virConnectRegisterCloseCallback(priv->conn, virt_viewer_conn_event, self, NULL) < 0) { DEBUG_LOG("Unable to register close callback on libvirt connection"); } return 0; } static gboolean virt_viewer_start(VirtViewerApp *app) { virt_viewer_events_register(); virSetErrorFunc(NULL, virt_viewer_error_func); if (virt_viewer_connect(app) < 0) return FALSE; return VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->start(app); } VirtViewer * virt_viewer_new(const char *uri, const char *name, gint zoom, gboolean direct, gboolean attach, gboolean waitvm, gboolean reconnect, gboolean verbose, GtkWidget *container) { VirtViewer *self; VirtViewerApp *app; VirtViewerPrivate *priv; self = g_object_new(VIRT_VIEWER_TYPE, "container", container, "verbose", verbose, "guest-name", name, NULL); app = VIRT_VIEWER_APP(self); priv = self->priv; /* Set initial title based on guest name arg, which can be a ID, * UUID, or NAME string. To be replaced with the real guest name later */ g_object_set(app, "title", name, NULL); virt_viewer_window_set_zoom_level(virt_viewer_app_get_main_window(app), zoom); virt_viewer_app_set_direct(app, direct); virt_viewer_app_set_attach(app, attach); /* should probably be properties instead */ priv->uri = g_strdup(uri); priv->domkey = g_strdup(name); priv->waitvm = waitvm; priv->reconnect = reconnect; return self; } /* * Local variables: * c-indent-level: 4 * c-basic-offset: 4 * indent-tabs-mode: nil * End: */