Merge branch 'mcatanzaro/#2211' into 'master'

Various improvements in GSocketClient

Closes #2211 and #1994

See merge request GNOME/glib!1686
This commit is contained in:
Philip Withnall 2020-10-14 10:20:07 +00:00
commit 06587fbfd7

View File

@ -828,7 +828,7 @@ g_socket_client_class_init (GSocketClientClass *class)
* multiple times (or not at all) for a given connectable (in * multiple times (or not at all) for a given connectable (in
* particular, if @client ends up attempting to connect to more than * particular, if @client ends up attempting to connect to more than
* one address). However, if @client emits the #GSocketClient::event * one address). However, if @client emits the #GSocketClient::event
* signal at all for a given connectable, that it will always emit * signal at all for a given connectable, then it will always emit
* it with %G_SOCKET_CLIENT_COMPLETE when it is done. * it with %G_SOCKET_CLIENT_COMPLETE when it is done.
* *
* Note that there may be additional #GSocketClientEvent values in * Note that there may be additional #GSocketClientEvent values in
@ -945,7 +945,7 @@ g_socket_client_class_init (GSocketClientClass *class)
static void static void
g_socket_client_emit_event (GSocketClient *client, g_socket_client_emit_event (GSocketClient *client,
GSocketClientEvent event, GSocketClientEvent event,
GSocketConnectable *connectable, GSocketConnectable *connectable,
GIOStream *connection) GIOStream *connection)
{ {
@ -953,6 +953,72 @@ g_socket_client_emit_event (GSocketClient *client,
event, connectable, connection); event, connectable, connection);
} }
/* Originally, GSocketClient returned whatever error occured last. Turns
* out this doesn't work well in practice. Consider the following case:
* DNS returns an IPv4 and IPv6 address. First we'll connect() to the
* IPv4 address, and say that succeeds, but TLS is enabled and the TLS
* handshake fails. Then we try the IPv6 address and receive ENETUNREACH
* because IPv6 isn't supported. We wind up returning NETWORK_UNREACHABLE
* even though the address can be pinged and a TLS error would be more
* appropriate. So instead, we now try to return the error corresponding
* to the latest attempted GSocketClientEvent in the connection process.
* TLS errors take precedence over proxy errors, which take precedence
* over connect() errors, which take precedence over DNS errors.
*
* Note that the example above considers a sync codepath, but this is an
* issue for the async codepath too, where events and errors may occur
* in confusing orders.
*/
typedef struct
{
GError *tmp_error;
GError *best_error;
GSocketClientEvent best_error_event;
} SocketClientErrorInfo;
static SocketClientErrorInfo *
socket_client_error_info_new (void)
{
return g_new0 (SocketClientErrorInfo, 1);
}
static void
socket_client_error_info_free (SocketClientErrorInfo *info)
{
g_assert (info->tmp_error == NULL);
g_clear_error (&info->best_error);
g_free (info);
}
static void
consider_tmp_error (SocketClientErrorInfo *info,
GSocketClientEvent event)
{
if (info->tmp_error == NULL)
return;
/* If we ever add more GSocketClientEvents in the future, then we'll
* no longer be able to use >= for this comparison, because future
* events will compare greater than G_SOCKET_CLIENT_COMPLETE. Until
* then, this is convenient. Note G_SOCKET_CLIENT_RESOLVING is 0 so we
* need to use >= here or those errors would never be set. That means
* if we get two errors on the same GSocketClientEvent, we wind up
* preferring the last one, which is fine.
*/
g_assert (event <= G_SOCKET_CLIENT_COMPLETE);
if (event >= info->best_error_event)
{
g_clear_error (&info->best_error);
info->best_error = info->tmp_error;
info->tmp_error = NULL;
info->best_error_event = event;
}
else
{
g_clear_error (&info->tmp_error);
}
}
/** /**
* g_socket_client_connect: * g_socket_client_connect:
* @client: a #GSocketClient. * @client: a #GSocketClient.
@ -991,9 +1057,10 @@ g_socket_client_connect (GSocketClient *client,
{ {
GIOStream *connection = NULL; GIOStream *connection = NULL;
GSocketAddressEnumerator *enumerator = NULL; GSocketAddressEnumerator *enumerator = NULL;
GError *last_error, *tmp_error; SocketClientErrorInfo *error_info;
gboolean ever_resolved = FALSE;
last_error = NULL; error_info = socket_client_error_info_new ();
if (can_use_proxy (client)) if (can_use_proxy (client))
{ {
@ -1018,44 +1085,38 @@ g_socket_client_connect (GSocketClient *client,
if (g_cancellable_is_cancelled (cancellable)) if (g_cancellable_is_cancelled (cancellable))
{ {
g_clear_error (error); g_clear_error (&error_info->best_error);
g_clear_error (&last_error); g_cancellable_set_error_if_cancelled (cancellable, &error_info->best_error);
g_cancellable_set_error_if_cancelled (cancellable, error);
break; break;
} }
tmp_error = NULL; if (!ever_resolved)
g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVING, {
connectable, NULL); g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVING,
connectable, NULL);
}
address = g_socket_address_enumerator_next (enumerator, cancellable, address = g_socket_address_enumerator_next (enumerator, cancellable,
&tmp_error); &error_info->tmp_error);
consider_tmp_error (error_info, G_SOCKET_CLIENT_RESOLVING);
if (!ever_resolved)
{
g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVED,
connectable, NULL);
ever_resolved = TRUE;
}
if (address == NULL) if (address == NULL)
{ {
if (tmp_error) /* Enumeration is finished. */
{ g_assert (&error_info->best_error != NULL);
g_clear_error (&last_error);
g_propagate_error (error, tmp_error);
}
else if (last_error)
{
g_propagate_error (error, last_error);
}
else
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unknown error on connect"));
break; break;
} }
g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVED,
connectable, NULL);
using_proxy = (G_IS_PROXY_ADDRESS (address) && using_proxy = (G_IS_PROXY_ADDRESS (address) &&
client->priv->enable_proxy); client->priv->enable_proxy);
/* clear error from previous attempt */ socket = create_socket (client, address, &error_info->tmp_error);
g_clear_error (&last_error); consider_tmp_error (error_info, G_SOCKET_CLIENT_CONNECTING);
socket = create_socket (client, address, &last_error);
if (socket == NULL) if (socket == NULL)
{ {
g_object_unref (address); g_object_unref (address);
@ -1067,14 +1128,15 @@ g_socket_client_connect (GSocketClient *client,
g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTING, connectable, connection); g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTING, connectable, connection);
if (g_socket_connection_connect (G_SOCKET_CONNECTION (connection), if (g_socket_connection_connect (G_SOCKET_CONNECTION (connection),
address, cancellable, &last_error)) address, cancellable, &error_info->tmp_error))
{ {
g_socket_connection_set_cached_remote_address ((GSocketConnection*)connection, NULL); g_socket_connection_set_cached_remote_address ((GSocketConnection*)connection, NULL);
g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTED, connectable, connection); g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTED, connectable, connection);
} }
else else
{ {
clarify_connect_error (last_error, connectable, address); clarify_connect_error (error_info->tmp_error, connectable, address);
consider_tmp_error (error_info, G_SOCKET_CLIENT_CONNECTING);
g_object_unref (connection); g_object_unref (connection);
connection = NULL; connection = NULL;
} }
@ -1095,9 +1157,10 @@ g_socket_client_connect (GSocketClient *client,
g_critical ("Trying to proxy over non-TCP connection, this is " g_critical ("Trying to proxy over non-TCP connection, this is "
"most likely a bug in GLib IO library."); "most likely a bug in GLib IO library.");
g_set_error_literal (&last_error, g_set_error_literal (&error_info->tmp_error,
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Proxying over a non-TCP connection is not supported.")); _("Proxying over a non-TCP connection is not supported."));
consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
g_object_unref (connection); g_object_unref (connection);
connection = NULL; connection = NULL;
@ -1115,7 +1178,9 @@ g_socket_client_connect (GSocketClient *client,
connection, connection,
proxy_addr, proxy_addr,
cancellable, cancellable,
&last_error); &error_info->tmp_error);
consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
g_object_unref (connection); g_object_unref (connection);
connection = proxy_connection; connection = proxy_connection;
g_object_unref (proxy); g_object_unref (proxy);
@ -1125,9 +1190,10 @@ g_socket_client_connect (GSocketClient *client,
} }
else else
{ {
g_set_error (&last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, g_set_error (&error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Proxy protocol “%s” is not supported."), _("Proxy protocol “%s” is not supported."),
protocol); protocol);
consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
g_object_unref (connection); g_object_unref (connection);
connection = NULL; connection = NULL;
} }
@ -1137,7 +1203,7 @@ g_socket_client_connect (GSocketClient *client,
{ {
GIOStream *tlsconn; GIOStream *tlsconn;
tlsconn = g_tls_client_connection_new (connection, connectable, &last_error); tlsconn = g_tls_client_connection_new (connection, connectable, &error_info->tmp_error);
g_object_unref (connection); g_object_unref (connection);
connection = tlsconn; connection = tlsconn;
@ -1147,16 +1213,21 @@ g_socket_client_connect (GSocketClient *client,
client->priv->tls_validation_flags); client->priv->tls_validation_flags);
g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKING, connectable, connection); g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKING, connectable, connection);
if (g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), if (g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn),
cancellable, &last_error)) cancellable, &error_info->tmp_error))
{ {
g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKED, connectable, connection); g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKED, connectable, connection);
} }
else else
{ {
consider_tmp_error (error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING);
g_object_unref (tlsconn); g_object_unref (tlsconn);
connection = NULL; connection = NULL;
} }
} }
else
{
consider_tmp_error (error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING);
}
} }
if (connection && !G_IS_SOCKET_CONNECTION (connection)) if (connection && !G_IS_SOCKET_CONNECTION (connection))
@ -1173,6 +1244,10 @@ g_socket_client_connect (GSocketClient *client,
} }
g_object_unref (enumerator); g_object_unref (enumerator);
if (!connection)
g_propagate_error (error, g_steal_pointer (&error_info->best_error));
socket_client_error_info_free (error_info);
g_socket_client_emit_event (client, G_SOCKET_CLIENT_COMPLETE, connectable, connection); g_socket_client_emit_event (client, G_SOCKET_CLIENT_COMPLETE, connectable, connection);
return G_SOCKET_CONNECTION (connection); return G_SOCKET_CONNECTION (connection);
} }
@ -1350,7 +1425,7 @@ typedef struct
GSList *connection_attempts; GSList *connection_attempts;
GSList *successful_connections; GSList *successful_connections;
GError *last_error; SocketClientErrorInfo *error_info;
gboolean enumerated_at_least_once; gboolean enumerated_at_least_once;
gboolean enumeration_completed; gboolean enumeration_completed;
@ -1370,7 +1445,7 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
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_slist_free_full (data->successful_connections, connection_attempt_unref);
g_clear_error (&data->last_error); g_clear_pointer (&data->error_info, socket_client_error_info_free);
g_slice_free (GSocketClientAsyncConnectData, data); g_slice_free (GSocketClientAsyncConnectData, data);
} }
@ -1492,14 +1567,6 @@ g_socket_client_enumerator_callback (GObject *object,
GAsyncResult *result, GAsyncResult *result,
gpointer user_data); gpointer user_data);
static void
set_last_error (GSocketClientAsyncConnectData *data,
GError *error)
{
g_clear_error (&data->last_error);
data->last_error = error;
}
static void static void
enumerator_next_async (GSocketClientAsyncConnectData *data, enumerator_next_async (GSocketClientAsyncConnectData *data,
gboolean add_task_ref) gboolean add_task_ref)
@ -1509,7 +1576,8 @@ enumerator_next_async (GSocketClientAsyncConnectData *data,
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); if (!data->enumerated_at_least_once)
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL);
g_debug ("GSocketClient: Starting new address enumeration"); g_debug ("GSocketClient: Starting new address enumeration");
g_socket_address_enumerator_next_async (data->enumerator, g_socket_address_enumerator_next_async (data->enumerator,
data->enumeration_cancellable, data->enumeration_cancellable,
@ -1529,7 +1597,7 @@ g_socket_client_tls_handshake_callback (GObject *object,
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->error_info->tmp_error))
{ {
g_object_unref (attempt->connection); g_object_unref (attempt->connection);
attempt->connection = G_IO_STREAM (object); attempt->connection = G_IO_STREAM (object);
@ -1542,7 +1610,9 @@ g_socket_client_tls_handshake_callback (GObject *object,
{ {
g_object_unref (object); g_object_unref (object);
connection_attempt_unref (attempt); connection_attempt_unref (attempt);
g_debug ("GSocketClient: TLS handshake failed: %s", data->last_error->message);
g_debug ("GSocketClient: TLS handshake failed: %s", data->error_info->tmp_error->message);
consider_tmp_error (data->error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING);
try_next_connection_or_finish (data, TRUE); try_next_connection_or_finish (data, TRUE);
} }
} }
@ -1562,7 +1632,7 @@ g_socket_client_tls_handshake (ConnectionAttempt *attempt)
g_debug ("GSocketClient: Starting TLS handshake"); g_debug ("GSocketClient: Starting TLS handshake");
tlsconn = g_tls_client_connection_new (attempt->connection, tlsconn = g_tls_client_connection_new (attempt->connection,
data->connectable, data->connectable,
&data->last_error); &data->error_info->tmp_error);
if (tlsconn) if (tlsconn)
{ {
g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn), g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn),
@ -1577,6 +1647,8 @@ g_socket_client_tls_handshake (ConnectionAttempt *attempt)
else else
{ {
connection_attempt_unref (attempt); connection_attempt_unref (attempt);
consider_tmp_error (data->error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING);
try_next_connection_or_finish (data, TRUE); try_next_connection_or_finish (data, TRUE);
} }
} }
@ -1592,19 +1664,19 @@ g_socket_client_proxy_connect_callback (GObject *object,
g_object_unref (attempt->connection); g_object_unref (attempt->connection);
attempt->connection = g_proxy_connect_finish (G_PROXY (object), attempt->connection = g_proxy_connect_finish (G_PROXY (object),
result, result,
&data->last_error); &data->error_info->tmp_error);
if (attempt->connection) if (attempt->connection)
{ {
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection); g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection);
g_socket_client_tls_handshake (attempt);
} }
else else
{ {
connection_attempt_unref (attempt); connection_attempt_unref (attempt);
try_next_connection_or_finish (data, TRUE);
return;
}
g_socket_client_tls_handshake (attempt); consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
try_next_connection_or_finish (data, TRUE);
}
} }
static void static void
@ -1672,9 +1744,10 @@ try_next_successful_connection (GSocketClientAsyncConnectData *data)
g_critical ("Trying to proxy over non-TCP connection, this is " g_critical ("Trying to proxy over non-TCP connection, this is "
"most likely a bug in GLib IO library."); "most likely a bug in GLib IO library.");
g_set_error_literal (&data->last_error, g_set_error_literal (&data->error_info->tmp_error,
G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Proxying over a non-TCP connection is not supported.")); _("Proxying over a non-TCP connection is not supported."));
consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
} }
else if (g_hash_table_contains (data->client->priv->app_proxies, protocol)) else if (g_hash_table_contains (data->client->priv->app_proxies, protocol))
{ {
@ -1701,11 +1774,10 @@ try_next_successful_connection (GSocketClientAsyncConnectData *data)
} }
else else
{ {
g_clear_error (&data->last_error); g_set_error (&data->error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Proxy protocol “%s” is not supported."), _("Proxy protocol “%s” is not supported."),
protocol); protocol);
consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING);
} }
data->connection_in_progress = FALSE; data->connection_in_progress = FALSE;
@ -1736,7 +1808,7 @@ try_next_connection_or_finish (GSocketClientAsyncConnectData *data,
return; return;
} }
complete_connection_with_error (data, data->last_error); complete_connection_with_error (data, g_steal_pointer (&data->error_info->best_error));
} }
static void static void
@ -1746,7 +1818,6 @@ g_socket_client_connected_callback (GObject *source,
{ {
ConnectionAttempt *attempt = user_data; ConnectionAttempt *attempt = user_data;
GSocketClientAsyncConnectData *data = attempt->data; GSocketClientAsyncConnectData *data = attempt->data;
GError *error = NULL;
if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable)) if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable))
{ {
@ -1762,20 +1833,20 @@ g_socket_client_connected_callback (GObject *source,
} }
if (!g_socket_connection_connect_finish (G_SOCKET_CONNECTION (source), if (!g_socket_connection_connect_finish (G_SOCKET_CONNECTION (source),
result, &error)) result, &data->error_info->tmp_error))
{ {
if (!g_cancellable_is_cancelled (attempt->cancellable)) if (!g_cancellable_is_cancelled (attempt->cancellable))
{ {
clarify_connect_error (error, data->connectable, attempt->address); clarify_connect_error (data->error_info->tmp_error, data->connectable, attempt->address);
set_last_error (data, error); consider_tmp_error (data->error_info, G_SOCKET_CLIENT_CONNECTING);
g_debug ("GSocketClient: Connection attempt failed: %s", error->message); g_debug ("GSocketClient: Connection attempt failed: %s", data->error_info->tmp_error->message);
connection_attempt_remove (attempt); connection_attempt_remove (attempt);
connection_attempt_unref (attempt); connection_attempt_unref (attempt);
try_next_connection_or_finish (data, FALSE); try_next_connection_or_finish (data, FALSE);
} }
else /* Silently ignore cancelled attempts */ else /* Silently ignore cancelled attempts */
{ {
g_clear_error (&error); g_clear_error (&data->error_info->tmp_error);
g_object_unref (data->task); g_object_unref (data->task);
connection_attempt_unref (attempt); connection_attempt_unref (attempt);
} }
@ -1833,7 +1904,6 @@ g_socket_client_enumerator_callback (GObject *object,
GSocketAddress *address = NULL; GSocketAddress *address = NULL;
GSocket *socket; GSocket *socket;
ConnectionAttempt *attempt; ConnectionAttempt *attempt;
GError *error = NULL;
if (task_completed_or_cancelled (data)) if (task_completed_or_cancelled (data))
{ {
@ -1842,7 +1912,7 @@ g_socket_client_enumerator_callback (GObject *object,
} }
address = g_socket_address_enumerator_next_finish (data->enumerator, address = g_socket_address_enumerator_next_finish (data->enumerator,
result, &error); result, &data->error_info->tmp_error);
if (address == NULL) if (address == NULL)
{ {
if (G_UNLIKELY (data->enumeration_completed)) if (G_UNLIKELY (data->enumeration_completed))
@ -1851,7 +1921,7 @@ g_socket_client_enumerator_callback (GObject *object,
data->enumeration_completed = TRUE; data->enumeration_completed = TRUE;
g_debug ("GSocketClient: Address enumeration completed (out of addresses)"); g_debug ("GSocketClient: Address enumeration completed (out of addresses)");
/* As per API docs: We only care about error if its the first call, /* As per API docs: We only care about error if it's the first call,
after that the enumerator is done. after that the enumerator is done.
Note that we don't care about cancellation errors because Note that we don't care about cancellation errors because
@ -1862,20 +1932,11 @@ g_socket_client_enumerator_callback (GObject *object,
if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) || if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) ||
!data->enumerated_at_least_once) !data->enumerated_at_least_once)
{ {
g_debug ("GSocketClient: Address enumeration failed: %s", error ? error->message : NULL); g_debug ("GSocketClient: Address enumeration failed: %s",
if (data->last_error) data->error_info->tmp_error ? data->error_info->tmp_error->message : NULL);
{ consider_tmp_error (data->error_info, G_SOCKET_CLIENT_RESOLVING);
g_clear_error (&error); g_assert (data->error_info->best_error);
error = data->last_error; complete_connection_with_error (data, g_steal_pointer (&data->error_info->best_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);
} }
/* Enumeration should never trigger again, drop our ref */ /* Enumeration should never trigger again, drop our ref */
@ -1883,17 +1944,19 @@ g_socket_client_enumerator_callback (GObject *object,
return; return;
} }
data->enumerated_at_least_once = TRUE;
g_debug ("GSocketClient: Address enumeration succeeded"); g_debug ("GSocketClient: Address enumeration succeeded");
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED, if (!data->enumerated_at_least_once)
data->connectable, NULL); {
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED,
data->connectable, NULL);
data->enumerated_at_least_once = TRUE;
}
g_clear_error (&data->last_error); socket = create_socket (data->client, address, &data->error_info->tmp_error);
socket = create_socket (data->client, address, &data->last_error);
if (socket == NULL) if (socket == NULL)
{ {
g_object_unref (address); g_object_unref (address);
consider_tmp_error (data->error_info, G_SOCKET_CLIENT_CONNECTING);
enumerator_next_async (data, FALSE); enumerator_next_async (data, FALSE);
return; return;
} }
@ -1936,6 +1999,15 @@ g_socket_client_enumerator_callback (GObject *object,
* *
* This is the asynchronous version of g_socket_client_connect(). * This is the asynchronous version of g_socket_client_connect().
* *
* You may wish to prefer the asynchronous version even in synchronous
* command line programs because, since 2.60, it implements
* [RFC 8305](https://tools.ietf.org/html/rfc8305) "Happy Eyeballs"
* recommendations to work around long connection timeouts in networks
* where IPv6 is broken by performing an IPv4 connection simultaneously
* without waiting for IPv6 to time out, which is not supported by the
* synchronous call. (This is not an API guarantee, and may change in
* the future.)
*
* When the operation is finished @callback will be * When the operation is finished @callback will be
* called. You can then call g_socket_client_connect_finish() to get * called. You can then call g_socket_client_connect_finish() to get
* the result of the operation. * the result of the operation.
@ -1956,6 +2028,7 @@ g_socket_client_connect_async (GSocketClient *client,
data = g_slice_new0 (GSocketClientAsyncConnectData); data = g_slice_new0 (GSocketClientAsyncConnectData);
data->client = client; data->client = client;
data->connectable = g_object_ref (connectable); data->connectable = g_object_ref (connectable);
data->error_info = socket_client_error_info_new ();
if (can_use_proxy (client)) if (can_use_proxy (client))
{ {