* [Qemu-devel] [PATCH 0/3 v5] vnc: added initial websocket protocol support
@ 2013-01-02 13:29 Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function Tim Hardeck
` (2 more replies)
0 siblings, 3 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-02 13:29 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, Tim Hardeck, alevy, kraxel,
corentin.chary
This patch set 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.
Changes v2
* removed automatic websocket recognition
* added new lwebsock socket on port 5700 + display when the vnc option
"websocket" is passed on
* adapted vnc_connect vnc_listen_read to differ between websocket
* added separate event handler to read the Websocket handshake
Changes v3
* added manual port specification by using ",websocket=<port>"
* switched from memmem() to g_strstr_len()
* removed masked_size from vncws_decode_frame()
* resetted vnc_tls variable to default in the configure script
Changes v4
* incorporated suggestions from Stefan Hajnoczi
* moved websockets encoding from vnc_write to its own client_write function
* moved websockets decoding to its own client_read function
* added initialization checks to vnc_disconnect to prevent crashes if a regular client connects to the websocket port
Changes v5
* added initialized variable to VncState to prevent crashes during vnc_disconnect - the previously added initialization checks didn't prevent segfaults when a websocket client was connected
Tim Hardeck (3):
vnc: added buffer_advance function
vnc: added initial websocket protocol support
vnc: fix possible uninitialized removals
configure | 27 +++++-
qemu-options.hx | 8 ++
ui/Makefile.objs | 1 +
ui/vnc-ws.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ui/vnc-ws.h | 92 ++++++++++++++++++
ui/vnc.c | 211 +++++++++++++++++++++++++++++++++++-----
ui/vnc.h | 21 ++++
7 files changed, 614 insertions(+), 28 deletions(-)
create mode 100644 ui/vnc-ws.c
create mode 100644 ui/vnc-ws.h
--
1.7.10.4
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function
2013-01-02 13:29 [Qemu-devel] [PATCH 0/3 v5] vnc: added initial websocket protocol support Tim Hardeck
@ 2013-01-02 13:29 ` Tim Hardeck
2013-01-07 19:38 ` Anthony Liguori
2013-01-02 13:29 ` [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 3/3] vnc: fix possible uninitialized removals Tim Hardeck
2 siblings, 1 reply; 14+ messages in thread
From: Tim Hardeck @ 2013-01-02 13:29 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, Tim Hardeck, alevy, kraxel,
corentin.chary
Following Anthony Liguori's Websocket implementation I have added the
buffer_advance function to VNC and replaced all related buffer memmove
operations with it.
Signed-off-by: Tim Hardeck <thardeck@suse.de>
---
ui/vnc.c | 13 +++++++++----
ui/vnc.h | 1 +
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/ui/vnc.c b/ui/vnc.c
index 8912b78..ddf01f1 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -510,6 +510,13 @@ void buffer_append(Buffer *buffer, const void *data, size_t len)
buffer->offset += len;
}
+void buffer_advance(Buffer *buf, size_t len)
+{
+ memmove(buf->buffer, buf->buffer + len,
+ (buf->offset - len));
+ buf->offset -= len;
+}
+
static void vnc_desktop_resize(VncState *vs)
{
DisplayState *ds = vs->ds;
@@ -1166,8 +1173,7 @@ static long vnc_client_write_plain(VncState *vs)
if (!ret)
return 0;
- memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret));
- vs->output.offset -= ret;
+ buffer_advance(&vs->output, ret);
if (vs->output.offset == 0) {
qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
@@ -1313,8 +1319,7 @@ void vnc_client_read(void *opaque)
}
if (!ret) {
- memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len));
- vs->input.offset -= len;
+ buffer_advance(&vs->input, len);
} else {
vs->read_handler_expect = ret;
}
diff --git a/ui/vnc.h b/ui/vnc.h
index 8b40f09..5059cbe 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -510,6 +510,7 @@ void buffer_reserve(Buffer *buffer, size_t len);
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);
/* Misc helpers */
--
1.7.10.4
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-02 13:29 [Qemu-devel] [PATCH 0/3 v5] vnc: added initial websocket protocol support Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function Tim Hardeck
@ 2013-01-02 13:29 ` Tim Hardeck
2013-01-04 20:20 ` Blue Swirl
2013-01-07 19:52 ` Anthony Liguori
2013-01-02 13:29 ` [Qemu-devel] [PATCH 3/3] vnc: fix possible uninitialized removals Tim Hardeck
2 siblings, 2 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-02 13:29 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, Tim Hardeck, alevy, kraxel,
corentin.chary
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>
---
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.
+ */
+
+#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 <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 {
+ 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=<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
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH 3/3] vnc: fix possible uninitialized removals
2013-01-02 13:29 [Qemu-devel] [PATCH 0/3 v5] vnc: added initial websocket protocol support Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support Tim Hardeck
@ 2013-01-02 13:29 ` Tim Hardeck
2 siblings, 0 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-02 13:29 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, Tim Hardeck, alevy, kraxel,
corentin.chary
Some VncState values are not initialized before the Websocket handshake.
If it fails QEMU segfaults during the cleanup. To prevent this behavior
intialization checks are added.
Signed-off-by: Tim Hardeck <thardeck@suse.de>
---
ui/vnc.c | 11 ++++++++---
ui/vnc.h | 1 +
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/ui/vnc.c b/ui/vnc.c
index ee08894..ff4e2ae 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1053,20 +1053,24 @@ void vnc_disconnect_finish(VncState *vs)
audio_del(vs);
vnc_release_modifiers(vs);
- QTAILQ_REMOVE(&vs->vd->clients, vs, next);
+ if (vs->initialized) {
+ QTAILQ_REMOVE(&vs->vd->clients, vs, next);
+ qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
+ }
if (QTAILQ_EMPTY(&vs->vd->clients)) {
dcl->idle = 1;
}
- qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
vnc_remove_timer(vs->vd);
if (vs->vd->lock_key_sync)
qemu_remove_led_event_handler(vs->led);
vnc_unlock_output(vs);
qemu_mutex_destroy(&vs->output_mutex);
- qemu_bh_delete(vs->bh);
+ if (vs->bh != NULL) {
+ qemu_bh_delete(vs->bh);
+ }
buffer_free(&vs->jobs_buffer);
for (i = 0; i < VNC_STAT_ROWS; ++i) {
@@ -2749,6 +2753,7 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth, bool websocket)
void vnc_init_state(VncState *vs)
{
+ vs->initialized = true;
VncDisplay *vd = vs->vd;
vs->ds = vd->ds;
diff --git a/ui/vnc.h b/ui/vnc.h
index f93c89a..45d7686 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -306,6 +306,7 @@ struct VncState
QEMUPutLEDEntry *led;
bool abort;
+ bool initialized;
QemuMutex output_mutex;
QEMUBH *bh;
Buffer jobs_buffer;
--
1.7.10.4
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-02 13:29 ` [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support Tim Hardeck
@ 2013-01-04 20:20 ` Blue Swirl
2013-01-05 18:02 ` Tim Hardeck
2013-01-07 19:52 ` Anthony Liguori
1 sibling, 1 reply; 14+ messages in thread
From: Blue Swirl @ 2013-01-04 20:20 UTC (permalink / raw)
To: Tim Hardeck
Cc: aliguori, stefanha, github, qemu-devel, alevy, kraxel,
corentin.chary
On Wed, Jan 2, 2013 at 1:29 PM, Tim Hardeck <thardeck@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 <thardeck@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
>
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-04 20:20 ` Blue Swirl
@ 2013-01-05 18:02 ` Tim Hardeck
0 siblings, 0 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-05 18:02 UTC (permalink / raw)
To: Blue Swirl
Cc: aliguori, stefanha, github, qemu-devel, alevy, kraxel,
corentin.chary
[-- Attachment #1: Type: text/plain, Size: 1243 bytes --]
Hi,
thanks for your suggestions.
On Fri, 2013-01-04 at 20:20 +0000, Blue Swirl wrote:
> On Wed, Jan 2, 2013 at 1:29 PM, Tim Hardeck <thardeck@suse.de> wrote:
> > + * 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.
According to
http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Type-Attributes.html the
packed attribute should work like you suggest but if I don't add
QEMU_PACKED to the member structs too I get decoding issues.
I have incorporated everything else in my new patch set but am going to
wait for more feedback.
Regards
Tim
--
SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix
Imendörffer, HRB 16746 (AG Nürnberg)
Maxfeldstr. 5, 90409 Nürnberg, Germany
T: +49 (0) 911 74053-0 F: +49 (0) 911 74053-483
http://www.suse.de/
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 490 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function
2013-01-02 13:29 ` [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function Tim Hardeck
@ 2013-01-07 19:38 ` Anthony Liguori
0 siblings, 0 replies; 14+ messages in thread
From: Anthony Liguori @ 2013-01-07 19:38 UTC (permalink / raw)
To: Tim Hardeck, qemu-devel; +Cc: stefanha, github, alevy, kraxel, corentin.chary
Tim Hardeck <thardeck@suse.de> writes:
> Following Anthony Liguori's Websocket implementation I have added the
> buffer_advance function to VNC and replaced all related buffer memmove
> operations with it.
>
> Signed-off-by: Tim Hardeck <thardeck@suse.de>
Reviewed-by: Anthony Liguori <aliguori@us.ibm.com>
Regards,
Anthony Liguori
> ---
> ui/vnc.c | 13 +++++++++----
> ui/vnc.h | 1 +
> 2 files changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/ui/vnc.c b/ui/vnc.c
> index 8912b78..ddf01f1 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -510,6 +510,13 @@ void buffer_append(Buffer *buffer, const void *data, size_t len)
> buffer->offset += len;
> }
>
> +void buffer_advance(Buffer *buf, size_t len)
> +{
> + memmove(buf->buffer, buf->buffer + len,
> + (buf->offset - len));
> + buf->offset -= len;
> +}
> +
> static void vnc_desktop_resize(VncState *vs)
> {
> DisplayState *ds = vs->ds;
> @@ -1166,8 +1173,7 @@ static long vnc_client_write_plain(VncState *vs)
> if (!ret)
> return 0;
>
> - memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret));
> - vs->output.offset -= ret;
> + buffer_advance(&vs->output, ret);
>
> if (vs->output.offset == 0) {
> qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
> @@ -1313,8 +1319,7 @@ void vnc_client_read(void *opaque)
> }
>
> if (!ret) {
> - memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len));
> - vs->input.offset -= len;
> + buffer_advance(&vs->input, len);
> } else {
> vs->read_handler_expect = ret;
> }
> diff --git a/ui/vnc.h b/ui/vnc.h
> index 8b40f09..5059cbe 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -510,6 +510,7 @@ void buffer_reserve(Buffer *buffer, size_t len);
> 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);
>
>
> /* Misc helpers */
> --
> 1.7.10.4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-02 13:29 ` [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support Tim Hardeck
2013-01-04 20:20 ` Blue Swirl
@ 2013-01-07 19:52 ` Anthony Liguori
2013-01-07 23:57 ` Tim Hardeck
1 sibling, 1 reply; 14+ messages in thread
From: Anthony Liguori @ 2013-01-07 19:52 UTC (permalink / raw)
To: Tim Hardeck, qemu-devel; +Cc: stefanha, github, alevy, kraxel, corentin.chary
Tim Hardeck <thardeck@suse.de> writes:
> 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>
> ---
> 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.
> + */
> +
> +#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);
This is making a bad assumption. You're assuming that
vnc_client_read_buf() is always going to return at least the amount of
data you need to do the handshake and never any more data.
You need something more sophisticated that keeps reading data until
you've gotten the complete set of headers and the trailing double EOL.
> +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);
This likewise seems wrong. What happens if you get a read of a partial
frame? This code will fail, no?
Regards,
Anthony Liguori
> +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 <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 {
> + 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=<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
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-07 19:52 ` Anthony Liguori
@ 2013-01-07 23:57 ` Tim Hardeck
2013-01-08 0:38 ` Anthony Liguori
0 siblings, 1 reply; 14+ messages in thread
From: Tim Hardeck @ 2013-01-07 23:57 UTC (permalink / raw)
To: Anthony Liguori
Cc: stefanha, github, qemu-devel, alevy, kraxel, corentin.chary
[-- Attachment #1: Type: text/plain, Size: 3956 bytes --]
Hi Anthony,
thanks for your feedback.
On Mon, 2013-01-07 at 13:52 -0600, Anthony Liguori wrote:
> Tim Hardeck <thardeck@suse.de> writes:
> > +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);
>
> This is making a bad assumption. You're assuming that
> vnc_client_read_buf() is always going to return at least the amount of
> data you need to do the handshake and never any more data.
Following the Websocket protocol specification there shouldn't be more
data until a handshake response from the server.
> You need something more sophisticated that keeps reading data until
> you've gotten the complete set of headers and the trailing double EOL.
How about that:
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 6b98d6b..298bc20 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -35,7 +35,8 @@ void vncws_handshake_read(void *opaque)
}
vs->ws_input.offset += ret;
- if (vs->ws_input.offset > 0) {
+ if (g_strstr_len((char *)vs->ws_input.buffer, vs->ws_input.offset,
+ WS_HANDSHAKE_END)) {
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);
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 7402e77..c8dfe34 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -38,6 +38,7 @@ Sec-WebSocket-Accept: %s\r\n\
Sec-WebSocket-Protocol: binary\r\n\
\r\n"
#define WS_HANDSHAKE_DELIM "\r\n"
+#define WS_HANDSHAKE_END "\r\n\r\n"
#define WS_SUPPORTED_VERSION "13"
#define WS_HEAD_MIN_LEN sizeof(uint16_t)
We also could calculate the handshake size and use buffer_advance
instead but since there shouldn't be any additional data received from
the client until a server response anyway I would prefer it this way.
>
> > +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);
>
> This likewise seems wrong. What happens if you get a read of a partial
> frame? This code will fail, no?
In case of a partial frame err would be 0 and QEMU would wait for more
data until processing.
Regards
Tim
--
SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix
Imendörffer, HRB 16746 (AG Nürnberg)
Maxfeldstr. 5, 90409 Nürnberg, Germany
T: +49 (0) 911 74053-0 F: +49 (0) 911 74053-483
http://www.suse.de/
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 490 bytes --]
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-07 23:57 ` Tim Hardeck
@ 2013-01-08 0:38 ` Anthony Liguori
2013-01-11 14:12 ` Tim Hardeck
0 siblings, 1 reply; 14+ messages in thread
From: Anthony Liguori @ 2013-01-08 0:38 UTC (permalink / raw)
To: Tim Hardeck; +Cc: stefanha, github, qemu-devel, alevy, kraxel, corentin.chary
Tim Hardeck <thardeck@suse.de> writes:
> Hi Anthony,
>
> thanks for your feedback.
>
> On Mon, 2013-01-07 at 13:52 -0600, Anthony Liguori wrote:
>> Tim Hardeck <thardeck@suse.de> writes:
> > +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);
>>
>> This is making a bad assumption. You're assuming that
>> vnc_client_read_buf() is always going to return at least the amount of
>> data you need to do the handshake and never any more data.
>
> Following the Websocket protocol specification there shouldn't be more
> data until a handshake response from the server.
>
>> You need something more sophisticated that keeps reading data until
>> you've gotten the complete set of headers and the trailing double EOL.
>
> How about that:
Better, but I still think it's better to advance the buffer based on the
parsed header sizes that to assume there's no additional data.
Regards,
Anthony Liguori
>
> diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
> index 6b98d6b..298bc20 100644
> --- a/ui/vnc-ws.c
> +++ b/ui/vnc-ws.c
> @@ -35,7 +35,8 @@ void vncws_handshake_read(void *opaque)
> }
> vs->ws_input.offset += ret;
>
> - if (vs->ws_input.offset > 0) {
> + if (g_strstr_len((char *)vs->ws_input.buffer, vs->ws_input.offset,
> + WS_HANDSHAKE_END)) {
> 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);
> diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
> index 7402e77..c8dfe34 100644
> --- a/ui/vnc-ws.h
> +++ b/ui/vnc-ws.h
> @@ -38,6 +38,7 @@ Sec-WebSocket-Accept: %s\r\n\
> Sec-WebSocket-Protocol: binary\r\n\
> \r\n"
> #define WS_HANDSHAKE_DELIM "\r\n"
> +#define WS_HANDSHAKE_END "\r\n\r\n"
> #define WS_SUPPORTED_VERSION "13"
>
> #define WS_HEAD_MIN_LEN sizeof(uint16_t)
>
>
> We also could calculate the handshake size and use buffer_advance
> instead but since there shouldn't be any additional data received from
> the client until a server response anyway I would prefer it this way.
>
>
>>
>> > +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);
>>
>> This likewise seems wrong. What happens if you get a read of a partial
>> frame? This code will fail, no?
> In case of a partial frame err would be 0 and QEMU would wait for more
> data until processing.
>
> Regards
> Tim
>
>
> --
> SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix
> Imendörffer, HRB 16746 (AG Nürnberg)
> Maxfeldstr. 5, 90409 Nürnberg, Germany
> T: +49 (0) 911 74053-0 F: +49 (0) 911 74053-483
> http://www.suse.de/
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-08 10:27 [Qemu-devel] [PATCH 0/3 v6] vnc: added initial websocket protocol support Tim Hardeck
@ 2013-01-08 10:27 ` Tim Hardeck
2013-01-09 20:47 ` Blue Swirl
0 siblings, 1 reply; 14+ messages in thread
From: Tim Hardeck @ 2013-01-08 10:27 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, blauwirbel, Tim Hardeck, alevy,
kraxel, corentin.chary
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>
---
configure | 27 +++++-
qemu-options.hx | 8 ++
ui/Makefile.objs | 1 +
ui/vnc-ws.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ui/vnc-ws.h | 86 +++++++++++++++++
ui/vnc.c | 187 +++++++++++++++++++++++++++++++----
ui/vnc.h | 19 ++++
7 files changed, 591 insertions(+), 21 deletions(-)
create mode 100644 ui/vnc-ws.c
create mode 100644 ui/vnc-ws.h
diff --git a/configure b/configure
index 837a84a..ac3198c 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=""
@@ -715,6 +716,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"
@@ -1064,6 +1069,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"
@@ -1707,8 +1714,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; }
@@ -1716,14 +1723,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
@@ -3263,6 +3279,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"
@@ -3437,6 +3454,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..9ccdc19
--- /dev/null
+++ b/ui/vnc-ws.c
@@ -0,0 +1,284 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vnc.h"
+
+void vncws_handshake_read(void *opaque)
+{
+ VncState *vs = opaque;
+ uint8_t *handshake_end;
+ 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;
+
+ handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
+ vs->ws_input.offset, WS_HANDSHAKE_END);
+ if (handshake_end) {
+ 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_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
+ strlen(WS_HANDSHAKE_END));
+ }
+}
+
+
+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 = cpu_to_be16((uint16_t)payload_size);
+ header_size = 4;
+ } else {
+ header.ws.b1 = 0x7f;
+ header.ws.u.s64.l64 = cpu_to_be64(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 = be16_to_cpu(header->u.s16.l16);
+ header_size = 8;
+ mask = header->u.s16.m16;
+ } else if (*payload_size == 127 && input->offset >= 14) {
+ *payload_size = be64_to_cpu(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..c8dfe34
--- /dev/null
+++ b/ui/vnc-ws.h
@@ -0,0 +1,86 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __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_HANDSHAKE_END "\r\n\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 WsMask {
+ char c[4];
+ uint32_t u;
+} WsMask;
+
+typedef struct QEMU_PACKED WsHeader {
+ unsigned char b0;
+ unsigned char b1;
+ union {
+ struct QEMU_PACKED {
+ uint16_t l16;
+ WsMask m16;
+ } s16;
+ struct QEMU_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
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-08 10:27 ` [Qemu-devel] [PATCH 2/3] " Tim Hardeck
@ 2013-01-09 20:47 ` Blue Swirl
0 siblings, 0 replies; 14+ messages in thread
From: Blue Swirl @ 2013-01-09 20:47 UTC (permalink / raw)
To: Tim Hardeck
Cc: aliguori, stefanha, github, qemu-devel, alevy, kraxel,
corentin.chary
On Tue, Jan 8, 2013 at 10:27 AM, Tim Hardeck <thardeck@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 <thardeck@suse.de>
> ---
> configure | 27 +++++-
> qemu-options.hx | 8 ++
> ui/Makefile.objs | 1 +
> ui/vnc-ws.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> ui/vnc-ws.h | 86 +++++++++++++++++
> ui/vnc.c | 187 +++++++++++++++++++++++++++++++----
> ui/vnc.h | 19 ++++
> 7 files changed, 591 insertions(+), 21 deletions(-)
> create mode 100644 ui/vnc-ws.c
> create mode 100644 ui/vnc-ws.h
>
> diff --git a/configure b/configure
> index 837a84a..ac3198c 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=""
> @@ -715,6 +716,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"
> @@ -1064,6 +1069,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"
> @@ -1707,8 +1714,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; }
> @@ -1716,14 +1723,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
>
> @@ -3263,6 +3279,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"
> @@ -3437,6 +3454,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..9ccdc19
> --- /dev/null
> +++ b/ui/vnc-ws.c
> @@ -0,0 +1,284 @@
> +/*
> + * 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, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "vnc.h"
> +
> +void vncws_handshake_read(void *opaque)
> +{
> + VncState *vs = opaque;
> + uint8_t *handshake_end;
> + 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;
> +
> + handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
> + vs->ws_input.offset, WS_HANDSHAKE_END);
> + if (handshake_end) {
> + 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_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
> + strlen(WS_HANDSHAKE_END));
> + }
> +}
> +
> +
> +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 = cpu_to_be16((uint16_t)payload_size);
> + header_size = 4;
> + } else {
> + header.ws.b1 = 0x7f;
> + header.ws.u.s64.l64 = cpu_to_be64(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 = be16_to_cpu(header->u.s16.l16);
> + header_size = 8;
> + mask = header->u.s16.m16;
> + } else if (*payload_size == 127 && input->offset >= 14) {
> + *payload_size = be64_to_cpu(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..c8dfe34
> --- /dev/null
> +++ b/ui/vnc-ws.h
> @@ -0,0 +1,86 @@
> +/*
> + * 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, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef __QEMU_UI_VNC_WS_H
> +#define __QEMU_VNC_WS_H
Now this does not match the above #ifndef so the protection for
multiple inclusion is broken.
> +
> +#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_HANDSHAKE_END "\r\n\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 WsMask {
> + char c[4];
> + uint32_t u;
> +} WsMask;
> +
> +typedef struct QEMU_PACKED WsHeader {
> + unsigned char b0;
> + unsigned char b1;
> + union {
> + struct QEMU_PACKED {
> + uint16_t l16;
> + WsMask m16;
> + } s16;
> + struct QEMU_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 */
Also here.
> 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
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-08 0:38 ` Anthony Liguori
@ 2013-01-11 14:12 ` Tim Hardeck
0 siblings, 0 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-11 14:12 UTC (permalink / raw)
To: Anthony Liguori
Cc: stefanha, github, qemu-devel, alevy, kraxel, corentin.chary
[-- Attachment #1: Type: text/plain, Size: 522 bytes --]
On 01/08/2013 01:38 AM, Anthony Liguori wrote:
> Better, but I still think it's better to advance the buffer based on the
> parsed header sizes that to assume there's no additional data.
I have used buffer_advance in the patch set v6. Does anything else need
to be changed?
Regards
Tim
--
SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix
Imendörffer, HRB 16746 (AG Nürnberg)
Maxfeldstr. 5, 90409 Nürnberg, Germany
T: +49 (0) 911 74053-0 F: +49 (0) 911 74053-483
http://www.suse.de/
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 490 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
2013-01-21 10:04 [Qemu-devel] [PATCH 0/3 v7] " Tim Hardeck
@ 2013-01-21 10:04 ` Tim Hardeck
0 siblings, 0 replies; 14+ messages in thread
From: Tim Hardeck @ 2013-01-21 10:04 UTC (permalink / raw)
To: qemu-devel
Cc: aliguori, stefanha, github, blauwirbel, Tim Hardeck, alevy,
kraxel, corentin.chary
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>
---
configure | 27 +++++-
qemu-options.hx | 8 ++
ui/Makefile.objs | 1 +
ui/vnc-ws.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ui/vnc-ws.h | 86 +++++++++++++++++
ui/vnc.c | 187 +++++++++++++++++++++++++++++++----
ui/vnc.h | 19 ++++
7 files changed, 591 insertions(+), 21 deletions(-)
create mode 100644 ui/vnc-ws.c
create mode 100644 ui/vnc-ws.h
diff --git a/configure b/configure
index 837a84a..ac3198c 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=""
@@ -715,6 +716,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"
@@ -1064,6 +1069,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"
@@ -1707,8 +1714,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; }
@@ -1716,14 +1723,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
@@ -3263,6 +3279,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"
@@ -3437,6 +3454,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..9ccdc19
--- /dev/null
+++ b/ui/vnc-ws.c
@@ -0,0 +1,284 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vnc.h"
+
+void vncws_handshake_read(void *opaque)
+{
+ VncState *vs = opaque;
+ uint8_t *handshake_end;
+ 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;
+
+ handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
+ vs->ws_input.offset, WS_HANDSHAKE_END);
+ if (handshake_end) {
+ 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_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
+ strlen(WS_HANDSHAKE_END));
+ }
+}
+
+
+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 = cpu_to_be16((uint16_t)payload_size);
+ header_size = 4;
+ } else {
+ header.ws.b1 = 0x7f;
+ header.ws.u.s64.l64 = cpu_to_be64(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 = be16_to_cpu(header->u.s16.l16);
+ header_size = 8;
+ mask = header->u.s16.m16;
+ } else if (*payload_size == 127 && input->offset >= 14) {
+ *payload_size = be64_to_cpu(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..039a587
--- /dev/null
+++ b/ui/vnc-ws.h
@@ -0,0 +1,86 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __QEMU_UI_VNC_WS_H
+#define __QEMU_UI_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_HANDSHAKE_END "\r\n\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 WsMask {
+ char c[4];
+ uint32_t u;
+} WsMask;
+
+typedef struct QEMU_PACKED WsHeader {
+ unsigned char b0;
+ unsigned char b1;
+ union {
+ struct QEMU_PACKED {
+ uint16_t l16;
+ WsMask m16;
+ } s16;
+ struct QEMU_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_UI_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
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2013-01-21 10:05 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-01-02 13:29 [Qemu-devel] [PATCH 0/3 v5] vnc: added initial websocket protocol support Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 1/3] vnc: added buffer_advance function Tim Hardeck
2013-01-07 19:38 ` Anthony Liguori
2013-01-02 13:29 ` [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support Tim Hardeck
2013-01-04 20:20 ` Blue Swirl
2013-01-05 18:02 ` Tim Hardeck
2013-01-07 19:52 ` Anthony Liguori
2013-01-07 23:57 ` Tim Hardeck
2013-01-08 0:38 ` Anthony Liguori
2013-01-11 14:12 ` Tim Hardeck
2013-01-02 13:29 ` [Qemu-devel] [PATCH 3/3] vnc: fix possible uninitialized removals Tim Hardeck
-- strict thread matches above, loose matches on Subject: below --
2013-01-08 10:27 [Qemu-devel] [PATCH 0/3 v6] vnc: added initial websocket protocol support Tim Hardeck
2013-01-08 10:27 ` [Qemu-devel] [PATCH 2/3] " Tim Hardeck
2013-01-09 20:47 ` Blue Swirl
2013-01-21 10:04 [Qemu-devel] [PATCH 0/3 v7] " Tim Hardeck
2013-01-21 10:04 ` [Qemu-devel] [PATCH 2/3] " Tim Hardeck
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).