Merge branch 'wip/tingping/happy-eyeballs'

Merging this manually because the CI is broken.

Fixes https://gitlab.gnome.org/GNOME/glib/merge_requests/421
This commit is contained in:
Michael Catanzaro 2018-12-12 17:08:58 -06:00
commit 99d7894d48
15 changed files with 1795 additions and 308 deletions

View File

@ -1917,6 +1917,10 @@ g_resolver_set_default
g_resolver_lookup_by_name
g_resolver_lookup_by_name_async
g_resolver_lookup_by_name_finish
GResolverNameLookupFlags
g_resolver_lookup_by_name_with_flags
g_resolver_lookup_by_name_with_flags_async
g_resolver_lookup_by_name_with_flags_finish
g_resolver_free_addresses
g_resolver_lookup_by_address
g_resolver_lookup_by_address_async

View File

@ -3,6 +3,7 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -215,24 +216,29 @@ g_network_address_get_property (GObject *object,
}
/*
* g_network_address_add_addresses:
* @addr: A #GNetworkAddress
* @addresses: (transfer full): List of #GSocketAddress
* @resolver_serial: Serial of #GResolver used
*
* Consumes address list and adds them to internal list.
*/
static void
g_network_address_set_addresses (GNetworkAddress *addr,
g_network_address_add_addresses (GNetworkAddress *addr,
GList *addresses,
guint64 resolver_serial)
{
GList *a;
GSocketAddress *sockaddr;
g_return_if_fail (addresses != NULL && addr->priv->sockaddrs == NULL);
for (a = addresses; a; a = a->next)
{
sockaddr = g_inet_socket_address_new (a->data, addr->priv->port);
addr->priv->sockaddrs = g_list_prepend (addr->priv->sockaddrs, sockaddr);
addr->priv->sockaddrs = g_list_append (addr->priv->sockaddrs, sockaddr);
g_object_unref (a->data);
}
g_list_free (addresses);
addr->priv->sockaddrs = g_list_reverse (addr->priv->sockaddrs);
addr->priv->resolver_serial = resolver_serial;
}
@ -246,7 +252,7 @@ g_network_address_parse_sockaddr (GNetworkAddress *addr)
addr->priv->port);
if (sockaddr)
{
addr->priv->sockaddrs = g_list_prepend (addr->priv->sockaddrs, sockaddr);
addr->priv->sockaddrs = g_list_append (addr->priv->sockaddrs, sockaddr);
return TRUE;
}
else
@ -315,7 +321,7 @@ g_network_address_new_loopback (guint16 port)
addrs = g_list_append (addrs, g_inet_address_new_loopback (AF_INET6));
addrs = g_list_append (addrs, g_inet_address_new_loopback (AF_INET));
g_network_address_set_addresses (addr, addrs, 0);
g_network_address_add_addresses (addr, g_steal_pointer (&addrs), 0);
return G_SOCKET_CONNECTABLE (addr);
}
@ -876,9 +882,14 @@ g_network_address_get_scheme (GNetworkAddress *addr)
typedef struct {
GSocketAddressEnumerator parent_instance;
GNetworkAddress *addr;
GList *addresses;
GList *next;
GNetworkAddress *addr; /* (owned) */
GList *addresses; /* (owned) (nullable) */
GList *last_tail; /* (unowned) (nullable) */
GList *current_item; /* (unowned) (nullable) */
GTask *queued_task; /* (owned) (nullable) */
GError *last_error; /* (owned) (nullable) */
GSource *wait_source; /* (owned) (nullable) */
GMainContext *context; /* (owned) (nullable) */
} GNetworkAddressAddressEnumerator;
typedef struct {
@ -895,11 +906,139 @@ g_network_address_address_enumerator_finalize (GObject *object)
GNetworkAddressAddressEnumerator *addr_enum =
G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (object);
if (addr_enum->wait_source)
{
g_source_destroy (addr_enum->wait_source);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
g_clear_object (&addr_enum->queued_task);
g_clear_error (&addr_enum->last_error);
g_object_unref (addr_enum->addr);
g_clear_pointer (&addr_enum->addresses, g_list_free);
g_clear_pointer (&addr_enum->context, g_main_context_unref);
G_OBJECT_CLASS (_g_network_address_address_enumerator_parent_class)->finalize (object);
}
static inline GSocketFamily
get_address_family (GInetSocketAddress *address)
{
return g_inet_address_get_family (g_inet_socket_address_get_address (address));
}
static void
list_split_families (GList *list,
GList **out_ipv4,
GList **out_ipv6)
{
g_assert (out_ipv4);
g_assert (out_ipv6);
while (list)
{
GSocketFamily family = get_address_family (list->data);
switch (family)
{
case G_SOCKET_FAMILY_IPV4:
*out_ipv4 = g_list_prepend (*out_ipv4, list->data);
break;
case G_SOCKET_FAMILY_IPV6:
*out_ipv6 = g_list_prepend (*out_ipv6, list->data);
break;
case G_SOCKET_FAMILY_INVALID:
case G_SOCKET_FAMILY_UNIX:
g_assert_not_reached ();
}
list = g_list_next (list);
}
*out_ipv4 = g_list_reverse (*out_ipv4);
*out_ipv6 = g_list_reverse (*out_ipv6);
}
static GList *
list_interleave_families (GList *list1,
GList *list2)
{
GList *interleaved = NULL;
while (list1 || list2)
{
if (list1)
{
interleaved = g_list_append (interleaved, list1->data);
list1 = g_list_delete_link (list1, list1);
}
if (list2)
{
interleaved = g_list_append (interleaved, list2->data);
list2 = g_list_delete_link (list2, list2);
}
}
return interleaved;
}
/* list_copy_interleaved:
* @list: (transfer container): List to copy
*
* Does a shallow copy of a list with address families interleaved.
*
* For example:
* Input: [ipv6, ipv6, ipv4, ipv4]
* Output: [ipv6, ipv4, ipv6, ipv4]
*
* Returns: (transfer container): A new list
*/
static GList *
list_copy_interleaved (GList *list)
{
GList *ipv4 = NULL, *ipv6 = NULL;
list_split_families (list, &ipv4, &ipv6);
return list_interleave_families (ipv6, ipv4);
}
/* list_concat_interleaved:
* @current_item: (transfer container): Already existing list
* @new_list: (transfer none): New list to be interleaved and concatenated
*
* This differs from g_list_concat() + list_copy_interleaved() in that it sorts
* items in the previous list starting from @current_item.
*
* Returns: (transfer container): New start of list
*/
static GList *
list_concat_interleaved (GList *current_item,
GList *new_list)
{
GList *ipv4 = NULL, *ipv6 = NULL, *interleaved, *trailing = NULL;
GSocketFamily last_family = G_SOCKET_FAMILY_IPV4; /* Default to starting with ipv6 */
if (current_item)
{
last_family = get_address_family (current_item->data);
/* Unused addresses will get removed, resorted, then readded */
trailing = g_list_next (current_item);
current_item->next = NULL;
}
list_split_families (trailing, &ipv4, &ipv6);
list_split_families (new_list, &ipv4, &ipv6);
if (trailing)
g_list_free (trailing);
if (last_family == G_SOCKET_FAMILY_IPV4)
interleaved = list_interleave_families (ipv6, ipv4);
else
interleaved = list_interleave_families (ipv4, ipv6);
return g_list_concat (current_item, interleaved);
}
static GSocketAddress *
g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator,
GCancellable *cancellable,
@ -938,36 +1077,34 @@ g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator
return NULL;
}
g_network_address_set_addresses (addr, addresses, serial);
g_network_address_add_addresses (addr, g_steal_pointer (&addresses), serial);
}
addr_enum->addresses = addr->priv->sockaddrs;
addr_enum->next = addr_enum->addresses;
addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
addr_enum->last_tail = g_list_last (addr->priv->sockaddrs);
g_object_unref (resolver);
}
if (addr_enum->next == NULL)
if (addr_enum->current_item == NULL)
return NULL;
sockaddr = addr_enum->next->data;
addr_enum->next = addr_enum->next->next;
sockaddr = addr_enum->current_item->data;
addr_enum->current_item = g_list_next (addr_enum->current_item);
return g_object_ref (sockaddr);
}
static void
have_addresses (GNetworkAddressAddressEnumerator *addr_enum,
GTask *task, GError *error)
complete_queued_task (GNetworkAddressAddressEnumerator *addr_enum,
GTask *task,
GError *error)
{
GSocketAddress *sockaddr;
addr_enum->addresses = addr_enum->addr->priv->sockaddrs;
addr_enum->next = addr_enum->addresses;
addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr_enum->addr->priv->sockaddrs);
addr_enum->last_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
if (addr_enum->next)
{
sockaddr = g_object_ref (addr_enum->next->data);
addr_enum->next = addr_enum->next->next;
}
if (addr_enum->current_item)
sockaddr = g_object_ref (addr_enum->current_item->data);
else
sockaddr = NULL;
@ -978,28 +1115,125 @@ have_addresses (GNetworkAddressAddressEnumerator *addr_enum,
g_object_unref (task);
}
static void
got_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
static int
on_address_timeout (gpointer user_data)
{
GTask *task = user_data;
GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task);
GNetworkAddressAddressEnumerator *addr_enum = user_data;
/* If ipv6 didn't come in yet, just complete the task */
if (addr_enum->queued_task != NULL)
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&addr_enum->last_error));
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
return G_SOURCE_REMOVE;
}
static void
got_ipv6_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GNetworkAddressAddressEnumerator *addr_enum = user_data;
GResolver *resolver = G_RESOLVER (source_object);
GList *addresses;
GError *error = NULL;
if (!addr_enum->addr->priv->sockaddrs)
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
if (!error)
{
g_network_address_set_addresses (addr_enum->addr, addresses,
g_resolver_get_serial (resolver));
}
/* Regardless of which responds first we add them to the enumerator
* which does mean the timing of next_async() will potentially change
* the results */
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
g_resolver_get_serial (resolver));
}
have_addresses (addr_enum, task, error);
else
g_debug ("IPv6 DNS error: %s", error->message);
/* If ipv4 was first and waiting on us it can stop waiting */
if (addr_enum->wait_source)
{
g_source_destroy (addr_enum->wait_source);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
/* If we got an error before ipv4 then let it handle it.
* If we get ipv6 response first or error second then
* immediately complete the task.
*/
if (error != NULL && !addr_enum->last_error)
{
addr_enum->last_error = g_steal_pointer (&error);
/* This shouldn't happen often but avoid never responding. */
addr_enum->wait_source = g_timeout_source_new_seconds (1);
g_source_set_callback (addr_enum->wait_source,
on_address_timeout,
addr_enum, NULL);
g_source_attach (addr_enum->wait_source, addr_enum->context);
}
else if (addr_enum->queued_task != NULL)
{
g_clear_error (&addr_enum->last_error);
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&error));
}
else if (error != NULL)
g_clear_error (&error);
g_object_unref (addr_enum);
}
static void
got_ipv4_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GNetworkAddressAddressEnumerator *addr_enum = user_data;
GResolver *resolver = G_RESOLVER (source_object);
GList *addresses;
GError *error = NULL;
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
g_resolver_get_serial (resolver));
}
else
g_debug ("IPv4 DNS error: %s", error->message);
if (addr_enum->wait_source)
{
g_source_destroy (addr_enum->wait_source);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
/* If ipv6 already came in and errored then we return.
* If ipv6 returned successfully then we don't need to do anything.
* Otherwise we should wait a short while for ipv6 as RFC 8305 suggests.
*/
if (addr_enum->last_error)
{
g_assert (addr_enum->queued_task);
g_clear_error (&addr_enum->last_error);
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&error));
}
else if (addr_enum->queued_task != NULL)
{
addr_enum->last_error = g_steal_pointer (&error);
addr_enum->wait_source = g_timeout_source_new (50);
g_source_set_callback (addr_enum->wait_source,
on_address_timeout,
addr_enum, NULL);
g_source_attach (addr_enum->wait_source, addr_enum->context);
}
else if (error != NULL)
g_clear_error (&error);
g_object_unref (addr_enum);
}
static void
@ -1012,6 +1246,7 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum
G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator);
GSocketAddress *sockaddr;
GTask *task;
GNetworkAddress *addr = addr_enum->addr;
task = g_task_new (addr_enum, cancellable, callback, user_data);
g_task_set_source_tag (task, g_network_address_address_enumerator_next_async);
@ -1030,33 +1265,61 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum
addr->priv->sockaddrs = NULL;
}
if (!addr->priv->sockaddrs)
if (addr->priv->sockaddrs == NULL)
{
if (g_network_address_parse_sockaddr (addr))
have_addresses (addr_enum, task, NULL);
complete_queued_task (addr_enum, task, NULL);
else
{
g_resolver_lookup_by_name_async (resolver,
addr->priv->hostname,
cancellable,
got_addresses, task);
/* It does not make sense for this to be called multiple
* times before the initial callback has been called */
g_assert (addr_enum->queued_task == NULL);
addr_enum->queued_task = g_steal_pointer (&task);
/* Lookup in parallel as per RFC 8305 */
g_resolver_lookup_by_name_with_flags_async (resolver,
addr->priv->hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY,
cancellable,
got_ipv6_addresses, g_object_ref (addr_enum));
g_resolver_lookup_by_name_with_flags_async (resolver,
addr->priv->hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY,
cancellable,
got_ipv4_addresses, g_object_ref (addr_enum));
}
g_object_unref (resolver);
return;
}
addr_enum->addresses = addr->priv->sockaddrs;
addr_enum->next = addr_enum->addresses;
g_object_unref (resolver);
}
if (addr_enum->next)
if (addr_enum->addresses == NULL)
{
sockaddr = g_object_ref (addr_enum->next->data);
addr_enum->next = addr_enum->next->next;
g_assert (addr->priv->sockaddrs);
addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
sockaddr = g_object_ref (addr_enum->current_item->data);
}
else
sockaddr = NULL;
{
GList *parent_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
if (addr_enum->last_tail != parent_tail)
{
addr_enum->current_item = list_concat_interleaved (addr_enum->current_item, g_list_next (addr_enum->last_tail));
addr_enum->last_tail = parent_tail;
}
if (addr_enum->current_item->next)
{
addr_enum->current_item = g_list_next (addr_enum->current_item);
sockaddr = g_object_ref (addr_enum->current_item->data);
}
else
sockaddr = NULL;
}
g_task_return_pointer (task, sockaddr, g_object_unref);
g_object_unref (task);
@ -1075,6 +1338,7 @@ g_network_address_address_enumerator_next_finish (GSocketAddressEnumerator *enu
static void
_g_network_address_address_enumerator_init (GNetworkAddressAddressEnumerator *enumerator)
{
enumerator->context = g_main_context_ref_thread_default ();
}
static void

View File

@ -3,6 +3,7 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -30,6 +31,7 @@
#include "gsrvtarget.h"
#include "gthreadedresolver.h"
#include "gioerror.h"
#include "gcancellable.h"
#ifdef G_OS_UNIX
#include <sys/stat.h>
@ -347,6 +349,60 @@ handle_ip_address (const char *hostname,
return FALSE;
}
static GList *
lookup_by_name_real (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GError **error)
{
GList *addrs;
gchar *ascii_hostname = NULL;
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
g_return_val_if_fail (hostname != NULL, NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* Check if @hostname is just an IP address */
if (handle_ip_address (hostname, &addrs, error))
return addrs;
if (g_hostname_is_non_ascii (hostname))
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
if (!hostname)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Invalid hostname"));
return NULL;
}
g_resolver_maybe_reload (resolver);
if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
{
if (!G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
/* Translators: The placeholder is for a function name. */
_("%s not implemented"), "lookup_by_name_with_flags");
g_free (ascii_hostname);
return NULL;
}
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_with_flags (resolver, hostname, flags, cancellable, error);
}
else
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name (resolver, hostname, cancellable, error);
remove_duplicates (addrs);
g_free (ascii_hostname);
return addrs;
}
/**
* g_resolver_lookup_by_name:
* @resolver: a #GResolver
@ -391,36 +447,187 @@ g_resolver_lookup_by_name (GResolver *resolver,
GCancellable *cancellable,
GError **error)
{
GList *addrs;
gchar *ascii_hostname = NULL;
return lookup_by_name_real (resolver,
hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
cancellable,
error);
}
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
g_return_val_if_fail (hostname != NULL, NULL);
/**
* g_resolver_lookup_by_name_with_flags:
* @resolver: a #GResolver
* @hostname: the hostname to look up
* @flags: extra #GResolverNameLookupFlags for the lookup
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: (nullable): return location for a #GError, or %NULL
*
* This differs from g_resolver_lookup_by_name() in that you can modify
* the lookup behavior with @flags. For example this can be used to limit
* results with #G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY.
*
* Returns: (element-type GInetAddress) (transfer full): a non-empty #GList
* of #GInetAddress, or %NULL on error. You
* must unref each of the addresses and free the list when you are
* done with it. (You can use g_resolver_free_addresses() to do this.)
*
* Since: 2.60
*/
GList *
g_resolver_lookup_by_name_with_flags (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GError **error)
{
return lookup_by_name_real (resolver,
hostname,
flags,
cancellable,
error);
}
static void
lookup_by_name_async_real (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
gchar *ascii_hostname = NULL;
GList *addrs;
GError *error = NULL;
g_return_if_fail (G_IS_RESOLVER (resolver));
g_return_if_fail (hostname != NULL);
g_return_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY && flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY));
/* Check if @hostname is just an IP address */
if (handle_ip_address (hostname, &addrs, error))
return addrs;
if (handle_ip_address (hostname, &addrs, &error))
{
GTask *task;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, lookup_by_name_async_real);
if (addrs)
g_task_return_pointer (task, addrs, (GDestroyNotify) g_resolver_free_addresses);
else
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (g_hostname_is_non_ascii (hostname))
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
if (!hostname)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
GTask *task;
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Invalid hostname"));
return NULL;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, lookup_by_name_async_real);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_resolver_maybe_reload (resolver);
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name (resolver, hostname, cancellable, error);
if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
{
if (G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags_async == NULL)
{
GTask *task;
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
/* Translators: The placeholder is for a function name. */
_("%s not implemented"), "lookup_by_name_with_flags_async");
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, lookup_by_name_async_real);
g_task_return_error (task, error);
g_object_unref (task);
}
else
G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_with_flags_async (resolver, hostname, flags, cancellable, callback, user_data);
}
else
G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_async (resolver, hostname, cancellable, callback, user_data);
g_free (ascii_hostname);
}
static GList *
lookup_by_name_finish_real (GResolver *resolver,
GAsyncResult *result,
GError **error,
gboolean with_flags)
{
GList *addrs;
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (g_async_result_legacy_propagate_error (result, error))
return NULL;
else if (g_async_result_is_tagged (result, lookup_by_name_async_real))
{
/* Handle the stringified-IP-addr case */
return g_task_propagate_pointer (G_TASK (result), error);
}
if (with_flags)
{
g_assert (G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags_finish != NULL);
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_with_flags_finish (resolver, result, error);
}
else
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_finish (resolver, result, error);
remove_duplicates (addrs);
g_free (ascii_hostname);
return addrs;
}
/**
* g_resolver_lookup_by_name_with_flags_async:
* @resolver: a #GResolver
* @hostname: the hostname to look up the address of
* @flags: extra #GResolverNameLookupFlags for the lookup
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: (scope async): callback to call after resolution completes
* @user_data: (closure): data for @callback
*
* Begins asynchronously resolving @hostname to determine its
* associated IP address(es), and eventually calls @callback, which
* must call g_resolver_lookup_by_name_with_flags_finish() to get the result.
* See g_resolver_lookup_by_name() for more details.
*
* Since: 2.60
*/
void
g_resolver_lookup_by_name_with_flags_async (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
lookup_by_name_async_real (resolver,
hostname,
flags,
cancellable,
callback,
user_data);
}
/**
* g_resolver_lookup_by_name_async:
* @resolver: a #GResolver
@ -443,49 +650,12 @@ g_resolver_lookup_by_name_async (GResolver *resolver,
GAsyncReadyCallback callback,
gpointer user_data)
{
gchar *ascii_hostname = NULL;
GList *addrs;
GError *error = NULL;
g_return_if_fail (G_IS_RESOLVER (resolver));
g_return_if_fail (hostname != NULL);
/* Check if @hostname is just an IP address */
if (handle_ip_address (hostname, &addrs, &error))
{
GTask *task;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_resolver_lookup_by_name_async);
if (addrs)
g_task_return_pointer (task, addrs, (GDestroyNotify) g_resolver_free_addresses);
else
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (g_hostname_is_non_ascii (hostname))
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
if (!hostname)
{
GTask *task;
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Invalid hostname"));
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_resolver_lookup_by_name_async);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_resolver_maybe_reload (resolver);
G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_async (resolver, hostname, cancellable, callback, user_data);
g_free (ascii_hostname);
lookup_by_name_async_real (resolver,
hostname,
0,
cancellable,
callback,
user_data);
}
/**
@ -512,24 +682,40 @@ g_resolver_lookup_by_name_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
GList *addrs;
return lookup_by_name_finish_real (resolver,
result,
error,
FALSE);
}
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
if (g_async_result_legacy_propagate_error (result, error))
return NULL;
else if (g_async_result_is_tagged (result, g_resolver_lookup_by_name_async))
{
/* Handle the stringified-IP-addr case */
return g_task_propagate_pointer (G_TASK (result), error);
}
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_finish (resolver, result, error);
remove_duplicates (addrs);
return addrs;
/**
* g_resolver_lookup_by_name_with_flags_finish:
* @resolver: a #GResolver
* @result: the result passed to your #GAsyncReadyCallback
* @error: return location for a #GError, or %NULL
*
* Retrieves the result of a call to
* g_resolver_lookup_by_name_with_flags_async().
*
* If the DNS resolution failed, @error (if non-%NULL) will be set to
* a value from #GResolverError. If the operation was cancelled,
* @error will be set to %G_IO_ERROR_CANCELLED.
*
* Returns: (element-type GInetAddress) (transfer full): a #GList
* of #GInetAddress, or %NULL on error. See g_resolver_lookup_by_name()
* for more details.
*
* Since: 2.60
*/
GList *
g_resolver_lookup_by_name_with_flags_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
return lookup_by_name_finish_real (resolver,
result,
error,
TRUE);
}
/**

View File

@ -1,6 +1,7 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -43,158 +44,237 @@ struct _GResolver {
GResolverPrivate *priv;
};
/**
* GResolverNameLookupFlags:
* @G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT: default behavior (same as g_resolver_lookup_by_name())
* @G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY: only resolve ipv4 addresses
* @G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY: only resolve ipv6 addresses
*
* Flags to modify lookup behavior.
*
* Since: 2.60
*/
typedef enum {
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT = 0,
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY = 1 << 0,
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY = 1 << 1,
} GResolverNameLookupFlags;
struct _GResolverClass {
GObjectClass parent_class;
/* Signals */
void ( *reload) (GResolver *resolver);
void ( *reload) (GResolver *resolver);
/* Virtual methods */
GList * ( *lookup_by_name) (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GError **error);
void ( *lookup_by_name_async) (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_by_name_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList * ( *lookup_by_name) (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GError **error);
void ( *lookup_by_name_async) (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_by_name_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
gchar * ( *lookup_by_address) (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GError **error);
void ( *lookup_by_address_async) (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gchar * ( *lookup_by_address_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
gchar * ( *lookup_by_address) (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GError **error);
void ( *lookup_by_address_async) (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gchar * ( *lookup_by_address_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList * ( *lookup_service) (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GError **error);
void ( *lookup_service_async) (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_service_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList * ( *lookup_service) (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GError **error);
void ( *lookup_service_async) (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_service_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList * ( *lookup_records) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
GList * ( *lookup_records) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
void ( *lookup_records_async) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void ( *lookup_records_async) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_records_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
/* Padding for future expansion */
void (*_g_reserved4) (void);
void (*_g_reserved5) (void);
void (*_g_reserved6) (void);
GList * ( *lookup_records_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
/**
* GResolverClass::lookup_by_name_with_flags_async:
* @resolver: a #GResolver
* @hostname: the hostname to resolve
* @flags: extra #GResolverNameLookupFlags to modify the lookup
* @cancellable: (nullable): a #GCancellable
* @callback: (scope async): a #GAsyncReadyCallback to call when completed
* @user_data: (closure): data to pass to @callback
*
* Asynchronous version of GResolverClass::lookup_by_name_with_flags
*
* GResolverClass::lookup_by_name_with_flags_finish will be called to get
* the result.
*
* Since: 2.60
*/
void ( *lookup_by_name_with_flags_async) (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
/**
* GResolverClass::lookup_by_name_with_flags_finish:
* @resolver: a #GResolver
* @result: a #GAsyncResult
* @error: (nullable): a pointer to a %NULL #GError
*
* Gets the result from GResolverClass::lookup_by_name_with_flags_async
*
* Returns: (element-type GInetAddress) (transfer full): List of #GInetAddress.
* Since: 2.60
*/
GList * ( *lookup_by_name_with_flags_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
/**
* GResolverClass::lookup_by_name_with_flags:
* @resolver: a #GResolver
* @hostname: the hostname to resolve
* @flags: extra #GResolverNameLookupFlags to modify the lookup
* @cancellable: (nullable): a #GCancellable
* @error: (nullable): a pointer to a %NULL #GError
*
* This is identical to GResolverClass::lookup_by_name except it takes
* @flags which modifies the behavior of the lookup. See #GResolverNameLookupFlags
* for more details.
*
* Returns: (element-type GInetAddress) (transfer full): List of #GInetAddress.
* Since: 2.60
*/
GList * ( *lookup_by_name_with_flags) (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GError **error);
};
GLIB_AVAILABLE_IN_ALL
GType g_resolver_get_type (void) G_GNUC_CONST;
GType g_resolver_get_type (void) G_GNUC_CONST;
GLIB_AVAILABLE_IN_ALL
GResolver *g_resolver_get_default (void);
GResolver *g_resolver_get_default (void);
GLIB_AVAILABLE_IN_ALL
void g_resolver_set_default (GResolver *resolver);
void g_resolver_set_default (GResolver *resolver);
GLIB_AVAILABLE_IN_ALL
GList *g_resolver_lookup_by_name (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GError **error);
GList *g_resolver_lookup_by_name (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_ALL
void g_resolver_lookup_by_name_async (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void g_resolver_lookup_by_name_async (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_ALL
GList *g_resolver_lookup_by_name_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList *g_resolver_lookup_by_name_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_2_60
void g_resolver_lookup_by_name_with_flags_async (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_2_60
GList *g_resolver_lookup_by_name_with_flags_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_2_60
GList *g_resolver_lookup_by_name_with_flags (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_ALL
void g_resolver_free_addresses (GList *addresses);
void g_resolver_free_addresses (GList *addresses);
GLIB_AVAILABLE_IN_ALL
gchar *g_resolver_lookup_by_address (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GError **error);
gchar *g_resolver_lookup_by_address (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_ALL
void g_resolver_lookup_by_address_async (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void g_resolver_lookup_by_address_async (GResolver *resolver,
GInetAddress *address,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_ALL
gchar *g_resolver_lookup_by_address_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
gchar *g_resolver_lookup_by_address_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_ALL
GList *g_resolver_lookup_service (GResolver *resolver,
const gchar *service,
const gchar *protocol,
const gchar *domain,
GCancellable *cancellable,
GError **error);
GList *g_resolver_lookup_service (GResolver *resolver,
const gchar *service,
const gchar *protocol,
const gchar *domain,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_ALL
void g_resolver_lookup_service_async (GResolver *resolver,
const gchar *service,
const gchar *protocol,
const gchar *domain,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void g_resolver_lookup_service_async (GResolver *resolver,
const gchar *service,
const gchar *protocol,
const gchar *domain,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_ALL
GList *g_resolver_lookup_service_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList *g_resolver_lookup_service_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_2_34
GList *g_resolver_lookup_records (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
GList *g_resolver_lookup_records (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_2_34
void g_resolver_lookup_records_async (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
void g_resolver_lookup_records_async (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_2_34
GList *g_resolver_lookup_records_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList *g_resolver_lookup_records_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_ALL
void g_resolver_free_targets (GList *targets);
void g_resolver_free_targets (GList *targets);
/**
* G_RESOLVER_ERROR:

View File

@ -1,5 +1,5 @@
/* GIO - GLib Input, Output and Streaming Library
*
*
* Copyright (C) 2008 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
@ -120,6 +120,8 @@ g_socket_address_enumerator_real_next_async (GSocketAddressEnumerator *enumerato
* Asynchronously retrieves the next #GSocketAddress from @enumerator
* and then calls @callback, which must call
* g_socket_address_enumerator_next_finish() to get the result.
*
* It is an error to call this multiple times before the previous callback has finished.
*/
void
g_socket_address_enumerator_next_async (GSocketAddressEnumerator *enumerator,

View File

@ -2,6 +2,7 @@
*
* Copyright © 2008, 2009 codethink
* Copyright © 2009 Red Hat, Inc
* Copyright © 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -49,6 +50,10 @@
#include <gio/ginetaddress.h>
#include "glibintl.h"
/* As recommended by RFC 8305 this is the time it waits
* on a connection before starting another concurrent attempt.
*/
#define HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS 250
/**
* SECTION:gsocketclient
@ -1328,28 +1333,82 @@ typedef struct
GSocketConnectable *connectable;
GSocketAddressEnumerator *enumerator;
GProxyAddress *proxy_addr;
GSocketAddress *current_addr;
GSocket *current_socket;
GSocket *socket;
GIOStream *connection;
GSList *connection_attempts;
GError *last_error;
} GSocketClientAsyncConnectData;
static void connection_attempt_unref (gpointer attempt);
static void
g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
{
g_clear_object (&data->connectable);
g_clear_object (&data->enumerator);
g_clear_object (&data->proxy_addr);
g_clear_object (&data->current_addr);
g_clear_object (&data->current_socket);
g_clear_object (&data->socket);
g_clear_object (&data->connection);
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
g_clear_error (&data->last_error);
g_slice_free (GSocketClientAsyncConnectData, data);
}
typedef struct
{
GSocketAddress *address;
GSocket *socket;
GIOStream *connection;
GSocketClientAsyncConnectData *data; /* unowned */
GSource *timeout_source;
GCancellable *cancellable;
grefcount ref;
} ConnectionAttempt;
static ConnectionAttempt *
connection_attempt_new (void)
{
ConnectionAttempt *attempt = g_new0 (ConnectionAttempt, 1);
g_ref_count_init (&attempt->ref);
return attempt;
}
static ConnectionAttempt *
connection_attempt_ref (ConnectionAttempt *attempt)
{
g_ref_count_inc (&attempt->ref);
return attempt;
}
static void
connection_attempt_unref (gpointer pointer)
{
ConnectionAttempt *attempt = pointer;
if (g_ref_count_dec (&attempt->ref))
{
g_clear_object (&attempt->address);
g_clear_object (&attempt->socket);
g_clear_object (&attempt->connection);
g_clear_object (&attempt->cancellable);
if (attempt->timeout_source)
{
g_source_destroy (attempt->timeout_source);
g_source_unref (attempt->timeout_source);
}
g_free (attempt);
}
}
static void
connection_attempt_remove (ConnectionAttempt *attempt)
{
attempt->data->connection_attempts = g_slist_remove (attempt->data->connection_attempts, attempt);
connection_attempt_unref (attempt);
}
static void
g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
{
@ -1359,8 +1418,7 @@ g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
{
GSocketConnection *wrapper_connection;
wrapper_connection = g_tcp_wrapper_connection_new (data->connection,
data->current_socket);
wrapper_connection = g_tcp_wrapper_connection_new (data->connection, data->socket);
g_object_unref (data->connection);
data->connection = (GIOStream *)wrapper_connection;
}
@ -1389,8 +1447,7 @@ static void
enumerator_next_async (GSocketClientAsyncConnectData *data)
{
/* We need to cleanup the state */
g_clear_object (&data->current_socket);
g_clear_object (&data->current_addr);
g_clear_object (&data->socket);
g_clear_object (&data->proxy_addr);
g_clear_object (&data->connection);
@ -1485,34 +1542,68 @@ g_socket_client_connected_callback (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSocketClientAsyncConnectData *data = user_data;
ConnectionAttempt *attempt = user_data;
GSocketClientAsyncConnectData *data = attempt->data;
GSList *l;
GError *error = NULL;
GProxy *proxy;
const gchar *protocol;
if (g_task_return_error_if_cancelled (data->task))
/* data is NULL once the task is completed */
if (data && g_task_return_error_if_cancelled (data->task))
{
g_object_unref (data->task);
connection_attempt_unref (attempt);
return;
}
if (attempt->timeout_source)
{
g_source_destroy (attempt->timeout_source);
g_clear_pointer (&attempt->timeout_source, g_source_unref);
}
if (!g_socket_connection_connect_finish (G_SOCKET_CONNECTION (source),
result, &error))
{
clarify_connect_error (error, data->connectable,
data->current_addr);
set_last_error (data, error);
if (!g_cancellable_is_cancelled (attempt->cancellable))
{
clarify_connect_error (error, data->connectable, attempt->address);
set_last_error (data, error);
}
else
g_clear_error (&error);
if (data)
{
connection_attempt_remove (attempt);
enumerator_next_async (data);
}
else
connection_attempt_unref (attempt);
/* try next one */
enumerator_next_async (data);
return;
}
data->socket = g_steal_pointer (&attempt->socket);
data->connection = g_steal_pointer (&attempt->connection);
for (l = data->connection_attempts; l; l = g_slist_next (l))
{
ConnectionAttempt *attempt_entry = l->data;
g_cancellable_cancel (attempt_entry->cancellable);
attempt_entry->data = NULL;
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 */
g_socket_set_blocking (data->current_socket, TRUE);
g_socket_set_blocking (data->socket, TRUE);
if (!data->proxy_addr)
{
@ -1565,6 +1656,26 @@ g_socket_client_connected_callback (GObject *source,
}
}
static gboolean
on_connection_attempt_timeout (gpointer data)
{
ConnectionAttempt *attempt = data;
enumerator_next_async (attempt->data);
g_clear_pointer (&attempt->timeout_source, g_source_unref);
return G_SOURCE_REMOVE;
}
static void
on_connection_cancelled (GCancellable *cancellable,
gpointer data)
{
GCancellable *attempt_cancellable = data;
g_cancellable_cancel (attempt_cancellable);
}
static void
g_socket_client_enumerator_callback (GObject *object,
GAsyncResult *result,
@ -1573,6 +1684,7 @@ g_socket_client_enumerator_callback (GObject *object,
GSocketClientAsyncConnectData *data = user_data;
GSocketAddress *address = NULL;
GSocket *socket;
ConnectionAttempt *attempt;
GError *error = NULL;
if (g_task_return_error_if_cancelled (data->task))
@ -1585,6 +1697,9 @@ g_socket_client_enumerator_callback (GObject *object,
result, &error);
if (address == NULL)
{
if (data->connection_attempts)
return;
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
if (!error)
{
@ -1621,16 +1736,27 @@ g_socket_client_enumerator_callback (GObject *object,
return;
}
data->current_socket = socket;
data->current_addr = address;
data->connection = (GIOStream *) g_socket_connection_factory_create_connection (socket);
attempt = connection_attempt_new ();
attempt->data = data;
attempt->socket = socket;
attempt->address = address;
attempt->cancellable = g_cancellable_new ();
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
g_source_attach (attempt->timeout_source, g_main_context_get_thread_default ());
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, address);
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, data->connection);
g_socket_connection_connect_async (G_SOCKET_CONNECTION (data->connection),
if (g_task_get_cancellable (data->task))
g_cancellable_connect (g_task_get_cancellable (data->task), G_CALLBACK (on_connection_cancelled),
g_object_ref (attempt->cancellable), g_object_unref);
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
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),
address,
g_task_get_cancellable (data->task),
g_socket_client_connected_callback, data);
attempt->cancellable,
g_socket_client_connected_callback, connection_attempt_ref (attempt));
}
/**

View File

@ -3,6 +3,7 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -63,7 +64,27 @@ g_resolver_error_from_addrinfo_error (gint err)
}
}
static struct addrinfo addrinfo_hints;
typedef struct {
char *hostname;
int address_family;
} LookupData;
static LookupData *
lookup_data_new (const char *hostname,
int address_family)
{
LookupData *data = g_new (LookupData, 1);
data->hostname = g_strdup (hostname);
data->address_family = address_family;
return data;
}
static void
lookup_data_free (LookupData *data)
{
g_free (data->hostname);
g_free (data);
}
static void
do_lookup_by_name (GTask *task,
@ -71,11 +92,24 @@ do_lookup_by_name (GTask *task,
gpointer task_data,
GCancellable *cancellable)
{
const char *hostname = task_data;
LookupData *lookup_data = task_data;
const char *hostname = lookup_data->hostname;
struct addrinfo *res = NULL;
GList *addresses;
gint retval;
struct addrinfo addrinfo_hints = { 0 };
#ifdef AI_ADDRCONFIG
addrinfo_hints.ai_flags = AI_ADDRCONFIG;
#endif
/* socktype and protocol don't actually matter, they just get copied into the
* returned addrinfo structures (and then we ignore them). But if
* we leave them unset, we'll get back duplicate answers.
*/
addrinfo_hints.ai_socktype = SOCK_STREAM;
addrinfo_hints.ai_protocol = IPPROTO_TCP;
addrinfo_hints.ai_family = lookup_data->address_family;
retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res);
if (retval == 0)
@ -139,10 +173,53 @@ lookup_by_name (GResolver *resolver,
{
GTask *task;
GList *addresses;
LookupData *data;
data = lookup_data_new (hostname, AF_UNSPEC);
task = g_task_new (resolver, cancellable, NULL, NULL);
g_task_set_source_tag (task, lookup_by_name);
g_task_set_task_data (task, g_strdup (hostname), g_free);
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread_sync (task, do_lookup_by_name);
addresses = g_task_propagate_pointer (task, error);
g_object_unref (task);
return addresses;
}
static int
flags_to_family (GResolverNameLookupFlags flags)
{
int address_family = AF_UNSPEC;
if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
address_family = AF_INET;
if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
{
address_family = AF_INET6;
/* You can only filter by one family at a time */
g_return_val_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY), address_family);
}
return address_family;
}
static GList *
lookup_by_name_with_flags (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GError **error)
{
GTask *task;
GList *addresses;
LookupData *data;
data = lookup_data_new (hostname, AF_UNSPEC);
task = g_task_new (resolver, cancellable, NULL, NULL);
g_task_set_source_tag (task, lookup_by_name_with_flags);
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread_sync (task, do_lookup_by_name);
addresses = g_task_propagate_pointer (task, error);
@ -151,6 +228,26 @@ lookup_by_name (GResolver *resolver,
return addresses;
}
static void
lookup_by_name_with_flags_async (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
LookupData *data;
data = lookup_data_new (hostname, flags_to_family (flags));
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, lookup_by_name_with_flags_async);
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread (task, do_lookup_by_name);
g_object_unref (task);
}
static void
lookup_by_name_async (GResolver *resolver,
const gchar *hostname,
@ -158,14 +255,12 @@ lookup_by_name_async (GResolver *resolver,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, lookup_by_name_async);
g_task_set_task_data (task, g_strdup (hostname), g_free);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread (task, do_lookup_by_name);
g_object_unref (task);
lookup_by_name_with_flags_async (resolver,
hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
cancellable,
callback,
user_data);
}
static GList *
@ -178,6 +273,15 @@ lookup_by_name_finish (GResolver *resolver,
return g_task_propagate_pointer (G_TASK (result), error);
}
static GList *
lookup_by_name_with_flags_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
do_lookup_by_address (GTask *task,
@ -970,24 +1074,16 @@ g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
{
GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class);
resolver_class->lookup_by_name = lookup_by_name;
resolver_class->lookup_by_name_async = lookup_by_name_async;
resolver_class->lookup_by_name_finish = lookup_by_name_finish;
resolver_class->lookup_by_address = lookup_by_address;
resolver_class->lookup_by_address_async = lookup_by_address_async;
resolver_class->lookup_by_address_finish = lookup_by_address_finish;
resolver_class->lookup_records = lookup_records;
resolver_class->lookup_records_async = lookup_records_async;
resolver_class->lookup_records_finish = lookup_records_finish;
/* Initialize addrinfo_hints */
#ifdef AI_ADDRCONFIG
addrinfo_hints.ai_flags |= AI_ADDRCONFIG;
#endif
/* These two don't actually matter, they just get copied into the
* returned addrinfo structures (and then we ignore them). But if
* we leave them unset, we'll get back duplicate answers.
*/
addrinfo_hints.ai_socktype = SOCK_STREAM;
addrinfo_hints.ai_protocol = IPPROTO_TCP;
resolver_class->lookup_by_name = lookup_by_name;
resolver_class->lookup_by_name_async = lookup_by_name_async;
resolver_class->lookup_by_name_finish = lookup_by_name_finish;
resolver_class->lookup_by_name_with_flags = lookup_by_name_with_flags;
resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async;
resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish;
resolver_class->lookup_by_address = lookup_by_address;
resolver_class->lookup_by_address_async = lookup_by_address_async;
resolver_class->lookup_by_address_finish = lookup_by_address_finish;
resolver_class->lookup_records = lookup_records;
resolver_class->lookup_records_async = lookup_records_async;
resolver_class->lookup_records_finish = lookup_records_finish;
}

View File

@ -50,7 +50,6 @@ test_programs = \
memory-output-stream \
monitor \
mount-operation \
network-address \
network-monitor \
network-monitor-race \
permission \
@ -195,6 +194,12 @@ schema_tests = \
wrong-category.gschema.xml \
$(NULL)
test_programs += network-address
network_address_SOURCES = \
network-address.c \
mock-resolver.c \
mock-resolver.h
test_programs += thumbnail-verification
dist_test_data += $(thumbnail_data_files)
thumbnail_data_files = $(addprefix thumbnails/,$(thumbnail_tests))

View File

@ -0,0 +1,74 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <gio/gio.h>
static void
on_connected (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GSocketConnection *conn;
GError *error = NULL;
conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
g_assert_no_error (error);
g_object_unref (conn);
g_main_loop_quit (user_data);
}
static void
test_happy_eyeballs (void)
{
GSocketClient *client;
GSocketService *service;
GError *error = NULL;
guint16 port;
GMainLoop *loop;
loop = g_main_loop_new (NULL, FALSE);
service = g_socket_service_new ();
port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
g_assert_no_error (error);
g_socket_service_start (service);
/* All of the magic here actually happens in slow-connect-preload.c
* which as you would guess is preloaded. So this is just making a
* normal connection that happens to take 600ms each time. This will
* trigger the logic to make multiple parallel connections.
*/
client = g_socket_client_new ();
g_socket_client_connect_to_host_async (client, "localhost", port, NULL, on_connected, loop);
g_main_loop_run (loop);
g_main_loop_unref (loop);
g_object_unref (service);
g_object_unref (client);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/socket-client/happy-eyeballs", test_happy_eyeballs);
return g_test_run ();
}

View File

@ -55,7 +55,7 @@ gio_tests = {
'memory-output-stream' : {},
'monitor' : {},
'mount-operation' : {},
'network-address' : {},
'network-address' : {'extra_sources': ['mock-resolver.c']},
'network-monitor' : {},
'network-monitor-race' : {},
'permission' : {},
@ -134,6 +134,18 @@ if host_machine.system() != 'windows'
'unix-mounts' : {},
'unix-streams' : {},
'g-file-info-filesystem-readonly' : {},
'gsocketclient-slow' : {
'depends' : [
shared_library('slow-connect-preload',
'slow-connect-preload.c',
name_prefix : '',
dependencies: cc.find_library('dl'),
)
],
'env' : {
'LD_PRELOAD': '@0@/slow-connect-preload.so'.format(meson.current_build_dir())
}
},
'gschema-compile' : {'install' : false},
'trash' : {},
}
@ -528,12 +540,19 @@ foreach test_name, extra_args : gio_tests
suite = ['gio'] + extra_args.get('suite', [])
timeout = suite.contains('slow') ? test_timeout_slow : test_timeout
local_test_env = test_env
foreach var, value : extra_args.get('env', {})
local_test_env.append(var, value)
endforeach
test(test_name, exe,
env : test_env,
env : local_test_env,
timeout : timeout,
suite : suite,
args : ['--tap'],
is_parallel : extra_args.get('is_parallel', true),
depends : extra_args.get('depends', []),
)
endforeach

169
gio/tests/mock-resolver.c Normal file
View File

@ -0,0 +1,169 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "mock-resolver.h"
struct _MockResolver
{
GResolver parent_instance;
guint ipv4_delay_ms;
guint ipv6_delay_ms;
GList *ipv4_results;
GList *ipv6_results;
GError *ipv4_error;
GError *ipv6_error;
};
G_DEFINE_TYPE (MockResolver, mock_resolver, G_TYPE_RESOLVER)
MockResolver *
mock_resolver_new (void)
{
return g_object_new (MOCK_TYPE_RESOLVER, NULL);
}
void
mock_resolver_set_ipv4_delay_ms (MockResolver *self, guint delay_ms)
{
self->ipv4_delay_ms = delay_ms;
}
static gpointer
copy_object (gconstpointer obj, gpointer user_data)
{
return g_object_ref (G_OBJECT (obj));
}
void
mock_resolver_set_ipv4_results (MockResolver *self, GList *results)
{
if (self->ipv4_results)
g_list_free_full (self->ipv4_results, g_object_unref);
self->ipv4_results = g_list_copy_deep (results, copy_object, NULL);
}
void
mock_resolver_set_ipv4_error (MockResolver *self, GError *error)
{
g_clear_error (&self->ipv4_error);
if (error)
self->ipv4_error = g_error_copy (error);
}
void
mock_resolver_set_ipv6_delay_ms (MockResolver *self, guint delay_ms)
{
self->ipv6_delay_ms = delay_ms;
}
void
mock_resolver_set_ipv6_results (MockResolver *self, GList *results)
{
if (self->ipv6_results)
g_list_free_full (self->ipv6_results, g_object_unref);
self->ipv6_results = g_list_copy_deep (results, copy_object, NULL);
}
void
mock_resolver_set_ipv6_error (MockResolver *self, GError *error)
{
g_clear_error (&self->ipv6_error);
if (error)
self->ipv6_error = g_error_copy (error);
}
static void
do_lookup_by_name (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
MockResolver *self = source_object;
GResolverNameLookupFlags flags = GPOINTER_TO_UINT(task_data);
if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
{
g_usleep (self->ipv4_delay_ms * 1000);
if (self->ipv4_error)
g_task_return_error (task, g_error_copy (self->ipv4_error));
else
g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL);
}
else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
{
g_usleep (self->ipv6_delay_ms * 1000);
if (self->ipv6_error)
g_task_return_error (task, g_error_copy (self->ipv6_error));
else
g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL);
}
else
g_assert_not_reached ();
}
static void
lookup_by_name_with_flags_async (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_task_data (task, GUINT_TO_POINTER(flags), NULL);
g_task_run_in_thread (task, do_lookup_by_name);
g_object_unref (task);
}
static GList *
lookup_by_name_with_flags_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
mock_resolver_finalize (GObject *object)
{
MockResolver *self = (MockResolver*)object;
g_clear_error (&self->ipv4_error);
g_clear_error (&self->ipv6_error);
if (self->ipv6_results)
g_list_free_full (self->ipv6_results, g_object_unref);
if (self->ipv4_results)
g_list_free_full (self->ipv4_results, g_object_unref);
G_OBJECT_CLASS (mock_resolver_parent_class)->finalize (object);
}
static void
mock_resolver_class_init (MockResolverClass *klass)
{
GResolverClass *resolver_class = G_RESOLVER_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async;
resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish;
object_class->finalize = mock_resolver_finalize;
}
static void
mock_resolver_init (MockResolver *self)
{
}

35
gio/tests/mock-resolver.h Normal file
View File

@ -0,0 +1,35 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gio/gio.h>
G_BEGIN_DECLS
#define MOCK_TYPE_RESOLVER (mock_resolver_get_type())
G_DECLARE_FINAL_TYPE (MockResolver, mock_resolver, MOCK, RESOLVER, GResolver)
MockResolver *mock_resolver_new (void);
void mock_resolver_set_ipv4_delay_ms (MockResolver *self, guint delay_ms);
void mock_resolver_set_ipv4_results (MockResolver *self, GList *results);
void mock_resolver_set_ipv4_error (MockResolver *self, GError *error);
void mock_resolver_set_ipv6_delay_ms (MockResolver *self, guint delay_ms);
void mock_resolver_set_ipv6_results (MockResolver *self, GList *results);
void mock_resolver_set_ipv6_error (MockResolver *self, GError *error);
G_END_DECLS

View File

@ -1,4 +1,5 @@
#include "config.h"
#include "mock-resolver.h"
#include <gio/gio.h>
#include <gio/gnetworking.h>
@ -416,6 +417,8 @@ test_loopback_sync (void)
typedef struct {
GList/*<owned GSocketAddress> */ *addrs; /* owned */
GMainLoop *loop; /* owned */
guint delay_ms;
gint expected_error_code;
} AsyncData;
static void
@ -430,7 +433,14 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
data = user_data;
a = g_socket_address_enumerator_next_finish (enumerator, result, &error);
g_assert_no_error (error);
if (data->expected_error_code)
{
g_assert_error (error, G_IO_ERROR, data->expected_error_code);
g_clear_error (&error);
}
else
g_assert_no_error (error);
if (a == NULL)
{
@ -443,6 +453,9 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
g_assert (G_IS_INET_SOCKET_ADDRESS (a));
data->addrs = g_list_prepend (data->addrs, a);
if (data->delay_ms)
g_usleep (data->delay_ms * 1000);
g_socket_address_enumerator_next_async (enumerator, NULL,
got_addr, user_data);
}
@ -515,6 +528,336 @@ test_to_string (void)
g_object_unref (addr);
}
static int
sort_addresses (gconstpointer a, gconstpointer b)
{
GSocketFamily family_a = g_inet_address_get_family (G_INET_ADDRESS (a));
GSocketFamily family_b = g_inet_address_get_family (G_INET_ADDRESS (b));
if (family_a == family_b)
return 0;
else if (family_a == G_SOCKET_FAMILY_IPV4)
return -1;
else
return 1;
}
static int
sort_socket_addresses (gconstpointer a, gconstpointer b)
{
GInetAddress *addr_a = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (a));
GInetAddress *addr_b = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (b));
return sort_addresses (addr_a, addr_b);
}
static void
assert_list_matches_expected (GList *result, GList *expected)
{
g_assert_cmpint (g_list_length (result), ==, g_list_length (expected));
/* Sort by ipv4 first which matches the expected list */
result = g_list_sort (result, sort_socket_addresses);
for (; result != NULL; result = result->next, expected = expected->next)
{
GInetAddress *address = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (result->data));
g_assert_true (g_inet_address_equal (address, expected->data));
}
}
typedef struct {
MockResolver *mock_resolver;
GResolver *original_resolver;
GList *input_ipv4_results;
GList *input_ipv6_results;
GList *input_all_results;
GSocketConnectable *addr;
GSocketAddressEnumerator *enumerator;
GMainLoop *loop;
} HappyEyeballsFixture;
static void
happy_eyeballs_setup (HappyEyeballsFixture *fixture,
gconstpointer data)
{
static const char * const ipv4_address_strings[] = { "1.1.1.1", "2.2.2.2" };
static const char * const ipv6_address_strings[] = { "ff::11", "ff::22" };
gsize i;
fixture->original_resolver = g_resolver_get_default ();
fixture->mock_resolver = mock_resolver_new ();
g_resolver_set_default (G_RESOLVER (fixture->mock_resolver));
for (i = 0; i < G_N_ELEMENTS (ipv4_address_strings); ++i)
{
GInetAddress *ipv4_addr = g_inet_address_new_from_string (ipv4_address_strings[i]);
GInetAddress *ipv6_addr = g_inet_address_new_from_string (ipv6_address_strings[i]);
fixture->input_ipv4_results = g_list_append (fixture->input_ipv4_results, ipv4_addr);
fixture->input_ipv6_results = g_list_append (fixture->input_ipv6_results, ipv6_addr);
fixture->input_all_results = g_list_append (fixture->input_all_results, ipv4_addr);
fixture->input_all_results = g_list_append (fixture->input_all_results, ipv6_addr);
}
fixture->input_all_results = g_list_sort (fixture->input_all_results, sort_addresses);
mock_resolver_set_ipv4_results (fixture->mock_resolver, fixture->input_ipv4_results);
mock_resolver_set_ipv6_results (fixture->mock_resolver, fixture->input_ipv6_results);
fixture->addr = g_network_address_new ("test.fake", 80);
fixture->enumerator = g_socket_connectable_enumerate (fixture->addr);
fixture->loop = g_main_loop_new (NULL, FALSE);
}
static void
happy_eyeballs_teardown (HappyEyeballsFixture *fixture,
gconstpointer data)
{
g_object_unref (fixture->addr);
g_object_unref (fixture->enumerator);
g_resolver_free_addresses (fixture->input_all_results);
g_list_free (fixture->input_ipv4_results);
g_list_free (fixture->input_ipv6_results);
g_resolver_set_default (fixture->original_resolver);
g_object_unref (fixture->original_resolver);
g_object_unref (fixture->mock_resolver);
g_main_loop_unref (fixture->loop);
}
static void
test_happy_eyeballs_basic (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
data.delay_ms = 10;
data.loop = fixture->loop;
/* This just tests in the common case it gets all results */
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_all_results);
}
static void
test_happy_eyeballs_slow_ipv4 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
/* If ipv4 dns response is a bit slow we just don't get them */
data.loop = fixture->loop;
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 25);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_ipv6_results);
}
static void
test_happy_eyeballs_slow_ipv6 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
/* If ipv6 is a bit slow it waits for them */
data.loop = fixture->loop;
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 25);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_all_results);
}
static void
test_happy_eyeballs_very_slow_ipv6 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
/* If ipv6 is very slow we don't get them */
data.loop = fixture->loop;
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 200);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_ipv4_results);
}
static void
test_happy_eyeballs_slow_connection_and_ipv4 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
/* Even if the dns response is slow we still get them if our connection attempts
* take long enough. */
data.loop = fixture->loop;
data.delay_ms = 500;
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 200);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_all_results);
}
static void
test_happy_eyeballs_ipv6_error (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv6_error;
/* If ipv6 fails we still get ipv4. */
data.loop = fixture->loop;
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_ipv4_results);
g_error_free (ipv6_error);
}
static void
test_happy_eyeballs_ipv4_error (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv4_error;
/* If ipv4 fails we still get ipv6. */
data.loop = fixture->loop;
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
assert_list_matches_expected (data.addrs, fixture->input_ipv6_results);
g_error_free (ipv4_error);
}
static void
test_happy_eyeballs_both_error (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv4_error, *ipv6_error;
/* If both fail we get an error. */
data.loop = fixture->loop;
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
g_assert_null (data.addrs);
g_error_free (ipv4_error);
g_error_free (ipv6_error);
}
static void
test_happy_eyeballs_both_error_delays_1 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv4_error, *ipv6_error;
/* The same with some different timings */
data.loop = fixture->loop;
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 25);
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
g_assert_null (data.addrs);
g_error_free (ipv4_error);
g_error_free (ipv6_error);
}
static void
test_happy_eyeballs_both_error_delays_2 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv4_error, *ipv6_error;
/* The same with some different timings */
data.loop = fixture->loop;
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 25);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
g_assert_null (data.addrs);
g_error_free (ipv4_error);
g_error_free (ipv6_error);
}
static void
test_happy_eyeballs_both_error_delays_3 (HappyEyeballsFixture *fixture,
gconstpointer user_data)
{
AsyncData data = { 0 };
GError *ipv4_error, *ipv6_error;
/* The same with some different timings */
data.loop = fixture->loop;
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 200);
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
g_main_loop_run (fixture->loop);
g_assert_null (data.addrs);
g_error_free (ipv4_error);
g_error_free (ipv6_error);
}
int
main (int argc, char *argv[])
{
@ -560,5 +903,28 @@ main (int argc, char *argv[])
g_test_add_func ("/network-address/loopback/async", test_loopback_async);
g_test_add_func ("/network-address/to-string", test_to_string);
g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_basic, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/slow-ipv4", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_slow_ipv4, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/slow-ipv6", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_slow_ipv6, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/very-slow-ipv6", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_very_slow_ipv6, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/slow-connection-and-ipv4", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_slow_connection_and_ipv4, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/ipv6-error", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_ipv6_error, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/ipv4-error", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_ipv4_error, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/both-error", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_both_error, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/both-error-delays-1", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_1, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/both-error-delays-2", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_2, happy_eyeballs_teardown);
g_test_add ("/network-address/happy-eyeballs/both-error-delays-3", HappyEyeballsFixture, NULL,
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_3, happy_eyeballs_teardown);
return g_test_run ();
}

View File

@ -772,6 +772,22 @@ g_fake_resolver_lookup_by_name_async (GResolver *resolver,
g_object_unref (task);
}
static void
g_fake_resolver_lookup_by_name_with_flags_async (GResolver *resolver,
const gchar *hostname,
GResolverNameLookupFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* Note this isn't a real implementation as it ignores the flags */
g_fake_resolver_lookup_by_name_async (resolver,
hostname,
cancellable,
callback,
user_data);
}
static GList *
g_fake_resolver_lookup_by_name_finish (GResolver *resolver,
GAsyncResult *result,
@ -785,9 +801,11 @@ g_fake_resolver_class_init (GFakeResolverClass *fake_class)
{
GResolverClass *resolver_class = G_RESOLVER_CLASS (fake_class);
resolver_class->lookup_by_name = g_fake_resolver_lookup_by_name;
resolver_class->lookup_by_name_async = g_fake_resolver_lookup_by_name_async;
resolver_class->lookup_by_name_finish = g_fake_resolver_lookup_by_name_finish;
resolver_class->lookup_by_name = g_fake_resolver_lookup_by_name;
resolver_class->lookup_by_name_async = g_fake_resolver_lookup_by_name_async;
resolver_class->lookup_by_name_finish = g_fake_resolver_lookup_by_name_finish;
resolver_class->lookup_by_name_with_flags_async = g_fake_resolver_lookup_by_name_with_flags_async;
resolver_class->lookup_by_name_with_flags_finish = g_fake_resolver_lookup_by_name_finish;
}

View File

@ -0,0 +1,43 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <dlfcn.h>
/* This is used in gsocketclient-slow.c used to test
* and get coverage on how GSocketClient reacts to
* slow connections.
*/
int
connect (int sockfd,
const struct sockaddr *addr,
socklen_t addrlen)
{
static int (*real_connect)(int, const struct sockaddr *, socklen_t);
if (real_connect == NULL)
real_connect = dlsym (RTLD_NEXT, "connect");
/* This is long enough for multiple connection attempts to be done
* in parallel given that their timeout is 250ms */
usleep (600 * 1000);
return real_connect (sockfd, addr, addrlen);
}