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;
}