Blame SOURCES/0007-Fix-incompatibility-with-libvncserver-websockets-han.patch

bf78cb
From 7079fa855bfbaff0d14122eac27e96a6a6637a17 Mon Sep 17 00:00:00 2001
bf78cb
From: "Daniel P. Berrange" <berrange@redhat.com>
bf78cb
Date: Tue, 11 Apr 2017 11:41:03 +0100
bf78cb
Subject: [PATCH] Fix incompatibility with libvncserver websockets handling
bf78cb
bf78cb
The previous commit:
bf78cb
bf78cb
  commit 7f4f2fe8da72ed9fef5dd4319e19feb2b4f3d62e
bf78cb
  Author: Daniel P. Berrange <berrange@redhat.com>
bf78cb
  Date:   Thu Jan 26 09:31:40 2017 +0000
bf78cb
bf78cb
    Add workaround to avoid hangs when connecting to SPICE
bf78cb
bf78cb
changed the code so that it would send the bytes "RFB " to the
bf78cb
server before we received its own greeting. This works fine for
bf78cb
VNC servers which follow the RFB protocol spec exclusively. The
bf78cb
libvncserver code though tries to implement websockets tunnelling
bf78cb
support on the same port as the normal RFB service. The way it
bf78cb
does this is by waiting 100ms after the client connects to see
bf78cb
if the client sends any data. If the client sends data, then it
bf78cb
tries to interpret this as an HTTP GET request to initiate the
bf78cb
websockets connection. This breaks when it sees our "RFB " bytes
bf78cb
being sent. Ideally the libvncserver would have just run a normal
bf78cb
RFB connection in this case, but that's not what happens, and
bf78cb
given the libvncserver code is in the wild we need a workaround.
bf78cb
bf78cb
So instead of immediately sending the 'RFB ' bytes to the VNC
bf78cb
server, we introduce a 2 second wait. ie, we'll wait for the
bf78cb
normal VNC server greeting and if it doesn't arrive after 2 seconds,
bf78cb
we'll send our 'RFB ' bytes proactively, and continue waiting. If we
bf78cb
are on a real VNC server, we'll get our connection initialized
bf78cb
eventually. If connecting to a SPICE server by mistake, we'll get a
bf78cb
clean disconnect, and we'll avoid upsetting libvncserver, because its
bf78cb
100ms wait for HTTP GET will have long since finished.
bf78cb
bf78cb
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
bf78cb
(cherry picked from commit f5623cbc63bb0a835bc662d451cc5128d683bd5d)
bf78cb
---
bf78cb
 src/vncconnection.c | 134 +++++++++++++++++++++++++++++++++++-----------------
bf78cb
 1 file changed, 90 insertions(+), 44 deletions(-)
bf78cb
bf78cb
diff --git a/src/vncconnection.c b/src/vncconnection.c
bf78cb
index 2b2bdbb..1ddf38d 100644
bf78cb
--- a/src/vncconnection.c
bf78cb
+++ b/src/vncconnection.c
bf78cb
@@ -347,6 +347,23 @@ static GIOCondition g_io_wait(GSocket *sock, GIOCondition cond)
bf78cb
 }
bf78cb
 
bf78cb
 
bf78cb
+static void g_io_wakeup(struct wait_queue *wait)
bf78cb
+{
bf78cb
+    if (wait->waiting)
bf78cb
+        coroutine_yieldto(wait->context, NULL);
bf78cb
+}
bf78cb
+
bf78cb
+
bf78cb
+static gboolean vnc_connection_timeout(gpointer data)
bf78cb
+{
bf78cb
+    struct wait_queue *wait = data;
bf78cb
+
bf78cb
+    g_io_wakeup(wait);
bf78cb
+
bf78cb
+    return FALSE;
bf78cb
+}
bf78cb
+
bf78cb
+
bf78cb
 static GIOCondition g_io_wait_interruptable(struct wait_queue *wait,
bf78cb
                                             GSocket *sock,
bf78cb
                                             GIOCondition cond)
bf78cb
@@ -373,13 +390,6 @@ static GIOCondition g_io_wait_interruptable(struct wait_queue *wait,
bf78cb
         return *ret;
bf78cb
 }
bf78cb
 
bf78cb
-static void g_io_wakeup(struct wait_queue *wait)
bf78cb
-{
bf78cb
-    if (wait->waiting)
bf78cb
-        coroutine_yieldto(wait->context, NULL);
bf78cb
-}
bf78cb
-
bf78cb
-
bf78cb
 /*
bf78cb
  * Call immediately before the main loop does an iteration. Returns
bf78cb
  * true if the condition we're checking is ready for dispatch
bf78cb
@@ -921,8 +931,13 @@ static int vnc_connection_read(VncConnection *conn, void *data, size_t len)
bf78cb
         } else if (priv->read_offset == priv->read_size) {
bf78cb
             int ret = vnc_connection_read_buf(conn);
bf78cb
 
bf78cb
-            if (ret < 0)
bf78cb
-                return ret;
bf78cb
+            if (ret < 0) {
bf78cb
+                if (ret == -EAGAIN) {
bf78cb
+                    return offset == 0 ? -EAGAIN : offset;
bf78cb
+                } else {
bf78cb
+                    return ret;
bf78cb
+                }
bf78cb
+            }
bf78cb
             priv->read_offset = 0;
bf78cb
             priv->read_size = ret;
bf78cb
         }
bf78cb
@@ -935,7 +950,7 @@ static int vnc_connection_read(VncConnection *conn, void *data, size_t len)
bf78cb
         offset += tmp;
bf78cb
     }
bf78cb
 
bf78cb
-    return 0;
bf78cb
+    return len;
bf78cb
 }
bf78cb
 
bf78cb
 /*
bf78cb
@@ -5239,34 +5254,66 @@ static gboolean vnc_connection_after_version (VncConnection *conn, int major, in
bf78cb
 static gboolean vnc_connection_initialize(VncConnection *conn)
bf78cb
 {
bf78cb
     VncConnectionPrivate *priv = conn->priv;
bf78cb
-    int ret, i;
bf78cb
+    int ret, i, want;
bf78cb
     char version[13];
bf78cb
     guint32 n_name;
bf78cb
+    gboolean partialGreeting = FALSE;
bf78cb
+    guint timeout;
bf78cb
 
bf78cb
     priv->absPointer = TRUE;
bf78cb
 
bf78cb
-    /* We should technically read the server greeting first.
bf78cb
-     * If the user mistakenly connects to a SPICE server
bf78cb
-     * though, we'll never see the greeting, because with
bf78cb
-     * SPICE the client starts first.
bf78cb
-     *
bf78cb
-     * By sending these 4 bytes first, if the user has
bf78cb
-     * accidentally connected to a SPICE server, it will
bf78cb
-     * notice this garbage and close the connection, avoiding
bf78cb
-     * us waiting forever for a VNC greeting that'll never
bf78cb
-     * come.
bf78cb
-     *
bf78cb
-     * This is harmless for real VNC servers, since the
bf78cb
-     * early send will just be queued until they've sent
bf78cb
-     * their greeting
bf78cb
-     */
bf78cb
-    vnc_connection_write(conn, "RFB ", 4);
bf78cb
-    vnc_connection_flush(conn);
bf78cb
+    timeout = g_timeout_add_seconds(2, vnc_connection_timeout, &priv->wait);
bf78cb
+    want = 12;
bf78cb
+    while (want > 0) {
bf78cb
+        priv->wait_interruptable = 1;
bf78cb
+        ret = vnc_connection_read(conn, version + (12 - want), want);
bf78cb
+        priv->wait_interruptable = 0;
bf78cb
+        if (vnc_connection_has_error(conn)) {
bf78cb
+            VNC_DEBUG("Error while reading server version");
bf78cb
+            goto fail;
bf78cb
+        }
bf78cb
+        if (ret >= 0) {
bf78cb
+            want -= ret;
bf78cb
+            if (ret != 12)  {
bf78cb
+                timeout = 0;
bf78cb
+            }
bf78cb
+        } else {
bf78cb
+            if (ret == -EAGAIN) {
bf78cb
+                /*
bf78cb
+                 * We didn't see any RFB greeting before our
bf78cb
+                 * timeout. We might have mistakenly connected
bf78cb
+                 * to a SPICE server, in which case we might
bf78cb
+                 * wait forever, since SPICE expects the client
bf78cb
+                 * to send first.
bf78cb
+                 *
bf78cb
+                 * We'll proactively send the 'RFB ' bytes to the
bf78cb
+                 * sever. If we've just got a slow VNC server, it'll
bf78cb
+                 * be harmless, but if we've got a SPICE server, this
bf78cb
+                 * should trigger it to close the connection, avoiding
bf78cb
+                 * us waiting foever.
bf78cb
+                 *
bf78cb
+                 * NB, while we could just send the "RFB " bytes
bf78cb
+                 * immediately, the libvncserver code does something
bf78cb
+                 * really crazy. When it sees a client connection, it
bf78cb
+                 * waits 100ms for an HTTP GET request to indicate
bf78cb
+                 * use of websockets proxy. If it sees the RFB bytes
bf78cb
+                 * it doesn't run a normal VNC connection, it just kills
bf78cb
+                 * the connection :-(
bf78cb
+                 */
bf78cb
+                VNC_DEBUG("No server greeting, sending partial client greeting");
bf78cb
+                vnc_connection_write(conn, "RFB ", 4);
bf78cb
+                vnc_connection_flush(conn);
bf78cb
+                partialGreeting = TRUE;
bf78cb
+                timeout = 0;
bf78cb
+            } else {
bf78cb
+                VNC_DEBUG("Unexpected read error during greeting");
bf78cb
+                goto fail;
bf78cb
+            }
bf78cb
+        }
bf78cb
+    }
bf78cb
 
bf78cb
-    vnc_connection_read(conn, version, 12);
bf78cb
-    if (vnc_connection_has_error(conn)) {
bf78cb
-        VNC_DEBUG("Error while reading server version");
bf78cb
-        goto fail;
bf78cb
+    if (timeout != 0) {
bf78cb
+        g_source_remove(timeout);
bf78cb
     }
bf78cb
 
bf78cb
     version[12] = 0;
bf78cb
@@ -5291,8 +5338,16 @@ static gboolean vnc_connection_initialize(VncConnection *conn)
bf78cb
         priv->minor = 8;
bf78cb
     }
bf78cb
 
bf78cb
-    snprintf(version, 13, "%03d.%03d\n", priv->major, priv->minor);
bf78cb
-    vnc_connection_write(conn, version, 8);
bf78cb
+    if (partialGreeting) {
bf78cb
+        VNC_DEBUG("Sending rest of greeting");
bf78cb
+        snprintf(version, 13, "%03d.%03d\n", priv->major, priv->minor);
bf78cb
+        want = 8;
bf78cb
+    } else {
bf78cb
+        VNC_DEBUG("Sending full greeting");
bf78cb
+        snprintf(version, 13, "RFB %03d.%03d\n", priv->major, priv->minor);
bf78cb
+        want = 12;
bf78cb
+    }
bf78cb
+    vnc_connection_write(conn, version, want);
bf78cb
     vnc_connection_flush(conn);
bf78cb
     VNC_DEBUG("Using version: %d.%d", priv->major, priv->minor);
bf78cb
 
bf78cb
@@ -5358,15 +5413,6 @@ static gboolean vnc_connection_open_fd_internal(VncConnection *conn)
bf78cb
     return !vnc_connection_has_error(conn);
bf78cb
 }
bf78cb
 
bf78cb
-static gboolean connect_timeout(gpointer data)
bf78cb
-{
bf78cb
-    struct wait_queue *wait = data;
bf78cb
-
bf78cb
-    g_io_wakeup(wait);
bf78cb
-
bf78cb
-    return FALSE;
bf78cb
-}
bf78cb
-
bf78cb
 static GSocket *vnc_connection_connect_socket(struct wait_queue *wait,
bf78cb
                                               GSocketAddress *sockaddr,
bf78cb
                                               GError **error)
bf78cb
@@ -5379,7 +5425,7 @@ static GSocket *vnc_connection_connect_socket(struct wait_queue *wait,
bf78cb
     if (!sock)
bf78cb
         return NULL;
bf78cb
 
bf78cb
-    guint timeout = g_timeout_add_seconds(10, connect_timeout, wait);
bf78cb
+    guint timeout = g_timeout_add_seconds(10, vnc_connection_timeout, wait);
bf78cb
 
bf78cb
     g_socket_set_blocking(sock, FALSE);
bf78cb
     if (!g_socket_connect(sock, sockaddr, NULL, error)) {