From 64f9bf96fdc47ab7fedd91d291f805c3e10322b4 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sun, 23 Nov 2014 12:53:03 -0500 Subject: [PATCH] gnetworkaddress: Add g_network_address_new_loopback() constructor 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 | 5 +- gio/gnetworkaddress.c | 45 ++++++++++++ gio/gnetworkaddress.h | 2 + gio/tests/network-address.c | 132 ++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 80980fd..fbaac6b 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1867,11 +1867,12 @@ g_socket_connectable_get_type GNetworkAddress 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 GNetworkAddressClass GNetworkAddressPrivate diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 5cb4c74..8e073fd 100644 --- a/gio/gnetworkaddress.c +++ b/gio/gnetworkaddress.c @@ -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 diff --git a/gio/gnetworkaddress.h b/gio/gnetworkaddress.h index a9800f4..d1e7470 100644 --- a/gio/gnetworkaddress.h +++ b/gio/gnetworkaddress.h @@ -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, diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 44b6990..73e323c 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -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/* */ *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 (); } -- 2.7.4