129b0040bSGerd Hoffmann /* 229b0040bSGerd Hoffmann * Copyright (C) 2010 Red Hat, Inc. 329b0040bSGerd Hoffmann * 429b0040bSGerd Hoffmann * This program is free software; you can redistribute it and/or 529b0040bSGerd Hoffmann * modify it under the terms of the GNU General Public License as 629b0040bSGerd Hoffmann * published by the Free Software Foundation; either version 2 or 729b0040bSGerd Hoffmann * (at your option) version 3 of the License. 829b0040bSGerd Hoffmann * 929b0040bSGerd Hoffmann * This program is distributed in the hope that it will be useful, 1029b0040bSGerd Hoffmann * but WITHOUT ANY WARRANTY; without even the implied warranty of 1129b0040bSGerd Hoffmann * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1229b0040bSGerd Hoffmann * GNU General Public License for more details. 1329b0040bSGerd Hoffmann * 1429b0040bSGerd Hoffmann * You should have received a copy of the GNU General Public License 1529b0040bSGerd Hoffmann * along with this program; if not, see <http://www.gnu.org/licenses/>. 1629b0040bSGerd Hoffmann */ 1729b0040bSGerd Hoffmann 1829b0040bSGerd Hoffmann #include <spice.h> 1929b0040bSGerd Hoffmann #include <spice-experimental.h> 2029b0040bSGerd Hoffmann 216f8c63fbSGerd Hoffmann #include <netdb.h> 229c17d615SPaolo Bonzini #include "sysemu/sysemu.h" 236f8c63fbSGerd Hoffmann 2429b0040bSGerd Hoffmann #include "qemu-common.h" 2528ecbaeeSPaolo Bonzini #include "ui/qemu-spice.h" 261de7afc9SPaolo Bonzini #include "qemu/thread.h" 271de7afc9SPaolo Bonzini #include "qemu/timer.h" 281de7afc9SPaolo Bonzini #include "qemu/queue.h" 29c448e855SGerd Hoffmann #include "qemu-x509.h" 301de7afc9SPaolo Bonzini #include "qemu/sockets.h" 31d1f29646SLuiz Capitulino #include "qmp-commands.h" 327b1b5d19SPaolo Bonzini #include "qapi/qmp/qint.h" 337b1b5d19SPaolo Bonzini #include "qapi/qmp/qbool.h" 347b1b5d19SPaolo Bonzini #include "qapi/qmp/qstring.h" 357b1b5d19SPaolo Bonzini #include "qapi/qmp/qjson.h" 361de7afc9SPaolo Bonzini #include "qemu/notify.h" 37caf71f86SPaolo Bonzini #include "migration/migration.h" 3883c9089eSPaolo Bonzini #include "monitor/monitor.h" 39e866e239SGerd Hoffmann #include "hw/hw.h" 4028ecbaeeSPaolo Bonzini #include "ui/spice-display.h" 4129b0040bSGerd Hoffmann 4229b0040bSGerd Hoffmann /* core bits */ 4329b0040bSGerd Hoffmann 4429b0040bSGerd Hoffmann static SpiceServer *spice_server; 45e866e239SGerd Hoffmann static Notifier migration_state; 466f8c63fbSGerd Hoffmann static const char *auth = "spice"; 477572150cSGerd Hoffmann static char *auth_passwd; 487572150cSGerd Hoffmann static time_t auth_expires = TIME_MAX; 4961c4efe2SYonit Halperin static int spice_migration_completed; 5029b0040bSGerd Hoffmann int using_spice = 0; 5129b0040bSGerd Hoffmann 52f9ab6091SJan Kiszka static QemuThread me; 5322b626e2SGerd Hoffmann 5429b0040bSGerd Hoffmann struct SpiceTimer { 5529b0040bSGerd Hoffmann QEMUTimer *timer; 5629b0040bSGerd Hoffmann QTAILQ_ENTRY(SpiceTimer) next; 5729b0040bSGerd Hoffmann }; 5829b0040bSGerd Hoffmann static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers); 5929b0040bSGerd Hoffmann 6029b0040bSGerd Hoffmann static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) 6129b0040bSGerd Hoffmann { 6229b0040bSGerd Hoffmann SpiceTimer *timer; 6329b0040bSGerd Hoffmann 647267c094SAnthony Liguori timer = g_malloc0(sizeof(*timer)); 657bd427d8SPaolo Bonzini timer->timer = qemu_new_timer_ms(rt_clock, func, opaque); 6629b0040bSGerd Hoffmann QTAILQ_INSERT_TAIL(&timers, timer, next); 6729b0040bSGerd Hoffmann return timer; 6829b0040bSGerd Hoffmann } 6929b0040bSGerd Hoffmann 7029b0040bSGerd Hoffmann static void timer_start(SpiceTimer *timer, uint32_t ms) 7129b0040bSGerd Hoffmann { 727bd427d8SPaolo Bonzini qemu_mod_timer(timer->timer, qemu_get_clock_ms(rt_clock) + ms); 7329b0040bSGerd Hoffmann } 7429b0040bSGerd Hoffmann 7529b0040bSGerd Hoffmann static void timer_cancel(SpiceTimer *timer) 7629b0040bSGerd Hoffmann { 7729b0040bSGerd Hoffmann qemu_del_timer(timer->timer); 7829b0040bSGerd Hoffmann } 7929b0040bSGerd Hoffmann 8029b0040bSGerd Hoffmann static void timer_remove(SpiceTimer *timer) 8129b0040bSGerd Hoffmann { 8229b0040bSGerd Hoffmann qemu_del_timer(timer->timer); 8329b0040bSGerd Hoffmann qemu_free_timer(timer->timer); 8429b0040bSGerd Hoffmann QTAILQ_REMOVE(&timers, timer, next); 857267c094SAnthony Liguori g_free(timer); 8629b0040bSGerd Hoffmann } 8729b0040bSGerd Hoffmann 8829b0040bSGerd Hoffmann struct SpiceWatch { 8929b0040bSGerd Hoffmann int fd; 9029b0040bSGerd Hoffmann int event_mask; 9129b0040bSGerd Hoffmann SpiceWatchFunc func; 9229b0040bSGerd Hoffmann void *opaque; 9329b0040bSGerd Hoffmann QTAILQ_ENTRY(SpiceWatch) next; 9429b0040bSGerd Hoffmann }; 9529b0040bSGerd Hoffmann static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches); 9629b0040bSGerd Hoffmann 9729b0040bSGerd Hoffmann static void watch_read(void *opaque) 9829b0040bSGerd Hoffmann { 9929b0040bSGerd Hoffmann SpiceWatch *watch = opaque; 10029b0040bSGerd Hoffmann watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque); 10129b0040bSGerd Hoffmann } 10229b0040bSGerd Hoffmann 10329b0040bSGerd Hoffmann static void watch_write(void *opaque) 10429b0040bSGerd Hoffmann { 10529b0040bSGerd Hoffmann SpiceWatch *watch = opaque; 10629b0040bSGerd Hoffmann watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque); 10729b0040bSGerd Hoffmann } 10829b0040bSGerd Hoffmann 10929b0040bSGerd Hoffmann static void watch_update_mask(SpiceWatch *watch, int event_mask) 11029b0040bSGerd Hoffmann { 11129b0040bSGerd Hoffmann IOHandler *on_read = NULL; 11229b0040bSGerd Hoffmann IOHandler *on_write = NULL; 11329b0040bSGerd Hoffmann 11429b0040bSGerd Hoffmann watch->event_mask = event_mask; 11529b0040bSGerd Hoffmann if (watch->event_mask & SPICE_WATCH_EVENT_READ) { 11629b0040bSGerd Hoffmann on_read = watch_read; 11729b0040bSGerd Hoffmann } 11829b0040bSGerd Hoffmann if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) { 1193d6d306cSHans de Goede on_write = watch_write; 12029b0040bSGerd Hoffmann } 12129b0040bSGerd Hoffmann qemu_set_fd_handler(watch->fd, on_read, on_write, watch); 12229b0040bSGerd Hoffmann } 12329b0040bSGerd Hoffmann 12429b0040bSGerd Hoffmann static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque) 12529b0040bSGerd Hoffmann { 12629b0040bSGerd Hoffmann SpiceWatch *watch; 12729b0040bSGerd Hoffmann 1287267c094SAnthony Liguori watch = g_malloc0(sizeof(*watch)); 12929b0040bSGerd Hoffmann watch->fd = fd; 13029b0040bSGerd Hoffmann watch->func = func; 13129b0040bSGerd Hoffmann watch->opaque = opaque; 13229b0040bSGerd Hoffmann QTAILQ_INSERT_TAIL(&watches, watch, next); 13329b0040bSGerd Hoffmann 13429b0040bSGerd Hoffmann watch_update_mask(watch, event_mask); 13529b0040bSGerd Hoffmann return watch; 13629b0040bSGerd Hoffmann } 13729b0040bSGerd Hoffmann 13829b0040bSGerd Hoffmann static void watch_remove(SpiceWatch *watch) 13929b0040bSGerd Hoffmann { 14008cc67f3SGerd Hoffmann qemu_set_fd_handler(watch->fd, NULL, NULL, NULL); 14129b0040bSGerd Hoffmann QTAILQ_REMOVE(&watches, watch, next); 1427267c094SAnthony Liguori g_free(watch); 14329b0040bSGerd Hoffmann } 14429b0040bSGerd Hoffmann 145cb42a870SGerd Hoffmann typedef struct ChannelList ChannelList; 146cb42a870SGerd Hoffmann struct ChannelList { 147cb42a870SGerd Hoffmann SpiceChannelEventInfo *info; 148cb42a870SGerd Hoffmann QTAILQ_ENTRY(ChannelList) link; 149cb42a870SGerd Hoffmann }; 150cb42a870SGerd Hoffmann static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list); 151cb42a870SGerd Hoffmann 152cb42a870SGerd Hoffmann static void channel_list_add(SpiceChannelEventInfo *info) 153cb42a870SGerd Hoffmann { 154cb42a870SGerd Hoffmann ChannelList *item; 155cb42a870SGerd Hoffmann 1567267c094SAnthony Liguori item = g_malloc0(sizeof(*item)); 157cb42a870SGerd Hoffmann item->info = info; 158cb42a870SGerd Hoffmann QTAILQ_INSERT_TAIL(&channel_list, item, link); 159cb42a870SGerd Hoffmann } 160cb42a870SGerd Hoffmann 161cb42a870SGerd Hoffmann static void channel_list_del(SpiceChannelEventInfo *info) 162cb42a870SGerd Hoffmann { 163cb42a870SGerd Hoffmann ChannelList *item; 164cb42a870SGerd Hoffmann 165cb42a870SGerd Hoffmann QTAILQ_FOREACH(item, &channel_list, link) { 166cb42a870SGerd Hoffmann if (item->info != info) { 167cb42a870SGerd Hoffmann continue; 168cb42a870SGerd Hoffmann } 169cb42a870SGerd Hoffmann QTAILQ_REMOVE(&channel_list, item, link); 1707267c094SAnthony Liguori g_free(item); 171cb42a870SGerd Hoffmann return; 172cb42a870SGerd Hoffmann } 173cb42a870SGerd Hoffmann } 174cb42a870SGerd Hoffmann 1756f8c63fbSGerd Hoffmann static void add_addr_info(QDict *dict, struct sockaddr *addr, int len) 1766f8c63fbSGerd Hoffmann { 1776f8c63fbSGerd Hoffmann char host[NI_MAXHOST], port[NI_MAXSERV]; 1786f8c63fbSGerd Hoffmann const char *family; 1796f8c63fbSGerd Hoffmann 1806f8c63fbSGerd Hoffmann getnameinfo(addr, len, host, sizeof(host), port, sizeof(port), 1816f8c63fbSGerd Hoffmann NI_NUMERICHOST | NI_NUMERICSERV); 1826f8c63fbSGerd Hoffmann family = inet_strfamily(addr->sa_family); 1836f8c63fbSGerd Hoffmann 1846f8c63fbSGerd Hoffmann qdict_put(dict, "host", qstring_from_str(host)); 1856f8c63fbSGerd Hoffmann qdict_put(dict, "port", qstring_from_str(port)); 1866f8c63fbSGerd Hoffmann qdict_put(dict, "family", qstring_from_str(family)); 1876f8c63fbSGerd Hoffmann } 1886f8c63fbSGerd Hoffmann 1896f8c63fbSGerd Hoffmann static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info) 1906f8c63fbSGerd Hoffmann { 1916f8c63fbSGerd Hoffmann int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; 1926f8c63fbSGerd Hoffmann 1936f8c63fbSGerd Hoffmann qdict_put(dict, "connection-id", qint_from_int(info->connection_id)); 1946f8c63fbSGerd Hoffmann qdict_put(dict, "channel-type", qint_from_int(info->type)); 1956f8c63fbSGerd Hoffmann qdict_put(dict, "channel-id", qint_from_int(info->id)); 1966f8c63fbSGerd Hoffmann qdict_put(dict, "tls", qbool_from_int(tls)); 1976f8c63fbSGerd Hoffmann } 1986f8c63fbSGerd Hoffmann 1996f8c63fbSGerd Hoffmann static void channel_event(int event, SpiceChannelEventInfo *info) 2006f8c63fbSGerd Hoffmann { 2016f8c63fbSGerd Hoffmann static const int qevent[] = { 2026f8c63fbSGerd Hoffmann [ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED, 2036f8c63fbSGerd Hoffmann [ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED, 2046f8c63fbSGerd Hoffmann [ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED, 2056f8c63fbSGerd Hoffmann }; 2066f8c63fbSGerd Hoffmann QDict *server, *client; 2076f8c63fbSGerd Hoffmann QObject *data; 2086f8c63fbSGerd Hoffmann 20922b626e2SGerd Hoffmann /* 21022b626e2SGerd Hoffmann * Spice server might have called us from spice worker thread 21122b626e2SGerd Hoffmann * context (happens on display channel disconnects). Spice should 21222b626e2SGerd Hoffmann * not do that. It isn't that easy to fix it in spice and even 21322b626e2SGerd Hoffmann * when it is fixed we still should cover the already released 21422b626e2SGerd Hoffmann * spice versions. So detect that we've been called from another 21522b626e2SGerd Hoffmann * thread and grab the iothread lock if so before calling qemu 21622b626e2SGerd Hoffmann * functions. 21722b626e2SGerd Hoffmann */ 218f9ab6091SJan Kiszka bool need_lock = !qemu_thread_is_self(&me); 21922b626e2SGerd Hoffmann if (need_lock) { 22022b626e2SGerd Hoffmann qemu_mutex_lock_iothread(); 22122b626e2SGerd Hoffmann } 22222b626e2SGerd Hoffmann 2236f8c63fbSGerd Hoffmann client = qdict_new(); 2246f8c63fbSGerd Hoffmann server = qdict_new(); 225faa98223SYonit Halperin 226faa98223SYonit Halperin if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) { 227faa98223SYonit Halperin add_addr_info(client, (struct sockaddr *)&info->paddr_ext, 228faa98223SYonit Halperin info->plen_ext); 229faa98223SYonit Halperin add_addr_info(server, (struct sockaddr *)&info->laddr_ext, 230faa98223SYonit Halperin info->llen_ext); 231faa98223SYonit Halperin } else { 232339a475fSChristophe Fergeau error_report("spice: %s, extended address is expected", 233faa98223SYonit Halperin __func__); 234faa98223SYonit Halperin } 2356f8c63fbSGerd Hoffmann 2366f8c63fbSGerd Hoffmann if (event == SPICE_CHANNEL_EVENT_INITIALIZED) { 2376f8c63fbSGerd Hoffmann qdict_put(server, "auth", qstring_from_str(auth)); 2386f8c63fbSGerd Hoffmann add_channel_info(client, info); 239cb42a870SGerd Hoffmann channel_list_add(info); 240cb42a870SGerd Hoffmann } 241cb42a870SGerd Hoffmann if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) { 242cb42a870SGerd Hoffmann channel_list_del(info); 2436f8c63fbSGerd Hoffmann } 2446f8c63fbSGerd Hoffmann 2456f8c63fbSGerd Hoffmann data = qobject_from_jsonf("{ 'client': %p, 'server': %p }", 2466f8c63fbSGerd Hoffmann QOBJECT(client), QOBJECT(server)); 2476f8c63fbSGerd Hoffmann monitor_protocol_event(qevent[event], data); 2486f8c63fbSGerd Hoffmann qobject_decref(data); 24922b626e2SGerd Hoffmann 25022b626e2SGerd Hoffmann if (need_lock) { 25122b626e2SGerd Hoffmann qemu_mutex_unlock_iothread(); 25222b626e2SGerd Hoffmann } 2536f8c63fbSGerd Hoffmann } 2546f8c63fbSGerd Hoffmann 25529b0040bSGerd Hoffmann static SpiceCoreInterface core_interface = { 25629b0040bSGerd Hoffmann .base.type = SPICE_INTERFACE_CORE, 25729b0040bSGerd Hoffmann .base.description = "qemu core services", 25829b0040bSGerd Hoffmann .base.major_version = SPICE_INTERFACE_CORE_MAJOR, 25929b0040bSGerd Hoffmann .base.minor_version = SPICE_INTERFACE_CORE_MINOR, 26029b0040bSGerd Hoffmann 26129b0040bSGerd Hoffmann .timer_add = timer_add, 26229b0040bSGerd Hoffmann .timer_start = timer_start, 26329b0040bSGerd Hoffmann .timer_cancel = timer_cancel, 26429b0040bSGerd Hoffmann .timer_remove = timer_remove, 26529b0040bSGerd Hoffmann 26629b0040bSGerd Hoffmann .watch_add = watch_add, 26729b0040bSGerd Hoffmann .watch_update_mask = watch_update_mask, 26829b0040bSGerd Hoffmann .watch_remove = watch_remove, 2696f8c63fbSGerd Hoffmann 2706f8c63fbSGerd Hoffmann .channel_event = channel_event, 27129b0040bSGerd Hoffmann }; 27229b0040bSGerd Hoffmann 273026f773fSYonit Halperin typedef struct SpiceMigration { 274026f773fSYonit Halperin SpiceMigrateInstance sin; 275026f773fSYonit Halperin struct { 276026f773fSYonit Halperin MonitorCompletion *cb; 277026f773fSYonit Halperin void *opaque; 278026f773fSYonit Halperin } connect_complete; 279026f773fSYonit Halperin } SpiceMigration; 280026f773fSYonit Halperin 281026f773fSYonit Halperin static void migrate_connect_complete_cb(SpiceMigrateInstance *sin); 2822fdd16e2SYonit Halperin static void migrate_end_complete_cb(SpiceMigrateInstance *sin); 283026f773fSYonit Halperin 284026f773fSYonit Halperin static const SpiceMigrateInterface migrate_interface = { 285026f773fSYonit Halperin .base.type = SPICE_INTERFACE_MIGRATION, 286026f773fSYonit Halperin .base.description = "migration", 287026f773fSYonit Halperin .base.major_version = SPICE_INTERFACE_MIGRATION_MAJOR, 288026f773fSYonit Halperin .base.minor_version = SPICE_INTERFACE_MIGRATION_MINOR, 289026f773fSYonit Halperin .migrate_connect_complete = migrate_connect_complete_cb, 2902fdd16e2SYonit Halperin .migrate_end_complete = migrate_end_complete_cb, 291026f773fSYonit Halperin }; 292026f773fSYonit Halperin 293026f773fSYonit Halperin static SpiceMigration spice_migrate; 294026f773fSYonit Halperin 295026f773fSYonit Halperin static void migrate_connect_complete_cb(SpiceMigrateInstance *sin) 296026f773fSYonit Halperin { 297026f773fSYonit Halperin SpiceMigration *sm = container_of(sin, SpiceMigration, sin); 298026f773fSYonit Halperin if (sm->connect_complete.cb) { 299026f773fSYonit Halperin sm->connect_complete.cb(sm->connect_complete.opaque, NULL); 300026f773fSYonit Halperin } 301026f773fSYonit Halperin sm->connect_complete.cb = NULL; 302026f773fSYonit Halperin } 3032fdd16e2SYonit Halperin 3042fdd16e2SYonit Halperin static void migrate_end_complete_cb(SpiceMigrateInstance *sin) 3052fdd16e2SYonit Halperin { 3062fdd16e2SYonit Halperin monitor_protocol_event(QEVENT_SPICE_MIGRATE_COMPLETED, NULL); 30761c4efe2SYonit Halperin spice_migration_completed = true; 3082fdd16e2SYonit Halperin } 309026f773fSYonit Halperin 3109f04e09eSYonit Halperin /* config string parsing */ 3119f04e09eSYonit Halperin 3129f04e09eSYonit Halperin static int name2enum(const char *string, const char *table[], int entries) 3139f04e09eSYonit Halperin { 3149f04e09eSYonit Halperin int i; 3159f04e09eSYonit Halperin 3169f04e09eSYonit Halperin if (string) { 3179f04e09eSYonit Halperin for (i = 0; i < entries; i++) { 3189f04e09eSYonit Halperin if (!table[i]) { 3199f04e09eSYonit Halperin continue; 3209f04e09eSYonit Halperin } 3219f04e09eSYonit Halperin if (strcmp(string, table[i]) != 0) { 3229f04e09eSYonit Halperin continue; 3239f04e09eSYonit Halperin } 3249f04e09eSYonit Halperin return i; 3259f04e09eSYonit Halperin } 3269f04e09eSYonit Halperin } 3279f04e09eSYonit Halperin return -1; 3289f04e09eSYonit Halperin } 3299f04e09eSYonit Halperin 3309f04e09eSYonit Halperin static int parse_name(const char *string, const char *optname, 3319f04e09eSYonit Halperin const char *table[], int entries) 3329f04e09eSYonit Halperin { 3339f04e09eSYonit Halperin int value = name2enum(string, table, entries); 3349f04e09eSYonit Halperin 3359f04e09eSYonit Halperin if (value != -1) { 3369f04e09eSYonit Halperin return value; 3379f04e09eSYonit Halperin } 338339a475fSChristophe Fergeau error_report("spice: invalid %s: %s", optname, string); 3399f04e09eSYonit Halperin exit(1); 3409f04e09eSYonit Halperin } 3419f04e09eSYonit Halperin 34284a23f25SGerd Hoffmann static const char *stream_video_names[] = { 34384a23f25SGerd Hoffmann [ SPICE_STREAM_VIDEO_OFF ] = "off", 34484a23f25SGerd Hoffmann [ SPICE_STREAM_VIDEO_ALL ] = "all", 34584a23f25SGerd Hoffmann [ SPICE_STREAM_VIDEO_FILTER ] = "filter", 34684a23f25SGerd Hoffmann }; 34784a23f25SGerd Hoffmann #define parse_stream_video(_name) \ 348835cab85SChristophe Fergeau parse_name(_name, "stream video control", \ 349835cab85SChristophe Fergeau stream_video_names, ARRAY_SIZE(stream_video_names)) 35084a23f25SGerd Hoffmann 3519f04e09eSYonit Halperin static const char *compression_names[] = { 3529f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_OFF ] = "off", 3539f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz", 3549f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz", 3559f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic", 3569f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz", 3579f04e09eSYonit Halperin [ SPICE_IMAGE_COMPRESS_LZ ] = "lz", 3589f04e09eSYonit Halperin }; 3599f04e09eSYonit Halperin #define parse_compression(_name) \ 3609f04e09eSYonit Halperin parse_name(_name, "image compression", \ 3619f04e09eSYonit Halperin compression_names, ARRAY_SIZE(compression_names)) 3629f04e09eSYonit Halperin 3639f04e09eSYonit Halperin static const char *wan_compression_names[] = { 3649f04e09eSYonit Halperin [ SPICE_WAN_COMPRESSION_AUTO ] = "auto", 3659f04e09eSYonit Halperin [ SPICE_WAN_COMPRESSION_NEVER ] = "never", 3669f04e09eSYonit Halperin [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always", 3679f04e09eSYonit Halperin }; 3689f04e09eSYonit Halperin #define parse_wan_compression(_name) \ 3699f04e09eSYonit Halperin parse_name(_name, "wan compression", \ 3709f04e09eSYonit Halperin wan_compression_names, ARRAY_SIZE(wan_compression_names)) 3719f04e09eSYonit Halperin 37229b0040bSGerd Hoffmann /* functions for the rest of qemu */ 37329b0040bSGerd Hoffmann 374d1f29646SLuiz Capitulino static SpiceChannelList *qmp_query_spice_channels(void) 375cb42a870SGerd Hoffmann { 376d1f29646SLuiz Capitulino SpiceChannelList *cur_item = NULL, *head = NULL; 377d1f29646SLuiz Capitulino ChannelList *item; 378cb42a870SGerd Hoffmann 379d1f29646SLuiz Capitulino QTAILQ_FOREACH(item, &channel_list, link) { 380d1f29646SLuiz Capitulino SpiceChannelList *chan; 381d1f29646SLuiz Capitulino char host[NI_MAXHOST], port[NI_MAXSERV]; 382faa98223SYonit Halperin struct sockaddr *paddr; 383faa98223SYonit Halperin socklen_t plen; 384cb42a870SGerd Hoffmann 385d1f29646SLuiz Capitulino chan = g_malloc0(sizeof(*chan)); 386d1f29646SLuiz Capitulino chan->value = g_malloc0(sizeof(*chan->value)); 387cb42a870SGerd Hoffmann 388faa98223SYonit Halperin if (item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) { 389faa98223SYonit Halperin paddr = (struct sockaddr *)&item->info->paddr_ext; 390faa98223SYonit Halperin plen = item->info->plen_ext; 391faa98223SYonit Halperin } else { 392faa98223SYonit Halperin paddr = &item->info->paddr; 393faa98223SYonit Halperin plen = item->info->plen; 394faa98223SYonit Halperin } 395faa98223SYonit Halperin 396faa98223SYonit Halperin getnameinfo(paddr, plen, 397d1f29646SLuiz Capitulino host, sizeof(host), port, sizeof(port), 398d1f29646SLuiz Capitulino NI_NUMERICHOST | NI_NUMERICSERV); 399d1f29646SLuiz Capitulino chan->value->host = g_strdup(host); 400d1f29646SLuiz Capitulino chan->value->port = g_strdup(port); 401faa98223SYonit Halperin chan->value->family = g_strdup(inet_strfamily(paddr->sa_family)); 402cb42a870SGerd Hoffmann 403d1f29646SLuiz Capitulino chan->value->connection_id = item->info->connection_id; 404d1f29646SLuiz Capitulino chan->value->channel_type = item->info->type; 405d1f29646SLuiz Capitulino chan->value->channel_id = item->info->id; 406d1f29646SLuiz Capitulino chan->value->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; 407cb42a870SGerd Hoffmann 408d1f29646SLuiz Capitulino /* XXX: waiting for the qapi to support GSList */ 409d1f29646SLuiz Capitulino if (!cur_item) { 410d1f29646SLuiz Capitulino head = cur_item = chan; 411cb42a870SGerd Hoffmann } else { 412d1f29646SLuiz Capitulino cur_item->next = chan; 413d1f29646SLuiz Capitulino cur_item = chan; 414cb42a870SGerd Hoffmann } 415cb42a870SGerd Hoffmann } 416cb42a870SGerd Hoffmann 417d1f29646SLuiz Capitulino return head; 418d1f29646SLuiz Capitulino } 419d1f29646SLuiz Capitulino 4204d454574SPaolo Bonzini static QemuOptsList qemu_spice_opts = { 4214d454574SPaolo Bonzini .name = "spice", 4224d454574SPaolo Bonzini .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head), 4234d454574SPaolo Bonzini .desc = { 4244d454574SPaolo Bonzini { 4254d454574SPaolo Bonzini .name = "port", 4264d454574SPaolo Bonzini .type = QEMU_OPT_NUMBER, 4274d454574SPaolo Bonzini },{ 4284d454574SPaolo Bonzini .name = "tls-port", 4294d454574SPaolo Bonzini .type = QEMU_OPT_NUMBER, 4304d454574SPaolo Bonzini },{ 4314d454574SPaolo Bonzini .name = "addr", 4324d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4334d454574SPaolo Bonzini },{ 4344d454574SPaolo Bonzini .name = "ipv4", 4354d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4364d454574SPaolo Bonzini },{ 4374d454574SPaolo Bonzini .name = "ipv6", 4384d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4394d454574SPaolo Bonzini },{ 4404d454574SPaolo Bonzini .name = "password", 4414d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4424d454574SPaolo Bonzini },{ 4434d454574SPaolo Bonzini .name = "disable-ticketing", 4444d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4454d454574SPaolo Bonzini },{ 4464d454574SPaolo Bonzini .name = "disable-copy-paste", 4474d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4484d454574SPaolo Bonzini },{ 4494d454574SPaolo Bonzini .name = "sasl", 4504d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4514d454574SPaolo Bonzini },{ 4524d454574SPaolo Bonzini .name = "x509-dir", 4534d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4544d454574SPaolo Bonzini },{ 4554d454574SPaolo Bonzini .name = "x509-key-file", 4564d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4574d454574SPaolo Bonzini },{ 4584d454574SPaolo Bonzini .name = "x509-key-password", 4594d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4604d454574SPaolo Bonzini },{ 4614d454574SPaolo Bonzini .name = "x509-cert-file", 4624d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4634d454574SPaolo Bonzini },{ 4644d454574SPaolo Bonzini .name = "x509-cacert-file", 4654d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4664d454574SPaolo Bonzini },{ 4674d454574SPaolo Bonzini .name = "x509-dh-key-file", 4684d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4694d454574SPaolo Bonzini },{ 4704d454574SPaolo Bonzini .name = "tls-ciphers", 4714d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4724d454574SPaolo Bonzini },{ 4734d454574SPaolo Bonzini .name = "tls-channel", 4744d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4754d454574SPaolo Bonzini },{ 4764d454574SPaolo Bonzini .name = "plaintext-channel", 4774d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4784d454574SPaolo Bonzini },{ 4794d454574SPaolo Bonzini .name = "image-compression", 4804d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4814d454574SPaolo Bonzini },{ 4824d454574SPaolo Bonzini .name = "jpeg-wan-compression", 4834d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4844d454574SPaolo Bonzini },{ 4854d454574SPaolo Bonzini .name = "zlib-glz-wan-compression", 4864d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4874d454574SPaolo Bonzini },{ 4884d454574SPaolo Bonzini .name = "streaming-video", 4894d454574SPaolo Bonzini .type = QEMU_OPT_STRING, 4904d454574SPaolo Bonzini },{ 4914d454574SPaolo Bonzini .name = "agent-mouse", 4924d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4934d454574SPaolo Bonzini },{ 4944d454574SPaolo Bonzini .name = "playback-compression", 4954d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4964d454574SPaolo Bonzini }, { 4974d454574SPaolo Bonzini .name = "seamless-migration", 4984d454574SPaolo Bonzini .type = QEMU_OPT_BOOL, 4994d454574SPaolo Bonzini }, 5004d454574SPaolo Bonzini { /* end of list */ } 5014d454574SPaolo Bonzini }, 5024d454574SPaolo Bonzini }; 5034d454574SPaolo Bonzini 504d1f29646SLuiz Capitulino SpiceInfo *qmp_query_spice(Error **errp) 505cb42a870SGerd Hoffmann { 506cb42a870SGerd Hoffmann QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 507cb42a870SGerd Hoffmann int port, tls_port; 508d1f29646SLuiz Capitulino const char *addr; 509d1f29646SLuiz Capitulino SpiceInfo *info; 5108df0c7e8SAlon Levy char version_string[20]; /* 12 = |255.255.255\0| is the max */ 511cb42a870SGerd Hoffmann 512d1f29646SLuiz Capitulino info = g_malloc0(sizeof(*info)); 513d1f29646SLuiz Capitulino 5143bb781f3SAlon Levy if (!spice_server || !opts) { 515d1f29646SLuiz Capitulino info->enabled = false; 516d1f29646SLuiz Capitulino return info; 517cb42a870SGerd Hoffmann } 518cb42a870SGerd Hoffmann 519d1f29646SLuiz Capitulino info->enabled = true; 52061c4efe2SYonit Halperin info->migrated = spice_migration_completed; 521d1f29646SLuiz Capitulino 522cb42a870SGerd Hoffmann addr = qemu_opt_get(opts, "addr"); 523cb42a870SGerd Hoffmann port = qemu_opt_get_number(opts, "port", 0); 524cb42a870SGerd Hoffmann tls_port = qemu_opt_get_number(opts, "tls-port", 0); 525cb42a870SGerd Hoffmann 526d1f29646SLuiz Capitulino info->has_auth = true; 527d1f29646SLuiz Capitulino info->auth = g_strdup(auth); 528d1f29646SLuiz Capitulino 529d1f29646SLuiz Capitulino info->has_host = true; 530d1f29646SLuiz Capitulino info->host = g_strdup(addr ? addr : "0.0.0.0"); 531d1f29646SLuiz Capitulino 532d1f29646SLuiz Capitulino info->has_compiled_version = true; 5338df0c7e8SAlon Levy snprintf(version_string, sizeof(version_string), "%d.%d.%d", 5348df0c7e8SAlon Levy (SPICE_SERVER_VERSION & 0xff0000) >> 16, 5358df0c7e8SAlon Levy (SPICE_SERVER_VERSION & 0xff00) >> 8, 5368df0c7e8SAlon Levy SPICE_SERVER_VERSION & 0xff); 537d1f29646SLuiz Capitulino info->compiled_version = g_strdup(version_string); 538d1f29646SLuiz Capitulino 539cb42a870SGerd Hoffmann if (port) { 540d1f29646SLuiz Capitulino info->has_port = true; 541d1f29646SLuiz Capitulino info->port = port; 542cb42a870SGerd Hoffmann } 543cb42a870SGerd Hoffmann if (tls_port) { 544d1f29646SLuiz Capitulino info->has_tls_port = true; 545d1f29646SLuiz Capitulino info->tls_port = tls_port; 546cb42a870SGerd Hoffmann } 547cb42a870SGerd Hoffmann 5484efee029SAlon Levy info->mouse_mode = spice_server_is_server_mouse(spice_server) ? 5494efee029SAlon Levy SPICE_QUERY_MOUSE_MODE_SERVER : 5504efee029SAlon Levy SPICE_QUERY_MOUSE_MODE_CLIENT; 55167be6726SGerd Hoffmann 552d1f29646SLuiz Capitulino /* for compatibility with the original command */ 553d1f29646SLuiz Capitulino info->has_channels = true; 554d1f29646SLuiz Capitulino info->channels = qmp_query_spice_channels(); 555d1f29646SLuiz Capitulino 556d1f29646SLuiz Capitulino return info; 557cb42a870SGerd Hoffmann } 558cb42a870SGerd Hoffmann 5599e8dd451SJan Kiszka static void migration_state_notifier(Notifier *notifier, void *data) 560e866e239SGerd Hoffmann { 5617073693bSJuan Quintela MigrationState *s = data; 562e866e239SGerd Hoffmann 563026f773fSYonit Halperin if (migration_is_active(s)) { 564026f773fSYonit Halperin spice_server_migrate_start(spice_server); 565026f773fSYonit Halperin } else if (migration_has_finished(s)) { 566026f773fSYonit Halperin spice_server_migrate_end(spice_server, true); 567026f773fSYonit Halperin } else if (migration_has_failed(s)) { 568026f773fSYonit Halperin spice_server_migrate_end(spice_server, false); 569e866e239SGerd Hoffmann } 570e866e239SGerd Hoffmann } 571e866e239SGerd Hoffmann 572e866e239SGerd Hoffmann int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, 573edc5cb1aSYonit Halperin const char *subject, 574edc5cb1aSYonit Halperin MonitorCompletion *cb, void *opaque) 575e866e239SGerd Hoffmann { 576edc5cb1aSYonit Halperin int ret; 57767be6726SGerd Hoffmann 578026f773fSYonit Halperin spice_migrate.connect_complete.cb = cb; 579026f773fSYonit Halperin spice_migrate.connect_complete.opaque = opaque; 580026f773fSYonit Halperin ret = spice_server_migrate_connect(spice_server, hostname, 581026f773fSYonit Halperin port, tls_port, subject); 582edc5cb1aSYonit Halperin return ret; 583e866e239SGerd Hoffmann } 584e866e239SGerd Hoffmann 58517b6dea0SGerd Hoffmann static int add_channel(const char *name, const char *value, void *opaque) 58617b6dea0SGerd Hoffmann { 58717b6dea0SGerd Hoffmann int security = 0; 58817b6dea0SGerd Hoffmann int rc; 58917b6dea0SGerd Hoffmann 59017b6dea0SGerd Hoffmann if (strcmp(name, "tls-channel") == 0) { 59135c63329SChristophe Fergeau int *tls_port = opaque; 59235c63329SChristophe Fergeau if (!*tls_port) { 59335c63329SChristophe Fergeau error_report("spice: tried to setup tls-channel" 59435c63329SChristophe Fergeau " without specifying a TLS port"); 59535c63329SChristophe Fergeau exit(1); 59635c63329SChristophe Fergeau } 59717b6dea0SGerd Hoffmann security = SPICE_CHANNEL_SECURITY_SSL; 59817b6dea0SGerd Hoffmann } 59917b6dea0SGerd Hoffmann if (strcmp(name, "plaintext-channel") == 0) { 60017b6dea0SGerd Hoffmann security = SPICE_CHANNEL_SECURITY_NONE; 60117b6dea0SGerd Hoffmann } 60217b6dea0SGerd Hoffmann if (security == 0) { 60317b6dea0SGerd Hoffmann return 0; 60417b6dea0SGerd Hoffmann } 60517b6dea0SGerd Hoffmann if (strcmp(value, "default") == 0) { 60617b6dea0SGerd Hoffmann rc = spice_server_set_channel_security(spice_server, NULL, security); 60717b6dea0SGerd Hoffmann } else { 60817b6dea0SGerd Hoffmann rc = spice_server_set_channel_security(spice_server, value, security); 60917b6dea0SGerd Hoffmann } 61017b6dea0SGerd Hoffmann if (rc != 0) { 611339a475fSChristophe Fergeau error_report("spice: failed to set channel security for %s", value); 61217b6dea0SGerd Hoffmann exit(1); 61317b6dea0SGerd Hoffmann } 61417b6dea0SGerd Hoffmann return 0; 61517b6dea0SGerd Hoffmann } 61617b6dea0SGerd Hoffmann 617f5bb039cSYonit Halperin static void vm_change_state_handler(void *opaque, int running, 618f5bb039cSYonit Halperin RunState state) 619f5bb039cSYonit Halperin { 620f5bb039cSYonit Halperin if (running) { 62171d388d4SYonit Halperin qemu_spice_display_start(); 622f5bb039cSYonit Halperin spice_server_vm_start(spice_server); 623f5bb039cSYonit Halperin } else { 624f5bb039cSYonit Halperin spice_server_vm_stop(spice_server); 62571d388d4SYonit Halperin qemu_spice_display_stop(); 626f5bb039cSYonit Halperin } 627f5bb039cSYonit Halperin } 628f5bb039cSYonit Halperin 62929b0040bSGerd Hoffmann void qemu_spice_init(void) 63029b0040bSGerd Hoffmann { 63129b0040bSGerd Hoffmann QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 632333b0eebSGerd Hoffmann const char *password, *str, *x509_dir, *addr, 633c448e855SGerd Hoffmann *x509_key_password = NULL, 634c448e855SGerd Hoffmann *x509_dh_file = NULL, 635c448e855SGerd Hoffmann *tls_ciphers = NULL; 636c448e855SGerd Hoffmann char *x509_key_file = NULL, 637c448e855SGerd Hoffmann *x509_cert_file = NULL, 638c448e855SGerd Hoffmann *x509_cacert_file = NULL; 639f61d6960SGerd Hoffmann int port, tls_port, len, addr_flags; 6409f04e09eSYonit Halperin spice_image_compression_t compression; 6419f04e09eSYonit Halperin spice_wan_compression_t wan_compr; 6428c957053SYonit Halperin bool seamless_migration; 64329b0040bSGerd Hoffmann 644f9ab6091SJan Kiszka qemu_thread_get_self(&me); 64522b626e2SGerd Hoffmann 64629b0040bSGerd Hoffmann if (!opts) { 64729b0040bSGerd Hoffmann return; 64829b0040bSGerd Hoffmann } 64929b0040bSGerd Hoffmann port = qemu_opt_get_number(opts, "port", 0); 650c448e855SGerd Hoffmann tls_port = qemu_opt_get_number(opts, "tls-port", 0); 651c448e855SGerd Hoffmann if (!port && !tls_port) { 652339a475fSChristophe Fergeau error_report("neither port nor tls-port specified for spice"); 653df9cb669SGerd Hoffmann exit(1); 654df9cb669SGerd Hoffmann } 655df9cb669SGerd Hoffmann if (port < 0 || port > 65535) { 656339a475fSChristophe Fergeau error_report("spice port is out of range"); 657df9cb669SGerd Hoffmann exit(1); 658df9cb669SGerd Hoffmann } 659df9cb669SGerd Hoffmann if (tls_port < 0 || tls_port > 65535) { 660339a475fSChristophe Fergeau error_report("spice tls-port is out of range"); 661df9cb669SGerd Hoffmann exit(1); 66229b0040bSGerd Hoffmann } 66329b0040bSGerd Hoffmann password = qemu_opt_get(opts, "password"); 66429b0040bSGerd Hoffmann 665c448e855SGerd Hoffmann if (tls_port) { 666c448e855SGerd Hoffmann x509_dir = qemu_opt_get(opts, "x509-dir"); 667c448e855SGerd Hoffmann if (NULL == x509_dir) { 668c448e855SGerd Hoffmann x509_dir = "."; 669c448e855SGerd Hoffmann } 670c448e855SGerd Hoffmann len = strlen(x509_dir) + 32; 671c448e855SGerd Hoffmann 672c448e855SGerd Hoffmann str = qemu_opt_get(opts, "x509-key-file"); 673c448e855SGerd Hoffmann if (str) { 6747267c094SAnthony Liguori x509_key_file = g_strdup(str); 675c448e855SGerd Hoffmann } else { 6767267c094SAnthony Liguori x509_key_file = g_malloc(len); 677c448e855SGerd Hoffmann snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE); 678c448e855SGerd Hoffmann } 679c448e855SGerd Hoffmann 680c448e855SGerd Hoffmann str = qemu_opt_get(opts, "x509-cert-file"); 681c448e855SGerd Hoffmann if (str) { 6827267c094SAnthony Liguori x509_cert_file = g_strdup(str); 683c448e855SGerd Hoffmann } else { 6847267c094SAnthony Liguori x509_cert_file = g_malloc(len); 685c448e855SGerd Hoffmann snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE); 686c448e855SGerd Hoffmann } 687c448e855SGerd Hoffmann 688c448e855SGerd Hoffmann str = qemu_opt_get(opts, "x509-cacert-file"); 689c448e855SGerd Hoffmann if (str) { 6907267c094SAnthony Liguori x509_cacert_file = g_strdup(str); 691c448e855SGerd Hoffmann } else { 6927267c094SAnthony Liguori x509_cacert_file = g_malloc(len); 693c448e855SGerd Hoffmann snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE); 694c448e855SGerd Hoffmann } 695c448e855SGerd Hoffmann 696c448e855SGerd Hoffmann x509_key_password = qemu_opt_get(opts, "x509-key-password"); 6979995c0b7SLei Li x509_dh_file = qemu_opt_get(opts, "x509-dh-key-file"); 698c448e855SGerd Hoffmann tls_ciphers = qemu_opt_get(opts, "tls-ciphers"); 699c448e855SGerd Hoffmann } 700c448e855SGerd Hoffmann 701333b0eebSGerd Hoffmann addr = qemu_opt_get(opts, "addr"); 702333b0eebSGerd Hoffmann addr_flags = 0; 703333b0eebSGerd Hoffmann if (qemu_opt_get_bool(opts, "ipv4", 0)) { 704333b0eebSGerd Hoffmann addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY; 705333b0eebSGerd Hoffmann } else if (qemu_opt_get_bool(opts, "ipv6", 0)) { 706333b0eebSGerd Hoffmann addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY; 707333b0eebSGerd Hoffmann } 708333b0eebSGerd Hoffmann 70929b0040bSGerd Hoffmann spice_server = spice_server_new(); 710333b0eebSGerd Hoffmann spice_server_set_addr(spice_server, addr ? addr : "", addr_flags); 711c448e855SGerd Hoffmann if (port) { 71229b0040bSGerd Hoffmann spice_server_set_port(spice_server, port); 713c448e855SGerd Hoffmann } 714c448e855SGerd Hoffmann if (tls_port) { 715c448e855SGerd Hoffmann spice_server_set_tls(spice_server, tls_port, 716c448e855SGerd Hoffmann x509_cacert_file, 717c448e855SGerd Hoffmann x509_cert_file, 718c448e855SGerd Hoffmann x509_key_file, 719c448e855SGerd Hoffmann x509_key_password, 720c448e855SGerd Hoffmann x509_dh_file, 721c448e855SGerd Hoffmann tls_ciphers); 722c448e855SGerd Hoffmann } 72329b0040bSGerd Hoffmann if (password) { 72429b0040bSGerd Hoffmann spice_server_set_ticket(spice_server, password, 0, 0, 0); 72529b0040bSGerd Hoffmann } 72648b3ed0aSMarc-André Lureau if (qemu_opt_get_bool(opts, "sasl", 0)) { 72748b3ed0aSMarc-André Lureau if (spice_server_set_sasl_appname(spice_server, "qemu") == -1 || 72848b3ed0aSMarc-André Lureau spice_server_set_sasl(spice_server, 1) == -1) { 729339a475fSChristophe Fergeau error_report("spice: failed to enable sasl"); 73048b3ed0aSMarc-André Lureau exit(1); 73148b3ed0aSMarc-André Lureau } 73248b3ed0aSMarc-André Lureau } 73329b0040bSGerd Hoffmann if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) { 7346f8c63fbSGerd Hoffmann auth = "none"; 73529b0040bSGerd Hoffmann spice_server_set_noauth(spice_server); 73629b0040bSGerd Hoffmann } 73729b0040bSGerd Hoffmann 738d4970b07SHans de Goede if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) { 739d4970b07SHans de Goede spice_server_set_agent_copypaste(spice_server, false); 740d4970b07SHans de Goede } 741d4970b07SHans de Goede 7429f04e09eSYonit Halperin compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ; 7439f04e09eSYonit Halperin str = qemu_opt_get(opts, "image-compression"); 7449f04e09eSYonit Halperin if (str) { 7459f04e09eSYonit Halperin compression = parse_compression(str); 7469f04e09eSYonit Halperin } 7479f04e09eSYonit Halperin spice_server_set_image_compression(spice_server, compression); 7489f04e09eSYonit Halperin 7499f04e09eSYonit Halperin wan_compr = SPICE_WAN_COMPRESSION_AUTO; 7509f04e09eSYonit Halperin str = qemu_opt_get(opts, "jpeg-wan-compression"); 7519f04e09eSYonit Halperin if (str) { 7529f04e09eSYonit Halperin wan_compr = parse_wan_compression(str); 7539f04e09eSYonit Halperin } 7549f04e09eSYonit Halperin spice_server_set_jpeg_compression(spice_server, wan_compr); 7559f04e09eSYonit Halperin 7569f04e09eSYonit Halperin wan_compr = SPICE_WAN_COMPRESSION_AUTO; 7579f04e09eSYonit Halperin str = qemu_opt_get(opts, "zlib-glz-wan-compression"); 7589f04e09eSYonit Halperin if (str) { 7599f04e09eSYonit Halperin wan_compr = parse_wan_compression(str); 7609f04e09eSYonit Halperin } 7619f04e09eSYonit Halperin spice_server_set_zlib_glz_compression(spice_server, wan_compr); 76229b0040bSGerd Hoffmann 76384a23f25SGerd Hoffmann str = qemu_opt_get(opts, "streaming-video"); 76484a23f25SGerd Hoffmann if (str) { 765f61d6960SGerd Hoffmann int streaming_video = parse_stream_video(str); 76684a23f25SGerd Hoffmann spice_server_set_streaming_video(spice_server, streaming_video); 76784a23f25SGerd Hoffmann } 76884a23f25SGerd Hoffmann 76984a23f25SGerd Hoffmann spice_server_set_agent_mouse 77084a23f25SGerd Hoffmann (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); 77184a23f25SGerd Hoffmann spice_server_set_playback_compression 77284a23f25SGerd Hoffmann (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1)); 77384a23f25SGerd Hoffmann 77435c63329SChristophe Fergeau qemu_opt_foreach(opts, add_channel, &tls_port, 0); 77517b6dea0SGerd Hoffmann 776d0638b18SMarc-André Lureau spice_server_set_name(spice_server, qemu_name); 777d0638b18SMarc-André Lureau spice_server_set_uuid(spice_server, qemu_uuid); 778d0638b18SMarc-André Lureau 7798c957053SYonit Halperin seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0); 7808c957053SYonit Halperin spice_server_set_seamless_migration(spice_server, seamless_migration); 781fba810f1SGerd Hoffmann if (0 != spice_server_init(spice_server, &core_interface)) { 782339a475fSChristophe Fergeau error_report("failed to initialize spice server"); 783fba810f1SGerd Hoffmann exit(1); 784fba810f1SGerd Hoffmann }; 78529b0040bSGerd Hoffmann using_spice = 1; 786864401c2SGerd Hoffmann 787e866e239SGerd Hoffmann migration_state.notify = migration_state_notifier; 788e866e239SGerd Hoffmann add_migration_state_change_notifier(&migration_state); 789026f773fSYonit Halperin spice_migrate.sin.base.sif = &migrate_interface.base; 790026f773fSYonit Halperin spice_migrate.connect_complete.cb = NULL; 791026f773fSYonit Halperin qemu_spice_add_interface(&spice_migrate.sin.base); 792e866e239SGerd Hoffmann 793864401c2SGerd Hoffmann qemu_spice_input_init(); 7943e313753SGerd Hoffmann qemu_spice_audio_init(); 795c448e855SGerd Hoffmann 796bfb82a28SStefan Hajnoczi qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); 797f5bb039cSYonit Halperin 7987267c094SAnthony Liguori g_free(x509_key_file); 7997267c094SAnthony Liguori g_free(x509_cert_file); 8007267c094SAnthony Liguori g_free(x509_cacert_file); 801afd0b409SMarc-André Lureau 802afd0b409SMarc-André Lureau #if SPICE_SERVER_VERSION >= 0x000c02 803afd0b409SMarc-André Lureau qemu_spice_register_ports(); 804afd0b409SMarc-André Lureau #endif 80529b0040bSGerd Hoffmann } 80629b0040bSGerd Hoffmann 80729b0040bSGerd Hoffmann int qemu_spice_add_interface(SpiceBaseInstance *sin) 80829b0040bSGerd Hoffmann { 809a19cbfb3SGerd Hoffmann if (!spice_server) { 810a19cbfb3SGerd Hoffmann if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) { 811339a475fSChristophe Fergeau error_report("Oops: spice configured but not active"); 812a19cbfb3SGerd Hoffmann exit(1); 813a19cbfb3SGerd Hoffmann } 814a19cbfb3SGerd Hoffmann /* 815a19cbfb3SGerd Hoffmann * Create a spice server instance. 816a19cbfb3SGerd Hoffmann * It does *not* listen on the network. 817a19cbfb3SGerd Hoffmann * It handles QXL local rendering only. 818a19cbfb3SGerd Hoffmann * 819a19cbfb3SGerd Hoffmann * With a command line like '-vnc :0 -vga qxl' you'll end up here. 820a19cbfb3SGerd Hoffmann */ 821a19cbfb3SGerd Hoffmann spice_server = spice_server_new(); 822a19cbfb3SGerd Hoffmann spice_server_init(spice_server, &core_interface); 823bfb82a28SStefan Hajnoczi qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); 824a19cbfb3SGerd Hoffmann } 82571d388d4SYonit Halperin 82629b0040bSGerd Hoffmann return spice_server_add_interface(spice_server, sin); 82729b0040bSGerd Hoffmann } 82829b0040bSGerd Hoffmann 8297572150cSGerd Hoffmann static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) 8307572150cSGerd Hoffmann { 8317572150cSGerd Hoffmann time_t lifetime, now = time(NULL); 8327572150cSGerd Hoffmann char *passwd; 8337572150cSGerd Hoffmann 8347572150cSGerd Hoffmann if (now < auth_expires) { 8357572150cSGerd Hoffmann passwd = auth_passwd; 8367572150cSGerd Hoffmann lifetime = (auth_expires - now); 8377572150cSGerd Hoffmann if (lifetime > INT_MAX) { 8387572150cSGerd Hoffmann lifetime = INT_MAX; 8397572150cSGerd Hoffmann } 8407572150cSGerd Hoffmann } else { 8417572150cSGerd Hoffmann passwd = NULL; 8427572150cSGerd Hoffmann lifetime = 1; 8437572150cSGerd Hoffmann } 8447572150cSGerd Hoffmann return spice_server_set_ticket(spice_server, passwd, lifetime, 8457572150cSGerd Hoffmann fail_if_conn, disconnect_if_conn); 8467572150cSGerd Hoffmann } 8477572150cSGerd Hoffmann 8487572150cSGerd Hoffmann int qemu_spice_set_passwd(const char *passwd, 8497572150cSGerd Hoffmann bool fail_if_conn, bool disconnect_if_conn) 8507572150cSGerd Hoffmann { 851*fd3bea3fSMarkus Armbruster g_free(auth_passwd); 852*fd3bea3fSMarkus Armbruster auth_passwd = g_strdup(passwd); 8537572150cSGerd Hoffmann return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn); 8547572150cSGerd Hoffmann } 8557572150cSGerd Hoffmann 8567572150cSGerd Hoffmann int qemu_spice_set_pw_expire(time_t expires) 8577572150cSGerd Hoffmann { 8587572150cSGerd Hoffmann auth_expires = expires; 8597572150cSGerd Hoffmann return qemu_spice_set_ticket(false, false); 8607572150cSGerd Hoffmann } 8617572150cSGerd Hoffmann 862f1f5f407SDaniel P. Berrange int qemu_spice_display_add_client(int csock, int skipauth, int tls) 863f1f5f407SDaniel P. Berrange { 864f1f5f407SDaniel P. Berrange if (tls) { 865f1f5f407SDaniel P. Berrange return spice_server_add_ssl_client(spice_server, csock, skipauth); 866f1f5f407SDaniel P. Berrange } else { 867f1f5f407SDaniel P. Berrange return spice_server_add_client(spice_server, csock, skipauth); 868f1f5f407SDaniel P. Berrange } 869f1f5f407SDaniel P. Berrange } 870f1f5f407SDaniel P. Berrange 87129b0040bSGerd Hoffmann static void spice_register_config(void) 87229b0040bSGerd Hoffmann { 87329b0040bSGerd Hoffmann qemu_add_opts(&qemu_spice_opts); 87429b0040bSGerd Hoffmann } 87529b0040bSGerd Hoffmann machine_init(spice_register_config); 876