Patchwork [v2] vnc: added initial websockets support

login
register
mail settings
Submitter Tim Hardeck
Date Nov. 20, 2012, 9:21 a.m.
Message ID <1353403318-2877-1-git-send-email-thardeck@suse.de>
Download mbox | patch
Permalink /patch/200272/
State New
Headers show

Comments

Tim Hardeck - Nov. 20, 2012, 9:21 a.m.
This patch adds basic Websockets version 13 - RFC 6455 - support to QEMU
VNC. Binary encoding support on the client side is required.

Because of the GnuTLS requirement the Websockets implementation is
optional (--enable-vnc-ws).

To activate Websockets during runtime the VNC option "websocket" is
used, for example "-vnc :0,websocket" would activate Websockets.
The port for Websockets connections is (5700 + display) so if QEMU VNC
is started with :0 the websockets port would be 5700.

SHA1 is required for the handshake which is generated by GnuTLS.
Since using GnuTLS does automatically activate VNC-TLS, which has
warnings about deprecated parts, I have changed the configure script
to disable VNC-TLS if not explicitly enabled.

Parts of the implementation base on Anthony Liguori's QEMU Websockets
patch from 2010 and on Joel Martin's LibVNC Websockets implementation.

Signed-off-by: Tim Hardeck <thardeck@suse.de>

---

Changes to v1
* 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

Would it be Ok to use a public domain SHA1 implementation like
tests/tcg/sha1.c and if so where should the sha1.c be stored?
Without the GnuTLS dependency would it be Ok to make Websockets not
optional because this would clean up the patch/code quite a bit?

QEMU does segfault if a regular VNC client connects to the Websocket
port and then disconnects because of several unitialized lists since
vnc_init_state wasn't run before.
The segfault could be fixed by applying my previously sent patches
"[PATCH 0/2] fix segfaults triggered by failed vnc handshakes".

I have used parts of the LibVNC websockets implementation that's why
I have added the GPL header to the new files.

---
 configure        |   34 ++++++++-
 qemu-options.hx  |    6 ++
 ui/Makefile.objs |    1 +
 ui/vnc-ws.c      |  211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ui/vnc-ws.h      |  101 ++++++++++++++++++++++++++
 ui/vnc.c         |  185 +++++++++++++++++++++++++++++++++++++++++------
 ui/vnc.h         |   20 ++++++
 7 files changed, 537 insertions(+), 21 deletions(-)
 create mode 100644 ui/vnc-ws.c
 create mode 100644 ui/vnc-ws.h
Daniel P. Berrange - Nov. 20, 2012, 9:47 a.m.
On Tue, Nov 20, 2012 at 10:21:58AM +0100, Tim Hardeck wrote:
> This patch adds basic Websockets version 13 - RFC 6455 - support to QEMU
> VNC. Binary encoding support on the client side is required.
> 
> Because of the GnuTLS requirement the Websockets implementation is
> optional (--enable-vnc-ws).
> 
> To activate Websockets during runtime the VNC option "websocket" is
> used, for example "-vnc :0,websocket" would activate Websockets.
> The port for Websockets connections is (5700 + display) so if QEMU VNC
> is started with :0 the websockets port would be 5700.

We need to be able to specify an explicit port number for the websockets
listen address, separately from the main VNC port number. This automatic
pick of websockets port might be nice for a user starting QEMU manually,
but for management apps we need full direct control.

> Changes to v1
> * 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
> 
> Would it be Ok to use a public domain SHA1 implementation like
> tests/tcg/sha1.c and if so where should the sha1.c be stored?
> Without the GnuTLS dependency would it be Ok to make Websockets not
> optional because this would clean up the patch/code quite a bit?

IMHO using gcrypt/GNUTLS is a good thing. Creating our own copies of
encryption algorithms in source tree causes significant complications
getting QEMU security certified. GNUTLS is a common enough crypto
library that I don't think it is unreasonably onerous to expect it to
be used for WebSockets. It even works fine on Windows.

Regards,
Daniel
Tim Hardeck - Nov. 20, 2012, 10:18 a.m.
On 11/20/2012 10:47 AM, Daniel P. Berrange wrote:
> On Tue, Nov 20, 2012 at 10:21:58AM +0100, Tim Hardeck wrote:
>> This patch adds basic Websockets version 13 - RFC 6455 - support to QEMU
>> VNC. Binary encoding support on the client side is required.
>>
>> Because of the GnuTLS requirement the Websockets implementation is
>> optional (--enable-vnc-ws).
>>
>> To activate Websockets during runtime the VNC option "websocket" is
>> used, for example "-vnc :0,websocket" would activate Websockets.
>> The port for Websockets connections is (5700 + display) so if QEMU VNC
>> is started with :0 the websockets port would be 5700.
> 
> We need to be able to specify an explicit port number for the websockets
> listen address, separately from the main VNC port number. This automatic
> pick of websockets port might be nice for a user starting QEMU manually,
> but for management apps we need full direct control.
Ok, this should be no problem to add something like websocket=<port> but
I just thought that a correlation between the vnc and websocket port
would be quite useful especially when several QEMU instances are run on
one machine.
Would it be Ok to keep the correlation if no port is specified?

I am going to add the websocket port option after some more feedback.

>> Changes to v1
>> * 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
>>
>> Would it be Ok to use a public domain SHA1 implementation like
>> tests/tcg/sha1.c and if so where should the sha1.c be stored?
>> Without the GnuTLS dependency would it be Ok to make Websockets not
>> optional because this would clean up the patch/code quite a bit?
> 
> IMHO using gcrypt/GNUTLS is a good thing. Creating our own copies of
> encryption algorithms in source tree causes significant complications
> getting QEMU security certified. GNUTLS is a common enough crypto
> library that I don't think it is unreasonably onerous to expect it to
> be used for WebSockets. It even works fine on Windows.
That's one of the reasons why I have used GnuTLS but it is quite a huge
dependency for just one algorithm and the many ifdefs don't really help
the code quality.

Regards
Tim
Daniel P. Berrange - Nov. 20, 2012, 10:23 a.m.
On Tue, Nov 20, 2012 at 11:18:55AM +0100, Tim Hardeck wrote:
> On 11/20/2012 10:47 AM, Daniel P. Berrange wrote:
> > On Tue, Nov 20, 2012 at 10:21:58AM +0100, Tim Hardeck wrote:
> >> This patch adds basic Websockets version 13 - RFC 6455 - support to QEMU
> >> VNC. Binary encoding support on the client side is required.
> >>
> >> Because of the GnuTLS requirement the Websockets implementation is
> >> optional (--enable-vnc-ws).
> >>
> >> To activate Websockets during runtime the VNC option "websocket" is
> >> used, for example "-vnc :0,websocket" would activate Websockets.
> >> The port for Websockets connections is (5700 + display) so if QEMU VNC
> >> is started with :0 the websockets port would be 5700.
> > 
> > We need to be able to specify an explicit port number for the websockets
> > listen address, separately from the main VNC port number. This automatic
> > pick of websockets port might be nice for a user starting QEMU manually,
> > but for management apps we need full direct control.
> Ok, this should be no problem to add something like websocket=<port> but
> I just thought that a correlation between the vnc and websocket port
> would be quite useful especially when several QEMU instances are run on
> one machine.
> Would it be Ok to keep the correlation if no port is specified?

Yep, like I said, its useful for users starting QEMU directly. We
just need to make sure there's an override.

> >> Changes to v1
> >> * 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
> >>
> >> Would it be Ok to use a public domain SHA1 implementation like
> >> tests/tcg/sha1.c and if so where should the sha1.c be stored?
> >> Without the GnuTLS dependency would it be Ok to make Websockets not
> >> optional because this would clean up the patch/code quite a bit?
> > 
> > IMHO using gcrypt/GNUTLS is a good thing. Creating our own copies of
> > encryption algorithms in source tree causes significant complications
> > getting QEMU security certified. GNUTLS is a common enough crypto
> > library that I don't think it is unreasonably onerous to expect it to
> > be used for WebSockets. It even works fine on Windows.
> That's one of the reasons why I have used GnuTLS but it is quite a huge
> dependency for just one algorithm and the many ifdefs don't really help
> the code quality.

One of the things I'd like to try todo for QEMU is to have an centralized
internal interface for crypto functions. eg, so you could say

    h = crypt_hash_init("sha1");

    ...

and the actual impl of crypt_hash_init() whould then be backed by GNUTLS,
OpenSSL, or the Linux kernel (via socket AF_ALG family). This would let
all the #ifdef code be centralized in one place, making the VNC code
clean again. So while I agree with you that its ugly, I think that's
OK in the short term

Daniel

Patch

diff --git a/configure b/configure
index 780b19a..ac9da73 100755
--- a/configure
+++ b/configure
@@ -154,10 +154,11 @@  vnc="yes"
 sparse="no"
 uuid=""
 vde=""
-vnc_tls=""
+vnc_tls="no"
 vnc_sasl=""
 vnc_jpeg=""
 vnc_png=""
+vnc_ws=""
 xen=""
 xen_ctrl_version=""
 xen_pci_passthrough=""
@@ -703,6 +704,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"
@@ -1048,6 +1053,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"
@@ -1772,6 +1779,26 @@  EOF
 fi
 
 ##########################################
+# VNC WS detection
+if test "$vnc" = "yes" -a "$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; }
+EOF
+  vnc_ws_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
+  vnc_ws_libs=`$pkg_config --libs gnutls 2> /dev/null`
+  if compile_prog "$vnc_ws_cflags" "$vnc_ws_libs" ; then
+    vnc_ws=yes
+    libs_softmmu="$vnc_ws_libs $libs_softmmu"
+  else
+    if test "$vnc_ws" = "yes" ; then
+      feature_not_found "vnc-ws"
+    fi
+    vnc_ws=no
+  fi
+fi
+
+##########################################
 # fnmatch() probe, used for ACL routines
 fnmatch="no"
 cat > $TMPC << EOF
@@ -3194,6 +3221,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"
@@ -3370,6 +3398,10 @@  if test "$vnc_png" = "yes" ; then
   echo "CONFIG_VNC_PNG=y" >> $config_host_mak
   echo "VNC_PNG_CFLAGS=$vnc_png_cflags" >> $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 9bb29d3..3b3fff8 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1096,6 +1096,12 @@  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.
+
 @item password
 
 Require that password based authentication is used for client connections.
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index adc07be..58e191b 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
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
new file mode 100644
index 0000000..1e1d7cf
--- /dev/null
+++ b/ui/vnc-ws.c
@@ -0,0 +1,211 @@ 
+/*
+ * 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"
+
+char *vncws_extract_handshake_entry(const char *header, size_t header_len,
+        const char *name)
+{
+    int name_len = strlen(name);
+    char *begin, *end;
+    begin = memmem(header, header_len, name, name_len);
+    if (begin != NULL) {
+        begin += name_len;
+        end = memmem(begin, header_len - (begin - header), WS_HANDSHAKE_DELIM,
+                strlen(WS_HANDSHAKE_DELIM));
+        if (end != NULL) {
+            return g_strndup(begin, end - begin);
+        }
+    }
+    return NULL;
+}
+
+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
+         && memmem(protocols, strlen(protocols), "binary", 6) != NULL
+         && strcmp(version, WS_SUPPORTED_VERSION) == 0
+         && 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_send_handshake_response(VncState *vs, const char* key)
+{
+    char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
+    char response[WS_HANDSHAKE_MAX_LEN];
+    char hash[SHA1_DIGEST_SIZE + 1];
+    char *accept = NULL;
+    size_t hash_size = SHA1_DIGEST_SIZE, response_size = 0;
+    gnutls_datum_t in;
+
+    /* create combined key */
+    pstrcpy(combined_key, WS_CLIENT_KEY_LEN + 1, key);
+    pstrcat(combined_key, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1, WS_GUID);
+
+    /* 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((const guchar *)hash, SHA1_DIGEST_SIZE);
+    }
+    if (accept == NULL) {
+        VNC_DEBUG("Hashing Websocket combined key failed\n");
+        vnc_client_error(vs);
+        return;
+    }
+
+    /* create handshake response */
+    response_size = snprintf(response, WS_HANDSHAKE_MAX_LEN,
+            WS_HANDSHAKE, accept);
+    g_free(accept);
+
+    vnc_write(vs, response, response_size);
+    vnc_flush(vs);
+
+    vs->encode_ws = 1;
+    vnc_init_state(vs);
+}
+
+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;
+    char header_buf[WS_HEAD_MAX_LEN];
+    ws_header_t *header = (ws_header_t *)header_buf;
+
+    if (!payload_size) {
+        return;
+    }
+
+    header->b0 = 0x80 | (opcode & 0x0f);
+    if (payload_size <= 125) {
+        header->b1 = (uint8_t)payload_size;
+        header_size = 2;
+    } else if (payload_size < 65536) {
+        header->b1 = 0x7e;
+        header->u.s16.l16 = WS_HTON16((uint16_t)payload_size);
+        header_size = 4;
+    } else {
+        header->b1 = 0x7f;
+        header->u.s64.l64 = WS_HTON64(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;
+    uint32_t *payload32;
+    size_t header_size = 0;
+    ws_header_t *header = (ws_header_t *)input->buffer;
+    ws_mask_t mask;
+    int mask_size = 0;
+    int i;
+
+    if (input->offset < WS_HEAD_MIN_LEN) {
+        /* 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;
+    }
+
+    mask_size = 4 * has_mask;
+
+    if (*payload_size < 126) {
+        header_size = 2;
+        mask = header->u.m;
+    } else if (*payload_size == 126 && input->offset >= 4 + mask_size) {
+        *payload_size = WS_NTOH16(header->u.s16.l16);
+        header_size = 4;
+        mask = header->u.s16.m16;
+    } else if (*payload_size == 127 && input->offset >= 10 + mask_size) {
+        *payload_size = WS_NTOH64(header->u.s64.l64);
+        header_size = 10;
+        mask = header->u.s64.m64;
+    } else {
+        /* header not complete */
+        return 0;
+    }
+
+    header_size += mask_size;
+    *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..dc804f0
--- /dev/null
+++ b/ui/vnc-ws.h
@@ -0,0 +1,101 @@ 
+/*
+ * 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
+
+#ifndef CONFIG_VNC_TLS
+#include <gnutls/gnutls.h>
+#endif
+
+#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
+#define SHA1_DIGEST_SIZE 20
+
+#define WS_ACCEPT_LEN (B64LEN(SHA1_DIGEST_SIZE) + 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_MAX_LEN 192
+#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 2
+#define WS_HEAD_MAX_LEN 14  /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */
+
+#define WS_HTON64(n) htobe64(n)
+#define WS_HTON16(n) htobe16(n)
+#define WS_NTOH16(n) htobe16(n)
+#define WS_NTOH64(n) htobe64(n)
+
+typedef union ws_mask_s {
+    char c[4];
+    uint32_t u;
+} ws_mask_t;
+
+/* 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;
+            ws_mask_t m16;
+        } s16;
+        struct __attribute__ ((__packed__)) {
+            uint64_t l64;
+            ws_mask_t m64;
+        } s64;
+        ws_mask_t m;
+    } u;
+} ws_header_t;
+
+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
+};
+
+char *vncws_extract_handshake_entry(const char *header, size_t header_len,
+            const char *name);
+void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
+void vncws_send_handshake_response(VncState *vs, const char* key);
+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 861461b..8a2e253 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;
@@ -1027,6 +1034,9 @@  static void vnc_disconnect_finish(VncState *vs)
 
     buffer_free(&vs->input);
     buffer_free(&vs->output);
+#ifdef CONFIG_VNC_WS
+    buffer_free(&vs->ws_input);
+#endif
 
     qobject_decref(vs->info);
 
@@ -1168,8 +1178,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);
@@ -1282,6 +1291,26 @@  static void vnc_jobs_bh(void *opaque)
     vnc_jobs_consume_buffer(vs);
 }
 
+#ifdef CONFIG_VNC_WS
+void vncws_handshake_read(void *opaque)
+{
+    VncState *vs = opaque;
+    long ret = vnc_client_read_plain(vs);
+    if (!ret) {
+        if (vs->csock == -1) {
+            vnc_disconnect_finish(vs);
+        }
+        return;
+    }
+
+    if (vs->input.offset > 0) {
+        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+        vncws_process_handshake(vs, vs->input.buffer, vs->input.offset);
+        buffer_reset(&vs->input);
+    }
+}
+#endif
+
 /*
  * First function called whenever there is more data to be read from
  * the client socket. Will delegate actual work according to whether
@@ -1291,6 +1320,7 @@  void vnc_client_read(void *opaque)
 {
     VncState *vs = opaque;
     long ret;
+    Buffer *buf;
 
 #ifdef CONFIG_VNC_SASL
     if (vs->sasl.conn && vs->sasl.runSSF)
@@ -1304,19 +1334,49 @@  void vnc_client_read(void *opaque)
         return;
     }
 
-    while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
+#ifdef CONFIG_VNC_WS
+    if (vs->encode_ws) {
+        uint8_t *payload;
+        size_t payload_size, frame_size;
+
+        /* make sure that nothing is left in the input buffer */
+        do {
+            ret = vncws_decode_frame(&vs->input, &payload,
+                                  &payload_size, &frame_size);
+
+            if (ret == 0) {
+                /* not enough data to process, wait for more */
+                return;
+            } else if (ret == -1) {
+                vnc_disconnect_start(vs);
+                return;
+            } else if (ret == -2) {
+                vnc_client_error(vs);
+                return;
+            }
+
+            buffer_reserve(&vs->ws_input, payload_size);
+            buffer_append(&vs->ws_input, payload, payload_size);
+
+            buffer_advance(&vs->input, frame_size);
+        } while (vs->input.offset > 0);
+        buf = &vs->ws_input;
+    } else
+#endif /* CONFIG_VNC_WS */
+        buf = &vs->input;
+
+    while (vs->read_handler && buf->offset >= vs->read_handler_expect) {
         size_t len = vs->read_handler_expect;
         int ret;
 
-        ret = vs->read_handler(vs, vs->input.buffer, len);
+        ret = vs->read_handler(vs, buf->buffer, len);
         if (vs->csock == -1) {
             vnc_disconnect_finish(vs);
             return;
         }
 
         if (!ret) {
-            memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len));
-            vs->input.offset -= len;
+            buffer_advance(buf, len);
         } else {
             vs->read_handler_expect = ret;
         }
@@ -1325,13 +1385,26 @@  void vnc_client_read(void *opaque)
 
 void vnc_write(VncState *vs, const void *data, size_t len)
 {
-    buffer_reserve(&vs->output, len);
+#ifdef CONFIG_VNC_WS
+    if (vs->encode_ws) {
+        if (vs->csock != -1 && buffer_empty(&vs->output)) {
+            qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read,
+                    vnc_client_write, vs);
+        }
+        vncws_encode_frame(&vs->output, data, len);
+    } else {
+#endif /* CONFIG_VNC_WS */
+        buffer_reserve(&vs->output, len);
 
-    if (vs->csock != -1 && buffer_empty(&vs->output)) {
-        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs);
-    }
+        if (vs->csock != -1 && buffer_empty(&vs->output)) {
+            qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read,
+                    vnc_client_write, vs);
+        }
 
-    buffer_append(&vs->output, data, len);
+        buffer_append(&vs->output, data, len);
+#ifdef CONFIG_VNC_WS
+    }
+#endif
 }
 
 void vnc_write_s32(VncState *vs, int32_t value)
@@ -2659,12 +2732,15 @@  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, int websocket)
 {
     VncState *vs = g_malloc0(sizeof(VncState));
     int i;
 
     vs->csock = csock;
+#ifdef CONFIG_VNC_WS
+    vs->websocket = websocket;
+#endif
 
     if (skipauth) {
 	vs->auth = VNC_AUTH_NONE;
@@ -2686,13 +2762,30 @@  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 (vs->websocket) {
+        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
+    } else
+#endif
+        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) {
+        vnc_init_state(vs);
+    }
+}
+
+void vnc_init_state(VncState *vs)
+{
+    VncDisplay *vd = vs->vd;
+#endif /* CONFIG_VNC_WS */
+
     vs->ds = vd->ds;
     vs->last_x = -1;
     vs->last_y = -1;
@@ -2724,21 +2817,39 @@  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, int 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
+        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
+
 void vnc_display_init(DisplayState *ds)
 {
     VncDisplay *vs = g_malloc0(sizeof(*vs));
@@ -2750,6 +2861,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);
@@ -2791,6 +2905,13 @@  static void vnc_display_close(DisplayState *ds)
         close(vs->lsock);
         vs->lsock = -1;
     }
+#ifdef CONFIG_VNC_WS
+    if (vs->lwebsock != -1) {
+        qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL);
+        close(vs->lwebsock);
+        vs->lwebsock = -1;
+    }
+#endif
     vs->auth = VNC_AUTH_INVALID;
 #ifdef CONFIG_VNC_TLS
     vs->subauth = VNC_AUTH_INVALID;
@@ -2912,6 +3033,10 @@  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) {
+            vs->websocket = 1; /* enable websockets */
+#endif
 #ifdef CONFIG_VNC_TLS
         } else if (strncmp(options, "tls", 3) == 0) {
             tls = 1; /* Require TLS */
@@ -3070,6 +3195,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 {
@@ -3078,7 +3206,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;
@@ -3089,14 +3217,31 @@  void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
         } else {
             vs->lsock = inet_listen(display, dpy, 256,
                                     SOCK_STREAM, 5900, errp);
+#ifdef CONFIG_VNC_WS
+            if (vs->websocket) {
+                vs->lwebsock = inet_listen(display, NULL, 256,
+                        SOCK_STREAM, 5700, errp);
+            }
+#endif
         }
-        if (vs->lsock < 0) {
+        if (vs->lsock < 0
+#ifdef CONFIG_VNC_WS
+                || (vs->websocket && vs->lwebsock < 0)
+#endif
+                ) {
             g_free(dpy);
             goto fail;
         }
         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
     }
     return;
 
@@ -3109,5 +3254,5 @@  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 6141e88..22d57f0 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,10 @@  struct VncDisplay
     QEMUTimer *timer;
     int timer_interval;
     int lsock;
+#ifdef CONFIG_VNC_WS
+    int lwebsock;
+    int websocket;
+#endif
     DisplayState *ds;
     kbd_layout_t *kbd_layout;
     int lock_key_sync;
@@ -269,11 +276,18 @@  struct VncState
 #ifdef CONFIG_VNC_SASL
     VncStateSASL sasl;
 #endif
+#ifdef CONFIG_VNC_WS
+    int encode_ws;
+    int websocket;
+#endif
 
     QObject *info;
 
     Buffer output;
     Buffer input;
+#ifdef CONFIG_VNC_WS
+    Buffer ws_input;
+#endif
     /* current output mode information */
     VncWritePixels *write_pixels;
     PixelFormat client_pf;
@@ -505,11 +519,17 @@  int vnc_client_io_error(VncState *vs, int ret, int last_errno);
 void start_client_init(VncState *vs);
 void start_auth_vnc(VncState *vs);
 
+#ifdef CONFIG_VNC_WS
+void vncws_handshake_read(void *opaque);
+void vnc_init_state(VncState *vs);
+#endif
+
 /* Buffer management */
 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 */