[glib/wip/tingping/happy-eyeballs: 16/20] gnetworkaddress: Implement parallel ipv4 and ipv6 dns lookups



commit 35e41862c2a70920bc3960745d5c0f1e2aad1ffa
Author: Patrick Griffis <pgriffis igalia com>
Date:   Mon Oct 22 13:36:27 2018 -0400

    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.

 gio/gnetworkaddress.c          | 223 ++++++++++++++++++++-----
 gio/gsocketaddressenumerator.c |   2 +-
 gio/tests/Makefile.am          |   7 +-
 gio/tests/meson.build          |   2 +-
 gio/tests/mock-resolver.c      | 169 +++++++++++++++++++
 gio/tests/mock-resolver.h      |  35 ++++
 gio/tests/network-address.c    | 370 ++++++++++++++++++++++++++++++++++++++++-
 gio/tests/proxy-test.c         |  24 ++-
 8 files changed, 779 insertions(+), 53 deletions(-)
---
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 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
-got_addresses (GObject      *source_object,
-               GAsyncResult *result,
-               gpointer      user_data)
+got_ipv6_addresses (GObject      *source_object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
 {
-  GTask *task = user_data;
-  GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task);
+  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);
+      /* 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));
+    }
+  else
+    g_debug ("IPv6 DNS error: %s", error->message);
 
-      if (!error)
-        {
-          g_network_address_set_addresses (addr_enum->addr, addresses,
-                                           g_resolver_get_serial (resolver));
-        }
+  /* 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);
     }
-  have_addresses (addr_enum, task, error);
+
+  /* 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)
+    {
+      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)
     {
-      sockaddr = g_object_ref (addr_enum->next->data);
-      addr_enum->next = addr_enum->next->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 <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)
+{
+}
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 <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
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 <gio/gio.h>
 #include <gio/gnetworking.h>
@@ -416,6 +417,8 @@ test_loopback_sync (void)
 typedef struct {
   GList/*<owned GSocketAddress> */ *addrs;  /* owned */
   GMainLoop *loop;  /* owned */
+  guint delay_ms;
+  gint expected_error_code;
 } AsyncData;
 
 static void
@@ -430,7 +433,14 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
   data = user_data;
 
   a = g_socket_address_enumerator_next_finish (enumerator, result, &error);
-  g_assert_no_error (error);
+
+  if (data->expected_error_code)
+    {
+      g_assert_error (error, G_IO_ERROR, data->expected_error_code);
+      g_clear_error (&error);
+    }
+  else
+    g_assert_no_error (error);
 
   if (a == NULL)
     {
@@ -443,6 +453,9 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
       g_assert (G_IS_INET_SOCKET_ADDRESS (a));
       data->addrs = g_list_prepend (data->addrs, a);
 
+      if (data->delay_ms)
+        g_usleep (data->delay_ms * 1000);
+
       g_socket_address_enumerator_next_async (enumerator, NULL,
                                               got_addr, user_data);
     }
@@ -515,6 +528,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;
 }
 
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]