| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| |
| /* GIO - GLib Input, Output and Streaming Library |
| * |
| * Copyright (C) 2008 Red Hat, Inc. |
| * Copyright (C) 2018 Igalia S.L. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * 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 "config.h" |
| #include <glib.h> |
| #include "glibintl.h" |
| |
| #include <stdlib.h> |
| #include "gnetworkaddress.h" |
| #include "gasyncresult.h" |
| #include "ginetaddress.h" |
| #include "ginetsocketaddress.h" |
| #include "gnetworkingprivate.h" |
| #include "gproxyaddressenumerator.h" |
| #include "gresolver.h" |
| #include "gtask.h" |
| #include "gsocketaddressenumerator.h" |
| #include "gioerror.h" |
| #include "gsocketconnectable.h" |
| |
| #include <string.h> |
| |
| /* As recommended by RFC 8305 this is the time it waits for a following |
| DNS response to come in (ipv4 waiting on ipv6 generally) |
| */ |
| #define HAPPY_EYEBALLS_RESOLUTION_DELAY_MS 50 |
| |
| /** |
| * GNetworkAddress: |
| * |
| * `GNetworkAddress` provides an easy way to resolve a hostname and |
| * then attempt to connect to that host, handling the possibility of |
| * multiple IP addresses and multiple address families. |
| * |
| * The enumeration results of resolved addresses *may* be cached as long |
| * as this object is kept alive which may have unexpected results if |
| * alive for too long. |
| * |
| * See [iface@Gio.SocketConnectable] for an example of using the connectable |
| * interface. |
| */ |
| |
| struct _GNetworkAddressPrivate { |
| gchar *hostname; |
| guint16 port; |
| GList *cached_sockaddrs; |
| gchar *scheme; |
| |
| gint64 resolver_serial; |
| }; |
| |
| enum { |
| PROP_0, |
| PROP_HOSTNAME, |
| PROP_PORT, |
| PROP_SCHEME, |
| }; |
| |
| static void g_network_address_set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec); |
| static void g_network_address_get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec); |
| |
| static void g_network_address_connectable_iface_init (GSocketConnectableIface *iface); |
| static GSocketAddressEnumerator *g_network_address_connectable_enumerate (GSocketConnectable *connectable); |
| static GSocketAddressEnumerator *g_network_address_connectable_proxy_enumerate (GSocketConnectable *connectable); |
| static gchar *g_network_address_connectable_to_string (GSocketConnectable *connectable); |
| |
| G_DEFINE_TYPE_WITH_CODE (GNetworkAddress, g_network_address, G_TYPE_OBJECT, |
| G_ADD_PRIVATE (GNetworkAddress) |
| G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, |
| g_network_address_connectable_iface_init)) |
| |
| static void |
| g_network_address_finalize (GObject *object) |
| { |
| GNetworkAddress *addr = G_NETWORK_ADDRESS (object); |
| |
| g_free (addr->priv->hostname); |
| g_free (addr->priv->scheme); |
| g_list_free_full (addr->priv->cached_sockaddrs, g_object_unref); |
| |
| G_OBJECT_CLASS (g_network_address_parent_class)->finalize (object); |
| } |
| |
| static void |
| g_network_address_class_init (GNetworkAddressClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->set_property = g_network_address_set_property; |
| gobject_class->get_property = g_network_address_get_property; |
| gobject_class->finalize = g_network_address_finalize; |
| |
| /** |
| * GNetworkAddress:hostname: |
| * |
| * Hostname to resolve. |
| * |
| * Since: 2.22 |
| */ |
| g_object_class_install_property (gobject_class, PROP_HOSTNAME, |
| g_param_spec_string ("hostname", NULL, NULL, |
| NULL, |
| G_PARAM_READWRITE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GNetworkAddress:port: |
| * |
| * Network port. |
| * |
| * Since: 2.22 |
| */ |
| g_object_class_install_property (gobject_class, PROP_PORT, |
| g_param_spec_uint ("port", NULL, NULL, |
| 0, 65535, 0, |
| G_PARAM_READWRITE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GNetworkAddress:scheme: |
| * |
| * URI scheme. |
| * |
| * Since: 2.22 |
| */ |
| g_object_class_install_property (gobject_class, PROP_SCHEME, |
| g_param_spec_string ("scheme", NULL, NULL, |
| NULL, |
| G_PARAM_READWRITE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_STRINGS)); |
| } |
| |
| static void |
| g_network_address_connectable_iface_init (GSocketConnectableIface *connectable_iface) |
| { |
| connectable_iface->enumerate = g_network_address_connectable_enumerate; |
| connectable_iface->proxy_enumerate = g_network_address_connectable_proxy_enumerate; |
| connectable_iface->to_string = g_network_address_connectable_to_string; |
| } |
| |
| static void |
| g_network_address_init (GNetworkAddress *addr) |
| { |
| addr->priv = g_network_address_get_instance_private (addr); |
| } |
| |
| static void |
| g_network_address_set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| GNetworkAddress *addr = G_NETWORK_ADDRESS (object); |
| |
| switch (prop_id) |
| { |
| case PROP_HOSTNAME: |
| g_free (addr->priv->hostname); |
| addr->priv->hostname = g_value_dup_string (value); |
| break; |
| |
| case PROP_PORT: |
| addr->priv->port = g_value_get_uint (value); |
| break; |
| |
| case PROP_SCHEME: |
| g_free (addr->priv->scheme); |
| addr->priv->scheme = g_value_dup_string (value); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| |
| } |
| |
| static void |
| g_network_address_get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| GNetworkAddress *addr = G_NETWORK_ADDRESS (object); |
| |
| switch (prop_id) |
| { |
| case PROP_HOSTNAME: |
| g_value_set_string (value, addr->priv->hostname); |
| break; |
| |
| case PROP_PORT: |
| g_value_set_uint (value, addr->priv->port); |
| break; |
| |
| case PROP_SCHEME: |
| g_value_set_string (value, addr->priv->scheme); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| |
| } |
| |
| /* |
| * inet_addresses_to_inet_socket_addresses: |
| * @addresses: (transfer full): #GList of #GInetAddress |
| * |
| * Returns: (transfer full): #GList of #GInetSocketAddress |
| */ |
| static GList * |
| inet_addresses_to_inet_socket_addresses (GNetworkAddress *addr, |
| GList *addresses) |
| { |
| GList *a, *socket_addresses = NULL; |
| |
| for (a = addresses; a; a = a->next) |
| { |
| GSocketAddress *sockaddr = g_inet_socket_address_new (a->data, addr->priv->port); |
| socket_addresses = g_list_append (socket_addresses, g_steal_pointer (&sockaddr)); |
| g_object_unref (a->data); |
| } |
| |
| g_list_free (addresses); |
| return socket_addresses; |
| } |
| |
| /* |
| * g_network_address_set_cached_addresses: |
| * @addr: A #GNetworkAddress |
| * @addresses: (transfer full): List of #GInetAddress or #GInetSocketAddress |
| * @resolver_serial: Serial of #GResolver used |
| * |
| * Consumes @addresses and uses them to replace the current internal list. |
| */ |
| static void |
| g_network_address_set_cached_addresses (GNetworkAddress *addr, |
| GList *addresses, |
| guint64 resolver_serial) |
| { |
| g_assert (addresses != NULL); |
| |
| if (addr->priv->cached_sockaddrs) |
| g_list_free_full (addr->priv->cached_sockaddrs, g_object_unref); |
| |
| if (G_IS_INET_SOCKET_ADDRESS (addresses->data)) |
| addr->priv->cached_sockaddrs = g_steal_pointer (&addresses); |
| else |
| addr->priv->cached_sockaddrs = inet_addresses_to_inet_socket_addresses (addr, g_steal_pointer (&addresses)); |
| addr->priv->resolver_serial = resolver_serial; |
| } |
| |
| static gboolean |
| g_network_address_parse_sockaddr (GNetworkAddress *addr) |
| { |
| GSocketAddress *sockaddr; |
| |
| g_assert (addr->priv->cached_sockaddrs == NULL); |
| |
| sockaddr = g_inet_socket_address_new_from_string (addr->priv->hostname, |
| addr->priv->port); |
| if (sockaddr) |
| { |
| addr->priv->cached_sockaddrs = g_list_append (addr->priv->cached_sockaddrs, sockaddr); |
| return TRUE; |
| } |
| else |
| return FALSE; |
| } |
| |
| /** |
| * g_network_address_new: |
| * @hostname: the hostname |
| * @port: the port |
| * |
| * Creates a new #GSocketConnectable for connecting to the given |
| * @hostname and @port. |
| * |
| * Note that depending on the configuration of the machine, a |
| * @hostname of `localhost` may refer to the IPv4 loopback address |
| * only, or to both IPv4 and IPv6; use |
| * g_network_address_new_loopback() to create a #GNetworkAddress that |
| * is guaranteed to resolve to both addresses. |
| * |
| * Returns: (transfer full) (type GNetworkAddress): the new #GNetworkAddress |
| * |
| * Since: 2.22 |
| */ |
| GSocketConnectable * |
| g_network_address_new (const gchar *hostname, |
| guint16 port) |
| { |
| return g_object_new (G_TYPE_NETWORK_ADDRESS, |
| "hostname", hostname, |
| "port", port, |
| NULL); |
| } |
| |
| /** |
| * g_network_address_new_loopback: |
| * @port: the port |
| * |
| * Creates a new #GSocketConnectable for connecting to the local host |
| * over a loopback connection to the given @port. This is intended for |
| * use in connecting to local services which may be running on IPv4 or |
| * IPv6. |
| * |
| * The connectable will return IPv4 and IPv6 loopback addresses, |
| * regardless of how the host resolves `localhost`. By contrast, |
| * g_network_address_new() will often only return an IPv4 address when |
| * resolving `localhost`, and an IPv6 address for `localhost6`. |
| * |
| * g_network_address_get_hostname() will always return `localhost` for |
| * a #GNetworkAddress created with this constructor. |
| * |
| * Returns: (transfer full) (type GNetworkAddress): the new #GNetworkAddress |
| * |
| * Since: 2.44 |
| */ |
| GSocketConnectable * |
| g_network_address_new_loopback (guint16 port) |
| { |
| GNetworkAddress *addr; |
| GList *addrs = NULL; |
| |
| addr = g_object_new (G_TYPE_NETWORK_ADDRESS, |
| "hostname", "localhost", |
| "port", port, |
| NULL); |
| |
| 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_cached_addresses (addr, g_steal_pointer (&addrs), 0); |
| |
| return G_SOCKET_CONNECTABLE (addr); |
| } |
| |
| /** |
| * g_network_address_parse: |
| * @host_and_port: the hostname and optionally a port |
| * @default_port: the default port if not in @host_and_port |
| * @error: a pointer to a #GError, or %NULL |
| * |
| * Creates a new #GSocketConnectable for connecting to the given |
| * @hostname and @port. May fail and return %NULL in case |
| * parsing @host_and_port fails. |
| * |
| * @host_and_port may be in any of a number of recognised formats; an IPv6 |
| * address, an IPv4 address, or a domain name (in which case a DNS |
| * lookup is performed). Quoting with [] is supported for all address |
| * types. A port override may be specified in the usual way with a |
| * colon. |
| * |
| * If no port is specified in @host_and_port then @default_port will be |
| * used as the port number to connect to. |
| * |
| * In general, @host_and_port is expected to be provided by the user |
| * (allowing them to give the hostname, and a port override if necessary) |
| * and @default_port is expected to be provided by the application. |
| * |
| * (The port component of @host_and_port can also be specified as a |
| * service name rather than as a numeric port, but this functionality |
| * is deprecated, because it depends on the contents of /etc/services, |
| * which is generally quite sparse on platforms other than Linux.) |
| * |
| * Returns: (transfer full) (type GNetworkAddress): the new |
| * #GNetworkAddress, or %NULL on error |
| * |
| * Since: 2.22 |
| */ |
| GSocketConnectable * |
| g_network_address_parse (const gchar *host_and_port, |
| guint16 default_port, |
| GError **error) |
| { |
| GSocketConnectable *connectable; |
| const gchar *port; |
| guint16 portnum; |
| gchar *name; |
| |
| g_return_val_if_fail (host_and_port != NULL, NULL); |
| |
| port = NULL; |
| if (host_and_port[0] == '[') |
| /* escaped host part (to allow, eg. "[2001:db8::1]:888") */ |
| { |
| const gchar *end; |
| |
| end = strchr (host_and_port, ']'); |
| if (end == NULL) |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| _("Hostname “%s” contains “[” but not “]”"), host_and_port); |
| return NULL; |
| } |
| |
| if (end[1] == '\0') |
| port = NULL; |
| else if (end[1] == ':') |
| port = &end[2]; |
| else |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "The ']' character (in hostname '%s') must come at the" |
| " end or be immediately followed by ':' and a port", |
| host_and_port); |
| return NULL; |
| } |
| |
| name = g_strndup (host_and_port + 1, end - host_and_port - 1); |
| } |
| |
| else if ((port = strchr (host_and_port, ':'))) |
| /* string has a ':' in it */ |
| { |
| /* skip ':' */ |
| port++; |
| |
| if (strchr (port, ':')) |
| /* more than one ':' in string */ |
| { |
| /* this is actually an unescaped IPv6 address */ |
| name = g_strdup (host_and_port); |
| port = NULL; |
| } |
| else |
| name = g_strndup (host_and_port, port - host_and_port - 1); |
| } |
| |
| else |
| /* plain hostname, no port */ |
| name = g_strdup (host_and_port); |
| |
| if (port != NULL) |
| { |
| if (port[0] == '\0') |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "If a ':' character is given, it must be followed by a " |
| "port (in hostname '%s').", host_and_port); |
| g_free (name); |
| return NULL; |
| } |
| |
| else if ('0' <= port[0] && port[0] <= '9') |
| { |
| char *end; |
| long value; |
| |
| value = strtol (port, &end, 10); |
| if (*end != '\0' || value < 0 || value > G_MAXUINT16) |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "Invalid numeric port '%s' specified in hostname '%s'", |
| port, host_and_port); |
| g_free (name); |
| return NULL; |
| } |
| |
| portnum = value; |
| } |
| |
| else |
| { |
| if (!g_getservbyname_ntohs (port, "tcp", &portnum)) |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "Unknown service '%s' specified in hostname '%s'", |
| port, host_and_port); |
| #ifdef HAVE_ENDSERVENT |
| endservent (); |
| #endif |
| g_free (name); |
| return NULL; |
| } |
| |
| #ifdef HAVE_ENDSERVENT |
| endservent (); |
| #endif |
| } |
| } |
| else |
| { |
| /* No port in host_and_port */ |
| portnum = default_port; |
| } |
| |
| connectable = g_network_address_new (name, portnum); |
| g_free (name); |
| |
| return connectable; |
| } |
| |
| /** |
| * g_network_address_parse_uri: |
| * @uri: the hostname and optionally a port |
| * @default_port: The default port if none is found in the URI |
| * @error: a pointer to a #GError, or %NULL |
| * |
| * Creates a new #GSocketConnectable for connecting to the given |
| * @uri. May fail and return %NULL in case parsing @uri fails. |
| * |
| * Using this rather than g_network_address_new() or |
| * g_network_address_parse() allows #GSocketClient to determine |
| * when to use application-specific proxy protocols. |
| * |
| * Returns: (transfer full) (type GNetworkAddress): the new |
| * #GNetworkAddress, or %NULL on error |
| * |
| * Since: 2.26 |
| */ |
| GSocketConnectable * |
| g_network_address_parse_uri (const gchar *uri, |
| guint16 default_port, |
| GError **error) |
| { |
| GSocketConnectable *conn = NULL; |
| gchar *scheme = NULL; |
| gchar *hostname = NULL; |
| gint port; |
| |
| if (!g_uri_split_network (uri, G_URI_FLAGS_NONE, |
| &scheme, &hostname, &port, NULL)) |
| { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "Invalid URI ‘%s’", uri); |
| return NULL; |
| } |
| |
| if (port <= 0) |
| port = default_port; |
| |
| conn = g_object_new (G_TYPE_NETWORK_ADDRESS, |
| "hostname", hostname, |
| "port", (guint) port, |
| "scheme", scheme, |
| NULL); |
| g_free (scheme); |
| g_free (hostname); |
| |
| return conn; |
| } |
| |
| /** |
| * g_network_address_get_hostname: |
| * @addr: a #GNetworkAddress |
| * |
| * Gets @addr's hostname. This might be either UTF-8 or ASCII-encoded, |
| * depending on what @addr was created with. |
| * |
| * Returns: @addr's hostname |
| * |
| * Since: 2.22 |
| */ |
| const gchar * |
| g_network_address_get_hostname (GNetworkAddress *addr) |
| { |
| g_return_val_if_fail (G_IS_NETWORK_ADDRESS (addr), NULL); |
| |
| return addr->priv->hostname; |
| } |
| |
| /** |
| * g_network_address_get_port: |
| * @addr: a #GNetworkAddress |
| * |
| * Gets @addr's port number |
| * |
| * Returns: @addr's port (which may be 0) |
| * |
| * Since: 2.22 |
| */ |
| guint16 |
| g_network_address_get_port (GNetworkAddress *addr) |
| { |
| g_return_val_if_fail (G_IS_NETWORK_ADDRESS (addr), 0); |
| |
| return addr->priv->port; |
| } |
| |
| /** |
| * g_network_address_get_scheme: |
| * @addr: a #GNetworkAddress |
| * |
| * Gets @addr's scheme |
| * |
| * Returns: (nullable): @addr's scheme (%NULL if not built from URI) |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_network_address_get_scheme (GNetworkAddress *addr) |
| { |
| g_return_val_if_fail (G_IS_NETWORK_ADDRESS (addr), NULL); |
| |
| return addr->priv->scheme; |
| } |
| |
| #define G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (_g_network_address_address_enumerator_get_type ()) |
| #define G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR, GNetworkAddressAddressEnumerator)) |
| |
| typedef enum { |
| RESOLVE_STATE_NONE = 0, |
| RESOLVE_STATE_WAITING_ON_IPV4 = 1 << 0, |
| RESOLVE_STATE_WAITING_ON_IPV6 = 1 << 1, |
| } ResolveState; |
| |
| typedef struct { |
| GSocketAddressEnumerator parent_instance; |
| |
| GNetworkAddress *addr; /* (owned) */ |
| GList *addresses; /* (owned) (nullable) */ |
| GList *current_item; /* (unowned) (nullable) */ |
| GTask *queued_task; /* (owned) (nullable) */ |
| GTask *waiting_task; /* (owned) (nullable) */ |
| GError *last_error; /* (owned) (nullable) */ |
| GSource *wait_source; /* (owned) (nullable) */ |
| GMainContext *context; /* (owned) (nullable) */ |
| ResolveState state; |
| } GNetworkAddressAddressEnumerator; |
| |
| typedef struct { |
| GSocketAddressEnumeratorClass parent_class; |
| |
| } GNetworkAddressAddressEnumeratorClass; |
| |
| static GType _g_network_address_address_enumerator_get_type (void); |
| G_DEFINE_TYPE (GNetworkAddressAddressEnumerator, _g_network_address_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR) |
| |
| static void |
| 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_object (&addr_enum->waiting_task); |
| g_clear_error (&addr_enum->last_error); |
| g_object_unref (addr_enum->addr); |
| g_clear_pointer (&addr_enum->context, g_main_context_unref); |
| g_list_free_full (addr_enum->addresses, g_object_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: |
| * @parent_list: (transfer container): Already existing list |
| * @current_item: (transfer container): Item after which to resort |
| * @new_list: (transfer container): 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 and concats the results |
| * to @parent_list. |
| * |
| * Returns: (transfer container): New start of list |
| */ |
| static GList * |
| list_concat_interleaved (GList *parent_list, |
| 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); |
| g_list_free (new_list); |
| |
| 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 (parent_list, interleaved); |
| } |
| |
| static void |
| maybe_update_address_cache (GNetworkAddressAddressEnumerator *addr_enum, |
| GResolver *resolver) |
| { |
| GList *addresses, *p; |
| |
| /* Only cache complete results */ |
| if (addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV4 || addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV6) |
| return; |
| |
| /* The enumerators list will not necessarily be fully sorted */ |
| addresses = list_copy_interleaved (addr_enum->addresses); |
| for (p = addresses; p; p = p->next) |
| g_object_ref (p->data); |
| |
| g_network_address_set_cached_addresses (addr_enum->addr, g_steal_pointer (&addresses), g_resolver_get_serial (resolver)); |
| } |
| |
| static void |
| g_network_address_address_enumerator_add_addresses (GNetworkAddressAddressEnumerator *addr_enum, |
| GList *addresses, |
| GResolver *resolver) |
| { |
| GList *new_addresses = inet_addresses_to_inet_socket_addresses (addr_enum->addr, addresses); |
| |
| if (addr_enum->addresses == NULL) |
| addr_enum->addresses = g_steal_pointer (&new_addresses); |
| else |
| addr_enum->addresses = list_concat_interleaved (addr_enum->addresses, addr_enum->current_item, g_steal_pointer (&new_addresses)); |
| |
| maybe_update_address_cache (addr_enum, resolver); |
| } |
| |
| static gpointer |
| copy_object (gconstpointer src, |
| gpointer user_data) |
| { |
| return g_object_ref (G_OBJECT (src)); |
| } |
| |
| static GSocketAddress * |
| init_and_query_next_address (GNetworkAddressAddressEnumerator *addr_enum) |
| { |
| GList *next_item; |
| |
| if (addr_enum->addresses == NULL) |
| addr_enum->addresses = g_list_copy_deep (addr_enum->addr->priv->cached_sockaddrs, |
| copy_object, NULL); |
| |
| /* We always want to look at the next item at call time to get the latest results. |
| That means that sometimes ->next is NULL this call but is valid next call. |
| */ |
| if (addr_enum->current_item == NULL) |
| next_item = addr_enum->current_item = addr_enum->addresses; |
| else |
| next_item = g_list_next (addr_enum->current_item); |
| |
| if (next_item) |
| { |
| addr_enum->current_item = next_item; |
| return g_object_ref (addr_enum->current_item->data); |
| } |
| else |
| return NULL; |
| } |
| |
| static GSocketAddress * |
| g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GNetworkAddressAddressEnumerator *addr_enum = |
| G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); |
| |
| if (addr_enum->addresses == NULL) |
| { |
| GNetworkAddress *addr = addr_enum->addr; |
| GResolver *resolver = g_resolver_get_default (); |
| gint64 serial = g_resolver_get_serial (resolver); |
| |
| if (addr->priv->resolver_serial != 0 && |
| addr->priv->resolver_serial != serial) |
| { |
| /* Resolver has reloaded, discard cached addresses */ |
| g_list_free_full (addr->priv->cached_sockaddrs, g_object_unref); |
| addr->priv->cached_sockaddrs = NULL; |
| } |
| |
| if (!addr->priv->cached_sockaddrs) |
| g_network_address_parse_sockaddr (addr); |
| if (!addr->priv->cached_sockaddrs) |
| { |
| GList *addresses; |
| |
| addresses = g_resolver_lookup_by_name (resolver, |
| addr->priv->hostname, |
| cancellable, error); |
| if (!addresses) |
| { |
| g_object_unref (resolver); |
| return NULL; |
| } |
| |
| g_network_address_set_cached_addresses (addr, g_steal_pointer (&addresses), serial); |
| } |
| |
| g_object_unref (resolver); |
| } |
| |
| return init_and_query_next_address (addr_enum); |
| } |
| |
| static void |
| complete_queued_task (GNetworkAddressAddressEnumerator *addr_enum, |
| GTask *task, |
| GError *error) |
| { |
| if (error) |
| g_task_return_error (task, error); |
| else |
| { |
| GSocketAddress *sockaddr = init_and_query_next_address (addr_enum); |
| g_task_return_pointer (task, g_steal_pointer (&sockaddr), g_object_unref); |
| } |
| g_object_unref (task); |
| } |
| |
| static int |
| on_address_timeout (gpointer user_data) |
| { |
| GNetworkAddressAddressEnumerator *addr_enum = user_data; |
| |
| /* Upon completion it may get unref'd by the owner */ |
| g_object_ref (addr_enum); |
| |
| 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)); |
| else if (addr_enum->waiting_task != NULL) |
| complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task), |
| NULL); |
| |
| g_clear_pointer (&addr_enum->wait_source, g_source_unref); |
| g_object_unref (addr_enum); |
| |
| 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; |
| |
| addr_enum->state ^= RESOLVE_STATE_WAITING_ON_IPV6; |
| |
| addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error); |
| if (!error) |
| g_network_address_address_enumerator_add_addresses (addr_enum, g_steal_pointer (&addresses), resolver); |
| 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 its response handle it. |
| * If we get ipv6 response first or error second then |
| * immediately complete the task. |
| */ |
| if (error != NULL && !addr_enum->last_error && (addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV4)) |
| { |
| /* ipv6 lookup failed, but ipv4 is still outstanding. wait. */ |
| addr_enum->last_error = g_steal_pointer (&error); |
| } |
| else if (addr_enum->waiting_task != NULL) |
| { |
| complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task), NULL); |
| } |
| else if (addr_enum->queued_task != NULL) |
| { |
| GError *task_error = NULL; |
| |
| /* If both errored just use the ipv6 one, |
| but if ipv6 errored and ipv4 didn't we don't error */ |
| if (error != NULL && addr_enum->last_error) |
| task_error = g_steal_pointer (&error); |
| |
| g_clear_error (&addr_enum->last_error); |
| complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->queued_task), |
| g_steal_pointer (&task_error)); |
| } |
| |
| 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; |
| |
| addr_enum->state ^= RESOLVE_STATE_WAITING_ON_IPV4; |
| |
| addresses = g_resolver_lookup_by_name_with_flags_finish (resolver, result, &error); |
| if (!error) |
| g_network_address_address_enumerator_add_addresses (addr_enum, g_steal_pointer (&addresses), 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 unless |
| * another enumeration was waiting on us. |
| * If ipv6 hasn't come we should wait a short while for it as RFC 8305 suggests. |
| */ |
| if (addr_enum->last_error) |
| { |
| 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->waiting_task != NULL) |
| { |
| complete_queued_task (addr_enum, g_steal_pointer (&addr_enum->waiting_task), NULL); |
| } |
| else if (addr_enum->queued_task != NULL) |
| { |
| addr_enum->last_error = g_steal_pointer (&error); |
| addr_enum->wait_source = g_timeout_source_new (HAPPY_EYEBALLS_RESOLUTION_DELAY_MS); |
| g_source_set_callback (addr_enum->wait_source, |
| on_address_timeout, |
| addr_enum, NULL); |
| g_source_attach (addr_enum->wait_source, addr_enum->context); |
| } |
| |
| g_clear_error (&error); |
| g_object_unref (addr_enum); |
| } |
| |
| static void |
| g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enumerator, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GNetworkAddressAddressEnumerator *addr_enum = |
| G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); |
| GSocketAddress *sockaddr; |
| GTask *task; |
| |
| task = g_task_new (addr_enum, cancellable, callback, user_data); |
| g_task_set_source_tag (task, g_network_address_address_enumerator_next_async); |
| |
| if (addr_enum->addresses == NULL && addr_enum->state == RESOLVE_STATE_NONE) |
| { |
| GNetworkAddress *addr = addr_enum->addr; |
| GResolver *resolver = g_resolver_get_default (); |
| gint64 serial = g_resolver_get_serial (resolver); |
| |
| if (addr->priv->resolver_serial != 0 && |
| addr->priv->resolver_serial != serial) |
| { |
| /* Resolver has reloaded, discard cached addresses */ |
| g_list_free_full (addr->priv->cached_sockaddrs, g_object_unref); |
| addr->priv->cached_sockaddrs = NULL; |
| } |
| |
| if (addr->priv->cached_sockaddrs == NULL) |
| { |
| if (g_network_address_parse_sockaddr (addr)) |
| complete_queued_task (addr_enum, task, NULL); |
| else |
| { |
| /* 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->state = RESOLVE_STATE_WAITING_ON_IPV4 | RESOLVE_STATE_WAITING_ON_IPV6; |
| addr_enum->queued_task = g_steal_pointer (&task); |
| /* Look up 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; |
| } |
| |
| g_object_unref (resolver); |
| } |
| |
| sockaddr = init_and_query_next_address (addr_enum); |
| if (sockaddr == NULL && (addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV4 || |
| addr_enum->state & RESOLVE_STATE_WAITING_ON_IPV6)) |
| { |
| addr_enum->waiting_task = task; |
| } |
| else |
| { |
| g_task_return_pointer (task, sockaddr, g_object_unref); |
| g_object_unref (task); |
| } |
| } |
| |
| static GSocketAddress * |
| g_network_address_address_enumerator_next_finish (GSocketAddressEnumerator *enumerator, |
| GAsyncResult *result, |
| GError **error) |
| { |
| g_return_val_if_fail (g_task_is_valid (result, enumerator), NULL); |
| |
| return g_task_propagate_pointer (G_TASK (result), error); |
| } |
| |
| static void |
| _g_network_address_address_enumerator_init (GNetworkAddressAddressEnumerator *enumerator) |
| { |
| enumerator->context = g_main_context_ref_thread_default (); |
| } |
| |
| static void |
| _g_network_address_address_enumerator_class_init (GNetworkAddressAddressEnumeratorClass *addrenum_class) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (addrenum_class); |
| GSocketAddressEnumeratorClass *enumerator_class = |
| G_SOCKET_ADDRESS_ENUMERATOR_CLASS (addrenum_class); |
| |
| enumerator_class->next = g_network_address_address_enumerator_next; |
| enumerator_class->next_async = g_network_address_address_enumerator_next_async; |
| enumerator_class->next_finish = g_network_address_address_enumerator_next_finish; |
| object_class->finalize = g_network_address_address_enumerator_finalize; |
| } |
| |
| static GSocketAddressEnumerator * |
| g_network_address_connectable_enumerate (GSocketConnectable *connectable) |
| { |
| GNetworkAddressAddressEnumerator *addr_enum; |
| |
| addr_enum = g_object_new (G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR, NULL); |
| addr_enum->addr = g_object_ref (G_NETWORK_ADDRESS (connectable)); |
| |
| return (GSocketAddressEnumerator *)addr_enum; |
| } |
| |
| static GSocketAddressEnumerator * |
| g_network_address_connectable_proxy_enumerate (GSocketConnectable *connectable) |
| { |
| GNetworkAddress *self = G_NETWORK_ADDRESS (connectable); |
| GSocketAddressEnumerator *proxy_enum; |
| gchar *uri; |
| |
| uri = g_uri_join (G_URI_FLAGS_NONE, |
| self->priv->scheme ? self->priv->scheme : "none", |
| NULL, |
| self->priv->hostname, |
| self->priv->port, |
| "", |
| NULL, |
| NULL); |
| |
| proxy_enum = g_object_new (G_TYPE_PROXY_ADDRESS_ENUMERATOR, |
| "connectable", connectable, |
| "uri", uri, |
| NULL); |
| |
| g_free (uri); |
| |
| return proxy_enum; |
| } |
| |
| static gchar * |
| g_network_address_connectable_to_string (GSocketConnectable *connectable) |
| { |
| GNetworkAddress *addr; |
| const gchar *scheme; |
| guint16 port; |
| GString *out; /* owned */ |
| |
| addr = G_NETWORK_ADDRESS (connectable); |
| out = g_string_new (""); |
| |
| scheme = g_network_address_get_scheme (addr); |
| if (scheme != NULL) |
| g_string_append_printf (out, "%s:", scheme); |
| |
| g_string_append (out, g_network_address_get_hostname (addr)); |
| |
| port = g_network_address_get_port (addr); |
| if (port != 0) |
| g_string_append_printf (out, ":%u", port); |
| |
| return g_string_free (out, FALSE); |
| } |