diff options
author | Tim Hardeck <thardeck@suse.de> | 2013-01-21 11:04:44 +0100 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2013-01-21 13:33:12 -0600 |
commit | 7536ee4bc3da7e9b7fdadba5ba6ade63eaace430 (patch) | |
tree | dd9818e015ffe360e5636066e987cdc8c91d9839 /ui/vnc.c | |
parent | 32ed26808d4e59efb4a03290a4a85f5f8335f268 (diff) |
vnc: added initial websocket protocol support
This patch adds basic Websocket Protocol version 13 - RFC 6455 - support
to QEMU VNC. Binary encoding support on the client side is mandatory.
Because of the GnuTLS requirement the Websockets implementation is
optional (--enable-vnc-ws).
To activate Websocket support the VNC option "websocket"is used, for
example "-vnc :0,websocket".
The listen port for Websocket connections is (5700 + display) so if
QEMU VNC is started with :0 the Websocket port would be 5700.
As an alternative the Websocket port could be manually specified by
using ",websocket=<port>" instead.
Parts of the implementation base on Anthony Liguori's QEMU Websocket
patch from 2010 and on Joel Martin's LibVNC Websocket implementation.
Signed-off-by: Tim Hardeck <thardeck@suse.de>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'ui/vnc.c')
-rw-r--r-- | ui/vnc.c | 187 |
1 files changed, 169 insertions, 18 deletions
@@ -420,7 +420,6 @@ out_error: static int vnc_update_client(VncState *vs, int has_dirty); static int vnc_update_client_sync(VncState *vs, int has_dirty); static void vnc_disconnect_start(VncState *vs); -static void vnc_disconnect_finish(VncState *vs); static void vnc_init_timer(VncDisplay *vd); static void vnc_remove_timer(VncDisplay *vd); @@ -486,7 +485,7 @@ static int buffer_empty(Buffer *buffer) return buffer->offset == 0; } -static uint8_t *buffer_end(Buffer *buffer) +uint8_t *buffer_end(Buffer *buffer) { return buffer->buffer + buffer->offset; } @@ -1023,7 +1022,7 @@ static void vnc_disconnect_start(VncState *vs) vs->csock = -1; } -static void vnc_disconnect_finish(VncState *vs) +void vnc_disconnect_finish(VncState *vs) { int i; @@ -1034,6 +1033,10 @@ static void vnc_disconnect_finish(VncState *vs) buffer_free(&vs->input); buffer_free(&vs->output); +#ifdef CONFIG_VNC_WS + buffer_free(&vs->ws_input); + buffer_free(&vs->ws_output); +#endif /* CONFIG_VNC_WS */ qobject_decref(vs->info); @@ -1199,7 +1202,16 @@ static void vnc_client_write_locked(void *opaque) vnc_client_write_sasl(vs); } else #endif /* CONFIG_VNC_SASL */ - vnc_client_write_plain(vs); + { +#ifdef CONFIG_VNC_WS + if (vs->encode_ws) { + vnc_client_write_ws(vs); + } else +#endif /* CONFIG_VNC_WS */ + { + vnc_client_write_plain(vs); + } + } } void vnc_client_write(void *opaque) @@ -1207,7 +1219,11 @@ void vnc_client_write(void *opaque) VncState *vs = opaque; vnc_lock_output(vs); - if (vs->output.offset) { + if (vs->output.offset +#ifdef CONFIG_VNC_WS + || vs->ws_output.offset +#endif + ) { vnc_client_write_locked(opaque); } else if (vs->csock != -1) { qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); @@ -1301,7 +1317,21 @@ void vnc_client_read(void *opaque) ret = vnc_client_read_sasl(vs); else #endif /* CONFIG_VNC_SASL */ +#ifdef CONFIG_VNC_WS + if (vs->encode_ws) { + ret = vnc_client_read_ws(vs); + if (ret == -1) { + vnc_disconnect_start(vs); + return; + } else if (ret == -2) { + vnc_client_error(vs); + return; + } + } else +#endif /* CONFIG_VNC_WS */ + { ret = vnc_client_read_plain(vs); + } if (!ret) { if (vs->csock == -1) vnc_disconnect_finish(vs); @@ -1372,7 +1402,11 @@ void vnc_write_u8(VncState *vs, uint8_t value) void vnc_flush(VncState *vs) { vnc_lock_output(vs); - if (vs->csock != -1 && vs->output.offset) { + if (vs->csock != -1 && (vs->output.offset +#ifdef CONFIG_VNC_WS + || vs->ws_output.offset +#endif + )) { vnc_client_write_locked(vs); } vnc_unlock_output(vs); @@ -2662,7 +2696,7 @@ static void vnc_remove_timer(VncDisplay *vd) } } -static void vnc_connect(VncDisplay *vd, int csock, int skipauth) +static void vnc_connect(VncDisplay *vd, int csock, int skipauth, bool websocket) { VncState *vs = g_malloc0(sizeof(VncState)); int i; @@ -2689,13 +2723,34 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth) VNC_DEBUG("New client on socket %d\n", csock); dcl->idle = 0; socket_set_nonblock(vs->csock); - qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); +#ifdef CONFIG_VNC_WS + if (websocket) { + vs->websocket = 1; + qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs); + } else +#endif /* CONFIG_VNC_WS */ + { + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + } vnc_client_cache_addr(vs); vnc_qmp_event(vs, QEVENT_VNC_CONNECTED); vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING); vs->vd = vd; + +#ifdef CONFIG_VNC_WS + if (!vs->websocket) +#endif + { + vnc_init_state(vs); + } +} + +void vnc_init_state(VncState *vs) +{ + VncDisplay *vd = vs->vd; + vs->ds = vd->ds; vs->last_x = -1; vs->last_y = -1; @@ -2727,21 +2782,41 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth) /* vs might be free()ed here */ } -static void vnc_listen_read(void *opaque) +static void vnc_listen_read(void *opaque, bool websocket) { VncDisplay *vs = opaque; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); + int csock; /* Catch-up */ vga_hw_update(); +#ifdef CONFIG_VNC_WS + if (websocket) { + csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen); + } else +#endif /* CONFIG_VNC_WS */ + { + csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); + } - int csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); if (csock != -1) { - vnc_connect(vs, csock, 0); + vnc_connect(vs, csock, 0, websocket); } } +static void vnc_listen_regular_read(void *opaque) +{ + vnc_listen_read(opaque, 0); +} + +#ifdef CONFIG_VNC_WS +static void vnc_listen_websocket_read(void *opaque) +{ + vnc_listen_read(opaque, 1); +} +#endif /* CONFIG_VNC_WS */ + void vnc_display_init(DisplayState *ds) { VncDisplay *vs = g_malloc0(sizeof(*vs)); @@ -2753,6 +2828,9 @@ void vnc_display_init(DisplayState *ds) vnc_display = vs; vs->lsock = -1; +#ifdef CONFIG_VNC_WS + vs->lwebsock = -1; +#endif vs->ds = ds; QTAILQ_INIT(&vs->clients); @@ -2794,6 +2872,15 @@ static void vnc_display_close(DisplayState *ds) close(vs->lsock); vs->lsock = -1; } +#ifdef CONFIG_VNC_WS + g_free(vs->ws_display); + vs->ws_display = NULL; + if (vs->lwebsock != -1) { + qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL); + close(vs->lwebsock); + vs->lwebsock = -1; + } +#endif /* CONFIG_VNC_WS */ vs->auth = VNC_AUTH_INVALID; #ifdef CONFIG_VNC_TLS vs->subauth = VNC_AUTH_INVALID; @@ -2915,6 +3002,36 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) } else if (strncmp(options, "sasl", 4) == 0) { sasl = 1; /* Require SASL auth */ #endif +#ifdef CONFIG_VNC_WS + } else if (strncmp(options, "websocket", 9) == 0) { + char *start, *end; + vs->websocket = 1; + + /* Check for 'websocket=<port>' */ + start = strchr(options, '='); + end = strchr(options, ','); + if (start && (!end || (start < end))) { + int len = end ? end-(start+1) : strlen(start+1); + if (len < 6) { + /* extract the host specification from display */ + char *host = NULL, *port = NULL, *host_end = NULL; + port = g_strndup(start + 1, len); + + /* ipv6 hosts have colons */ + end = strchr(display, ','); + host_end = g_strrstr_len(display, end - display, ":"); + + if (host_end) { + host = g_strndup(display, host_end - display + 1); + } else { + host = g_strndup(":", 1); + } + vs->ws_display = g_strconcat(host, port, NULL); + g_free(host); + g_free(port); + } + } +#endif /* CONFIG_VNC_WS */ #ifdef CONFIG_VNC_TLS } else if (strncmp(options, "tls", 3) == 0) { tls = 1; /* Require TLS */ @@ -3073,6 +3190,9 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) /* connect to viewer */ int csock; vs->lsock = -1; +#ifdef CONFIG_VNC_WS + vs->lwebsock = -1; +#endif if (strncmp(display, "unix:", 5) == 0) { csock = unix_connect(display+5, errp); } else { @@ -3081,7 +3201,7 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) if (csock < 0) { goto fail; } - vnc_connect(vs, csock, 0); + vnc_connect(vs, csock, 0, 0); } else { /* listen for connects */ char *dpy; @@ -3092,25 +3212,56 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) } else { vs->lsock = inet_listen(display, dpy, 256, SOCK_STREAM, 5900, errp); - } - if (vs->lsock < 0) { - g_free(dpy); - goto fail; + if (vs->lsock < 0) { + g_free(dpy); + goto fail; + } +#ifdef CONFIG_VNC_WS + if (vs->websocket) { + if (vs->ws_display) { + vs->lwebsock = inet_listen(vs->ws_display, NULL, 256, + SOCK_STREAM, 0, errp); + } else { + vs->lwebsock = inet_listen(vs->display, NULL, 256, + SOCK_STREAM, 5700, errp); + } + + if (vs->lwebsock < 0) { + if (vs->lsock) { + close(vs->lsock); + vs->lsock = -1; + } + g_free(dpy); + goto fail; + } + } +#endif /* CONFIG_VNC_WS */ } g_free(vs->display); vs->display = dpy; - qemu_set_fd_handler2(vs->lsock, NULL, vnc_listen_read, NULL, vs); + qemu_set_fd_handler2(vs->lsock, NULL, + vnc_listen_regular_read, NULL, vs); +#ifdef CONFIG_VNC_WS + if (vs->websocket) { + qemu_set_fd_handler2(vs->lwebsock, NULL, + vnc_listen_websocket_read, NULL, vs); + } +#endif /* CONFIG_VNC_WS */ } return; fail: g_free(vs->display); vs->display = NULL; +#ifdef CONFIG_VNC_WS + g_free(vs->ws_display); + vs->ws_display = NULL; +#endif /* CONFIG_VNC_WS */ } void vnc_display_add_client(DisplayState *ds, int csock, int skipauth) { VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; - vnc_connect(vs, csock, skipauth); + vnc_connect(vs, csock, skipauth, 0); } |