diff options
Diffstat (limited to 'gio/gnetworkaddress.c')
-rw-r--r-- | gio/gnetworkaddress.c | 147 |
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 * |