diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2012-08-23 21:47:33 +0200 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2012-12-14 18:27:25 +0100 |
commit | 0f9a432c547d16529c0ca8b83048a065f620aaf1 (patch) | |
tree | e8689b8de09a0689415cb5320d856b4def0589ec | |
parent | 8e96f6e44f3111b0c22938b27d232f0e5fbaac31 (diff) |
session: allow to connect via HTTP CONNECT proxy
Allow to connect to a Spice server via a HTTP proxy with CONNECT
method. spice-gtk will use the SPICE_PROXY environment variable, which
can currently only have the following syntax: [http://]hostname[:port]
This is paving the way to more proxies support (socks4/socks5).
This code is now entirely sync (it was not even completely async), the
following patch will make it all async again.
Tested with Squid, locally only.
-rw-r--r-- | gtk/spice-session.c | 190 |
1 files changed, 134 insertions, 56 deletions
diff --git a/gtk/spice-session.c b/gtk/spice-session.c index e44ee08..06c1629 100644 --- a/gtk/spice-session.c +++ b/gtk/spice-session.c @@ -28,6 +28,7 @@ #include "gio-coroutine.h" #include "glib-compat.h" #include "wocky-http-proxy.h" +#include "spice-proxy.h" struct channel { SpiceChannel *channel; @@ -1573,43 +1574,128 @@ gboolean spice_session_has_channel_type(SpiceSession *session, gint type) /* ------------------------------------------------------------------ */ /* private functions */ -static GSocket *channel_connect_socket(SpiceChannel *channel, - GSocketAddress *sockaddr, - GError **error) +typedef struct spice_open_host spice_open_host; + +struct spice_open_host { + struct coroutine *from; + SpiceSession *session; + SpiceChannel *channel; + SpiceProxy *proxy; + int port; + GCancellable *cancellable; + GError *error; + GSocket *socket; +}; + +static void socket_client_connect_ready(GObject *source_object, GAsyncResult *result, + gpointer data) { - SpiceChannelPrivate *c = channel->priv; - GSocket *sock = g_socket_new(g_socket_address_get_family(sockaddr), - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_DEFAULT, - error); + GSocketClient *client = G_SOCKET_CLIENT(source_object); + spice_open_host *open_host = data; + GSocketConnection *connection = NULL; - if (!sock) - return NULL; + SPICE_DEBUG("connect ready"); + connection = g_socket_client_connect_finish(client, result, &open_host->error); + if (connection == NULL) + goto end; - g_socket_set_blocking(sock, FALSE); - g_socket_set_keepalive(sock, TRUE); + open_host->socket = g_socket_connection_get_socket(connection); + g_object_ref(open_host->socket); - if (!g_socket_connect(sock, sockaddr, NULL, error)) { - if (*error && (*error)->code == G_IO_ERROR_PENDING) { - g_clear_error(error); - CHANNEL_DEBUG(channel, "Socket pending"); - g_coroutine_socket_wait(&c->coroutine, sock, G_IO_OUT | G_IO_ERR | G_IO_HUP); +end: + g_object_unref(connection); + g_object_unref(client); - if (!g_socket_check_connect_result(sock, error)) { - CHANNEL_DEBUG(channel, "Failed to connect %s", (*error)->message); - g_object_unref(sock); - return NULL; - } - } else { - CHANNEL_DEBUG(channel, "Socket error: %s", *error ? (*error)->message : "unknown"); - g_object_unref(sock); - return NULL; - } + coroutine_yieldto(open_host->from, NULL); +} + +/* main context */ +static void open_host_connectable_connect(spice_open_host *open_host, GSocketConnectable *connectable) +{ + GSocketClient *client; + + SPICE_DEBUG("connecting %p...", open_host); + client = g_socket_client_new(); + g_socket_client_connect_async(client, connectable, open_host->cancellable, + socket_client_connect_ready, open_host); +} + +#if GLIB_CHECK_VERSION(2,26,0) +/* main context */ +static void proxy_lookup_ready(GObject *source_object, GAsyncResult *result, + gpointer data) +{ + spice_open_host *open_host = data; + SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(open_host->session); + GList *addresses = NULL, *it; + GSocketAddress *address; + + SPICE_DEBUG("proxy lookup ready"); + addresses = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object), + result, &open_host->error); + if (addresses == NULL || open_host->error) { + coroutine_yieldto(open_host->from, NULL); + return; + } + + for (it = addresses; it != NULL; it = it->next) { + address = g_proxy_address_new(G_INET_ADDRESS(it->data), + spice_proxy_get_port(open_host->proxy), "http", + s->host, open_host->port, NULL, NULL); + if (address != NULL) + break; } - CHANNEL_DEBUG(channel, "Finally connected"); + open_host_connectable_connect(open_host, G_SOCKET_CONNECTABLE(address)); + g_resolver_free_addresses(addresses); +} + +static SpiceProxy* get_proxy(GError **error) +{ + SpiceProxy *proxy; + + const gchar *proxy_env = g_getenv("SPICE_PROXY"); + if (proxy_env == NULL || strlen(proxy_env) == 0) + return NULL; + + proxy = spice_proxy_new(); + if (!spice_proxy_parse(proxy, proxy_env, error)) + g_clear_object(&proxy); - return sock; + return proxy; +} +#endif + +/* main context */ +static gboolean open_host_idle_cb(gpointer data) +{ + spice_open_host *open_host = data; + SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(open_host->session); + + g_return_val_if_fail(open_host != NULL, FALSE); + g_return_val_if_fail(open_host->socket == NULL, FALSE); + +#if GLIB_CHECK_VERSION(2,26,0) + open_host->proxy = get_proxy(&open_host->error); + if (open_host->proxy) { + g_resolver_lookup_by_name_async(g_resolver_get_default(), + spice_proxy_get_hostname(open_host->proxy), + open_host->cancellable, + proxy_lookup_ready, open_host); + } else +#endif + if (open_host->error != NULL) { + coroutine_yieldto(open_host->from, NULL); + return FALSE; + } else + open_host_connectable_connect(open_host, + g_network_address_new(s->host, open_host->port)); + + SPICE_DEBUG("open host %s:%d", s->host, open_host->port); + if (open_host->proxy != NULL) + SPICE_DEBUG("(with proxy %p)", open_host->proxy); + + return FALSE; } /* coroutine context */ @@ -1618,41 +1704,33 @@ GSocket* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *ch gboolean use_tls) { SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(session); - GSocketConnectable *addr; - GSocketAddressEnumerator *enumerator; - GSocketAddress *sockaddr; - GError *conn_error = NULL; - GSocket *sock = NULL; - int port; + spice_open_host open_host = { 0, }; if ((use_tls && !s->tls_port) || (!use_tls && !s->port)) return NULL; - port = atoi(use_tls ? s->tls_port : s->port); - - SPICE_DEBUG("Resolving host %s %d", s->host, port); + open_host.from = coroutine_self(); + open_host.session = session; + open_host.channel = channel; + open_host.port = atoi(use_tls ? s->tls_port : s->port); + g_idle_add(open_host_idle_cb, &open_host); - addr = g_network_address_new(s->host, port); + /* switch to main loop and wait for connection */ + coroutine_yield(NULL); + if (open_host.error != NULL) { + g_return_val_if_fail(open_host.socket == NULL, NULL); - enumerator = g_socket_connectable_enumerate (addr); - g_object_unref (addr); + g_warning("%s", open_host.error->message); + g_clear_error(&open_host.error); + } else { + g_return_val_if_fail(open_host.socket != NULL, NULL); - /* Try each sockaddr until we succeed. Record the first - * connection error, but not any further ones (since they'll probably - * be basically the same as the first). - */ - while (!sock && - (sockaddr = g_socket_address_enumerator_next(enumerator, NULL, &conn_error))) { - SPICE_DEBUG("Trying one socket"); - g_clear_error(&conn_error); - sock = channel_connect_socket(channel, sockaddr, &conn_error); - if (conn_error != NULL) - SPICE_DEBUG("%s", conn_error->message); - g_object_unref(sockaddr); + g_socket_set_blocking(open_host.socket, FALSE); + g_socket_set_keepalive(open_host.socket, TRUE); } - g_object_unref(enumerator); - g_clear_error(&conn_error); - return sock; + + g_clear_object(&open_host.proxy); + return open_host.socket; } |