On Wed, Jan 2, 2013 at 1:29 PM, Tim Hardeck <thard...@suse.de> wrote: > 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 <thard...@suse.de> > --- > configure | 27 +++++- > qemu-options.hx | 8 ++ > ui/Makefile.objs | 1 + > ui/vnc-ws.c | 282 > ++++++++++++++++++++++++++++++++++++++++++++++++++++++ > ui/vnc-ws.h | 92 ++++++++++++++++++ > ui/vnc.c | 187 ++++++++++++++++++++++++++++++++---- > ui/vnc.h | 19 ++++ > 7 files changed, 595 insertions(+), 21 deletions(-) > create mode 100644 ui/vnc-ws.c > create mode 100644 ui/vnc-ws.h > > diff --git a/configure b/configure > index b0c7e54..4aca750 100755 > --- a/configure > +++ b/configure > @@ -158,6 +158,7 @@ vnc_tls="" > vnc_sasl="" > vnc_jpeg="" > vnc_png="" > +vnc_ws="" > xen="" > xen_ctrl_version="" > xen_pci_passthrough="" > @@ -714,6 +715,10 @@ for opt do > ;; > --enable-vnc-png) vnc_png="yes" > ;; > + --disable-vnc-ws) vnc_ws="no" > + ;; > + --enable-vnc-ws) vnc_ws="yes" > + ;; > --disable-slirp) slirp="no" > ;; > --disable-uuid) uuid="no" > @@ -1059,6 +1064,8 @@ echo " --disable-vnc-jpeg disable JPEG lossy > compression for VNC server" > echo " --enable-vnc-jpeg enable JPEG lossy compression for VNC > server" > echo " --disable-vnc-png disable PNG compression for VNC server > (default)" > echo " --enable-vnc-png enable PNG compression for VNC server" > +echo " --disable-vnc-ws disable Websockets support for VNC server" > +echo " --enable-vnc-ws enable Websockets support for VNC server" > echo " --disable-curses disable curses output" > echo " --enable-curses enable curses output" > echo " --disable-curl disable curl connectivity" > @@ -1703,8 +1710,8 @@ EOF > fi > > ########################################## > -# VNC TLS detection > -if test "$vnc" = "yes" -a "$vnc_tls" != "no" ; then > +# VNC TLS/WS detection > +if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; > then > cat > $TMPC <<EOF > #include <gnutls/gnutls.h> > int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return > 0; } > @@ -1712,14 +1719,23 @@ EOF > vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null` > vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null` > if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then > - vnc_tls=yes > + if test "$vnc_tls" != "no" ; then > + vnc_tls=yes > + fi > + if test "$vnc_ws" != "no" ; then > + vnc_ws=yes > + fi > libs_softmmu="$vnc_tls_libs $libs_softmmu" > QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags" > else > if test "$vnc_tls" = "yes" ; then > feature_not_found "vnc-tls" > fi > + if test "$vnc_ws" = "yes" ; then > + feature_not_found "vnc-ws" > + fi > vnc_tls=no > + vnc_ws=no > fi > fi > > @@ -3248,6 +3264,7 @@ if test "$vnc" = "yes" ; then > echo "VNC SASL support $vnc_sasl" > echo "VNC JPEG support $vnc_jpeg" > echo "VNC PNG support $vnc_png" > + echo "VNC WS support $vnc_ws" > fi > if test -n "$sparc_cpu"; then > echo "Target Sparc Arch $sparc_cpu" > @@ -3419,6 +3436,10 @@ fi > if test "$vnc_png" = "yes" ; then > echo "CONFIG_VNC_PNG=y" >> $config_host_mak > fi > +if test "$vnc_ws" = "yes" ; then > + echo "CONFIG_VNC_WS=y" >> $config_host_mak > + echo "VNC_WS_CFLAGS=$vnc_ws_cflags" >> $config_host_mak > +fi > if test "$fnmatch" = "yes" ; then > echo "CONFIG_FNMATCH=y" >> $config_host_mak > fi > diff --git a/qemu-options.hx b/qemu-options.hx > index 9df0cde..38ff002 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -1096,6 +1096,14 @@ client is specified by the @var{display}. For reverse > network > connections (@var{host}:@var{d},@code{reverse}), the @var{d} argument > is a TCP port number, not a display number. > > +@item websocket > + > +Opens an additional TCP listening port dedicated to VNC Websocket > connections. > +By defintion the Websocket port is 5700+@var{display}. If @var{host} is > +specified connections will only be allowed from this host. > +As an alternative the Websocket port could be specified by using > +@code{websocket}=@var{port}. > + > @item password > > Require that password based authentication is used for client connections. > diff --git a/ui/Makefile.objs b/ui/Makefile.objs > index 6768bb7..d9db073 100644 > --- a/ui/Makefile.objs > +++ b/ui/Makefile.objs > @@ -4,6 +4,7 @@ vnc-obj-y += vnc-enc-tight.o vnc-palette.o > vnc-obj-y += vnc-enc-zrle.o > vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o > vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o > +vnc-obj-$(CONFIG_VNC_WS) += vnc-ws.o > vnc-obj-y += vnc-jobs.o > > common-obj-y += keymaps.o console.o cursor.o input.o qemu-pixman.o > diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c > new file mode 100644 > index 0000000..eac4a6e > --- /dev/null > +++ b/ui/vnc-ws.c > @@ -0,0 +1,282 @@ > +/* > + * QEMU VNC display driver: Websockets support > + * > + * Copyright (C) 2010 Joel Martin > + * Copyright (C) 2012 Tim Hardeck > + * > + * This is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This software is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this software; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, > + * USA.
Please use the recommended web version like other files. > + */ > + > +#include "vnc.h" > + > +void vncws_handshake_read(void *opaque) > +{ > + VncState *vs = opaque; > + long ret; > + buffer_reserve(&vs->ws_input, 4096); > + ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096); > + > + if (!ret) { > + if (vs->csock == -1) { > + vnc_disconnect_finish(vs); > + } > + return; > + } > + vs->ws_input.offset += ret; > + > + if (vs->ws_input.offset > 0) { > + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); > + vncws_process_handshake(vs, vs->ws_input.buffer, > vs->ws_input.offset); > + buffer_reset(&vs->ws_input); > + } > +} > + > + > +long vnc_client_read_ws(VncState *vs) > +{ > + int ret, err; > + uint8_t *payload; > + size_t payload_size, frame_size; > + VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer, > + vs->ws_input.capacity, vs->ws_input.offset); > + buffer_reserve(&vs->ws_input, 4096); > + ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096); > + if (!ret) { > + return 0; > + } > + vs->ws_input.offset += ret; > + > + /* make sure that nothing is left in the ws_input buffer */ > + do { > + err = vncws_decode_frame(&vs->ws_input, &payload, > + &payload_size, &frame_size); > + if (err <= 0) { > + return err; > + } > + > + buffer_reserve(&vs->input, payload_size); > + buffer_append(&vs->input, payload, payload_size); > + > + buffer_advance(&vs->ws_input, frame_size); > + } while (vs->ws_input.offset > 0); > + > + return ret; > +} > + > +long vnc_client_write_ws(VncState *vs) > +{ > + long ret; > + VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n", > + vs->output.buffer, vs->output.capacity, vs->output.offset); > + vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset); > + buffer_reset(&vs->output); > + ret = vnc_client_write_buf(vs, vs->ws_output.buffer, > vs->ws_output.offset); > + if (!ret) { > + return 0; > + } > + > + buffer_advance(&vs->ws_output, ret); > + > + if (vs->ws_output.offset == 0) { > + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); > + } > + > + return ret; > +} > + > +static char *vncws_extract_handshake_entry(const char *handshake, > + size_t handshake_len, const char *name) Indentation is off. > +{ > + char *begin, *end, *ret = NULL; > + char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name); > + begin = g_strstr_len(handshake, handshake_len, line); > + if (begin != NULL) { > + begin += strlen(line); > + end = g_strstr_len(begin, handshake_len - (begin - handshake), > + WS_HANDSHAKE_DELIM); Also here. > + if (end != NULL) { > + ret = g_strndup(begin, end - begin); > + } > + } > + g_free(line); > + return ret; > +} > + > +static void vncws_send_handshake_response(VncState *vs, const char* key) > +{ > + char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1]; > + char hash[SHA1_DIGEST_LEN]; > + size_t hash_size = SHA1_DIGEST_LEN; > + char *accept = NULL, *response = NULL; > + gnutls_datum_t in; > + > + g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1); > + g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1); > + > + /* hash and encode it */ > + in.data = (void *)combined_key; > + in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN; > + if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size) > + == GNUTLS_E_SUCCESS) { > + accept = g_base64_encode((guchar *)hash, SHA1_DIGEST_LEN); > + } > + if (accept == NULL) { > + VNC_DEBUG("Hashing Websocket combined key failed\n"); > + vnc_client_error(vs); > + return; > + } > + > + response = g_strdup_printf(WS_HANDSHAKE, accept); > + vnc_write(vs, response, strlen(response)); > + vnc_flush(vs); > + > + g_free(accept); > + g_free(response); > + > + vs->encode_ws = 1; > + vnc_init_state(vs); > +} > + > +void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size) > +{ > + char *protocols = vncws_extract_handshake_entry((const char *)line, size, > + "Sec-WebSocket-Protocol"); > + char *version = vncws_extract_handshake_entry((const char *)line, size, > + "Sec-WebSocket-Version"); > + char *key = vncws_extract_handshake_entry((const char *)line, size, > + "Sec-WebSocket-Key"); > + > + if (protocols && version && key > + && g_strrstr(protocols, "binary") > + && !strcmp(version, WS_SUPPORTED_VERSION) > + && strlen(key) == WS_CLIENT_KEY_LEN) { > + vncws_send_handshake_response(vs, key); > + } else { > + VNC_DEBUG("Defective Websockets header or unsupported protocol\n"); > + vnc_client_error(vs); > + } > + > + g_free(protocols); > + g_free(version); > + g_free(key); > +} > + > +void vncws_encode_frame(Buffer *output, const void *payload, > + const size_t payload_size) > +{ > + size_t header_size = 0; > + unsigned char opcode = WS_OPCODE_BINARY_FRAME; > + union { > + char buf[WS_HEAD_MAX_LEN]; > + WsHeader ws; > + } header; > + > + if (!payload_size) { > + return; > + } > + > + header.ws.b0 = 0x80 | (opcode & 0x0f); > + if (payload_size <= 125) { > + header.ws.b1 = (uint8_t)payload_size; > + header_size = 2; > + } else if (payload_size < 65536) { > + header.ws.b1 = 0x7e; > + header.ws.u.s16.l16 = htobe16((uint16_t)payload_size); htobe16() etc. are nonstandard, please use cpu_to_be16() etc. instead. > + header_size = 4; > + } else { > + header.ws.b1 = 0x7f; > + header.ws.u.s64.l64 = htobe64(payload_size); > + header_size = 10; > + } > + > + buffer_reserve(output, header_size + payload_size); > + buffer_append(output, header.buf, header_size); > + buffer_append(output, payload, payload_size); > +} > + > +int vncws_decode_frame(Buffer *input, uint8_t **payload, > + size_t *payload_size, size_t *frame_size) > +{ > + unsigned char opcode = 0, fin = 0, has_mask = 0; > + size_t header_size = 0; > + uint32_t *payload32; > + WsHeader *header = (WsHeader *)input->buffer; > + WsMask mask; > + int i; > + > + if (input->offset < WS_HEAD_MIN_LEN + 4) { > + /* header not complete */ > + return 0; > + } > + > + fin = (header->b0 & 0x80) >> 7; > + opcode = header->b0 & 0x0f; > + has_mask = (header->b1 & 0x80) >> 7; > + *payload_size = header->b1 & 0x7f; > + > + if (opcode == WS_OPCODE_CLOSE) { > + /* disconnect */ > + return -1; > + } > + > + /* Websocket frame sanity check: > + * * Websocket fragmentation is not supported. > + * * All websockets frames sent by a client have to be masked. > + * * Only binary encoding is supported. > + */ > + if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) { > + VNC_DEBUG("Received faulty/unsupported Websocket frame\n"); > + return -2; > + } > + > + if (*payload_size < 126) { > + header_size = 6; > + mask = header->u.m; > + } else if (*payload_size == 126 && input->offset >= 8) { > + *payload_size = htobe16(header->u.s16.l16); > + header_size = 8; > + mask = header->u.s16.m16; > + } else if (*payload_size == 127 && input->offset >= 14) { > + *payload_size = htobe64(header->u.s64.l64); > + header_size = 14; > + mask = header->u.s64.m64; > + } else { > + /* header not complete */ > + return 0; > + } > + > + *frame_size = header_size + *payload_size; > + > + if (input->offset < *frame_size) { > + /* frame not complete */ > + return 0; > + } > + > + *payload = input->buffer + header_size; > + > + /* unmask frame */ > + /* process 1 frame (32 bit op) */ > + payload32 = (uint32_t *)(*payload); > + for (i = 0; i < *payload_size / 4; i++) { > + payload32[i] ^= mask.u; > + } > + /* process the remaining bytes (if any) */ > + for (i *= 4; i < *payload_size; i++) { > + (*payload)[i] ^= mask.c[i % 4]; > + } > + > + return 1; > +} > diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h > new file mode 100644 > index 0000000..c61dfad > --- /dev/null > +++ b/ui/vnc-ws.h > @@ -0,0 +1,92 @@ > +/* > + * QEMU VNC display driver: Websockets support > + * > + * Copyright (C) 2010 Joel Martin > + * Copyright (C) 2012 Tim Hardeck > + * > + * This is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This software is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this software; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, > + * USA. > + */ > + > +#ifndef __QEMU_VNC_WS_H This should be QEMU_UI_VNC_WS_H. > +#define __QEMU_VNC_WS_H > + > +#include <gnutls/gnutls.h> > + > +#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) > +#define SHA1_DIGEST_LEN 20 > + > +#define WS_ACCEPT_LEN (B64LEN(SHA1_DIGEST_LEN) + 1) > +#define WS_CLIENT_KEY_LEN 24 > +#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" > +#define WS_GUID_LEN strlen(WS_GUID) > + > +#define WS_HANDSHAKE "HTTP/1.1 101 Switching Protocols\r\n\ > +Upgrade: websocket\r\n\ > +Connection: Upgrade\r\n\ > +Sec-WebSocket-Accept: %s\r\n\ > +Sec-WebSocket-Protocol: binary\r\n\ > +\r\n" > +#define WS_HANDSHAKE_DELIM "\r\n" > +#define WS_SUPPORTED_VERSION "13" > + > +#define WS_HEAD_MIN_LEN sizeof(uint16_t) > +#define WS_HEAD_MAX_LEN (WS_HEAD_MIN_LEN + sizeof(uint64_t) + > sizeof(uint32_t)) > + > +typedef union ws_mask_s { Usually the typedef name and the name of the struct or union match. > + char c[4]; > + uint32_t u; > +} WsMask; > + > +/* XXX: The union and the structs do not need to be named. Anonymous structs became standard only with C11 and we are aiming C99 with some C89isms. Please remove the comment. > + * We are working around a bug present in GCC < 4.6 which prevented > + * it from recognizing anonymous structs and unions. > + * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784 > + */ > +typedef struct __attribute__ ((__packed__)) ws_header_s { Please use QEMU_PACKED. > + unsigned char b0; > + unsigned char b1; > + union { > + struct __attribute__ ((__packed__)) { I think individual items inside a packed struct are automatically packed. > + uint16_t l16; > + WsMask m16; > + } s16; > + struct __attribute__ ((__packed__)) { > + uint64_t l64; > + WsMask m64; > + } s64; > + WsMask m; > + } u; > +} WsHeader; > + > +enum { > + WS_OPCODE_CONTINUATION = 0x0, > + WS_OPCODE_TEXT_FRAME = 0x1, > + WS_OPCODE_BINARY_FRAME = 0x2, > + WS_OPCODE_CLOSE = 0x8, > + WS_OPCODE_PING = 0x9, > + WS_OPCODE_PONG = 0xA > +}; > + > +void vncws_handshake_read(void *opaque); > +long vnc_client_write_ws(VncState *vs); > +long vnc_client_read_ws(VncState *vs); > +void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size); > +void vncws_encode_frame(Buffer *output, const void *payload, > + const size_t payload_size); > +int vncws_decode_frame(Buffer *input, uint8_t **payload, > + size_t *payload_size, size_t *frame_size); > + > +#endif /* __QEMU_VNC_WS_H */ > diff --git a/ui/vnc.c b/ui/vnc.c > index ddf01f1..ee08894 100644 > --- a/ui/vnc.c > +++ b/ui/vnc.c > @@ -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); > } > diff --git a/ui/vnc.h b/ui/vnc.h > index 5059cbe..f93c89a 100644 > --- a/ui/vnc.h > +++ b/ui/vnc.h > @@ -99,6 +99,9 @@ typedef struct VncDisplay VncDisplay; > #ifdef CONFIG_VNC_SASL > #include "vnc-auth-sasl.h" > #endif > +#ifdef CONFIG_VNC_WS > +#include "vnc-ws.h" > +#endif > > struct VncRectStat > { > @@ -142,6 +145,11 @@ struct VncDisplay > QEMUTimer *timer; > int timer_interval; > int lsock; > +#ifdef CONFIG_VNC_WS > + int lwebsock; > + bool websocket; > + char *ws_display; > +#endif > DisplayState *ds; > kbd_layout_t *kbd_layout; > int lock_key_sync; > @@ -269,11 +277,19 @@ struct VncState > #ifdef CONFIG_VNC_SASL > VncStateSASL sasl; > #endif > +#ifdef CONFIG_VNC_WS > + bool encode_ws; > + bool websocket; > +#endif > > QObject *info; > > Buffer output; > Buffer input; > +#ifdef CONFIG_VNC_WS > + Buffer ws_input; > + Buffer ws_output; > +#endif > /* current output mode information */ > VncWritePixels *write_pixels; > PixelFormat client_pf; > @@ -493,6 +509,8 @@ void vnc_write_u16(VncState *vs, uint16_t value); > void vnc_write_u8(VncState *vs, uint8_t value); > void vnc_flush(VncState *vs); > void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting); > +void vnc_disconnect_finish(VncState *vs); > +void vnc_init_state(VncState *vs); > > > /* Buffer I/O functions */ > @@ -511,6 +529,7 @@ void buffer_reset(Buffer *buffer); > void buffer_free(Buffer *buffer); > void buffer_append(Buffer *buffer, const void *data, size_t len); > void buffer_advance(Buffer *buf, size_t len); > +uint8_t *buffer_end(Buffer *buffer); > > > /* Misc helpers */ > -- > 1.7.10.4 > >