Commit 99d7894d authored by Michael Catanzaro's avatar Michael Catanzaro

Merge branch 'wip/tingping/happy-eyeballs'

Merging this manually because the CI is broken.

Fixes GNOME/glib!421
parents af39a373 4c472f8e
Pipeline #45704 failed with stages
in 8 minutes and 58 seconds
......@@ -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 int
on_address_timeout (gpointer user_data)
{
GNetworkAddressAddressEnumerator *addr_enum = user_data;
/* If ipv6 didn't come in yet, just complete the task */
if (addr_enum->queued_task != NULL)
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&addr_enum->last_error));
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
return G_SOURCE_REMOVE;
}
static void
got_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
got_ipv6_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = user_data;
GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task);
GNetworkAddressAddressEnumerator *addr_enum = user_data;
GResolver *resolver = G_RESOLVER (source_object);
GList *addresses;
GError *error = NULL;
if (!addr_enum->addr->priv->sockaddrs)
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
/* Regardless of which responds first we add them to the enumerator
* which does mean the timing of next_async() will potentially change
* the results */
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
g_resolver_get_serial (resolver));
}
else
g_debug ("IPv6 DNS error: %s", error->message);
if (!error)
{
g_network_address_set_addresses (addr_enum->addr, addresses,
g_resolver_get_serial (resolver));
}
/* If ipv4 was first and waiting on us it can stop waiting */
if (addr_enum->wait_source)
{
g_source_destroy (addr_enum->wait_source);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
/* If we got an error before ipv4 then let it handle it.
* If we get ipv6 response first or error second then
* immediately complete the task.
*/
if (error != NULL && !addr_enum->last_error)
{
addr_enum->last_error = g_steal_pointer (&error);
/* This shouldn't happen often but avoid never responding. */
addr_enum->wait_source = g_timeout_source_new_seconds (1);
g_source_set_callback (addr_enum->wait_source,
on_address_timeout,
addr_enum, NULL);
g_source_attach (addr_enum->wait_source, addr_enum->context);
}
else if (addr_enum->queued_task != NULL)
{
g_clear_error (&addr_enum->last_error);
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&error));
}
else if (error != NULL)
g_clear_error (&error);
g_object_unref (addr_enum);
}
static void
got_ipv4_addresses (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GNetworkAddressAddressEnumerator *addr_enum = user_data;
GResolver *resolver = G_RESOLVER (source_object);
GList *addresses;
GError *error = NULL;
addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error);
if (!error)
{
g_network_address_add_addresses (addr_enum->addr, g_steal_pointer (&addresses),
g_resolver_get_serial (resolver));
}
else
g_debug ("IPv4 DNS error: %s", error->message);
if (addr_enum->wait_source)
{
g_source_destroy (addr_enum->wait_source);
g_clear_pointer (&addr_enum->wait_source, g_source_unref);
}
have_addresses (addr_enum, task, error);
/* If ipv6 already came in and errored then we return.
* If ipv6 returned successfully then we don't need to do anything.
* Otherwise we should wait a short while for ipv6 as RFC 8305 suggests.
*/
if (addr_enum->last_error)
{
g_assert (addr_enum->queued_task);
g_clear_error (&addr_enum->last_error);
complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task),
g_steal_pointer (&error));
}
else if (addr_enum->queued_task != NULL)
{
addr_enum->last_error = g_steal_pointer (&error);
addr_enum->wait_source = g_timeout_source_new (50);
g_source_set_callback (addr_enum->wait_source,
on_address_timeout,
addr_enum, NULL);
g_source_attach (addr_enum->wait_source, addr_enum->context);
}
else if (error != NULL)
g_clear_error (&error);
g_object_unref (addr_enum);
}
static void
......@@ -1012,6 +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
......
......@@ -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,57 +447,53 @@ g_resolver_lookup_by_name (GResolver *resolver,
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);
/* 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);
addrs = G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name (resolver, hostname, cancellable, error);
remove_duplicates (addrs);
g_free (ascii_hostname);
return addrs;
return lookup_by_name_real (resolver,
hostname,
G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
cancellable,
error);
}
/**
* g_resolver_lookup_by_name_async:
* g_resolver_lookup_by_name_with_flags:
* @resolver: a #GResolver
* @hostname: the hostname to look up the address of
* @hostname: the hostname to look up
* @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
* @error: (nullable): return location for a #GError, or %NULL
*
* Begins asynchronously resolving @hostname to determine its
* associated IP address(es), and eventually calls @callback, which
* must call g_resolver_lookup_by_name_finish() to get the result.
* See g_resolver_lookup_by_name() for more details.
* 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.
*
* Since: 2.22
* 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
*/
void
g_resolver_lookup_by_name_async (GResolver *resolver,
const gchar *hostname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
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;
......@@ -449,6 +501,7 @@ g_resolver_lookup_by_name_async (GResolver *resolver,
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))
......@@ -456,7 +509,7 @@ g_resolver_lookup_by_name_async (GResolver *resolver,
GTask *task;
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_resolver_lookup_by_name_async);
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
......@@ -475,19 +528,136 @@ g_resolver_lookup_by_name_async (GResolver *resolver,
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_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);
G_RESOLVER_GET_CLASS (resolver)->
lookup_by_name_async (resolver, hostname, cancellable, callback, user_data);
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);
}