mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-09 20:35:49 +01:00
Refactor g_socket_client_connect_async()
This is a fairly large refactoring. The highlights are: - Removing in-progress connections/addresses from GSocketClientAsyncConnectData: This caused issues where multiple ConnectionAttempt's would step over eachother and modify shared state causing bugs like accidentally bypassing a set proxy. Fixes #1871 Fixes #1989 Fixes #1902 - Cancelling address enumeration on error/completion - Queuing successful TCP connections and doing application layer work serially: This is more in the spirit of Happy Eyeballs but it also greatly simplifies the flow of connection handling so fewer tasks are happening in parallel when they don't need to be. The behavior also should more closely match that of g_socket_client_connect(). - Better track the state of address enumeration: Previously we were over eager to treat enumeration finishing as an error. Fixes #1872 See also #1982 - Add more detailed documentation and logging. Closes #1995
This commit is contained in:
parent
4a153abebe
commit
2722620e32
@ -1337,13 +1337,15 @@ typedef struct
|
|||||||
|
|
||||||
GSocketConnectable *connectable;
|
GSocketConnectable *connectable;
|
||||||
GSocketAddressEnumerator *enumerator;
|
GSocketAddressEnumerator *enumerator;
|
||||||
GProxyAddress *proxy_addr;
|
GCancellable *enumeration_cancellable;
|
||||||
GSocket *socket;
|
|
||||||
GIOStream *connection;
|
|
||||||
|
|
||||||
GSList *connection_attempts;
|
GSList *connection_attempts;
|
||||||
|
GSList *successful_connections;
|
||||||
GError *last_error;
|
GError *last_error;
|
||||||
|
|
||||||
|
gboolean enumerated_at_least_once;
|
||||||
|
gboolean enumeration_completed;
|
||||||
|
gboolean connection_in_progress;
|
||||||
gboolean completed;
|
gboolean completed;
|
||||||
} GSocketClientAsyncConnectData;
|
} GSocketClientAsyncConnectData;
|
||||||
|
|
||||||
@ -1355,10 +1357,9 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
|
|||||||
data->task = NULL;
|
data->task = NULL;
|
||||||
g_clear_object (&data->connectable);
|
g_clear_object (&data->connectable);
|
||||||
g_clear_object (&data->enumerator);
|
g_clear_object (&data->enumerator);
|
||||||
g_clear_object (&data->proxy_addr);
|
g_clear_object (&data->enumeration_cancellable);
|
||||||
g_clear_object (&data->socket);
|
|
||||||
g_clear_object (&data->connection);
|
|
||||||
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
||||||
|
g_slist_free_full (data->successful_connections, connection_attempt_unref);
|
||||||
|
|
||||||
g_clear_error (&data->last_error);
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
@ -1370,6 +1371,7 @@ typedef struct
|
|||||||
GSocketAddress *address;
|
GSocketAddress *address;
|
||||||
GSocket *socket;
|
GSocket *socket;
|
||||||
GIOStream *connection;
|
GIOStream *connection;
|
||||||
|
GProxyAddress *proxy_addr;
|
||||||
GSocketClientAsyncConnectData *data; /* unowned */
|
GSocketClientAsyncConnectData *data; /* unowned */
|
||||||
GSource *timeout_source;
|
GSource *timeout_source;
|
||||||
GCancellable *cancellable;
|
GCancellable *cancellable;
|
||||||
@ -1401,6 +1403,7 @@ connection_attempt_unref (gpointer pointer)
|
|||||||
g_clear_object (&attempt->socket);
|
g_clear_object (&attempt->socket);
|
||||||
g_clear_object (&attempt->connection);
|
g_clear_object (&attempt->connection);
|
||||||
g_clear_object (&attempt->cancellable);
|
g_clear_object (&attempt->cancellable);
|
||||||
|
g_clear_object (&attempt->proxy_addr);
|
||||||
if (attempt->timeout_source)
|
if (attempt->timeout_source)
|
||||||
{
|
{
|
||||||
g_source_destroy (attempt->timeout_source);
|
g_source_destroy (attempt->timeout_source);
|
||||||
@ -1418,37 +1421,59 @@ connection_attempt_remove (ConnectionAttempt *attempt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
|
cancel_all_attempts (GSocketClientAsyncConnectData *data)
|
||||||
{
|
{
|
||||||
g_assert (data->connection);
|
GSList *l;
|
||||||
|
|
||||||
if (!G_IS_SOCKET_CONNECTION (data->connection))
|
for (l = data->connection_attempts; l; l = g_slist_next (l))
|
||||||
|
{
|
||||||
|
ConnectionAttempt *attempt_entry = l->data;
|
||||||
|
g_cancellable_cancel (attempt_entry->cancellable);
|
||||||
|
connection_attempt_unref (attempt_entry);
|
||||||
|
}
|
||||||
|
g_slist_free (data->connection_attempts);
|
||||||
|
data->connection_attempts = NULL;
|
||||||
|
|
||||||
|
g_slist_free_full (data->successful_connections, connection_attempt_unref);
|
||||||
|
data->successful_connections = NULL;
|
||||||
|
|
||||||
|
g_cancellable_cancel (data->enumeration_cancellable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
g_socket_client_async_connect_complete (ConnectionAttempt *attempt)
|
||||||
|
{
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
GError *error = NULL;
|
||||||
|
g_assert (attempt->connection);
|
||||||
|
g_assert (!data->completed);
|
||||||
|
|
||||||
|
if (!G_IS_SOCKET_CONNECTION (attempt->connection))
|
||||||
{
|
{
|
||||||
GSocketConnection *wrapper_connection;
|
GSocketConnection *wrapper_connection;
|
||||||
|
|
||||||
wrapper_connection = g_tcp_wrapper_connection_new (data->connection, data->socket);
|
wrapper_connection = g_tcp_wrapper_connection_new (attempt->connection, attempt->socket);
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = (GIOStream *)wrapper_connection;
|
attempt->connection = (GIOStream *)wrapper_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data->completed)
|
data->completed = TRUE;
|
||||||
|
cancel_all_attempts (data);
|
||||||
|
|
||||||
|
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error))
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
g_debug ("GSocketClient: Connection cancelled!");
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
||||||
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (data->task), &error))
|
g_task_return_error (data->task, g_steal_pointer (&error));
|
||||||
{
|
}
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
else
|
||||||
g_task_return_error (data->task, g_steal_pointer (&error));
|
{
|
||||||
}
|
g_debug ("GSocketClient: Connection successful!");
|
||||||
else
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, attempt->connection);
|
||||||
{
|
g_task_return_pointer (data->task, g_steal_pointer (&attempt->connection), g_object_unref);
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, data->connection);
|
|
||||||
g_task_return_pointer (data->task, g_steal_pointer (&data->connection), g_object_unref);
|
|
||||||
}
|
|
||||||
|
|
||||||
data->completed = TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection_attempt_unref (attempt);
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1470,59 +1495,63 @@ static void
|
|||||||
enumerator_next_async (GSocketClientAsyncConnectData *data,
|
enumerator_next_async (GSocketClientAsyncConnectData *data,
|
||||||
gboolean add_task_ref)
|
gboolean add_task_ref)
|
||||||
{
|
{
|
||||||
/* We need to cleanup the state */
|
|
||||||
g_clear_object (&data->socket);
|
|
||||||
g_clear_object (&data->proxy_addr);
|
|
||||||
g_clear_object (&data->connection);
|
|
||||||
|
|
||||||
/* Each enumeration takes a ref. This arg just avoids repeated unrefs when
|
/* Each enumeration takes a ref. This arg just avoids repeated unrefs when
|
||||||
an enumeration starts another enumeration */
|
an enumeration starts another enumeration */
|
||||||
if (add_task_ref)
|
if (add_task_ref)
|
||||||
g_object_ref (data->task);
|
g_object_ref (data->task);
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL);
|
||||||
|
g_debug ("GSocketClient: Starting new address enumeration");
|
||||||
g_socket_address_enumerator_next_async (data->enumerator,
|
g_socket_address_enumerator_next_async (data->enumerator,
|
||||||
g_task_get_cancellable (data->task),
|
data->enumeration_cancellable,
|
||||||
g_socket_client_enumerator_callback,
|
g_socket_client_enumerator_callback,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void try_next_connection_or_finish (GSocketClientAsyncConnectData *, gboolean);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_tls_handshake_callback (GObject *object,
|
g_socket_client_tls_handshake_callback (GObject *object,
|
||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
GSocketClientAsyncConnectData *data = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
|
||||||
if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object),
|
if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object),
|
||||||
result,
|
result,
|
||||||
&data->last_error))
|
&data->last_error))
|
||||||
{
|
{
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = G_IO_STREAM (object);
|
attempt->connection = G_IO_STREAM (object);
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, data->connection);
|
g_debug ("GSocketClient: TLS handshake succeeded");
|
||||||
g_socket_client_async_connect_complete (data);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKED, data->connectable, attempt->connection);
|
||||||
|
g_socket_client_async_connect_complete (attempt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g_object_unref (object);
|
g_object_unref (object);
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
g_debug ("GSocketClient: TLS handshake failed: %s", data->last_error->message);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data)
|
g_socket_client_tls_handshake (ConnectionAttempt *attempt)
|
||||||
{
|
{
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
GIOStream *tlsconn;
|
GIOStream *tlsconn;
|
||||||
|
|
||||||
if (!data->client->priv->tls)
|
if (!data->client->priv->tls)
|
||||||
{
|
{
|
||||||
g_socket_client_async_connect_complete (data);
|
g_socket_client_async_connect_complete (attempt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsconn = g_tls_client_connection_new (data->connection,
|
g_debug ("GSocketClient: Starting TLS handshake");
|
||||||
|
tlsconn = g_tls_client_connection_new (attempt->connection,
|
||||||
data->connectable,
|
data->connectable,
|
||||||
&data->last_error);
|
&data->last_error);
|
||||||
if (tlsconn)
|
if (tlsconn)
|
||||||
@ -1534,11 +1563,12 @@ g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data)
|
|||||||
G_PRIORITY_DEFAULT,
|
G_PRIORITY_DEFAULT,
|
||||||
g_task_get_cancellable (data->task),
|
g_task_get_cancellable (data->task),
|
||||||
g_socket_client_tls_handshake_callback,
|
g_socket_client_tls_handshake_callback,
|
||||||
data);
|
attempt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1547,23 +1577,38 @@ g_socket_client_proxy_connect_callback (GObject *object,
|
|||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
GSocketClientAsyncConnectData *data = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
|
|
||||||
g_object_unref (data->connection);
|
g_object_unref (attempt->connection);
|
||||||
data->connection = g_proxy_connect_finish (G_PROXY (object),
|
attempt->connection = g_proxy_connect_finish (G_PROXY (object),
|
||||||
result,
|
result,
|
||||||
&data->last_error);
|
&data->last_error);
|
||||||
if (data->connection)
|
if (attempt->connection)
|
||||||
{
|
{
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, data->connection);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enumerator_next_async (data, FALSE);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, TRUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_socket_client_tls_handshake (data);
|
g_socket_client_tls_handshake (attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
complete_connection_with_error (GSocketClientAsyncConnectData *data,
|
||||||
|
GError *error)
|
||||||
|
{
|
||||||
|
g_debug ("GSocketClient: Connection failed: %s", error->message);
|
||||||
|
g_assert (!data->completed);
|
||||||
|
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
||||||
|
data->completed = TRUE;
|
||||||
|
cancel_all_attempts (data);
|
||||||
|
g_task_return_error (data->task, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -1577,15 +1622,114 @@ task_completed_or_cancelled (GSocketClientAsyncConnectData *data)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
else if (g_cancellable_set_error_if_cancelled (cancellable, &error))
|
else if (g_cancellable_set_error_if_cancelled (cancellable, &error))
|
||||||
{
|
{
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
complete_connection_with_error (data, g_steal_pointer (&error));
|
||||||
g_task_return_error (task, g_steal_pointer (&error));
|
|
||||||
data->completed = TRUE;
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
try_next_successful_connection (GSocketClientAsyncConnectData *data)
|
||||||
|
{
|
||||||
|
ConnectionAttempt *attempt;
|
||||||
|
const gchar *protocol;
|
||||||
|
GProxy *proxy;
|
||||||
|
|
||||||
|
if (data->connection_in_progress)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_assert (data->successful_connections != NULL);
|
||||||
|
attempt = data->successful_connections->data;
|
||||||
|
g_assert (attempt != NULL);
|
||||||
|
data->successful_connections = g_slist_remove (data->successful_connections, attempt);
|
||||||
|
data->connection_in_progress = TRUE;
|
||||||
|
|
||||||
|
g_debug ("GSocketClient: Starting application layer connection");
|
||||||
|
|
||||||
|
if (!attempt->proxy_addr)
|
||||||
|
{
|
||||||
|
g_socket_client_tls_handshake (g_steal_pointer (&attempt));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol = g_proxy_address_get_protocol (attempt->proxy_addr);
|
||||||
|
|
||||||
|
/* The connection should not be anything other than TCP,
|
||||||
|
* but let's put a safety guard in case
|
||||||
|
*/
|
||||||
|
if (!G_IS_TCP_CONNECTION (attempt->connection))
|
||||||
|
{
|
||||||
|
g_critical ("Trying to proxy over non-TCP connection, this is "
|
||||||
|
"most likely a bug in GLib IO library.");
|
||||||
|
|
||||||
|
g_set_error_literal (&data->last_error,
|
||||||
|
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Proxying over a non-TCP connection is not supported."));
|
||||||
|
}
|
||||||
|
else if (g_hash_table_contains (data->client->priv->app_proxies, protocol))
|
||||||
|
{
|
||||||
|
/* Simply complete the connection, we don't want to do TLS handshake
|
||||||
|
* as the application proxy handling may need proxy handshake first */
|
||||||
|
g_socket_client_async_connect_complete (g_steal_pointer (&attempt));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else if ((proxy = g_proxy_get_default_for_protocol (protocol)))
|
||||||
|
{
|
||||||
|
GIOStream *connection = attempt->connection;
|
||||||
|
GProxyAddress *proxy_addr = attempt->proxy_addr;
|
||||||
|
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, attempt->connection);
|
||||||
|
g_debug ("GSocketClient: Starting proxy connection");
|
||||||
|
g_proxy_connect_async (proxy,
|
||||||
|
connection,
|
||||||
|
proxy_addr,
|
||||||
|
g_task_get_cancellable (data->task),
|
||||||
|
g_socket_client_proxy_connect_callback,
|
||||||
|
g_steal_pointer (&attempt));
|
||||||
|
g_object_unref (proxy);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
|
g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||||
|
_("Proxy protocol “%s” is not supported."),
|
||||||
|
protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->connection_in_progress = FALSE;
|
||||||
|
g_clear_pointer (&attempt, connection_attempt_unref);
|
||||||
|
return FALSE; /* All non-return paths are failures */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
try_next_connection_or_finish (GSocketClientAsyncConnectData *data,
|
||||||
|
gboolean end_current_connection)
|
||||||
|
{
|
||||||
|
if (end_current_connection)
|
||||||
|
data->connection_in_progress = FALSE;
|
||||||
|
|
||||||
|
if (data->connection_in_progress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Keep trying successful connections until one works, each iteration pops one */
|
||||||
|
while (data->successful_connections)
|
||||||
|
{
|
||||||
|
if (try_next_successful_connection (data))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data->enumeration_completed)
|
||||||
|
{
|
||||||
|
enumerator_next_async (data, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_connection_with_error (data, data->last_error);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
g_socket_client_connected_callback (GObject *source,
|
g_socket_client_connected_callback (GObject *source,
|
||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
@ -1593,10 +1737,7 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
{
|
{
|
||||||
ConnectionAttempt *attempt = user_data;
|
ConnectionAttempt *attempt = user_data;
|
||||||
GSocketClientAsyncConnectData *data = attempt->data;
|
GSocketClientAsyncConnectData *data = attempt->data;
|
||||||
GSList *l;
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
GProxy *proxy;
|
|
||||||
const gchar *protocol;
|
|
||||||
|
|
||||||
if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable))
|
if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable))
|
||||||
{
|
{
|
||||||
@ -1618,11 +1759,12 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
{
|
{
|
||||||
clarify_connect_error (error, data->connectable, attempt->address);
|
clarify_connect_error (error, data->connectable, attempt->address);
|
||||||
set_last_error (data, error);
|
set_last_error (data, error);
|
||||||
|
g_debug ("GSocketClient: Connection attempt failed: %s", error->message);
|
||||||
connection_attempt_remove (attempt);
|
connection_attempt_remove (attempt);
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
connection_attempt_unref (attempt);
|
connection_attempt_unref (attempt);
|
||||||
|
try_next_connection_or_finish (data, FALSE);
|
||||||
}
|
}
|
||||||
else
|
else /* Silently ignore cancelled attempts */
|
||||||
{
|
{
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
@ -1632,74 +1774,21 @@ g_socket_client_connected_callback (GObject *source,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->socket = g_steal_pointer (&attempt->socket);
|
g_socket_connection_set_cached_remote_address ((GSocketConnection*)attempt->connection, NULL);
|
||||||
data->connection = g_steal_pointer (&attempt->connection);
|
g_debug ("GSocketClient: TCP connection successful");
|
||||||
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, attempt->connection);
|
||||||
for (l = data->connection_attempts; l; l = g_slist_next (l))
|
|
||||||
{
|
|
||||||
ConnectionAttempt *attempt_entry = l->data;
|
|
||||||
g_cancellable_cancel (attempt_entry->cancellable);
|
|
||||||
connection_attempt_unref (attempt_entry);
|
|
||||||
}
|
|
||||||
g_slist_free (data->connection_attempts);
|
|
||||||
data->connection_attempts = NULL;
|
|
||||||
connection_attempt_unref (attempt);
|
|
||||||
|
|
||||||
g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, NULL);
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, data->connection);
|
|
||||||
|
|
||||||
/* wrong, but backward compatible */
|
/* wrong, but backward compatible */
|
||||||
g_socket_set_blocking (data->socket, TRUE);
|
g_socket_set_blocking (attempt->socket, TRUE);
|
||||||
|
|
||||||
if (!data->proxy_addr)
|
/* This ends the parallel "happy eyeballs" portion of connecting.
|
||||||
{
|
Now that we have a successful tcp connection we will attempt to connect
|
||||||
g_socket_client_tls_handshake (data);
|
at the TLS/Proxy layer. If those layers fail we will move on to the next
|
||||||
return;
|
connection.
|
||||||
}
|
|
||||||
|
|
||||||
protocol = g_proxy_address_get_protocol (data->proxy_addr);
|
|
||||||
|
|
||||||
/* The connection should not be anything other than TCP,
|
|
||||||
* but let's put a safety guard in case
|
|
||||||
*/
|
*/
|
||||||
if (!G_IS_TCP_CONNECTION (data->connection))
|
connection_attempt_remove (attempt);
|
||||||
{
|
data->successful_connections = g_slist_append (data->successful_connections, g_steal_pointer (&attempt));
|
||||||
g_critical ("Trying to proxy over non-TCP connection, this is "
|
try_next_connection_or_finish (data, FALSE);
|
||||||
"most likely a bug in GLib IO library.");
|
|
||||||
|
|
||||||
g_set_error_literal (&data->last_error,
|
|
||||||
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
||||||
_("Proxying over a non-TCP connection is not supported."));
|
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
}
|
|
||||||
else if (g_hash_table_contains (data->client->priv->app_proxies, protocol))
|
|
||||||
{
|
|
||||||
/* Simply complete the connection, we don't want to do TLS handshake
|
|
||||||
* as the application proxy handling may need proxy handshake first */
|
|
||||||
g_socket_client_async_connect_complete (data);
|
|
||||||
}
|
|
||||||
else if ((proxy = g_proxy_get_default_for_protocol (protocol)))
|
|
||||||
{
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATING, data->connectable, data->connection);
|
|
||||||
g_proxy_connect_async (proxy,
|
|
||||||
data->connection,
|
|
||||||
data->proxy_addr,
|
|
||||||
g_task_get_cancellable (data->task),
|
|
||||||
g_socket_client_proxy_connect_callback,
|
|
||||||
data);
|
|
||||||
g_object_unref (proxy);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_clear_error (&data->last_error);
|
|
||||||
|
|
||||||
g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
||||||
_("Proxy protocol “%s” is not supported."),
|
|
||||||
protocol);
|
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -1707,7 +1796,11 @@ on_connection_attempt_timeout (gpointer data)
|
|||||||
{
|
{
|
||||||
ConnectionAttempt *attempt = data;
|
ConnectionAttempt *attempt = data;
|
||||||
|
|
||||||
enumerator_next_async (attempt->data, TRUE);
|
if (!attempt->data->enumeration_completed)
|
||||||
|
{
|
||||||
|
g_debug ("GSocketClient: Timeout reached, trying another enumeration");
|
||||||
|
enumerator_next_async (attempt->data, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
@ -1717,9 +1810,9 @@ static void
|
|||||||
on_connection_cancelled (GCancellable *cancellable,
|
on_connection_cancelled (GCancellable *cancellable,
|
||||||
gpointer data)
|
gpointer data)
|
||||||
{
|
{
|
||||||
GCancellable *attempt_cancellable = data;
|
GCancellable *linked_cancellable = G_CANCELLABLE (data);
|
||||||
|
|
||||||
g_cancellable_cancel (attempt_cancellable);
|
g_cancellable_cancel (linked_cancellable);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1743,39 +1836,49 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
result, &error);
|
result, &error);
|
||||||
if (address == NULL)
|
if (address == NULL)
|
||||||
{
|
{
|
||||||
if (data->connection_attempts)
|
if (G_UNLIKELY (data->enumeration_completed))
|
||||||
|
return;
|
||||||
|
|
||||||
|
data->enumeration_completed = TRUE;
|
||||||
|
g_debug ("GSocketClient: Address enumeration completed (out of addresses)");
|
||||||
|
|
||||||
|
/* As per API docs: We only care about error if its the first call,
|
||||||
|
after that the enumerator is done.
|
||||||
|
|
||||||
|
Note that we don't care about cancellation errors because
|
||||||
|
task_completed_or_cancelled() above should handle that.
|
||||||
|
|
||||||
|
If this fails and nothing is in progress then we will complete task here.
|
||||||
|
*/
|
||||||
|
if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) ||
|
||||||
|
!data->enumerated_at_least_once)
|
||||||
{
|
{
|
||||||
g_object_unref (data->task);
|
g_debug ("GSocketClient: Address enumeration failed: %s", error ? error->message : NULL);
|
||||||
return;
|
if (data->last_error)
|
||||||
|
{
|
||||||
|
g_clear_error (&error);
|
||||||
|
error = data->last_error;
|
||||||
|
data->last_error = NULL;
|
||||||
|
}
|
||||||
|
else if (!error)
|
||||||
|
{
|
||||||
|
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
_("Unknown error on connect"));
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_connection_with_error (data, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
/* Enumeration should never trigger again, drop our ref */
|
||||||
data->completed = TRUE;
|
|
||||||
if (!error)
|
|
||||||
{
|
|
||||||
if (data->last_error)
|
|
||||||
{
|
|
||||||
error = data->last_error;
|
|
||||||
data->last_error = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
||||||
_("Unknown error on connect"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_task_return_error (data->task, error);
|
|
||||||
g_object_unref (data->task);
|
g_object_unref (data->task);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data->enumerated_at_least_once = TRUE;
|
||||||
|
g_debug ("GSocketClient: Address enumeration succeeded");
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED,
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED,
|
||||||
data->connectable, NULL);
|
data->connectable, NULL);
|
||||||
|
|
||||||
if (G_IS_PROXY_ADDRESS (address) &&
|
|
||||||
data->client->priv->enable_proxy)
|
|
||||||
data->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address));
|
|
||||||
|
|
||||||
g_clear_error (&data->last_error);
|
g_clear_error (&data->last_error);
|
||||||
|
|
||||||
socket = create_socket (data->client, address, &data->last_error);
|
socket = create_socket (data->client, address, &data->last_error);
|
||||||
@ -1793,6 +1896,10 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
attempt->cancellable = g_cancellable_new ();
|
attempt->cancellable = g_cancellable_new ();
|
||||||
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
||||||
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
|
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
|
||||||
|
|
||||||
|
if (G_IS_PROXY_ADDRESS (address) && data->client->priv->enable_proxy)
|
||||||
|
attempt->proxy_addr = g_object_ref (G_PROXY_ADDRESS (address));
|
||||||
|
|
||||||
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
|
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
|
||||||
g_source_attach (attempt->timeout_source, g_task_get_context (data->task));
|
g_source_attach (attempt->timeout_source, g_task_get_context (data->task));
|
||||||
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
|
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
|
||||||
@ -1802,6 +1909,7 @@ g_socket_client_enumerator_callback (GObject *object,
|
|||||||
g_object_ref (attempt->cancellable), g_object_unref);
|
g_object_ref (attempt->cancellable), g_object_unref);
|
||||||
|
|
||||||
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
||||||
|
g_debug ("GSocketClient: Starting TCP connection attempt");
|
||||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection);
|
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection);
|
||||||
g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection),
|
g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection),
|
||||||
address,
|
address,
|
||||||
@ -1854,24 +1962,48 @@ g_socket_client_connect_async (GSocketClient *client,
|
|||||||
else
|
else
|
||||||
data->enumerator = g_socket_connectable_enumerate (connectable);
|
data->enumerator = g_socket_connectable_enumerate (connectable);
|
||||||
|
|
||||||
/* The flow and ownership here isn't quite obvious:
|
/* This function tries to match the behavior of g_socket_client_connect ()
|
||||||
- The task starts an async attempt to connect.
|
which is simple enough but much of it is done in parallel to be as responsive
|
||||||
- Each attempt holds a single ref on task.
|
as possible as per Happy Eyeballs (RFC 8305). This complicates flow quite a
|
||||||
- Each attempt may create new attempts by timing out (not a failure) so
|
bit but we can describe it in 3 sections:
|
||||||
there are multiple attempts happening in parallel.
|
|
||||||
- Upon failure an attempt will start a new attempt that steals its ref
|
Firstly we have address enumeration (DNS):
|
||||||
until there are no more attempts left and it drops its ref.
|
- This may be triggered multiple times by enumerator_next_async().
|
||||||
- Upon success it will cancel all other attempts and continue on
|
- It also has its own cancellable (data->enumeration_cancellable).
|
||||||
to the rest of the connection (tls, proxies, etc) which do not
|
- Enumeration is done lazily because GNetworkAddressAddressEnumerator
|
||||||
happen in parallel and at the very end drop its ref.
|
also does work in parallel and may lazily add new addresses.
|
||||||
- Upon cancellation an attempt drops its ref.
|
- If the first enumeration errors then the task errors. Otherwise all enumerations
|
||||||
*/
|
will potentially be used (until task or enumeration is cancelled).
|
||||||
|
|
||||||
|
Then we start attempting connections (TCP):
|
||||||
|
- Each connection is independent and kept in a ConnectionAttempt object.
|
||||||
|
- They each hold a ref on the main task and have their own cancellable.
|
||||||
|
- Multiple attempts may happen in parallel as per Happy Eyeballs.
|
||||||
|
- Upon failure or timeouts more connection attempts are made.
|
||||||
|
- If no connections succeed the task errors.
|
||||||
|
- Upon success they are kept in a list of successful connections.
|
||||||
|
|
||||||
|
Lastly we connect at the application layer (TLS, Proxies):
|
||||||
|
- These are done in serial.
|
||||||
|
- The reasoning here is that Happy Eyeballs is about making bad connections responsive
|
||||||
|
at the IP/TCP layers. Issues at the application layer are generally not due to
|
||||||
|
connectivity issues but rather misconfiguration.
|
||||||
|
- Upon failure it will try the next TCP connection until it runs out and
|
||||||
|
the task errors.
|
||||||
|
- Upon success it cancels everything remaining (enumeration and connections)
|
||||||
|
and returns the connection.
|
||||||
|
*/
|
||||||
|
|
||||||
data->task = g_task_new (client, cancellable, callback, user_data);
|
data->task = g_task_new (client, cancellable, callback, user_data);
|
||||||
g_task_set_check_cancellable (data->task, FALSE); /* We handle this manually */
|
g_task_set_check_cancellable (data->task, FALSE); /* We handle this manually */
|
||||||
g_task_set_source_tag (data->task, g_socket_client_connect_async);
|
g_task_set_source_tag (data->task, g_socket_client_connect_async);
|
||||||
g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free);
|
g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free);
|
||||||
|
|
||||||
|
data->enumeration_cancellable = g_cancellable_new ();
|
||||||
|
if (cancellable)
|
||||||
|
g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled),
|
||||||
|
g_object_ref (data->enumeration_cancellable), g_object_unref);
|
||||||
|
|
||||||
enumerator_next_async (data, FALSE);
|
enumerator_next_async (data, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,6 +2122,7 @@ g_socket_client_connect_to_uri_async (GSocketClient *client,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
g_debug("g_socket_client_connect_to_uri_async");
|
||||||
g_socket_client_connect_async (client,
|
g_socket_client_connect_async (client,
|
||||||
connectable, cancellable,
|
connectable, cancellable,
|
||||||
callback, user_data);
|
callback, user_data);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user