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

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