qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol suppo


From: Anthony Liguori
Subject: Re: [Qemu-devel] [PATCH 2/3] vnc: added initial websocket protocol support
Date: Mon, 07 Jan 2013 13:52:07 -0600
User-agent: Notmuch/0.13.2+93~ged93d79 (http://notmuchmail.org) Emacs/23.3.1 (x86_64-pc-linux-gnu)

Tim Hardeck <address@hidden> 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 <address@hidden>
> ---
>  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.
>  
> address@hidden websocket
> +
> +Opens an additional TCP listening port dedicated to VNC Websocket 
> connections.
> +By defintion the Websocket port is address@hidden If @var{host} is
> +specified connections will only be allowed from this host.
> +As an alternative the Websocket port could be specified by using
> address@hidden@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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]