gnetworkaddress: Add g_network_address_new_loopback() constructor
authorDan Winship <danw@gnome.org>
Sun, 23 Nov 2014 17:53:03 +0000 (12:53 -0500)
committerMaciej Wereski <m.wereski@partner.samsung.com>
Fri, 10 Jul 2015 09:47:42 +0000 (11:47 +0200)
This is a convenience method for creating a GNetworkAddress which is
guaranteed to return IPv4 and IPv6 loopback addresses. The program
cannot guarantee that 'localhost' will resolve to both types of
address, so programs which wish to connect to a local service over
either IPv4 or IPv6 must currently manually create an IPv4 and another
IPv6 socket, and detect which of the two are working. This new API
allows the existing GSocketConnectable machinery to be used to
automate that.

Based on a patch from Philip Withnall.

https://bugzilla.gnome.org/show_bug.cgi?id=732317

docs/reference/gio/gio-sections.txt
gio/gnetworkaddress.c
gio/gnetworkaddress.h
gio/tests/network-address.c

index 80980fd..fbaac6b 100644 (file)
@@ -1867,11 +1867,12 @@ g_socket_connectable_get_type
 <TITLE>GNetworkAddress</TITLE>
 GNetworkAddress
 g_network_address_new
+g_network_address_new_loopback
+g_network_address_parse
+g_network_address_parse_uri
 g_network_address_get_hostname
 g_network_address_get_port
 g_network_address_get_scheme
-g_network_address_parse
-g_network_address_parse_uri
 <SUBSECTION Standard>
 GNetworkAddressClass
 GNetworkAddressPrivate
index 5cb4c74..8e073fd 100644 (file)
@@ -267,6 +267,12 @@ g_network_address_parse_sockaddr (GNetworkAddress *addr)
  * 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
@@ -282,6 +288,45 @@ g_network_address_new (const gchar *hostname,
 }
 
 /**
+ * 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
+ * #GNetworkAddresses 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_addresses (addr, 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
index a9800f4..d1e7470 100644 (file)
@@ -57,6 +57,8 @@ GType               g_network_address_get_type     (void) G_GNUC_CONST;
 GLIB_AVAILABLE_IN_ALL
 GSocketConnectable *g_network_address_new          (const gchar      *hostname,
                                                    guint16           port);
+GLIB_AVAILABLE_IN_2_44
+GSocketConnectable *g_network_address_new_loopback (guint16           port);
 GLIB_AVAILABLE_IN_ALL
 GSocketConnectable *g_network_address_parse        (const gchar      *host_and_port,
                                                    guint16           default_port,
index 44b6990..73e323c 100644 (file)
@@ -348,6 +348,135 @@ test_uri_scope_id (void)
   g_object_unref (addr);
 }
 
+static void
+test_loopback_basic (void)
+{
+  GNetworkAddress *addr;  /* owned */
+
+  addr = G_NETWORK_ADDRESS (g_network_address_new_loopback (666));
+
+  /* Test basic properties. */
+  g_assert_cmpstr (g_network_address_get_hostname (addr), ==, "localhost");
+  g_assert_cmpuint (g_network_address_get_port (addr), ==, 666);
+  g_assert_null (g_network_address_get_scheme (addr));
+
+  g_object_unref (addr);
+}
+
+static void
+assert_socket_address_matches (GSocketAddress *a,
+                               const gchar    *expected_address,
+                               guint16         expected_port)
+{
+  GInetSocketAddress *sa;
+  gchar *str;  /* owned */
+
+  g_assert (G_IS_INET_SOCKET_ADDRESS (a));
+
+  sa = G_INET_SOCKET_ADDRESS (a);
+  g_assert_cmpint (g_inet_socket_address_get_port (sa), ==, expected_port);
+
+  str = g_inet_address_to_string (g_inet_socket_address_get_address (sa));
+  g_assert_cmpstr (str, ==, expected_address);
+  g_free (str);
+}
+
+static void
+test_loopback_sync (void)
+{
+  GSocketConnectable *addr;  /* owned */
+  GSocketAddressEnumerator *enumerator;  /* owned */
+  GSocketAddress *a;  /* owned */
+  GError *error = NULL;
+
+  addr = g_network_address_new_loopback (616);
+  enumerator = g_socket_connectable_enumerate (addr);
+
+  /* IPv6 address. */
+  a = g_socket_address_enumerator_next (enumerator, NULL, &error);
+  g_assert_no_error (error);
+  assert_socket_address_matches (a, "::1", 616);
+  g_object_unref (a);
+
+  /* IPv4 address. */
+  a = g_socket_address_enumerator_next (enumerator, NULL, &error);
+  g_assert_no_error (error);
+  assert_socket_address_matches (a, "127.0.0.1", 616);
+  g_object_unref (a);
+
+  /* End of results. */
+  g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error));
+  g_assert_no_error (error);
+
+  g_object_unref (enumerator);
+  g_object_unref (addr);
+}
+
+typedef struct {
+  GList/*<owned GSocketAddress> */ *addrs;  /* owned */
+  GMainLoop *loop;  /* owned */
+} AsyncData;
+
+static void
+got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
+{
+  GSocketAddressEnumerator *enumerator;
+  AsyncData *data;
+  GSocketAddress *a;  /* owned */
+  GError *error = NULL;
+
+  enumerator = G_SOCKET_ADDRESS_ENUMERATOR (source_object);
+  data = user_data;
+
+  a = g_socket_address_enumerator_next_finish (enumerator, result, &error);
+  g_assert_no_error (error);
+
+  if (a == NULL)
+    {
+      /* End of results. */
+      data->addrs = g_list_reverse (data->addrs);
+      g_main_loop_quit (data->loop);
+    }
+  else
+    {
+      g_assert (G_IS_INET_SOCKET_ADDRESS (a));
+      data->addrs = g_list_prepend (data->addrs, a);
+
+      g_socket_address_enumerator_next_async (enumerator, NULL,
+                                              got_addr, user_data);
+    }
+}
+
+static void
+test_loopback_async (void)
+{
+  GSocketConnectable *addr;  /* owned */
+  GSocketAddressEnumerator *enumerator;  /* owned */
+  AsyncData data = { 0, };
+
+  addr = g_network_address_new_loopback (610);
+  enumerator = g_socket_connectable_enumerate (addr);
+
+  /* Get all the addresses. */
+  data.addrs = NULL;
+  data.loop = g_main_loop_new (NULL, FALSE);
+
+  g_socket_address_enumerator_next_async (enumerator, NULL, got_addr, &data);
+
+  g_main_loop_run (data.loop);
+  g_main_loop_unref (data.loop);
+
+  /* Check results. */
+  g_assert_cmpuint (g_list_length (data.addrs), ==, 2);
+  assert_socket_address_matches (data.addrs->data, "::1", 610);
+  assert_socket_address_matches (data.addrs->next->data, "127.0.0.1", 610);
+
+  g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref);
+
+  g_object_unref (enumerator);
+  g_object_unref (addr);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -388,6 +517,9 @@ main (int argc, char *argv[])
 
   g_test_add_func ("/network-address/scope-id", test_host_scope_id);
   g_test_add_func ("/network-address/uri-scope-id", test_uri_scope_id);
+  g_test_add_func ("/network-address/loopback/basic", test_loopback_basic);
+  g_test_add_func ("/network-address/loopback/sync", test_loopback_sync);
+  g_test_add_func ("/network-address/loopback/async", test_loopback_async);
 
   return g_test_run ();
 }