|
|
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)) {
|