diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 912ea5144..ff4697ab8 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,13 @@ 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 *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,7 +905,16 @@ 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); } @@ -938,35 +957,35 @@ 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; 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; - if (addr_enum->next) + if (addr_enum->current_item) { - sockaddr = g_object_ref (addr_enum->next->data); - addr_enum->next = addr_enum->next->next; + sockaddr = g_object_ref (addr_enum->current_item->data); } else sockaddr = NULL; @@ -978,28 +997,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 +1128,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,30 +1147,43 @@ 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); + 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->addresses = addr->priv->sockaddrs; + addr_enum->current_item = addr_enum->addresses; + sockaddr = g_object_ref (addr_enum->current_item->data); + } + else 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; @@ -1075,6 +1205,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/gsocketaddressenumerator.c b/gio/gsocketaddressenumerator.c index 03b1502c6..9e3942ffe 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 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/meson.build b/gio/tests/meson.build index d5cbfe15a..a72665335 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' : {}, 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..c10f84139 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,338 @@ 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); + /* Note that interleaving will not happen here because ipv6 was used before ipv4 + * responded */ +} + +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 +905,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; }