summaryrefslogtreecommitdiff
path: root/gio/gnetworkaddress.c
diff options
context:
space:
mode:
Diffstat (limited to 'gio/gnetworkaddress.c')
-rw-r--r--gio/gnetworkaddress.c147
1 files changed, 98 insertions, 49 deletions
diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c
index 60736874e..24af25c2c 100644
--- a/gio/gnetworkaddress.c
+++ b/gio/gnetworkaddress.c
@@ -38,6 +38,10 @@
#include <string.h>
+/* As recommended by RFC 8305 this is the time it waits for a following
+ DNS response to come in (ipv4 waiting on ipv6 generally)
+ */
+#define HAPPY_EYEBALLS_RESOLUTION_DELAY_MS 50
/**
* SECTION:gnetworkaddress
@@ -879,6 +883,12 @@ g_network_address_get_scheme (GNetworkAddress *addr)
#define G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (_g_network_address_address_enumerator_get_type ())
#define G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR, GNetworkAddressAddressEnumerator))
+typedef enum {
+ RESOLVE_STATE_NONE = 0,
+ RESOLVE_STATE_WAITING_ON_IPV4 = 1 << 0,
+ RESOLVE_STATE_WAITING_ON_IPV6 = 1 << 1,
+} ResolveState;
+
typedef struct {
GSocketAddressEnumerator parent_instance;
@@ -887,9 +897,11 @@ typedef struct {
GList *last_tail; /* (unowned) (nullable) */
GList *current_item; /* (unowned) (nullable) */
GTask *queued_task; /* (owned) (nullable) */
+ GTask *waiting_task; /* (owned) (nullable) */
GError *last_error; /* (owned) (nullable) */
GSource *wait_source; /* (owned) (nullable) */
GMainContext *context; /* (owned) (nullable) */
+ ResolveState state;
} GNetworkAddressAddressEnumerator;
typedef struct {
@@ -912,6 +924,7 @@ g_network_address_address_enumerator_finalize (GObject *object)
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
g_clear_object (&addr_enum->queued_task);
+ g_clear_object (&addr_enum->waiting_task);
g_clear_error (&addr_enum->last_error);
g_object_unref (addr_enum->addr);
g_clear_pointer (&addr_enum->addresses, g_list_free);
@@ -1093,20 +1106,54 @@ g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator
return g_object_ref (sockaddr);
}
+/*
+ * Each enumeration lazily initializes the internal address list from the
+ * master list. It does this since addresses come in asynchronously and
+ * they need to be resorted into the list already in use.
+ */
+static GSocketAddress *
+init_and_query_next_address (GNetworkAddressAddressEnumerator *addr_enum)
+{
+ GNetworkAddress *addr = addr_enum->addr;
+ GSocketAddress *sockaddr;
+
+ if (addr_enum->addresses == NULL)
+ {
+ addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
+ addr_enum->last_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
+ if (addr_enum->current_item)
+ sockaddr = g_object_ref (addr_enum->current_item->data);
+ else
+ sockaddr = NULL;
+ }
+ else
+ {
+ GList *parent_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
+
+ if (addr_enum->last_tail != parent_tail)
+ {
+ addr_enum->current_item = list_concat_interleaved (addr_enum->current_item, g_list_next (addr_enum->last_tail));
+ addr_enum->last_tail = parent_tail;
+ }
+
+ if (addr_enum->current_item->next)
+ {
+ addr_enum->current_item = g_list_next (addr_enum->current_item);
+ sockaddr = g_object_ref (addr_enum->current_item->data);
+ }
+ else
+ sockaddr = NULL;
+ }
+
+ return sockaddr;
+}
+
static void
complete_queued_task (GNetworkAddressAddressEnumerator *addr_enum,
GTask *task,
GError *error)
{
- GSocketAddress *sockaddr;
-
- addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr_enum->addr->priv->sockaddrs);
- addr_enum->last_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
-
- if (addr_enum->current_item)
- sockaddr = g_object_ref (addr_enum->current_item->data);
- else
- sockaddr = NULL;
+ GSocketAddress *sockaddr = init_and_query_next_address (addr_enum);
if (error)
g_task_return_error (task, error);
@@ -1123,10 +1170,12 @@ on_address_timeout (gpointer user_data)
/* Upon completion it may get unref'd by the owner */
g_object_ref (addr_enum);
- /* 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));
+ complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
+ g_steal_pointer (&addr_enum->last_error));
+ else if (addr_enum->waiting_task != NULL)
+ complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task),
+ NULL);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
g_object_unref (addr_enum);
@@ -1144,6 +1193,8 @@ got_ipv6_addresses (GObject *source_object,
GList *addresses;
GError *error = NULL;
+ addr_enum->state ^= RESOLVE_STATE_WAITING_ON_IPV6;
+
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
@@ -1163,30 +1214,39 @@ got_ipv6_addresses (GObject *source_object,
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
- /* If we got an error before ipv4 then let it handle it.
+ /* If we got an error before ipv4 then let its response handle it.
* If we get ipv6 response first or error second then
* immediately complete the task.
*/
- if (error != NULL && !addr_enum->last_error)
+ if (error != NULL && !addr_enum->last_error && (addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV4))
{
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);
+ addr_enum->wait_source = g_timeout_source_new (HAPPY_EYEBALLS_RESOLUTION_DELAY_MS);
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->waiting_task != NULL)
+ {
+ complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task), NULL);
+ }
else if (addr_enum->queued_task != NULL)
{
+ GError *task_error = NULL;
+
+ /* If both errored just use the ipv6 one,
+ but if ipv6 errored and ipv4 didn't we don't error */
+ if (error != NULL && addr_enum->last_error)
+ task_error = g_steal_pointer (&error);
+
g_clear_error (&addr_enum->last_error);
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
- g_steal_pointer (&error));
+ g_steal_pointer (&task_error));
}
- else if (error != NULL)
- g_clear_error (&error);
+ g_clear_error (&error);
g_object_unref (addr_enum);
}
@@ -1200,6 +1260,8 @@ got_ipv4_addresses (GObject *source_object,
GList *addresses;
GError *error = NULL;
+ addr_enum->state ^= RESOLVE_STATE_WAITING_ON_IPV4;
+
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
@@ -1216,8 +1278,9 @@ got_ipv4_addresses (GObject *source_object,
}
/* 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 ipv6 returned successfully then we don't need to do anything unless
+ * another enumeration was waiting on us.
+ * If ipv6 hasn't come we should wait a short while for it as RFC 8305 suggests.
*/
if (addr_enum->last_error)
{
@@ -1226,18 +1289,21 @@ got_ipv4_addresses (GObject *source_object,
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&error));
}
+ else if (addr_enum->waiting_task != NULL)
+ {
+ complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task), NULL);
+ }
else if (addr_enum->queued_task != NULL)
{
addr_enum->last_error = g_steal_pointer (&error);
- addr_enum->wait_source = g_timeout_source_new (50);
+ addr_enum->wait_source = g_timeout_source_new (HAPPY_EYEBALLS_RESOLUTION_DELAY_MS);
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_clear_error (&error);
g_object_unref (addr_enum);
}
@@ -1251,12 +1317,11 @@ 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);
- if (addr_enum->addresses == NULL)
+ if (addr_enum->addresses == NULL && addr_enum->state == RESOLVE_STATE_NONE)
{
GNetworkAddress *addr = addr_enum->addr;
GResolver *resolver = g_resolver_get_default ();
@@ -1280,6 +1345,7 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum
* times before the initial callback has been called */
g_assert (addr_enum->queued_task == NULL);
+ addr_enum->state = RESOLVE_STATE_WAITING_ON_IPV4 | RESOLVE_STATE_WAITING_ON_IPV6;
addr_enum->queued_task = g_steal_pointer (&task);
/* Lookup in parallel as per RFC 8305 */
g_resolver_lookup_by_name_with_flags_async (resolver,
@@ -1300,34 +1366,17 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum
g_object_unref (resolver);
}
- if (addr_enum->addresses == NULL)
+ sockaddr = init_and_query_next_address (addr_enum);
+ if (sockaddr == NULL && (addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV4 ||
+ addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV6))
{
- g_assert (addr->priv->sockaddrs);
-
- addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
- sockaddr = g_object_ref (addr_enum->current_item->data);
+ addr_enum->waiting_task = task;
}
else
{
- GList *parent_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
-
- if (addr_enum->last_tail != parent_tail)
- {
- addr_enum->current_item = list_concat_interleaved (addr_enum->current_item, g_list_next (addr_enum->last_tail));
- addr_enum->last_tail = parent_tail;
- }
-
- if (addr_enum->current_item->next)
- {
- addr_enum->current_item = g_list_next (addr_enum->current_item);
- sockaddr = g_object_ref (addr_enum->current_item->data);
- }
- else
- sockaddr = NULL;
+ g_task_return_pointer (task, sockaddr, g_object_unref);
+ g_object_unref (task);
}
-
- g_task_return_pointer (task, sockaddr, g_object_unref);
- g_object_unref (task);
}
static GSocketAddress *