1 /* 2 * Copyright (C) 2010 Red Hat, Inc. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License as 6 * published by the Free Software Foundation; either version 2 or 7 * (at your option) version 3 of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "qemu/osdep.h" 19 #include <spice.h> 20 21 #include "system/system.h" 22 #include "system/runstate.h" 23 #include "ui/qemu-spice.h" 24 #include "qemu/error-report.h" 25 #include "qemu/main-loop.h" 26 #include "qemu/module.h" 27 #include "qemu/thread.h" 28 #include "qemu/timer.h" 29 #include "qemu/queue.h" 30 #include "qemu-x509.h" 31 #include "qemu/sockets.h" 32 #include "qapi/error.h" 33 #include "qapi/qapi-commands-ui.h" 34 #include "qapi/qapi-events-ui.h" 35 #include "qemu/notify.h" 36 #include "qemu/option.h" 37 #include "crypto/secret_common.h" 38 #include "migration/misc.h" 39 #include "hw/pci/pci_bus.h" 40 #include "ui/spice-display.h" 41 42 /* core bits */ 43 44 static SpiceServer *spice_server; 45 static NotifierWithReturn migration_state; 46 static const char *auth = "spice"; 47 static char *auth_passwd; 48 static time_t auth_expires = TIME_MAX; 49 static int spice_migration_completed; 50 static int spice_display_is_running; 51 static int spice_have_target_host; 52 53 static QemuThread me; 54 55 struct SpiceTimer { 56 QEMUTimer *timer; 57 }; 58 59 #define DEFAULT_MAX_REFRESH_RATE 30 60 61 static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) 62 { 63 SpiceTimer *timer; 64 65 timer = g_malloc0(sizeof(*timer)); 66 timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque); 67 return timer; 68 } 69 70 static void timer_start(SpiceTimer *timer, uint32_t ms) 71 { 72 timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms); 73 } 74 75 static void timer_cancel(SpiceTimer *timer) 76 { 77 timer_del(timer->timer); 78 } 79 80 static void timer_remove(SpiceTimer *timer) 81 { 82 timer_free(timer->timer); 83 g_free(timer); 84 } 85 86 struct SpiceWatch { 87 int fd; 88 SpiceWatchFunc func; 89 void *opaque; 90 }; 91 92 static void watch_read(void *opaque) 93 { 94 SpiceWatch *watch = opaque; 95 int fd = watch->fd; 96 97 #ifdef WIN32 98 fd = _get_osfhandle(fd); 99 #endif 100 watch->func(fd, SPICE_WATCH_EVENT_READ, watch->opaque); 101 } 102 103 static void watch_write(void *opaque) 104 { 105 SpiceWatch *watch = opaque; 106 int fd = watch->fd; 107 108 #ifdef WIN32 109 fd = _get_osfhandle(fd); 110 #endif 111 watch->func(fd, SPICE_WATCH_EVENT_WRITE, watch->opaque); 112 } 113 114 static void watch_update_mask(SpiceWatch *watch, int event_mask) 115 { 116 IOHandler *on_read = NULL; 117 IOHandler *on_write = NULL; 118 119 if (event_mask & SPICE_WATCH_EVENT_READ) { 120 on_read = watch_read; 121 } 122 if (event_mask & SPICE_WATCH_EVENT_WRITE) { 123 on_write = watch_write; 124 } 125 qemu_set_fd_handler(watch->fd, on_read, on_write, watch); 126 } 127 128 static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque) 129 { 130 SpiceWatch *watch; 131 132 #ifdef WIN32 133 fd = _open_osfhandle(fd, _O_BINARY); 134 if (fd < 0) { 135 error_setg_win32(&error_warn, WSAGetLastError(), "Couldn't associate a FD with the SOCKET"); 136 return NULL; 137 } 138 #endif 139 140 watch = g_malloc0(sizeof(*watch)); 141 watch->fd = fd; 142 watch->func = func; 143 watch->opaque = opaque; 144 145 watch_update_mask(watch, event_mask); 146 return watch; 147 } 148 149 static void watch_remove(SpiceWatch *watch) 150 { 151 qemu_set_fd_handler(watch->fd, NULL, NULL, NULL); 152 #ifdef WIN32 153 /* SOCKET is owned by spice */ 154 qemu_close_socket_osfhandle(watch->fd); 155 #endif 156 g_free(watch); 157 } 158 159 typedef struct ChannelList ChannelList; 160 struct ChannelList { 161 SpiceChannelEventInfo *info; 162 QTAILQ_ENTRY(ChannelList) link; 163 }; 164 static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list); 165 166 static void channel_list_add(SpiceChannelEventInfo *info) 167 { 168 ChannelList *item; 169 170 item = g_malloc0(sizeof(*item)); 171 item->info = info; 172 QTAILQ_INSERT_TAIL(&channel_list, item, link); 173 } 174 175 static void channel_list_del(SpiceChannelEventInfo *info) 176 { 177 ChannelList *item; 178 179 QTAILQ_FOREACH(item, &channel_list, link) { 180 if (item->info != info) { 181 continue; 182 } 183 QTAILQ_REMOVE(&channel_list, item, link); 184 g_free(item); 185 return; 186 } 187 } 188 189 static void add_addr_info(SpiceBasicInfo *info, struct sockaddr *addr, int len) 190 { 191 char host[NI_MAXHOST], port[NI_MAXSERV]; 192 193 getnameinfo(addr, len, host, sizeof(host), port, sizeof(port), 194 NI_NUMERICHOST | NI_NUMERICSERV); 195 196 info->host = g_strdup(host); 197 info->port = g_strdup(port); 198 info->family = inet_netfamily(addr->sa_family); 199 } 200 201 static void add_channel_info(SpiceChannel *sc, SpiceChannelEventInfo *info) 202 { 203 int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; 204 205 sc->connection_id = info->connection_id; 206 sc->channel_type = info->type; 207 sc->channel_id = info->id; 208 sc->tls = !!tls; 209 } 210 211 static void channel_event(int event, SpiceChannelEventInfo *info) 212 { 213 SpiceServerInfo *server = g_malloc0(sizeof(*server)); 214 SpiceChannel *client = g_malloc0(sizeof(*client)); 215 216 /* 217 * Spice server might have called us from spice worker thread 218 * context (happens on display channel disconnects). Spice should 219 * not do that. It isn't that easy to fix it in spice and even 220 * when it is fixed we still should cover the already released 221 * spice versions. So detect that we've been called from another 222 * thread and grab the BQL if so before calling qemu 223 * functions. 224 */ 225 bool need_lock = !qemu_thread_is_self(&me); 226 if (need_lock) { 227 bql_lock(); 228 } 229 230 if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) { 231 add_addr_info(qapi_SpiceChannel_base(client), 232 (struct sockaddr *)&info->paddr_ext, 233 info->plen_ext); 234 add_addr_info(qapi_SpiceServerInfo_base(server), 235 (struct sockaddr *)&info->laddr_ext, 236 info->llen_ext); 237 } else { 238 error_report("spice: %s, extended address is expected", 239 __func__); 240 } 241 242 switch (event) { 243 case SPICE_CHANNEL_EVENT_CONNECTED: 244 qapi_event_send_spice_connected(qapi_SpiceServerInfo_base(server), 245 qapi_SpiceChannel_base(client)); 246 break; 247 case SPICE_CHANNEL_EVENT_INITIALIZED: 248 if (auth) { 249 server->auth = g_strdup(auth); 250 } 251 add_channel_info(client, info); 252 channel_list_add(info); 253 qapi_event_send_spice_initialized(server, client); 254 break; 255 case SPICE_CHANNEL_EVENT_DISCONNECTED: 256 channel_list_del(info); 257 qapi_event_send_spice_disconnected(qapi_SpiceServerInfo_base(server), 258 qapi_SpiceChannel_base(client)); 259 break; 260 default: 261 break; 262 } 263 264 if (need_lock) { 265 bql_unlock(); 266 } 267 268 qapi_free_SpiceServerInfo(server); 269 qapi_free_SpiceChannel(client); 270 } 271 272 static SpiceCoreInterface core_interface = { 273 .base.type = SPICE_INTERFACE_CORE, 274 .base.description = "qemu core services", 275 .base.major_version = SPICE_INTERFACE_CORE_MAJOR, 276 .base.minor_version = SPICE_INTERFACE_CORE_MINOR, 277 278 .timer_add = timer_add, 279 .timer_start = timer_start, 280 .timer_cancel = timer_cancel, 281 .timer_remove = timer_remove, 282 283 .watch_add = watch_add, 284 .watch_update_mask = watch_update_mask, 285 .watch_remove = watch_remove, 286 287 .channel_event = channel_event, 288 }; 289 290 static void migrate_connect_complete_cb(SpiceMigrateInstance *sin); 291 static void migrate_end_complete_cb(SpiceMigrateInstance *sin); 292 293 static const SpiceMigrateInterface migrate_interface = { 294 .base.type = SPICE_INTERFACE_MIGRATION, 295 .base.description = "migration", 296 .base.major_version = SPICE_INTERFACE_MIGRATION_MAJOR, 297 .base.minor_version = SPICE_INTERFACE_MIGRATION_MINOR, 298 .migrate_connect_complete = migrate_connect_complete_cb, 299 .migrate_end_complete = migrate_end_complete_cb, 300 }; 301 302 static SpiceMigrateInstance spice_migrate; 303 304 static void migrate_connect_complete_cb(SpiceMigrateInstance *sin) 305 { 306 /* nothing, but libspice-server expects this cb being present. */ 307 } 308 309 static void migrate_end_complete_cb(SpiceMigrateInstance *sin) 310 { 311 qapi_event_send_spice_migrate_completed(); 312 spice_migration_completed = true; 313 } 314 315 /* config string parsing */ 316 317 static int name2enum(const char *string, const char *table[], int entries) 318 { 319 int i; 320 321 if (string) { 322 for (i = 0; i < entries; i++) { 323 if (!table[i]) { 324 continue; 325 } 326 if (strcmp(string, table[i]) != 0) { 327 continue; 328 } 329 return i; 330 } 331 } 332 return -1; 333 } 334 335 static int parse_name(const char *string, const char *optname, 336 const char *table[], int entries) 337 { 338 int value = name2enum(string, table, entries); 339 340 if (value != -1) { 341 return value; 342 } 343 error_report("spice: invalid %s: %s", optname, string); 344 exit(1); 345 } 346 347 static const char *stream_video_names[] = { 348 [ SPICE_STREAM_VIDEO_OFF ] = "off", 349 [ SPICE_STREAM_VIDEO_ALL ] = "all", 350 [ SPICE_STREAM_VIDEO_FILTER ] = "filter", 351 }; 352 #define parse_stream_video(_name) \ 353 parse_name(_name, "stream video control", \ 354 stream_video_names, ARRAY_SIZE(stream_video_names)) 355 356 static const char *compression_names[] = { 357 [ SPICE_IMAGE_COMPRESS_OFF ] = "off", 358 [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz", 359 [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz", 360 [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic", 361 [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz", 362 [ SPICE_IMAGE_COMPRESS_LZ ] = "lz", 363 }; 364 #define parse_compression(_name) \ 365 parse_name(_name, "image compression", \ 366 compression_names, ARRAY_SIZE(compression_names)) 367 368 static const char *wan_compression_names[] = { 369 [ SPICE_WAN_COMPRESSION_AUTO ] = "auto", 370 [ SPICE_WAN_COMPRESSION_NEVER ] = "never", 371 [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always", 372 }; 373 #define parse_wan_compression(_name) \ 374 parse_name(_name, "wan compression", \ 375 wan_compression_names, ARRAY_SIZE(wan_compression_names)) 376 377 /* functions for the rest of qemu */ 378 379 static SpiceChannelList *qmp_query_spice_channels(void) 380 { 381 SpiceChannelList *head = NULL, **tail = &head; 382 ChannelList *item; 383 384 QTAILQ_FOREACH(item, &channel_list, link) { 385 SpiceChannel *chan; 386 char host[NI_MAXHOST], port[NI_MAXSERV]; 387 struct sockaddr *paddr; 388 socklen_t plen; 389 390 assert(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT); 391 392 chan = g_malloc0(sizeof(*chan)); 393 394 paddr = (struct sockaddr *)&item->info->paddr_ext; 395 plen = item->info->plen_ext; 396 getnameinfo(paddr, plen, 397 host, sizeof(host), port, sizeof(port), 398 NI_NUMERICHOST | NI_NUMERICSERV); 399 chan->host = g_strdup(host); 400 chan->port = g_strdup(port); 401 chan->family = inet_netfamily(paddr->sa_family); 402 403 chan->connection_id = item->info->connection_id; 404 chan->channel_type = item->info->type; 405 chan->channel_id = item->info->id; 406 chan->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; 407 408 QAPI_LIST_APPEND(tail, chan); 409 } 410 411 return head; 412 } 413 414 static QemuOptsList qemu_spice_opts = { 415 .name = "spice", 416 .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head), 417 .merge_lists = true, 418 .desc = { 419 { 420 .name = "port", 421 .type = QEMU_OPT_NUMBER, 422 },{ 423 .name = "tls-port", 424 .type = QEMU_OPT_NUMBER, 425 },{ 426 .name = "addr", 427 .type = QEMU_OPT_STRING, 428 },{ 429 .name = "ipv4", 430 .type = QEMU_OPT_BOOL, 431 },{ 432 .name = "ipv6", 433 .type = QEMU_OPT_BOOL, 434 #ifdef SPICE_ADDR_FLAG_UNIX_ONLY 435 },{ 436 .name = "unix", 437 .type = QEMU_OPT_BOOL, 438 #endif 439 },{ 440 .name = "password-secret", 441 .type = QEMU_OPT_STRING, 442 },{ 443 .name = "disable-ticketing", 444 .type = QEMU_OPT_BOOL, 445 },{ 446 .name = "disable-copy-paste", 447 .type = QEMU_OPT_BOOL, 448 },{ 449 .name = "disable-agent-file-xfer", 450 .type = QEMU_OPT_BOOL, 451 },{ 452 .name = "sasl", 453 .type = QEMU_OPT_BOOL, 454 },{ 455 .name = "x509-dir", 456 .type = QEMU_OPT_STRING, 457 },{ 458 .name = "x509-key-file", 459 .type = QEMU_OPT_STRING, 460 },{ 461 .name = "x509-key-password", 462 .type = QEMU_OPT_STRING, 463 },{ 464 .name = "x509-cert-file", 465 .type = QEMU_OPT_STRING, 466 },{ 467 .name = "x509-cacert-file", 468 .type = QEMU_OPT_STRING, 469 },{ 470 .name = "x509-dh-key-file", 471 .type = QEMU_OPT_STRING, 472 },{ 473 .name = "tls-ciphers", 474 .type = QEMU_OPT_STRING, 475 },{ 476 .name = "tls-channel", 477 .type = QEMU_OPT_STRING, 478 },{ 479 .name = "plaintext-channel", 480 .type = QEMU_OPT_STRING, 481 },{ 482 .name = "image-compression", 483 .type = QEMU_OPT_STRING, 484 },{ 485 .name = "jpeg-wan-compression", 486 .type = QEMU_OPT_STRING, 487 },{ 488 .name = "zlib-glz-wan-compression", 489 .type = QEMU_OPT_STRING, 490 },{ 491 .name = "streaming-video", 492 .type = QEMU_OPT_STRING, 493 },{ 494 .name = "video-codec", 495 .type = QEMU_OPT_STRING, 496 },{ 497 .name = "max-refresh-rate", 498 .type = QEMU_OPT_NUMBER, 499 },{ 500 .name = "agent-mouse", 501 .type = QEMU_OPT_BOOL, 502 },{ 503 .name = "playback-compression", 504 .type = QEMU_OPT_BOOL, 505 },{ 506 .name = "seamless-migration", 507 .type = QEMU_OPT_BOOL, 508 },{ 509 .name = "display", 510 .type = QEMU_OPT_STRING, 511 },{ 512 .name = "head", 513 .type = QEMU_OPT_NUMBER, 514 #ifdef HAVE_SPICE_GL 515 },{ 516 .name = "gl", 517 .type = QEMU_OPT_BOOL, 518 },{ 519 .name = "rendernode", 520 .type = QEMU_OPT_STRING, 521 #endif 522 }, 523 { /* end of list */ } 524 }, 525 }; 526 527 static SpiceInfo *qmp_query_spice_real(Error **errp) 528 { 529 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 530 int port, tls_port; 531 const char *addr; 532 SpiceInfo *info; 533 unsigned int major; 534 unsigned int minor; 535 unsigned int micro; 536 537 info = g_malloc0(sizeof(*info)); 538 539 if (!spice_server || !opts) { 540 info->enabled = false; 541 return info; 542 } 543 544 info->enabled = true; 545 info->migrated = spice_migration_completed; 546 547 addr = qemu_opt_get(opts, "addr"); 548 port = qemu_opt_get_number(opts, "port", 0); 549 tls_port = qemu_opt_get_number(opts, "tls-port", 0); 550 551 info->auth = g_strdup(auth); 552 info->host = g_strdup(addr ? addr : "*"); 553 554 major = (SPICE_SERVER_VERSION & 0xff0000) >> 16; 555 minor = (SPICE_SERVER_VERSION & 0xff00) >> 8; 556 micro = SPICE_SERVER_VERSION & 0xff; 557 info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro); 558 559 if (port) { 560 info->has_port = true; 561 info->port = port; 562 } 563 if (tls_port) { 564 info->has_tls_port = true; 565 info->tls_port = tls_port; 566 } 567 568 info->mouse_mode = spice_server_is_server_mouse(spice_server) ? 569 SPICE_QUERY_MOUSE_MODE_SERVER : 570 SPICE_QUERY_MOUSE_MODE_CLIENT; 571 572 /* for compatibility with the original command */ 573 info->has_channels = true; 574 info->channels = qmp_query_spice_channels(); 575 576 return info; 577 } 578 579 static int migration_state_notifier(NotifierWithReturn *notifier, 580 MigrationEvent *e, Error **errp) 581 { 582 if (!spice_have_target_host) { 583 return 0; 584 } 585 586 if (e->type == MIG_EVENT_PRECOPY_SETUP) { 587 spice_server_migrate_start(spice_server); 588 } else if (e->type == MIG_EVENT_PRECOPY_DONE) { 589 spice_server_migrate_end(spice_server, true); 590 spice_have_target_host = false; 591 } else if (e->type == MIG_EVENT_PRECOPY_FAILED) { 592 spice_server_migrate_end(spice_server, false); 593 spice_have_target_host = false; 594 } 595 return 0; 596 } 597 598 int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, 599 const char *subject) 600 { 601 int ret; 602 603 ret = spice_server_migrate_connect(spice_server, hostname, 604 port, tls_port, subject); 605 spice_have_target_host = true; 606 return ret; 607 } 608 609 static int add_channel(void *opaque, const char *name, const char *value, 610 Error **errp) 611 { 612 int security = 0; 613 int rc; 614 615 if (strcmp(name, "tls-channel") == 0) { 616 int *tls_port = opaque; 617 if (!*tls_port) { 618 error_setg(errp, "spice: tried to setup tls-channel" 619 " without specifying a TLS port"); 620 return -1; 621 } 622 security = SPICE_CHANNEL_SECURITY_SSL; 623 } 624 if (strcmp(name, "plaintext-channel") == 0) { 625 security = SPICE_CHANNEL_SECURITY_NONE; 626 } 627 if (security == 0) { 628 return 0; 629 } 630 if (strcmp(value, "default") == 0) { 631 rc = spice_server_set_channel_security(spice_server, NULL, security); 632 } else { 633 rc = spice_server_set_channel_security(spice_server, value, security); 634 } 635 if (rc != 0) { 636 error_setg(errp, "spice: failed to set channel security for %s", 637 value); 638 return -1; 639 } 640 return 0; 641 } 642 643 static void vm_change_state_handler(void *opaque, bool running, 644 RunState state) 645 { 646 if (running) { 647 qemu_spice_display_start(); 648 } else if (state != RUN_STATE_PAUSED) { 649 qemu_spice_display_stop(); 650 } 651 } 652 653 void qemu_spice_display_init_done(void) 654 { 655 if (runstate_is_running()) { 656 qemu_spice_display_start(); 657 } 658 qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); 659 } 660 661 static void qemu_spice_init(void) 662 { 663 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 664 char *password = NULL; 665 const char *passwordSecret; 666 const char *str, *x509_dir, *addr, 667 *x509_key_password = NULL, 668 *x509_dh_file = NULL, 669 *tls_ciphers = NULL; 670 char *x509_key_file = NULL, 671 *x509_cert_file = NULL, 672 *x509_cacert_file = NULL; 673 int port, tls_port, addr_flags; 674 spice_image_compression_t compression; 675 spice_wan_compression_t wan_compr; 676 bool seamless_migration; 677 678 qemu_thread_get_self(&me); 679 680 if (!opts) { 681 return; 682 } 683 port = qemu_opt_get_number(opts, "port", 0); 684 tls_port = qemu_opt_get_number(opts, "tls-port", 0); 685 if (port < 0 || port > 65535) { 686 error_report("spice port is out of range"); 687 exit(1); 688 } 689 if (tls_port < 0 || tls_port > 65535) { 690 error_report("spice tls-port is out of range"); 691 exit(1); 692 } 693 passwordSecret = qemu_opt_get(opts, "password-secret"); 694 if (passwordSecret) { 695 password = qcrypto_secret_lookup_as_utf8(passwordSecret, 696 &error_fatal); 697 } 698 699 if (tls_port) { 700 x509_dir = qemu_opt_get(opts, "x509-dir"); 701 if (!x509_dir) { 702 x509_dir = "."; 703 } 704 705 str = qemu_opt_get(opts, "x509-key-file"); 706 if (str) { 707 x509_key_file = g_strdup(str); 708 } else { 709 x509_key_file = g_strdup_printf("%s/%s", x509_dir, 710 X509_SERVER_KEY_FILE); 711 } 712 713 str = qemu_opt_get(opts, "x509-cert-file"); 714 if (str) { 715 x509_cert_file = g_strdup(str); 716 } else { 717 x509_cert_file = g_strdup_printf("%s/%s", x509_dir, 718 X509_SERVER_CERT_FILE); 719 } 720 721 str = qemu_opt_get(opts, "x509-cacert-file"); 722 if (str) { 723 x509_cacert_file = g_strdup(str); 724 } else { 725 x509_cacert_file = g_strdup_printf("%s/%s", x509_dir, 726 X509_CA_CERT_FILE); 727 } 728 729 x509_key_password = qemu_opt_get(opts, "x509-key-password"); 730 x509_dh_file = qemu_opt_get(opts, "x509-dh-key-file"); 731 tls_ciphers = qemu_opt_get(opts, "tls-ciphers"); 732 } 733 734 addr = qemu_opt_get(opts, "addr"); 735 addr_flags = 0; 736 if (qemu_opt_get_bool(opts, "ipv4", 0)) { 737 addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY; 738 } else if (qemu_opt_get_bool(opts, "ipv6", 0)) { 739 addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY; 740 #ifdef SPICE_ADDR_FLAG_UNIX_ONLY 741 } else if (qemu_opt_get_bool(opts, "unix", 0)) { 742 addr_flags |= SPICE_ADDR_FLAG_UNIX_ONLY; 743 #endif 744 } 745 746 spice_server = spice_server_new(); 747 spice_server_set_addr(spice_server, addr ? addr : "", addr_flags); 748 if (port) { 749 spice_server_set_port(spice_server, port); 750 } 751 if (tls_port) { 752 spice_server_set_tls(spice_server, tls_port, 753 x509_cacert_file, 754 x509_cert_file, 755 x509_key_file, 756 x509_key_password, 757 x509_dh_file, 758 tls_ciphers); 759 } 760 if (password) { 761 qemu_spice.set_passwd(password, false, false); 762 } 763 if (qemu_opt_get_bool(opts, "sasl", 0)) { 764 if (spice_server_set_sasl(spice_server, 1) == -1) { 765 error_report("spice: failed to enable sasl"); 766 exit(1); 767 } 768 auth = "sasl"; 769 } 770 if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) { 771 auth = "none"; 772 spice_server_set_noauth(spice_server); 773 } 774 775 if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) { 776 spice_server_set_agent_copypaste(spice_server, false); 777 } 778 779 if (qemu_opt_get_bool(opts, "disable-agent-file-xfer", 0)) { 780 spice_server_set_agent_file_xfer(spice_server, false); 781 } 782 783 compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ; 784 str = qemu_opt_get(opts, "image-compression"); 785 if (str) { 786 compression = parse_compression(str); 787 } 788 spice_server_set_image_compression(spice_server, compression); 789 790 wan_compr = SPICE_WAN_COMPRESSION_AUTO; 791 str = qemu_opt_get(opts, "jpeg-wan-compression"); 792 if (str) { 793 wan_compr = parse_wan_compression(str); 794 } 795 spice_server_set_jpeg_compression(spice_server, wan_compr); 796 797 wan_compr = SPICE_WAN_COMPRESSION_AUTO; 798 str = qemu_opt_get(opts, "zlib-glz-wan-compression"); 799 if (str) { 800 wan_compr = parse_wan_compression(str); 801 } 802 spice_server_set_zlib_glz_compression(spice_server, wan_compr); 803 804 str = qemu_opt_get(opts, "streaming-video"); 805 if (str) { 806 int streaming_video = parse_stream_video(str); 807 spice_server_set_streaming_video(spice_server, streaming_video); 808 } else { 809 spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF); 810 } 811 812 spice_max_refresh_rate = qemu_opt_get_number(opts, "max-refresh-rate", 813 DEFAULT_MAX_REFRESH_RATE); 814 if (spice_max_refresh_rate <= 0) { 815 error_report("max refresh rate/fps is invalid"); 816 exit(1); 817 } 818 819 spice_server_set_agent_mouse 820 (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); 821 spice_server_set_playback_compression 822 (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1)); 823 824 qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal); 825 826 spice_server_set_name(spice_server, qemu_name ?: "QEMU " QEMU_VERSION); 827 spice_server_set_uuid(spice_server, (unsigned char *)&qemu_uuid); 828 829 seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0); 830 spice_server_set_seamless_migration(spice_server, seamless_migration); 831 spice_server_set_sasl_appname(spice_server, "qemu"); 832 if (spice_server_init(spice_server, &core_interface) != 0) { 833 error_report("failed to initialize spice server"); 834 exit(1); 835 }; 836 using_spice = 1; 837 838 migration_add_notifier(&migration_state, migration_state_notifier); 839 spice_migrate.base.sif = &migrate_interface.base; 840 qemu_spice.add_interface(&spice_migrate.base); 841 842 qemu_spice_input_init(); 843 844 qemu_spice_display_stop(); 845 846 g_free(x509_key_file); 847 g_free(x509_cert_file); 848 g_free(x509_cacert_file); 849 g_free(password); 850 851 #ifdef HAVE_SPICE_GL 852 if (qemu_opt_get_bool(opts, "gl", 0)) { 853 if ((port != 0) || (tls_port != 0)) { 854 #if SPICE_SERVER_VERSION >= 0x000f03 /* release 0.15.3 */ 855 const char *video_codec = NULL; 856 g_autofree char *enc_codec = NULL; 857 858 spice_remote_client = 1; 859 860 video_codec = qemu_opt_get(opts, "video-codec"); 861 if (video_codec) { 862 enc_codec = g_strconcat("gstreamer:", video_codec, NULL); 863 } 864 if (spice_server_set_video_codecs(spice_server, 865 enc_codec ?: "gstreamer:h264")) { 866 error_report("invalid video codec"); 867 exit(1); 868 } 869 #else 870 error_report("SPICE GL support is local-only for now and " 871 "incompatible with -spice port/tls-port"); 872 exit(1); 873 #endif 874 } 875 egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal); 876 spice_opengl = 1; 877 } 878 #endif 879 } 880 881 static int qemu_spice_add_interface(SpiceBaseInstance *sin) 882 { 883 if (!spice_server) { 884 if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) { 885 error_report("Oops: spice configured but not active"); 886 exit(1); 887 } 888 /* 889 * Create a spice server instance. 890 * It does *not* listen on the network. 891 * It handles QXL local rendering only. 892 * 893 * With a command line like '-vnc :0 -vga qxl' you'll end up here. 894 */ 895 spice_server = spice_server_new(); 896 spice_server_set_sasl_appname(spice_server, "qemu"); 897 spice_server_init(spice_server, &core_interface); 898 qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); 899 } 900 901 return spice_server_add_interface(spice_server, sin); 902 } 903 904 static GSList *spice_consoles; 905 906 bool qemu_spice_have_display_interface(QemuConsole *con) 907 { 908 if (g_slist_find(spice_consoles, con)) { 909 return true; 910 } 911 return false; 912 } 913 914 int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con) 915 { 916 if (g_slist_find(spice_consoles, con)) { 917 return -1; 918 } 919 qxlin->id = qemu_console_get_index(con); 920 spice_consoles = g_slist_append(spice_consoles, con); 921 return qemu_spice_add_interface(&qxlin->base); 922 } 923 924 static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) 925 { 926 time_t lifetime, now = time(NULL); 927 char *passwd; 928 929 if (now < auth_expires) { 930 passwd = auth_passwd; 931 lifetime = (auth_expires - now); 932 if (lifetime > INT_MAX) { 933 lifetime = INT_MAX; 934 } 935 } else { 936 passwd = NULL; 937 lifetime = 1; 938 } 939 return spice_server_set_ticket(spice_server, passwd, lifetime, 940 fail_if_conn, disconnect_if_conn); 941 } 942 943 static int qemu_spice_set_passwd(const char *passwd, 944 bool fail_if_conn, bool disconnect_if_conn) 945 { 946 if (strcmp(auth, "spice") != 0) { 947 return -1; 948 } 949 950 g_free(auth_passwd); 951 auth_passwd = g_strdup(passwd); 952 return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn); 953 } 954 955 static int qemu_spice_set_pw_expire(time_t expires) 956 { 957 auth_expires = expires; 958 return qemu_spice_set_ticket(false, false); 959 } 960 961 static int qemu_spice_display_add_client(int csock, int skipauth, int tls) 962 { 963 #ifdef WIN32 964 csock = qemu_close_socket_osfhandle(csock); 965 #endif 966 if (tls) { 967 return spice_server_add_ssl_client(spice_server, csock, skipauth); 968 } else { 969 return spice_server_add_client(spice_server, csock, skipauth); 970 } 971 } 972 973 void qemu_spice_display_start(void) 974 { 975 if (spice_display_is_running) { 976 return; 977 } 978 979 spice_display_is_running = true; 980 spice_server_vm_start(spice_server); 981 } 982 983 void qemu_spice_display_stop(void) 984 { 985 if (!spice_display_is_running) { 986 return; 987 } 988 989 spice_server_vm_stop(spice_server); 990 spice_display_is_running = false; 991 } 992 993 int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd) 994 { 995 return spice_display_is_running; 996 } 997 998 static struct QemuSpiceOps real_spice_ops = { 999 .init = qemu_spice_init, 1000 .display_init = qemu_spice_display_init, 1001 .migrate_info = qemu_spice_migrate_info, 1002 .set_passwd = qemu_spice_set_passwd, 1003 .set_pw_expire = qemu_spice_set_pw_expire, 1004 .display_add_client = qemu_spice_display_add_client, 1005 .add_interface = qemu_spice_add_interface, 1006 .qmp_query = qmp_query_spice_real, 1007 }; 1008 1009 static void spice_register_config(void) 1010 { 1011 qemu_spice = real_spice_ops; 1012 qemu_add_opts(&qemu_spice_opts); 1013 } 1014 opts_init(spice_register_config); 1015 module_opts("spice"); 1016 1017 #ifdef HAVE_SPICE_GL 1018 module_dep("ui-opengl"); 1019 #endif 1020