From patchwork Wed Jan 2 13:29:45 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim Hardeck X-Patchwork-Id: 209044 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 9E8E22C0087 for ; Thu, 3 Jan 2013 00:32:12 +1100 (EST) Received: from localhost ([::1]:47057 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TqOQT-0006oA-Th for incoming@patchwork.ozlabs.org; Wed, 02 Jan 2013 08:32:09 -0500 Received: from eggs.gnu.org ([208.118.235.92]:50747) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TqOQ4-0006EJ-6D for qemu-devel@nongnu.org; Wed, 02 Jan 2013 08:31:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TqOPy-0003gC-1l for qemu-devel@nongnu.org; Wed, 02 Jan 2013 08:31:44 -0500 Received: from cantor2.suse.de ([195.135.220.15]:51105 helo=mx2.suse.de) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TqOPx-0003XU-ED for qemu-devel@nongnu.org; Wed, 02 Jan 2013 08:31:37 -0500 Received: from relay1.suse.de (unknown [195.135.220.254]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mx2.suse.de (Postfix) with ESMTP id 7412BA3CEE; Wed, 2 Jan 2013 14:30:56 +0100 (CET) From: Tim Hardeck To: qemu-devel@nongnu.org Date: Wed, 2 Jan 2013 14:29:45 +0100 Message-Id: <1357133386-7643-3-git-send-email-thardeck@suse.de> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1357133386-7643-1-git-send-email-thardeck@suse.de> References: <1357133386-7643-1-git-send-email-thardeck@suse.de> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x X-Received-From: 195.135.220.15 Cc: aliguori@us.ibm.com, stefanha@gmail.com, github@martintribe.org, Tim Hardeck , alevy@redhat.com, kraxel@redhat.com, corentin.chary@gmail.com Subject: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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=" 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 --- 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 < 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. + */ + +#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) +{ + 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); + 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); + 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 +#define __QEMU_VNC_WS_H + +#include + +#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 { + char c[4]; + uint32_t u; +} WsMask; + +/* XXX: The union and the structs do not need to be named. + * 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 { + unsigned char b0; + unsigned char b1; + union { + struct __attribute__ ((__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=' */ + 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 */