diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index eb024d27d..3c0491a7b 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -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 diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 912ea5144..1651f89ed 100644 --- a/gio/gnetworkaddress.c +++ b/gio/gnetworkaddress.c @@ -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 diff --git a/gio/gresolver.c b/gio/gresolver.c index b60544a20..6a33634c5 100644 --- a/gio/gresolver.c +++ b/gio/gresolver.c @@ -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 @@ -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); } /** diff --git a/gio/gresolver.h b/gio/gresolver.h index a39e8f7fd..dc4ba59a1 100644 --- a/gio/gresolver.h +++ b/gio/gresolver.h @@ -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: diff --git a/gio/gsocketaddressenumerator.c b/gio/gsocketaddressenumerator.c index 03b1502c6..7986f342c 100644 --- a/gio/gsocketaddressenumerator.c +++ b/gio/gsocketaddressenumerator.c @@ -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, diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index ddd149734..5c6513c3d 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -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 #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)); } /** diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c index 9e110cd0b..a6dd35c1b 100644 --- a/gio/gthreadedresolver.c +++ b/gio/gthreadedresolver.c @@ -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; } diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index ae86adf19..15ab66e93 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -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)) diff --git a/gio/tests/gsocketclient-slow.c b/gio/tests/gsocketclient-slow.c new file mode 100644 index 000000000..bc7d02772 --- /dev/null +++ b/gio/tests/gsocketclient-slow.c @@ -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 . + */ + +#include + +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 (); +} \ No newline at end of file diff --git a/gio/tests/meson.build b/gio/tests/meson.build index d5cbfe15a..091733cd1 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -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 diff --git a/gio/tests/mock-resolver.c b/gio/tests/mock-resolver.c new file mode 100644 index 000000000..271aa2cb1 --- /dev/null +++ b/gio/tests/mock-resolver.c @@ -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 . + */ + +#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) +{ +} diff --git a/gio/tests/mock-resolver.h b/gio/tests/mock-resolver.h new file mode 100644 index 000000000..54391d0c0 --- /dev/null +++ b/gio/tests/mock-resolver.h @@ -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 . + */ + +#pragma once + +#include + +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 diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 1a7f8e797..4a2718e24 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -1,4 +1,5 @@ #include "config.h" +#include "mock-resolver.h" #include #include @@ -416,6 +417,8 @@ test_loopback_sync (void) typedef struct { GList/* */ *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 (); } diff --git a/gio/tests/proxy-test.c b/gio/tests/proxy-test.c index 3855ae2f8..8f6dccf59 100644 --- a/gio/tests/proxy-test.c +++ b/gio/tests/proxy-test.c @@ -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; } diff --git a/gio/tests/slow-connect-preload.c b/gio/tests/slow-connect-preload.c new file mode 100644 index 000000000..9fdb431c6 --- /dev/null +++ b/gio/tests/slow-connect-preload.c @@ -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 . + */ + +#include +#include +#include +#include +#include + +/* 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); +}