gnetworkaddress: Implement parallel ipv4 and ipv6 dns lookups

As RFC 8305 recommends we can start multiple DNS queries in parallel
to more quickly make an initial response, especially when one is
particularly slow/broken.
This commit is contained in:
Patrick Griffis 2018-10-22 13:36:27 -04:00 committed by Patrick Griffis
parent d6afa6c988
commit 35e41862c2
8 changed files with 781 additions and 55 deletions

View File

@ -3,6 +3,7 @@
/* GIO - GLib Input, Output and Streaming Library /* GIO - GLib Input, Output and Streaming Library
* *
* Copyright (C) 2008 Red Hat, Inc. * Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2018 Igalia S.L.
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * 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 static void
g_network_address_set_addresses (GNetworkAddress *addr, g_network_address_add_addresses (GNetworkAddress *addr,
GList *addresses, GList *addresses,
guint64 resolver_serial) guint64 resolver_serial)
{ {
GList *a; GList *a;
GSocketAddress *sockaddr; GSocketAddress *sockaddr;
g_return_if_fail (addresses != NULL && addr->priv->sockaddrs == NULL);
for (a = addresses; a; a = a->next) for (a = addresses; a; a = a->next)
{ {
sockaddr = g_inet_socket_address_new (a->data, addr->priv->port); 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_object_unref (a->data);
} }
g_list_free (addresses); g_list_free (addresses);
addr->priv->sockaddrs = g_list_reverse (addr->priv->sockaddrs);
addr->priv->resolver_serial = resolver_serial; addr->priv->resolver_serial = resolver_serial;
} }
@ -246,7 +252,7 @@ g_network_address_parse_sockaddr (GNetworkAddress *addr)
addr->priv->port); addr->priv->port);
if (sockaddr) if (sockaddr)
{ {
addr->priv->sockaddrs = g_list_prepend (addr->priv->sockaddrs, sockaddr); addr->priv->sockaddrs = g_list_append (addr->priv->sockaddrs, sockaddr);
return TRUE; return TRUE;
} }
else 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_INET6));
addrs = g_list_append (addrs, g_inet_address_new_loopback (AF_INET)); 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); return G_SOCKET_CONNECTABLE (addr);
} }
@ -876,9 +882,13 @@ g_network_address_get_scheme (GNetworkAddress *addr)
typedef struct { typedef struct {
GSocketAddressEnumerator parent_instance; GSocketAddressEnumerator parent_instance;
GNetworkAddress *addr; GNetworkAddress *addr; /* (owned) */
GList *addresses; GList *addresses; /* (owned) (nullable) */
GList *next; 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; } GNetworkAddressAddressEnumerator;
typedef struct { typedef struct {
@ -895,7 +905,16 @@ g_network_address_address_enumerator_finalize (GObject *object)
GNetworkAddressAddressEnumerator *addr_enum = GNetworkAddressAddressEnumerator *addr_enum =
G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (object); 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_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); 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; 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->addresses = addr->priv->sockaddrs;
addr_enum->next = addr_enum->addresses; addr_enum->current_item = addr_enum->addresses;
g_object_unref (resolver); g_object_unref (resolver);
} }
if (addr_enum->next == NULL) if (addr_enum->current_item == NULL)
return NULL; return NULL;
sockaddr = addr_enum->next->data; sockaddr = addr_enum->current_item->data;
addr_enum->next = addr_enum->next->next; addr_enum->current_item = g_list_next (addr_enum->current_item);
return g_object_ref (sockaddr); return g_object_ref (sockaddr);
} }
static void static void
have_addresses (GNetworkAddressAddressEnumerator *addr_enum, complete_queued_task (GNetworkAddressAddressEnumerator *addr_enum,
GTask *task, GError *error) GTask *task,
GError *error)
{ {
GSocketAddress *sockaddr; GSocketAddress *sockaddr;
addr_enum->addresses = addr_enum->addr->priv->sockaddrs; 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); sockaddr = g_object_ref (addr_enum->current_item->data);
addr_enum->next = addr_enum->next->next;
} }
else else
sockaddr = NULL; sockaddr = NULL;
@ -978,28 +997,125 @@ have_addresses (GNetworkAddressAddressEnumerator *addr_enum,
g_object_unref (task); g_object_unref (task);
} }
static int
on_address_timeout (gpointer user_data)
{
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 static void
got_addresses (GObject *source_object, got_ipv6_addresses (GObject *source_object,
GAsyncResult *result, GAsyncResult *result,
gpointer user_data) gpointer user_data)
{ {
GTask *task = user_data; GNetworkAddressAddressEnumerator *addr_enum = user_data;
GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task);
GResolver *resolver = G_RESOLVER (source_object); GResolver *resolver = G_RESOLVER (source_object);
GList *addresses; GList *addresses;
GError *error = NULL; GError *error = NULL;
if (!addr_enum->addr->priv->sockaddrs) addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
{
addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
if (!error) if (!error)
{ {
g_network_address_set_addresses (addr_enum->addr, addresses, /* 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)); g_resolver_get_serial (resolver));
} }
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);
} }
have_addresses (addr_enum, task, error);
/* 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 static void
@ -1012,6 +1128,7 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum
G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator);
GSocketAddress *sockaddr; GSocketAddress *sockaddr;
GTask *task; GTask *task;
GNetworkAddress *addr = addr_enum->addr;
task = g_task_new (addr_enum, cancellable, callback, user_data); task = g_task_new (addr_enum, cancellable, callback, user_data);
g_task_set_source_tag (task, g_network_address_address_enumerator_next_async); 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; addr->priv->sockaddrs = NULL;
} }
if (!addr->priv->sockaddrs) if (addr->priv->sockaddrs == NULL)
{ {
if (g_network_address_parse_sockaddr (addr)) if (g_network_address_parse_sockaddr (addr))
have_addresses (addr_enum, task, NULL); complete_queued_task (addr_enum, task, NULL);
else else
{ {
g_resolver_lookup_by_name_async (resolver, 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, addr->priv->hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY,
cancellable, cancellable,
got_addresses, task); 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); g_object_unref (resolver);
return; return;
} }
addr_enum->addresses = addr->priv->sockaddrs;
addr_enum->next = addr_enum->addresses;
g_object_unref (resolver); g_object_unref (resolver);
} }
if (addr_enum->next) if (addr_enum->addresses == NULL)
{ {
sockaddr = g_object_ref (addr_enum->next->data); g_assert (addr->priv->sockaddrs);
addr_enum->next = addr_enum->next->next; 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 else
sockaddr = NULL; sockaddr = NULL;
@ -1075,6 +1205,7 @@ g_network_address_address_enumerator_next_finish (GSocketAddressEnumerator *enu
static void static void
_g_network_address_address_enumerator_init (GNetworkAddressAddressEnumerator *enumerator) _g_network_address_address_enumerator_init (GNetworkAddressAddressEnumerator *enumerator)
{ {
enumerator->context = g_main_context_ref_thread_default ();
} }
static void static void

View File

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

View File

@ -55,7 +55,7 @@ gio_tests = {
'memory-output-stream' : {}, 'memory-output-stream' : {},
'monitor' : {}, 'monitor' : {},
'mount-operation' : {}, 'mount-operation' : {},
'network-address' : {}, 'network-address' : {'extra_sources': ['mock-resolver.c']},
'network-monitor' : {}, 'network-monitor' : {},
'network-monitor-race' : {}, 'network-monitor-race' : {},
'permission' : {}, 'permission' : {},

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

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

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

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

View File

@ -1,4 +1,5 @@
#include "config.h" #include "config.h"
#include "mock-resolver.h"
#include <gio/gio.h> #include <gio/gio.h>
#include <gio/gnetworking.h> #include <gio/gnetworking.h>
@ -416,6 +417,8 @@ test_loopback_sync (void)
typedef struct { typedef struct {
GList/*<owned GSocketAddress> */ *addrs; /* owned */ GList/*<owned GSocketAddress> */ *addrs; /* owned */
GMainLoop *loop; /* owned */ GMainLoop *loop; /* owned */
guint delay_ms;
gint expected_error_code;
} AsyncData; } AsyncData;
static void static void
@ -430,6 +433,13 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
data = user_data; data = user_data;
a = g_socket_address_enumerator_next_finish (enumerator, result, &error); a = g_socket_address_enumerator_next_finish (enumerator, result, &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); g_assert_no_error (error);
if (a == NULL) 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)); g_assert (G_IS_INET_SOCKET_ADDRESS (a));
data->addrs = g_list_prepend (data->addrs, 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, g_socket_address_enumerator_next_async (enumerator, NULL,
got_addr, user_data); got_addr, user_data);
} }
@ -515,6 +528,338 @@ test_to_string (void)
g_object_unref (addr); 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 int
main (int argc, char *argv[]) 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/loopback/async", test_loopback_async);
g_test_add_func ("/network-address/to-string", test_to_string); 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 (); return g_test_run ();
} }

View File

@ -772,6 +772,22 @@ g_fake_resolver_lookup_by_name_async (GResolver *resolver,
g_object_unref (task); 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 * static GList *
g_fake_resolver_lookup_by_name_finish (GResolver *resolver, g_fake_resolver_lookup_by_name_finish (GResolver *resolver,
GAsyncResult *result, GAsyncResult *result,
@ -788,6 +804,8 @@ g_fake_resolver_class_init (GFakeResolverClass *fake_class)
resolver_class->lookup_by_name = g_fake_resolver_lookup_by_name; 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_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_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;
} }