mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-14 16:26:17 +01:00
Merge branch 'wip/tingping/happy-eyeballs'
Merging this manually because the CI is broken. Fixes https://gitlab.gnome.org/GNOME/glib/merge_requests/421
This commit is contained in:
commit
99d7894d48
@ -1917,6 +1917,10 @@ g_resolver_set_default
|
||||
g_resolver_lookup_by_name
|
||||
g_resolver_lookup_by_name_async
|
||||
g_resolver_lookup_by_name_finish
|
||||
GResolverNameLookupFlags
|
||||
g_resolver_lookup_by_name_with_flags
|
||||
g_resolver_lookup_by_name_with_flags_async
|
||||
g_resolver_lookup_by_name_with_flags_finish
|
||||
g_resolver_free_addresses
|
||||
g_resolver_lookup_by_address
|
||||
g_resolver_lookup_by_address_async
|
||||
|
@ -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,14 @@ 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 *last_tail; /* (unowned) (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,11 +906,139 @@ 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);
|
||||
}
|
||||
|
||||
static inline GSocketFamily
|
||||
get_address_family (GInetSocketAddress *address)
|
||||
{
|
||||
return g_inet_address_get_family (g_inet_socket_address_get_address (address));
|
||||
}
|
||||
|
||||
static void
|
||||
list_split_families (GList *list,
|
||||
GList **out_ipv4,
|
||||
GList **out_ipv6)
|
||||
{
|
||||
g_assert (out_ipv4);
|
||||
g_assert (out_ipv6);
|
||||
|
||||
while (list)
|
||||
{
|
||||
GSocketFamily family = get_address_family (list->data);
|
||||
switch (family)
|
||||
{
|
||||
case G_SOCKET_FAMILY_IPV4:
|
||||
*out_ipv4 = g_list_prepend (*out_ipv4, list->data);
|
||||
break;
|
||||
case G_SOCKET_FAMILY_IPV6:
|
||||
*out_ipv6 = g_list_prepend (*out_ipv6, list->data);
|
||||
break;
|
||||
case G_SOCKET_FAMILY_INVALID:
|
||||
case G_SOCKET_FAMILY_UNIX:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
list = g_list_next (list);
|
||||
}
|
||||
|
||||
*out_ipv4 = g_list_reverse (*out_ipv4);
|
||||
*out_ipv6 = g_list_reverse (*out_ipv6);
|
||||
}
|
||||
|
||||
static GList *
|
||||
list_interleave_families (GList *list1,
|
||||
GList *list2)
|
||||
{
|
||||
GList *interleaved = NULL;
|
||||
|
||||
while (list1 || list2)
|
||||
{
|
||||
if (list1)
|
||||
{
|
||||
interleaved = g_list_append (interleaved, list1->data);
|
||||
list1 = g_list_delete_link (list1, list1);
|
||||
}
|
||||
if (list2)
|
||||
{
|
||||
interleaved = g_list_append (interleaved, list2->data);
|
||||
list2 = g_list_delete_link (list2, list2);
|
||||
}
|
||||
}
|
||||
|
||||
return interleaved;
|
||||
}
|
||||
|
||||
/* list_copy_interleaved:
|
||||
* @list: (transfer container): List to copy
|
||||
*
|
||||
* Does a shallow copy of a list with address families interleaved.
|
||||
*
|
||||
* For example:
|
||||
* Input: [ipv6, ipv6, ipv4, ipv4]
|
||||
* Output: [ipv6, ipv4, ipv6, ipv4]
|
||||
*
|
||||
* Returns: (transfer container): A new list
|
||||
*/
|
||||
static GList *
|
||||
list_copy_interleaved (GList *list)
|
||||
{
|
||||
GList *ipv4 = NULL, *ipv6 = NULL;
|
||||
|
||||
list_split_families (list, &ipv4, &ipv6);
|
||||
return list_interleave_families (ipv6, ipv4);
|
||||
}
|
||||
|
||||
/* list_concat_interleaved:
|
||||
* @current_item: (transfer container): Already existing list
|
||||
* @new_list: (transfer none): New list to be interleaved and concatenated
|
||||
*
|
||||
* This differs from g_list_concat() + list_copy_interleaved() in that it sorts
|
||||
* items in the previous list starting from @current_item.
|
||||
*
|
||||
* Returns: (transfer container): New start of list
|
||||
*/
|
||||
static GList *
|
||||
list_concat_interleaved (GList *current_item,
|
||||
GList *new_list)
|
||||
{
|
||||
GList *ipv4 = NULL, *ipv6 = NULL, *interleaved, *trailing = NULL;
|
||||
GSocketFamily last_family = G_SOCKET_FAMILY_IPV4; /* Default to starting with ipv6 */
|
||||
|
||||
if (current_item)
|
||||
{
|
||||
last_family = get_address_family (current_item->data);
|
||||
|
||||
/* Unused addresses will get removed, resorted, then readded */
|
||||
trailing = g_list_next (current_item);
|
||||
current_item->next = NULL;
|
||||
}
|
||||
|
||||
list_split_families (trailing, &ipv4, &ipv6);
|
||||
list_split_families (new_list, &ipv4, &ipv6);
|
||||
|
||||
if (trailing)
|
||||
g_list_free (trailing);
|
||||
|
||||
if (last_family == G_SOCKET_FAMILY_IPV4)
|
||||
interleaved = list_interleave_families (ipv6, ipv4);
|
||||
else
|
||||
interleaved = list_interleave_families (ipv4, ipv6);
|
||||
|
||||
return g_list_concat (current_item, interleaved);
|
||||
}
|
||||
|
||||
static GSocketAddress *
|
||||
g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator,
|
||||
GCancellable *cancellable,
|
||||
@ -938,36 +1077,34 @@ 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 = list_copy_interleaved (addr->priv->sockaddrs);
|
||||
addr_enum->last_tail = g_list_last (addr->priv->sockaddrs);
|
||||
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 = list_copy_interleaved (addr_enum->addr->priv->sockaddrs);
|
||||
addr_enum->last_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
|
||||
|
||||
if (addr_enum->next)
|
||||
{
|
||||
sockaddr = g_object_ref (addr_enum->next->data);
|
||||
addr_enum->next = addr_enum->next->next;
|
||||
}
|
||||
if (addr_enum->current_item)
|
||||
sockaddr = g_object_ref (addr_enum->current_item->data);
|
||||
else
|
||||
sockaddr = NULL;
|
||||
|
||||
@ -978,28 +1115,125 @@ have_addresses (GNetworkAddressAddressEnumerator *addr_enum,
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
got_addresses (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
static int
|
||||
on_address_timeout (gpointer user_data)
|
||||
{
|
||||
GTask *task = user_data;
|
||||
GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task);
|
||||
GNetworkAddressAddressEnumerator *addr_enum = user_data;
|
||||
|
||||
/* If ipv6 didn't come in yet, just complete the task */
|
||||
if (addr_enum->queued_task != NULL)
|
||||
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
|
||||
g_steal_pointer (&addr_enum->last_error));
|
||||
|
||||
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
got_ipv6_addresses (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNetworkAddressAddressEnumerator *addr_enum = user_data;
|
||||
GResolver *resolver = G_RESOLVER (source_object);
|
||||
GList *addresses;
|
||||
GError *error = NULL;
|
||||
|
||||
if (!addr_enum->addr->priv->sockaddrs)
|
||||
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
|
||||
if (!error)
|
||||
{
|
||||
addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
|
||||
|
||||
if (!error)
|
||||
{
|
||||
g_network_address_set_addresses (addr_enum->addr, addresses,
|
||||
g_resolver_get_serial (resolver));
|
||||
}
|
||||
/* Regardless of which responds first we add them to the enumerator
|
||||
* which does mean the timing of next_async() will potentially change
|
||||
* the results */
|
||||
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
|
||||
g_resolver_get_serial (resolver));
|
||||
}
|
||||
have_addresses (addr_enum, task, error);
|
||||
else
|
||||
g_debug ("IPv6 DNS error: %s", error->message);
|
||||
|
||||
/* If ipv4 was first and waiting on us it can stop waiting */
|
||||
if (addr_enum->wait_source)
|
||||
{
|
||||
g_source_destroy (addr_enum->wait_source);
|
||||
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
|
||||
}
|
||||
|
||||
/* If we got an error before ipv4 then let it handle it.
|
||||
* If we get ipv6 response first or error second then
|
||||
* immediately complete the task.
|
||||
*/
|
||||
if (error != NULL && !addr_enum->last_error)
|
||||
{
|
||||
addr_enum->last_error = g_steal_pointer (&error);
|
||||
|
||||
/* This shouldn't happen often but avoid never responding. */
|
||||
addr_enum->wait_source = g_timeout_source_new_seconds (1);
|
||||
g_source_set_callback (addr_enum->wait_source,
|
||||
on_address_timeout,
|
||||
addr_enum, NULL);
|
||||
g_source_attach (addr_enum->wait_source, addr_enum->context);
|
||||
}
|
||||
else if (addr_enum->queued_task != NULL)
|
||||
{
|
||||
g_clear_error (&addr_enum->last_error);
|
||||
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
|
||||
g_steal_pointer (&error));
|
||||
}
|
||||
else if (error != NULL)
|
||||
g_clear_error (&error);
|
||||
|
||||
g_object_unref (addr_enum);
|
||||
}
|
||||
|
||||
static void
|
||||
got_ipv4_addresses (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GNetworkAddressAddressEnumerator *addr_enum = user_data;
|
||||
GResolver *resolver = G_RESOLVER (source_object);
|
||||
GList *addresses;
|
||||
GError *error = NULL;
|
||||
|
||||
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
|
||||
if (!error)
|
||||
{
|
||||
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
|
||||
g_resolver_get_serial (resolver));
|
||||
}
|
||||
else
|
||||
g_debug ("IPv4 DNS error: %s", error->message);
|
||||
|
||||
if (addr_enum->wait_source)
|
||||
{
|
||||
g_source_destroy (addr_enum->wait_source);
|
||||
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
|
||||
}
|
||||
|
||||
/* If ipv6 already came in and errored then we return.
|
||||
* If ipv6 returned successfully then we don't need to do anything.
|
||||
* Otherwise we should wait a short while for ipv6 as RFC 8305 suggests.
|
||||
*/
|
||||
if (addr_enum->last_error)
|
||||
{
|
||||
g_assert (addr_enum->queued_task);
|
||||
g_clear_error (&addr_enum->last_error);
|
||||
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
|
||||
g_steal_pointer (&error));
|
||||
}
|
||||
else if (addr_enum->queued_task != NULL)
|
||||
{
|
||||
addr_enum->last_error = g_steal_pointer (&error);
|
||||
addr_enum->wait_source = g_timeout_source_new (50);
|
||||
g_source_set_callback (addr_enum->wait_source,
|
||||
on_address_timeout,
|
||||
addr_enum, NULL);
|
||||
g_source_attach (addr_enum->wait_source, addr_enum->context);
|
||||
}
|
||||
else if (error != NULL)
|
||||
g_clear_error (&error);
|
||||
|
||||
g_object_unref (addr_enum);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1012,6 +1246,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,33 +1265,61 @@ 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);
|
||||
/* It does not make sense for this to be called multiple
|
||||
* times before the initial callback has been called */
|
||||
g_assert (addr_enum->queued_task == NULL);
|
||||
|
||||
addr_enum->queued_task = g_steal_pointer (&task);
|
||||
/* Lookup in parallel as per RFC 8305 */
|
||||
g_resolver_lookup_by_name_with_flags_async (resolver,
|
||||
addr->priv->hostname,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY,
|
||||
cancellable,
|
||||
got_ipv6_addresses, g_object_ref (addr_enum));
|
||||
g_resolver_lookup_by_name_with_flags_async (resolver,
|
||||
addr->priv->hostname,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY,
|
||||
cancellable,
|
||||
got_ipv4_addresses, g_object_ref (addr_enum));
|
||||
}
|
||||
g_object_unref (resolver);
|
||||
return;
|
||||
}
|
||||
|
||||
addr_enum->addresses = addr->priv->sockaddrs;
|
||||
addr_enum->next = addr_enum->addresses;
|
||||
g_object_unref (resolver);
|
||||
}
|
||||
|
||||
if (addr_enum->next)
|
||||
if (addr_enum->addresses == NULL)
|
||||
{
|
||||
sockaddr = g_object_ref (addr_enum->next->data);
|
||||
addr_enum->next = addr_enum->next->next;
|
||||
g_assert (addr->priv->sockaddrs);
|
||||
|
||||
addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
|
||||
sockaddr = g_object_ref (addr_enum->current_item->data);
|
||||
}
|
||||
else
|
||||
sockaddr = NULL;
|
||||
{
|
||||
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);
|
||||
@ -1075,6 +1338,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
|
||||
|
328
gio/gresolver.c
328
gio/gresolver.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
|
||||
@ -30,6 +31,7 @@
|
||||
#include "gsrvtarget.h"
|
||||
#include "gthreadedresolver.h"
|
||||
#include "gioerror.h"
|
||||
#include "gcancellable.h"
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
#include <sys/stat.h>
|
||||
@ -347,6 +349,60 @@ handle_ip_address (const char *hostname,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GList *
|
||||
lookup_by_name_real (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GList *addrs;
|
||||
gchar *ascii_hostname = NULL;
|
||||
|
||||
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
|
||||
g_return_val_if_fail (hostname != NULL, NULL);
|
||||
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
/* Check if @hostname is just an IP address */
|
||||
if (handle_ip_address (hostname, &addrs, error))
|
||||
return addrs;
|
||||
|
||||
if (g_hostname_is_non_ascii (hostname))
|
||||
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
|
||||
|
||||
if (!hostname)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Invalid hostname"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_resolver_maybe_reload (resolver);
|
||||
|
||||
if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
|
||||
{
|
||||
if (!G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
/* Translators: The placeholder is for a function name. */
|
||||
_("%s not implemented"), "lookup_by_name_with_flags");
|
||||
g_free (ascii_hostname);
|
||||
return NULL;
|
||||
}
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_with_flags (resolver, hostname, flags, cancellable, error);
|
||||
}
|
||||
else
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name (resolver, hostname, cancellable, error);
|
||||
|
||||
remove_duplicates (addrs);
|
||||
|
||||
g_free (ascii_hostname);
|
||||
return addrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_resolver_lookup_by_name:
|
||||
* @resolver: a #GResolver
|
||||
@ -391,36 +447,187 @@ g_resolver_lookup_by_name (GResolver *resolver,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GList *addrs;
|
||||
gchar *ascii_hostname = NULL;
|
||||
return lookup_by_name_real (resolver,
|
||||
hostname,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
|
||||
cancellable,
|
||||
error);
|
||||
}
|
||||
|
||||
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
|
||||
g_return_val_if_fail (hostname != NULL, NULL);
|
||||
/**
|
||||
* g_resolver_lookup_by_name_with_flags:
|
||||
* @resolver: a #GResolver
|
||||
* @hostname: the hostname to look up
|
||||
* @flags: extra #GResolverNameLookupFlags for the lookup
|
||||
* @cancellable: (nullable): a #GCancellable, or %NULL
|
||||
* @error: (nullable): return location for a #GError, or %NULL
|
||||
*
|
||||
* This differs from g_resolver_lookup_by_name() in that you can modify
|
||||
* the lookup behavior with @flags. For example this can be used to limit
|
||||
* results with #G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY.
|
||||
*
|
||||
* Returns: (element-type GInetAddress) (transfer full): a non-empty #GList
|
||||
* of #GInetAddress, or %NULL on error. You
|
||||
* must unref each of the addresses and free the list when you are
|
||||
* done with it. (You can use g_resolver_free_addresses() to do this.)
|
||||
*
|
||||
* Since: 2.60
|
||||
*/
|
||||
GList *
|
||||
g_resolver_lookup_by_name_with_flags (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
return lookup_by_name_real (resolver,
|
||||
hostname,
|
||||
flags,
|
||||
cancellable,
|
||||
error);
|
||||
}
|
||||
|
||||
static void
|
||||
lookup_by_name_async_real (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar *ascii_hostname = NULL;
|
||||
GList *addrs;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (G_IS_RESOLVER (resolver));
|
||||
g_return_if_fail (hostname != NULL);
|
||||
g_return_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY && flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY));
|
||||
|
||||
/* Check if @hostname is just an IP address */
|
||||
if (handle_ip_address (hostname, &addrs, error))
|
||||
return addrs;
|
||||
if (handle_ip_address (hostname, &addrs, &error))
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, lookup_by_name_async_real);
|
||||
if (addrs)
|
||||
g_task_return_pointer (task, addrs, (GDestroyNotify) g_resolver_free_addresses);
|
||||
else
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_hostname_is_non_ascii (hostname))
|
||||
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
|
||||
|
||||
if (!hostname)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
GTask *task;
|
||||
|
||||
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Invalid hostname"));
|
||||
return NULL;
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, lookup_by_name_async_real);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_resolver_maybe_reload (resolver);
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name (resolver, hostname, cancellable, error);
|
||||
|
||||
if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
|
||||
{
|
||||
if (G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags_async == NULL)
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
/* Translators: The placeholder is for a function name. */
|
||||
_("%s not implemented"), "lookup_by_name_with_flags_async");
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, lookup_by_name_async_real);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
}
|
||||
else
|
||||
G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_with_flags_async (resolver, hostname, flags, cancellable, callback, user_data);
|
||||
}
|
||||
else
|
||||
G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_async (resolver, hostname, cancellable, callback, user_data);
|
||||
|
||||
g_free (ascii_hostname);
|
||||
}
|
||||
|
||||
static GList *
|
||||
lookup_by_name_finish_real (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error,
|
||||
gboolean with_flags)
|
||||
{
|
||||
GList *addrs;
|
||||
|
||||
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
|
||||
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
if (g_async_result_legacy_propagate_error (result, error))
|
||||
return NULL;
|
||||
else if (g_async_result_is_tagged (result, lookup_by_name_async_real))
|
||||
{
|
||||
/* Handle the stringified-IP-addr case */
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
if (with_flags)
|
||||
{
|
||||
g_assert (G_RESOLVER_GET_CLASS (resolver)->lookup_by_name_with_flags_finish != NULL);
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_with_flags_finish (resolver, result, error);
|
||||
}
|
||||
else
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_finish (resolver, result, error);
|
||||
|
||||
remove_duplicates (addrs);
|
||||
|
||||
g_free (ascii_hostname);
|
||||
return addrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_resolver_lookup_by_name_with_flags_async:
|
||||
* @resolver: a #GResolver
|
||||
* @hostname: the hostname to look up the address of
|
||||
* @flags: extra #GResolverNameLookupFlags for the lookup
|
||||
* @cancellable: (nullable): a #GCancellable, or %NULL
|
||||
* @callback: (scope async): callback to call after resolution completes
|
||||
* @user_data: (closure): data for @callback
|
||||
*
|
||||
* Begins asynchronously resolving @hostname to determine its
|
||||
* associated IP address(es), and eventually calls @callback, which
|
||||
* must call g_resolver_lookup_by_name_with_flags_finish() to get the result.
|
||||
* See g_resolver_lookup_by_name() for more details.
|
||||
*
|
||||
* Since: 2.60
|
||||
*/
|
||||
void
|
||||
g_resolver_lookup_by_name_with_flags_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
lookup_by_name_async_real (resolver,
|
||||
hostname,
|
||||
flags,
|
||||
cancellable,
|
||||
callback,
|
||||
user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_resolver_lookup_by_name_async:
|
||||
* @resolver: a #GResolver
|
||||
@ -443,49 +650,12 @@ g_resolver_lookup_by_name_async (GResolver *resolver,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
gchar *ascii_hostname = NULL;
|
||||
GList *addrs;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (G_IS_RESOLVER (resolver));
|
||||
g_return_if_fail (hostname != NULL);
|
||||
|
||||
/* Check if @hostname is just an IP address */
|
||||
if (handle_ip_address (hostname, &addrs, &error))
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, g_resolver_lookup_by_name_async);
|
||||
if (addrs)
|
||||
g_task_return_pointer (task, addrs, (GDestroyNotify) g_resolver_free_addresses);
|
||||
else
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_hostname_is_non_ascii (hostname))
|
||||
hostname = ascii_hostname = g_hostname_to_ascii (hostname);
|
||||
|
||||
if (!hostname)
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Invalid hostname"));
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, g_resolver_lookup_by_name_async);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_resolver_maybe_reload (resolver);
|
||||
G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_async (resolver, hostname, cancellable, callback, user_data);
|
||||
|
||||
g_free (ascii_hostname);
|
||||
lookup_by_name_async_real (resolver,
|
||||
hostname,
|
||||
0,
|
||||
cancellable,
|
||||
callback,
|
||||
user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -512,24 +682,40 @@ g_resolver_lookup_by_name_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
GList *addrs;
|
||||
return lookup_by_name_finish_real (resolver,
|
||||
result,
|
||||
error,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
|
||||
|
||||
if (g_async_result_legacy_propagate_error (result, error))
|
||||
return NULL;
|
||||
else if (g_async_result_is_tagged (result, g_resolver_lookup_by_name_async))
|
||||
{
|
||||
/* Handle the stringified-IP-addr case */
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
addrs = G_RESOLVER_GET_CLASS (resolver)->
|
||||
lookup_by_name_finish (resolver, result, error);
|
||||
|
||||
remove_duplicates (addrs);
|
||||
|
||||
return addrs;
|
||||
/**
|
||||
* g_resolver_lookup_by_name_with_flags_finish:
|
||||
* @resolver: a #GResolver
|
||||
* @result: the result passed to your #GAsyncReadyCallback
|
||||
* @error: return location for a #GError, or %NULL
|
||||
*
|
||||
* Retrieves the result of a call to
|
||||
* g_resolver_lookup_by_name_with_flags_async().
|
||||
*
|
||||
* If the DNS resolution failed, @error (if non-%NULL) will be set to
|
||||
* a value from #GResolverError. If the operation was cancelled,
|
||||
* @error will be set to %G_IO_ERROR_CANCELLED.
|
||||
*
|
||||
* Returns: (element-type GInetAddress) (transfer full): a #GList
|
||||
* of #GInetAddress, or %NULL on error. See g_resolver_lookup_by_name()
|
||||
* for more details.
|
||||
*
|
||||
* Since: 2.60
|
||||
*/
|
||||
GList *
|
||||
g_resolver_lookup_by_name_with_flags_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
return lookup_by_name_finish_real (resolver,
|
||||
result,
|
||||
error,
|
||||
TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
322
gio/gresolver.h
322
gio/gresolver.h
@ -1,6 +1,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
|
||||
@ -43,158 +44,237 @@ struct _GResolver {
|
||||
GResolverPrivate *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* GResolverNameLookupFlags:
|
||||
* @G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT: default behavior (same as g_resolver_lookup_by_name())
|
||||
* @G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY: only resolve ipv4 addresses
|
||||
* @G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY: only resolve ipv6 addresses
|
||||
*
|
||||
* Flags to modify lookup behavior.
|
||||
*
|
||||
* Since: 2.60
|
||||
*/
|
||||
typedef enum {
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT = 0,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY = 1 << 0,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY = 1 << 1,
|
||||
} GResolverNameLookupFlags;
|
||||
|
||||
struct _GResolverClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* Signals */
|
||||
void ( *reload) (GResolver *resolver);
|
||||
void ( *reload) (GResolver *resolver);
|
||||
|
||||
/* Virtual methods */
|
||||
GList * ( *lookup_by_name) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_by_name_async) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GList * ( *lookup_by_name_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GList * ( *lookup_by_name) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_by_name_async) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GList * ( *lookup_by_name_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
gchar * ( *lookup_by_address) (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_by_address_async) (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gchar * ( *lookup_by_address_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
gchar * ( *lookup_by_address) (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_by_address_async) (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gchar * ( *lookup_by_address_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GList * ( *lookup_service) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_service_async) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GList * ( *lookup_service_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GList * ( *lookup_service) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void ( *lookup_service_async) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GList * ( *lookup_service_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GList * ( *lookup_records) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GList * ( *lookup_records) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
void ( *lookup_records_async) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void ( *lookup_records_async) (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
GList * ( *lookup_records_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
/* Padding for future expansion */
|
||||
void (*_g_reserved4) (void);
|
||||
void (*_g_reserved5) (void);
|
||||
void (*_g_reserved6) (void);
|
||||
GList * ( *lookup_records_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
/**
|
||||
* GResolverClass::lookup_by_name_with_flags_async:
|
||||
* @resolver: a #GResolver
|
||||
* @hostname: the hostname to resolve
|
||||
* @flags: extra #GResolverNameLookupFlags to modify the lookup
|
||||
* @cancellable: (nullable): a #GCancellable
|
||||
* @callback: (scope async): a #GAsyncReadyCallback to call when completed
|
||||
* @user_data: (closure): data to pass to @callback
|
||||
*
|
||||
* Asynchronous version of GResolverClass::lookup_by_name_with_flags
|
||||
*
|
||||
* GResolverClass::lookup_by_name_with_flags_finish will be called to get
|
||||
* the result.
|
||||
*
|
||||
* Since: 2.60
|
||||
*/
|
||||
void ( *lookup_by_name_with_flags_async) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
/**
|
||||
* GResolverClass::lookup_by_name_with_flags_finish:
|
||||
* @resolver: a #GResolver
|
||||
* @result: a #GAsyncResult
|
||||
* @error: (nullable): a pointer to a %NULL #GError
|
||||
*
|
||||
* Gets the result from GResolverClass::lookup_by_name_with_flags_async
|
||||
*
|
||||
* Returns: (element-type GInetAddress) (transfer full): List of #GInetAddress.
|
||||
* Since: 2.60
|
||||
*/
|
||||
GList * ( *lookup_by_name_with_flags_finish) (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
/**
|
||||
* GResolverClass::lookup_by_name_with_flags:
|
||||
* @resolver: a #GResolver
|
||||
* @hostname: the hostname to resolve
|
||||
* @flags: extra #GResolverNameLookupFlags to modify the lookup
|
||||
* @cancellable: (nullable): a #GCancellable
|
||||
* @error: (nullable): a pointer to a %NULL #GError
|
||||
*
|
||||
* This is identical to GResolverClass::lookup_by_name except it takes
|
||||
* @flags which modifies the behavior of the lookup. See #GResolverNameLookupFlags
|
||||
* for more details.
|
||||
*
|
||||
* Returns: (element-type GInetAddress) (transfer full): List of #GInetAddress.
|
||||
* Since: 2.60
|
||||
*/
|
||||
GList * ( *lookup_by_name_with_flags) (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
};
|
||||
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GType g_resolver_get_type (void) G_GNUC_CONST;
|
||||
GType g_resolver_get_type (void) G_GNUC_CONST;
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GResolver *g_resolver_get_default (void);
|
||||
GResolver *g_resolver_get_default (void);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_set_default (GResolver *resolver);
|
||||
|
||||
void g_resolver_set_default (GResolver *resolver);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GList *g_resolver_lookup_by_name (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GList *g_resolver_lookup_by_name (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_lookup_by_name_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void g_resolver_lookup_by_name_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GList *g_resolver_lookup_by_name_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GList *g_resolver_lookup_by_name_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_2_60
|
||||
void g_resolver_lookup_by_name_with_flags_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GLIB_AVAILABLE_IN_2_60
|
||||
GList *g_resolver_lookup_by_name_with_flags_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_2_60
|
||||
GList *g_resolver_lookup_by_name_with_flags (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_free_addresses (GList *addresses);
|
||||
|
||||
void g_resolver_free_addresses (GList *addresses);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
gchar *g_resolver_lookup_by_address (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
gchar *g_resolver_lookup_by_address (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_lookup_by_address_async (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void g_resolver_lookup_by_address_async (GResolver *resolver,
|
||||
GInetAddress *address,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
gchar *g_resolver_lookup_by_address_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
gchar *g_resolver_lookup_by_address_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GList *g_resolver_lookup_service (GResolver *resolver,
|
||||
const gchar *service,
|
||||
const gchar *protocol,
|
||||
const gchar *domain,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GList *g_resolver_lookup_service (GResolver *resolver,
|
||||
const gchar *service,
|
||||
const gchar *protocol,
|
||||
const gchar *domain,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_lookup_service_async (GResolver *resolver,
|
||||
const gchar *service,
|
||||
const gchar *protocol,
|
||||
const gchar *domain,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void g_resolver_lookup_service_async (GResolver *resolver,
|
||||
const gchar *service,
|
||||
const gchar *protocol,
|
||||
const gchar *domain,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GList *g_resolver_lookup_service_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GList *g_resolver_lookup_service_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_2_34
|
||||
GList *g_resolver_lookup_records (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GList *g_resolver_lookup_records (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_2_34
|
||||
void g_resolver_lookup_records_async (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
void g_resolver_lookup_records_async (GResolver *resolver,
|
||||
const gchar *rrname,
|
||||
GResolverRecordType record_type,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GLIB_AVAILABLE_IN_2_34
|
||||
GList *g_resolver_lookup_records_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GList *g_resolver_lookup_records_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
void g_resolver_free_targets (GList *targets);
|
||||
void g_resolver_free_targets (GList *targets);
|
||||
|
||||
|
||||
/**
|
||||
* G_RESOLVER_ERROR:
|
||||
|
@ -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
|
||||
@ -120,6 +120,8 @@ g_socket_address_enumerator_real_next_async (GSocketAddressEnumerator *enumerato
|
||||
* Asynchronously retrieves the next #GSocketAddress from @enumerator
|
||||
* and then calls @callback, which must call
|
||||
* g_socket_address_enumerator_next_finish() to get the result.
|
||||
*
|
||||
* It is an error to call this multiple times before the previous callback has finished.
|
||||
*/
|
||||
void
|
||||
g_socket_address_enumerator_next_async (GSocketAddressEnumerator *enumerator,
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright © 2008, 2009 codethink
|
||||
* Copyright © 2009 Red Hat, Inc
|
||||
* Copyright © 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
|
||||
@ -49,6 +50,10 @@
|
||||
#include <gio/ginetaddress.h>
|
||||
#include "glibintl.h"
|
||||
|
||||
/* As recommended by RFC 8305 this is the time it waits
|
||||
* on a connection before starting another concurrent attempt.
|
||||
*/
|
||||
#define HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS 250
|
||||
|
||||
/**
|
||||
* SECTION:gsocketclient
|
||||
@ -1328,28 +1333,82 @@ typedef struct
|
||||
GSocketConnectable *connectable;
|
||||
GSocketAddressEnumerator *enumerator;
|
||||
GProxyAddress *proxy_addr;
|
||||
GSocketAddress *current_addr;
|
||||
GSocket *current_socket;
|
||||
GSocket *socket;
|
||||
GIOStream *connection;
|
||||
|
||||
GSList *connection_attempts;
|
||||
GError *last_error;
|
||||
} GSocketClientAsyncConnectData;
|
||||
|
||||
static void connection_attempt_unref (gpointer attempt);
|
||||
|
||||
static void
|
||||
g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data)
|
||||
{
|
||||
g_clear_object (&data->connectable);
|
||||
g_clear_object (&data->enumerator);
|
||||
g_clear_object (&data->proxy_addr);
|
||||
g_clear_object (&data->current_addr);
|
||||
g_clear_object (&data->current_socket);
|
||||
g_clear_object (&data->socket);
|
||||
g_clear_object (&data->connection);
|
||||
g_slist_free_full (data->connection_attempts, connection_attempt_unref);
|
||||
|
||||
g_clear_error (&data->last_error);
|
||||
|
||||
g_slice_free (GSocketClientAsyncConnectData, data);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSocketAddress *address;
|
||||
GSocket *socket;
|
||||
GIOStream *connection;
|
||||
GSocketClientAsyncConnectData *data; /* unowned */
|
||||
GSource *timeout_source;
|
||||
GCancellable *cancellable;
|
||||
grefcount ref;
|
||||
} ConnectionAttempt;
|
||||
|
||||
static ConnectionAttempt *
|
||||
connection_attempt_new (void)
|
||||
{
|
||||
ConnectionAttempt *attempt = g_new0 (ConnectionAttempt, 1);
|
||||
g_ref_count_init (&attempt->ref);
|
||||
return attempt;
|
||||
}
|
||||
|
||||
static ConnectionAttempt *
|
||||
connection_attempt_ref (ConnectionAttempt *attempt)
|
||||
{
|
||||
g_ref_count_inc (&attempt->ref);
|
||||
return attempt;
|
||||
}
|
||||
|
||||
static void
|
||||
connection_attempt_unref (gpointer pointer)
|
||||
{
|
||||
ConnectionAttempt *attempt = pointer;
|
||||
if (g_ref_count_dec (&attempt->ref))
|
||||
{
|
||||
g_clear_object (&attempt->address);
|
||||
g_clear_object (&attempt->socket);
|
||||
g_clear_object (&attempt->connection);
|
||||
g_clear_object (&attempt->cancellable);
|
||||
if (attempt->timeout_source)
|
||||
{
|
||||
g_source_destroy (attempt->timeout_source);
|
||||
g_source_unref (attempt->timeout_source);
|
||||
}
|
||||
g_free (attempt);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
connection_attempt_remove (ConnectionAttempt *attempt)
|
||||
{
|
||||
attempt->data->connection_attempts = g_slist_remove (attempt->data->connection_attempts, attempt);
|
||||
connection_attempt_unref (attempt);
|
||||
}
|
||||
|
||||
static void
|
||||
g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
|
||||
{
|
||||
@ -1359,8 +1418,7 @@ g_socket_client_async_connect_complete (GSocketClientAsyncConnectData *data)
|
||||
{
|
||||
GSocketConnection *wrapper_connection;
|
||||
|
||||
wrapper_connection = g_tcp_wrapper_connection_new (data->connection,
|
||||
data->current_socket);
|
||||
wrapper_connection = g_tcp_wrapper_connection_new (data->connection, data->socket);
|
||||
g_object_unref (data->connection);
|
||||
data->connection = (GIOStream *)wrapper_connection;
|
||||
}
|
||||
@ -1389,8 +1447,7 @@ static void
|
||||
enumerator_next_async (GSocketClientAsyncConnectData *data)
|
||||
{
|
||||
/* We need to cleanup the state */
|
||||
g_clear_object (&data->current_socket);
|
||||
g_clear_object (&data->current_addr);
|
||||
g_clear_object (&data->socket);
|
||||
g_clear_object (&data->proxy_addr);
|
||||
g_clear_object (&data->connection);
|
||||
|
||||
@ -1485,34 +1542,68 @@ g_socket_client_connected_callback (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSocketClientAsyncConnectData *data = user_data;
|
||||
ConnectionAttempt *attempt = user_data;
|
||||
GSocketClientAsyncConnectData *data = attempt->data;
|
||||
GSList *l;
|
||||
GError *error = NULL;
|
||||
GProxy *proxy;
|
||||
const gchar *protocol;
|
||||
|
||||
if (g_task_return_error_if_cancelled (data->task))
|
||||
/* data is NULL once the task is completed */
|
||||
if (data && g_task_return_error_if_cancelled (data->task))
|
||||
{
|
||||
g_object_unref (data->task);
|
||||
connection_attempt_unref (attempt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attempt->timeout_source)
|
||||
{
|
||||
g_source_destroy (attempt->timeout_source);
|
||||
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
||||
}
|
||||
|
||||
if (!g_socket_connection_connect_finish (G_SOCKET_CONNECTION (source),
|
||||
result, &error))
|
||||
{
|
||||
clarify_connect_error (error, data->connectable,
|
||||
data->current_addr);
|
||||
set_last_error (data, error);
|
||||
if (!g_cancellable_is_cancelled (attempt->cancellable))
|
||||
{
|
||||
clarify_connect_error (error, data->connectable, attempt->address);
|
||||
set_last_error (data, error);
|
||||
}
|
||||
else
|
||||
g_clear_error (&error);
|
||||
|
||||
if (data)
|
||||
{
|
||||
connection_attempt_remove (attempt);
|
||||
enumerator_next_async (data);
|
||||
}
|
||||
else
|
||||
connection_attempt_unref (attempt);
|
||||
|
||||
/* try next one */
|
||||
enumerator_next_async (data);
|
||||
return;
|
||||
}
|
||||
|
||||
data->socket = g_steal_pointer (&attempt->socket);
|
||||
data->connection = g_steal_pointer (&attempt->connection);
|
||||
|
||||
for (l = data->connection_attempts; l; l = g_slist_next (l))
|
||||
{
|
||||
ConnectionAttempt *attempt_entry = l->data;
|
||||
g_cancellable_cancel (attempt_entry->cancellable);
|
||||
attempt_entry->data = NULL;
|
||||
connection_attempt_unref (attempt_entry);
|
||||
}
|
||||
g_slist_free (data->connection_attempts);
|
||||
data->connection_attempts = NULL;
|
||||
connection_attempt_unref (attempt);
|
||||
|
||||
g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, NULL);
|
||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTED, data->connectable, data->connection);
|
||||
|
||||
/* wrong, but backward compatible */
|
||||
g_socket_set_blocking (data->current_socket, TRUE);
|
||||
g_socket_set_blocking (data->socket, TRUE);
|
||||
|
||||
if (!data->proxy_addr)
|
||||
{
|
||||
@ -1565,6 +1656,26 @@ g_socket_client_connected_callback (GObject *source,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_connection_attempt_timeout (gpointer data)
|
||||
{
|
||||
ConnectionAttempt *attempt = data;
|
||||
|
||||
enumerator_next_async (attempt->data);
|
||||
|
||||
g_clear_pointer (&attempt->timeout_source, g_source_unref);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_connection_cancelled (GCancellable *cancellable,
|
||||
gpointer data)
|
||||
{
|
||||
GCancellable *attempt_cancellable = data;
|
||||
|
||||
g_cancellable_cancel (attempt_cancellable);
|
||||
}
|
||||
|
||||
static void
|
||||
g_socket_client_enumerator_callback (GObject *object,
|
||||
GAsyncResult *result,
|
||||
@ -1573,6 +1684,7 @@ g_socket_client_enumerator_callback (GObject *object,
|
||||
GSocketClientAsyncConnectData *data = user_data;
|
||||
GSocketAddress *address = NULL;
|
||||
GSocket *socket;
|
||||
ConnectionAttempt *attempt;
|
||||
GError *error = NULL;
|
||||
|
||||
if (g_task_return_error_if_cancelled (data->task))
|
||||
@ -1585,6 +1697,9 @@ g_socket_client_enumerator_callback (GObject *object,
|
||||
result, &error);
|
||||
if (address == NULL)
|
||||
{
|
||||
if (data->connection_attempts)
|
||||
return;
|
||||
|
||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL);
|
||||
if (!error)
|
||||
{
|
||||
@ -1621,16 +1736,27 @@ g_socket_client_enumerator_callback (GObject *object,
|
||||
return;
|
||||
}
|
||||
|
||||
data->current_socket = socket;
|
||||
data->current_addr = address;
|
||||
data->connection = (GIOStream *) g_socket_connection_factory_create_connection (socket);
|
||||
attempt = connection_attempt_new ();
|
||||
attempt->data = data;
|
||||
attempt->socket = socket;
|
||||
attempt->address = address;
|
||||
attempt->cancellable = g_cancellable_new ();
|
||||
attempt->connection = (GIOStream *)g_socket_connection_factory_create_connection (socket);
|
||||
attempt->timeout_source = g_timeout_source_new (HAPPY_EYEBALLS_CONNECTION_ATTEMPT_TIMEOUT_MS);
|
||||
g_source_set_callback (attempt->timeout_source, on_connection_attempt_timeout, attempt, NULL);
|
||||
g_source_attach (attempt->timeout_source, g_main_context_get_thread_default ());
|
||||
data->connection_attempts = g_slist_append (data->connection_attempts, attempt);
|
||||
|
||||
g_socket_connection_set_cached_remote_address ((GSocketConnection*)data->connection, address);
|
||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, data->connection);
|
||||
g_socket_connection_connect_async (G_SOCKET_CONNECTION (data->connection),
|
||||
if (g_task_get_cancellable (data->task))
|
||||
g_cancellable_connect (g_task_get_cancellable (data->task), G_CALLBACK (on_connection_cancelled),
|
||||
g_object_ref (attempt->cancellable), g_object_unref);
|
||||
|
||||
g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address);
|
||||
g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_CONNECTING, data->connectable, attempt->connection);
|
||||
g_socket_connection_connect_async (G_SOCKET_CONNECTION (attempt->connection),
|
||||
address,
|
||||
g_task_get_cancellable (data->task),
|
||||
g_socket_client_connected_callback, data);
|
||||
attempt->cancellable,
|
||||
g_socket_client_connected_callback, connection_attempt_ref (attempt));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
@ -63,7 +64,27 @@ g_resolver_error_from_addrinfo_error (gint err)
|
||||
}
|
||||
}
|
||||
|
||||
static struct addrinfo addrinfo_hints;
|
||||
typedef struct {
|
||||
char *hostname;
|
||||
int address_family;
|
||||
} LookupData;
|
||||
|
||||
static LookupData *
|
||||
lookup_data_new (const char *hostname,
|
||||
int address_family)
|
||||
{
|
||||
LookupData *data = g_new (LookupData, 1);
|
||||
data->hostname = g_strdup (hostname);
|
||||
data->address_family = address_family;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
lookup_data_free (LookupData *data)
|
||||
{
|
||||
g_free (data->hostname);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
do_lookup_by_name (GTask *task,
|
||||
@ -71,11 +92,24 @@ do_lookup_by_name (GTask *task,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
const char *hostname = task_data;
|
||||
LookupData *lookup_data = task_data;
|
||||
const char *hostname = lookup_data->hostname;
|
||||
struct addrinfo *res = NULL;
|
||||
GList *addresses;
|
||||
gint retval;
|
||||
struct addrinfo addrinfo_hints = { 0 };
|
||||
|
||||
#ifdef AI_ADDRCONFIG
|
||||
addrinfo_hints.ai_flags = AI_ADDRCONFIG;
|
||||
#endif
|
||||
/* socktype and protocol don't actually matter, they just get copied into the
|
||||
* returned addrinfo structures (and then we ignore them). But if
|
||||
* we leave them unset, we'll get back duplicate answers.
|
||||
*/
|
||||
addrinfo_hints.ai_socktype = SOCK_STREAM;
|
||||
addrinfo_hints.ai_protocol = IPPROTO_TCP;
|
||||
|
||||
addrinfo_hints.ai_family = lookup_data->address_family;
|
||||
retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res);
|
||||
|
||||
if (retval == 0)
|
||||
@ -139,10 +173,53 @@ lookup_by_name (GResolver *resolver,
|
||||
{
|
||||
GTask *task;
|
||||
GList *addresses;
|
||||
LookupData *data;
|
||||
|
||||
data = lookup_data_new (hostname, AF_UNSPEC);
|
||||
task = g_task_new (resolver, cancellable, NULL, NULL);
|
||||
g_task_set_source_tag (task, lookup_by_name);
|
||||
g_task_set_task_data (task, g_strdup (hostname), g_free);
|
||||
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
|
||||
g_task_set_return_on_cancel (task, TRUE);
|
||||
g_task_run_in_thread_sync (task, do_lookup_by_name);
|
||||
addresses = g_task_propagate_pointer (task, error);
|
||||
g_object_unref (task);
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
static int
|
||||
flags_to_family (GResolverNameLookupFlags flags)
|
||||
{
|
||||
int address_family = AF_UNSPEC;
|
||||
|
||||
if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
|
||||
address_family = AF_INET;
|
||||
|
||||
if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
|
||||
{
|
||||
address_family = AF_INET6;
|
||||
/* You can only filter by one family at a time */
|
||||
g_return_val_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY), address_family);
|
||||
}
|
||||
|
||||
return address_family;
|
||||
}
|
||||
|
||||
static GList *
|
||||
lookup_by_name_with_flags (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GTask *task;
|
||||
GList *addresses;
|
||||
LookupData *data;
|
||||
|
||||
data = lookup_data_new (hostname, AF_UNSPEC);
|
||||
task = g_task_new (resolver, cancellable, NULL, NULL);
|
||||
g_task_set_source_tag (task, lookup_by_name_with_flags);
|
||||
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
|
||||
g_task_set_return_on_cancel (task, TRUE);
|
||||
g_task_run_in_thread_sync (task, do_lookup_by_name);
|
||||
addresses = g_task_propagate_pointer (task, error);
|
||||
@ -151,6 +228,26 @@ lookup_by_name (GResolver *resolver,
|
||||
return addresses;
|
||||
}
|
||||
|
||||
static void
|
||||
lookup_by_name_with_flags_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
GResolverNameLookupFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
LookupData *data;
|
||||
|
||||
data = lookup_data_new (hostname, flags_to_family (flags));
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, lookup_by_name_with_flags_async);
|
||||
g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
|
||||
g_task_set_return_on_cancel (task, TRUE);
|
||||
g_task_run_in_thread (task, do_lookup_by_name);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
lookup_by_name_async (GResolver *resolver,
|
||||
const gchar *hostname,
|
||||
@ -158,14 +255,12 @@ lookup_by_name_async (GResolver *resolver,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (resolver, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, lookup_by_name_async);
|
||||
g_task_set_task_data (task, g_strdup (hostname), g_free);
|
||||
g_task_set_return_on_cancel (task, TRUE);
|
||||
g_task_run_in_thread (task, do_lookup_by_name);
|
||||
g_object_unref (task);
|
||||
lookup_by_name_with_flags_async (resolver,
|
||||
hostname,
|
||||
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
|
||||
cancellable,
|
||||
callback,
|
||||
user_data);
|
||||
}
|
||||
|
||||
static GList *
|
||||
@ -178,6 +273,15 @@ lookup_by_name_finish (GResolver *resolver,
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static GList *
|
||||
lookup_by_name_with_flags_finish (GResolver *resolver,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static void
|
||||
do_lookup_by_address (GTask *task,
|
||||
@ -970,24 +1074,16 @@ g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
|
||||
{
|
||||
GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class);
|
||||
|
||||
resolver_class->lookup_by_name = lookup_by_name;
|
||||
resolver_class->lookup_by_name_async = lookup_by_name_async;
|
||||
resolver_class->lookup_by_name_finish = lookup_by_name_finish;
|
||||
resolver_class->lookup_by_address = lookup_by_address;
|
||||
resolver_class->lookup_by_address_async = lookup_by_address_async;
|
||||
resolver_class->lookup_by_address_finish = lookup_by_address_finish;
|
||||
resolver_class->lookup_records = lookup_records;
|
||||
resolver_class->lookup_records_async = lookup_records_async;
|
||||
resolver_class->lookup_records_finish = lookup_records_finish;
|
||||
|
||||
/* Initialize addrinfo_hints */
|
||||
#ifdef AI_ADDRCONFIG
|
||||
addrinfo_hints.ai_flags |= AI_ADDRCONFIG;
|
||||
#endif
|
||||
/* These two don't actually matter, they just get copied into the
|
||||
* returned addrinfo structures (and then we ignore them). But if
|
||||
* we leave them unset, we'll get back duplicate answers.
|
||||
*/
|
||||
addrinfo_hints.ai_socktype = SOCK_STREAM;
|
||||
addrinfo_hints.ai_protocol = IPPROTO_TCP;
|
||||
resolver_class->lookup_by_name = lookup_by_name;
|
||||
resolver_class->lookup_by_name_async = lookup_by_name_async;
|
||||
resolver_class->lookup_by_name_finish = lookup_by_name_finish;
|
||||
resolver_class->lookup_by_name_with_flags = lookup_by_name_with_flags;
|
||||
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;
|
||||
resolver_class->lookup_by_address = lookup_by_address;
|
||||
resolver_class->lookup_by_address_async = lookup_by_address_async;
|
||||
resolver_class->lookup_by_address_finish = lookup_by_address_finish;
|
||||
resolver_class->lookup_records = lookup_records;
|
||||
resolver_class->lookup_records_async = lookup_records_async;
|
||||
resolver_class->lookup_records_finish = lookup_records_finish;
|
||||
}
|
||||
|
@ -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))
|
||||
|
74
gio/tests/gsocketclient-slow.c
Normal file
74
gio/tests/gsocketclient-slow.c
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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 <gio/gio.h>
|
||||
|
||||
static void
|
||||
on_connected (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSocketConnection *conn;
|
||||
GError *error = NULL;
|
||||
|
||||
conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_object_unref (conn);
|
||||
g_main_loop_quit (user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs (void)
|
||||
{
|
||||
GSocketClient *client;
|
||||
GSocketService *service;
|
||||
GError *error = NULL;
|
||||
guint16 port;
|
||||
GMainLoop *loop;
|
||||
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
service = g_socket_service_new ();
|
||||
port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_socket_service_start (service);
|
||||
|
||||
/* All of the magic here actually happens in slow-connect-preload.c
|
||||
* which as you would guess is preloaded. So this is just making a
|
||||
* normal connection that happens to take 600ms each time. This will
|
||||
* trigger the logic to make multiple parallel connections.
|
||||
*/
|
||||
client = g_socket_client_new ();
|
||||
g_socket_client_connect_to_host_async (client, "localhost", port, NULL, on_connected, loop);
|
||||
g_main_loop_run (loop);
|
||||
|
||||
g_main_loop_unref (loop);
|
||||
g_object_unref (service);
|
||||
g_object_unref (client);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/socket-client/happy-eyeballs", test_happy_eyeballs);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@ -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' : {},
|
||||
@ -134,6 +134,18 @@ if host_machine.system() != 'windows'
|
||||
'unix-mounts' : {},
|
||||
'unix-streams' : {},
|
||||
'g-file-info-filesystem-readonly' : {},
|
||||
'gsocketclient-slow' : {
|
||||
'depends' : [
|
||||
shared_library('slow-connect-preload',
|
||||
'slow-connect-preload.c',
|
||||
name_prefix : '',
|
||||
dependencies: cc.find_library('dl'),
|
||||
)
|
||||
],
|
||||
'env' : {
|
||||
'LD_PRELOAD': '@0@/slow-connect-preload.so'.format(meson.current_build_dir())
|
||||
}
|
||||
},
|
||||
'gschema-compile' : {'install' : false},
|
||||
'trash' : {},
|
||||
}
|
||||
@ -528,12 +540,19 @@ foreach test_name, extra_args : gio_tests
|
||||
|
||||
suite = ['gio'] + extra_args.get('suite', [])
|
||||
timeout = suite.contains('slow') ? test_timeout_slow : test_timeout
|
||||
local_test_env = test_env
|
||||
|
||||
foreach var, value : extra_args.get('env', {})
|
||||
local_test_env.append(var, value)
|
||||
endforeach
|
||||
|
||||
test(test_name, exe,
|
||||
env : test_env,
|
||||
env : local_test_env,
|
||||
timeout : timeout,
|
||||
suite : suite,
|
||||
args : ['--tap'],
|
||||
is_parallel : extra_args.get('is_parallel', true),
|
||||
depends : extra_args.get('depends', []),
|
||||
)
|
||||
endforeach
|
||||
|
||||
|
169
gio/tests/mock-resolver.c
Normal file
169
gio/tests/mock-resolver.c
Normal 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
35
gio/tests/mock-resolver.h
Normal 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
|
@ -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,336 @@ 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);
|
||||
}
|
||||
|
||||
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 +903,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 ();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
43
gio/tests/slow-connect-preload.c
Normal file
43
gio/tests/slow-connect-preload.c
Normal file
@ -0,0 +1,43 @@
|
||||
/* 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 <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
/* This is used in gsocketclient-slow.c used to test
|
||||
* and get coverage on how GSocketClient reacts to
|
||||
* slow connections.
|
||||
*/
|
||||
int
|
||||
connect (int sockfd,
|
||||
const struct sockaddr *addr,
|
||||
socklen_t addrlen)
|
||||
{
|
||||
static int (*real_connect)(int, const struct sockaddr *, socklen_t);
|
||||
|
||||
if (real_connect == NULL)
|
||||
real_connect = dlsym (RTLD_NEXT, "connect");
|
||||
|
||||
/* This is long enough for multiple connection attempts to be done
|
||||
* in parallel given that their timeout is 250ms */
|
||||
usleep (600 * 1000);
|
||||
return real_connect (sockfd, addr, addrlen);
|
||||
}
|
Loading…
Reference in New Issue
Block a user