]> git.proxmox.com Git - qemu.git/blob - ui/spice-core.c
spice: connection events.
[qemu.git] / ui / spice-core.c
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 <spice.h>
19 #include <spice-experimental.h>
20
21 #include <netdb.h>
22
23 #include "qemu-common.h"
24 #include "qemu-spice.h"
25 #include "qemu-timer.h"
26 #include "qemu-queue.h"
27 #include "qemu-x509.h"
28 #include "qemu_socket.h"
29 #include "qint.h"
30 #include "qbool.h"
31 #include "qstring.h"
32 #include "qjson.h"
33 #include "monitor.h"
34
35 /* core bits */
36
37 static SpiceServer *spice_server;
38 static const char *auth = "spice";
39 int using_spice = 0;
40
41 struct SpiceTimer {
42 QEMUTimer *timer;
43 QTAILQ_ENTRY(SpiceTimer) next;
44 };
45 static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers);
46
47 static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
48 {
49 SpiceTimer *timer;
50
51 timer = qemu_mallocz(sizeof(*timer));
52 timer->timer = qemu_new_timer(rt_clock, func, opaque);
53 QTAILQ_INSERT_TAIL(&timers, timer, next);
54 return timer;
55 }
56
57 static void timer_start(SpiceTimer *timer, uint32_t ms)
58 {
59 qemu_mod_timer(timer->timer, qemu_get_clock(rt_clock) + ms);
60 }
61
62 static void timer_cancel(SpiceTimer *timer)
63 {
64 qemu_del_timer(timer->timer);
65 }
66
67 static void timer_remove(SpiceTimer *timer)
68 {
69 qemu_del_timer(timer->timer);
70 qemu_free_timer(timer->timer);
71 QTAILQ_REMOVE(&timers, timer, next);
72 qemu_free(timer);
73 }
74
75 struct SpiceWatch {
76 int fd;
77 int event_mask;
78 SpiceWatchFunc func;
79 void *opaque;
80 QTAILQ_ENTRY(SpiceWatch) next;
81 };
82 static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches);
83
84 static void watch_read(void *opaque)
85 {
86 SpiceWatch *watch = opaque;
87 watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque);
88 }
89
90 static void watch_write(void *opaque)
91 {
92 SpiceWatch *watch = opaque;
93 watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque);
94 }
95
96 static void watch_update_mask(SpiceWatch *watch, int event_mask)
97 {
98 IOHandler *on_read = NULL;
99 IOHandler *on_write = NULL;
100
101 watch->event_mask = event_mask;
102 if (watch->event_mask & SPICE_WATCH_EVENT_READ) {
103 on_read = watch_read;
104 }
105 if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) {
106 on_write = watch_write;
107 }
108 qemu_set_fd_handler(watch->fd, on_read, on_write, watch);
109 }
110
111 static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
112 {
113 SpiceWatch *watch;
114
115 watch = qemu_mallocz(sizeof(*watch));
116 watch->fd = fd;
117 watch->func = func;
118 watch->opaque = opaque;
119 QTAILQ_INSERT_TAIL(&watches, watch, next);
120
121 watch_update_mask(watch, event_mask);
122 return watch;
123 }
124
125 static void watch_remove(SpiceWatch *watch)
126 {
127 watch_update_mask(watch, 0);
128 QTAILQ_REMOVE(&watches, watch, next);
129 qemu_free(watch);
130 }
131
132 #if SPICE_INTERFACE_CORE_MINOR >= 3
133
134 static void add_addr_info(QDict *dict, struct sockaddr *addr, int len)
135 {
136 char host[NI_MAXHOST], port[NI_MAXSERV];
137 const char *family;
138
139 getnameinfo(addr, len, host, sizeof(host), port, sizeof(port),
140 NI_NUMERICHOST | NI_NUMERICSERV);
141 family = inet_strfamily(addr->sa_family);
142
143 qdict_put(dict, "host", qstring_from_str(host));
144 qdict_put(dict, "port", qstring_from_str(port));
145 qdict_put(dict, "family", qstring_from_str(family));
146 }
147
148 static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info)
149 {
150 int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
151
152 qdict_put(dict, "connection-id", qint_from_int(info->connection_id));
153 qdict_put(dict, "channel-type", qint_from_int(info->type));
154 qdict_put(dict, "channel-id", qint_from_int(info->id));
155 qdict_put(dict, "tls", qbool_from_int(tls));
156 }
157
158 static void channel_event(int event, SpiceChannelEventInfo *info)
159 {
160 static const int qevent[] = {
161 [ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED,
162 [ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED,
163 [ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED,
164 };
165 QDict *server, *client;
166 QObject *data;
167
168 client = qdict_new();
169 add_addr_info(client, &info->paddr, info->plen);
170
171 server = qdict_new();
172 add_addr_info(server, &info->laddr, info->llen);
173
174 if (event == SPICE_CHANNEL_EVENT_INITIALIZED) {
175 qdict_put(server, "auth", qstring_from_str(auth));
176 add_channel_info(client, info);
177 }
178
179 data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
180 QOBJECT(client), QOBJECT(server));
181 monitor_protocol_event(qevent[event], data);
182 qobject_decref(data);
183 }
184
185 #else /* SPICE_INTERFACE_CORE_MINOR >= 3 */
186
187 static QList *channel_list_get(void)
188 {
189 return NULL;
190 }
191
192 #endif /* SPICE_INTERFACE_CORE_MINOR >= 3 */
193
194 static SpiceCoreInterface core_interface = {
195 .base.type = SPICE_INTERFACE_CORE,
196 .base.description = "qemu core services",
197 .base.major_version = SPICE_INTERFACE_CORE_MAJOR,
198 .base.minor_version = SPICE_INTERFACE_CORE_MINOR,
199
200 .timer_add = timer_add,
201 .timer_start = timer_start,
202 .timer_cancel = timer_cancel,
203 .timer_remove = timer_remove,
204
205 .watch_add = watch_add,
206 .watch_update_mask = watch_update_mask,
207 .watch_remove = watch_remove,
208
209 #if SPICE_INTERFACE_CORE_MINOR >= 3
210 .channel_event = channel_event,
211 #endif
212 };
213
214 /* config string parsing */
215
216 static int name2enum(const char *string, const char *table[], int entries)
217 {
218 int i;
219
220 if (string) {
221 for (i = 0; i < entries; i++) {
222 if (!table[i]) {
223 continue;
224 }
225 if (strcmp(string, table[i]) != 0) {
226 continue;
227 }
228 return i;
229 }
230 }
231 return -1;
232 }
233
234 static int parse_name(const char *string, const char *optname,
235 const char *table[], int entries)
236 {
237 int value = name2enum(string, table, entries);
238
239 if (value != -1) {
240 return value;
241 }
242 fprintf(stderr, "spice: invalid %s: %s\n", optname, string);
243 exit(1);
244 }
245
246 #if SPICE_SERVER_VERSION >= 0x000600 /* 0.6.0 */
247
248 static const char *stream_video_names[] = {
249 [ SPICE_STREAM_VIDEO_OFF ] = "off",
250 [ SPICE_STREAM_VIDEO_ALL ] = "all",
251 [ SPICE_STREAM_VIDEO_FILTER ] = "filter",
252 };
253 #define parse_stream_video(_name) \
254 name2enum(_name, stream_video_names, ARRAY_SIZE(stream_video_names))
255
256 #endif /* >= 0.6.0 */
257
258 static const char *compression_names[] = {
259 [ SPICE_IMAGE_COMPRESS_OFF ] = "off",
260 [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz",
261 [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz",
262 [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic",
263 [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz",
264 [ SPICE_IMAGE_COMPRESS_LZ ] = "lz",
265 };
266 #define parse_compression(_name) \
267 parse_name(_name, "image compression", \
268 compression_names, ARRAY_SIZE(compression_names))
269
270 static const char *wan_compression_names[] = {
271 [ SPICE_WAN_COMPRESSION_AUTO ] = "auto",
272 [ SPICE_WAN_COMPRESSION_NEVER ] = "never",
273 [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always",
274 };
275 #define parse_wan_compression(_name) \
276 parse_name(_name, "wan compression", \
277 wan_compression_names, ARRAY_SIZE(wan_compression_names))
278
279 /* functions for the rest of qemu */
280
281 static int add_channel(const char *name, const char *value, void *opaque)
282 {
283 int security = 0;
284 int rc;
285
286 if (strcmp(name, "tls-channel") == 0) {
287 security = SPICE_CHANNEL_SECURITY_SSL;
288 }
289 if (strcmp(name, "plaintext-channel") == 0) {
290 security = SPICE_CHANNEL_SECURITY_NONE;
291 }
292 if (security == 0) {
293 return 0;
294 }
295 if (strcmp(value, "default") == 0) {
296 rc = spice_server_set_channel_security(spice_server, NULL, security);
297 } else {
298 rc = spice_server_set_channel_security(spice_server, value, security);
299 }
300 if (rc != 0) {
301 fprintf(stderr, "spice: failed to set channel security for %s\n", value);
302 exit(1);
303 }
304 return 0;
305 }
306
307 void qemu_spice_init(void)
308 {
309 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
310 const char *password, *str, *x509_dir, *addr,
311 *x509_key_password = NULL,
312 *x509_dh_file = NULL,
313 *tls_ciphers = NULL;
314 char *x509_key_file = NULL,
315 *x509_cert_file = NULL,
316 *x509_cacert_file = NULL;
317 int port, tls_port, len, addr_flags;
318 spice_image_compression_t compression;
319 spice_wan_compression_t wan_compr;
320
321 if (!opts) {
322 return;
323 }
324 port = qemu_opt_get_number(opts, "port", 0);
325 tls_port = qemu_opt_get_number(opts, "tls-port", 0);
326 if (!port && !tls_port) {
327 return;
328 }
329 password = qemu_opt_get(opts, "password");
330
331 if (tls_port) {
332 x509_dir = qemu_opt_get(opts, "x509-dir");
333 if (NULL == x509_dir) {
334 x509_dir = ".";
335 }
336 len = strlen(x509_dir) + 32;
337
338 str = qemu_opt_get(opts, "x509-key-file");
339 if (str) {
340 x509_key_file = qemu_strdup(str);
341 } else {
342 x509_key_file = qemu_malloc(len);
343 snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE);
344 }
345
346 str = qemu_opt_get(opts, "x509-cert-file");
347 if (str) {
348 x509_cert_file = qemu_strdup(str);
349 } else {
350 x509_cert_file = qemu_malloc(len);
351 snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE);
352 }
353
354 str = qemu_opt_get(opts, "x509-cacert-file");
355 if (str) {
356 x509_cacert_file = qemu_strdup(str);
357 } else {
358 x509_cacert_file = qemu_malloc(len);
359 snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE);
360 }
361
362 x509_key_password = qemu_opt_get(opts, "x509-key-password");
363 x509_dh_file = qemu_opt_get(opts, "x509-dh-file");
364 tls_ciphers = qemu_opt_get(opts, "tls-ciphers");
365 }
366
367 addr = qemu_opt_get(opts, "addr");
368 addr_flags = 0;
369 if (qemu_opt_get_bool(opts, "ipv4", 0)) {
370 addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY;
371 } else if (qemu_opt_get_bool(opts, "ipv6", 0)) {
372 addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY;
373 }
374
375 spice_server = spice_server_new();
376 spice_server_set_addr(spice_server, addr ? addr : "", addr_flags);
377 if (port) {
378 spice_server_set_port(spice_server, port);
379 }
380 if (tls_port) {
381 spice_server_set_tls(spice_server, tls_port,
382 x509_cacert_file,
383 x509_cert_file,
384 x509_key_file,
385 x509_key_password,
386 x509_dh_file,
387 tls_ciphers);
388 }
389 if (password) {
390 spice_server_set_ticket(spice_server, password, 0, 0, 0);
391 }
392 if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
393 auth = "none";
394 spice_server_set_noauth(spice_server);
395 }
396
397 compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
398 str = qemu_opt_get(opts, "image-compression");
399 if (str) {
400 compression = parse_compression(str);
401 }
402 spice_server_set_image_compression(spice_server, compression);
403
404 wan_compr = SPICE_WAN_COMPRESSION_AUTO;
405 str = qemu_opt_get(opts, "jpeg-wan-compression");
406 if (str) {
407 wan_compr = parse_wan_compression(str);
408 }
409 spice_server_set_jpeg_compression(spice_server, wan_compr);
410
411 wan_compr = SPICE_WAN_COMPRESSION_AUTO;
412 str = qemu_opt_get(opts, "zlib-glz-wan-compression");
413 if (str) {
414 wan_compr = parse_wan_compression(str);
415 }
416 spice_server_set_zlib_glz_compression(spice_server, wan_compr);
417
418 #if SPICE_SERVER_VERSION >= 0x000600 /* 0.6.0 */
419
420 str = qemu_opt_get(opts, "streaming-video");
421 if (str) {
422 int streaming_video = parse_stream_video(str);
423 spice_server_set_streaming_video(spice_server, streaming_video);
424 }
425
426 spice_server_set_agent_mouse
427 (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1));
428 spice_server_set_playback_compression
429 (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1));
430
431 #endif /* >= 0.6.0 */
432
433 qemu_opt_foreach(opts, add_channel, NULL, 0);
434
435 spice_server_init(spice_server, &core_interface);
436 using_spice = 1;
437
438 qemu_spice_input_init();
439 qemu_spice_audio_init();
440
441 qemu_free(x509_key_file);
442 qemu_free(x509_cert_file);
443 qemu_free(x509_cacert_file);
444 }
445
446 int qemu_spice_add_interface(SpiceBaseInstance *sin)
447 {
448 if (!spice_server) {
449 if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) {
450 fprintf(stderr, "Oops: spice configured but not active\n");
451 exit(1);
452 }
453 /*
454 * Create a spice server instance.
455 * It does *not* listen on the network.
456 * It handles QXL local rendering only.
457 *
458 * With a command line like '-vnc :0 -vga qxl' you'll end up here.
459 */
460 spice_server = spice_server_new();
461 spice_server_init(spice_server, &core_interface);
462 }
463 return spice_server_add_interface(spice_server, sin);
464 }
465
466 static void spice_register_config(void)
467 {
468 qemu_add_opts(&qemu_spice_opts);
469 }
470 machine_init(spice_register_config);
471
472 static void spice_initialize(void)
473 {
474 qemu_spice_init();
475 }
476 device_init(spice_initialize);