mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
64f9bf96fd
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
526 lines
15 KiB
C
526 lines
15 KiB
C
#include "config.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <gio/gnetworking.h>
|
|
|
|
static void
|
|
test_basic (void)
|
|
{
|
|
GNetworkAddress *address;
|
|
guint port;
|
|
gchar *hostname;
|
|
gchar *scheme;
|
|
|
|
address = (GNetworkAddress*)g_network_address_new ("www.gnome.org", 8080);
|
|
|
|
g_assert_cmpstr (g_network_address_get_hostname (address), ==, "www.gnome.org");
|
|
g_assert_cmpint (g_network_address_get_port (address), ==, 8080);
|
|
|
|
g_object_get (address, "hostname", &hostname, "port", &port, "scheme", &scheme, NULL);
|
|
g_assert_cmpstr (hostname, ==, "www.gnome.org");
|
|
g_assert_cmpint (port, ==, 8080);
|
|
g_assert (scheme == NULL);
|
|
g_free (hostname);
|
|
|
|
g_object_unref (address);
|
|
}
|
|
|
|
typedef struct {
|
|
const gchar *input;
|
|
const gchar *scheme;
|
|
const gchar *hostname;
|
|
guint16 port;
|
|
gint error_code;
|
|
} ParseTest;
|
|
|
|
static ParseTest uri_tests[] = {
|
|
{ "http://www.gnome.org:2020/start", "http", "www.gnome.org", 2020, -1 },
|
|
{ "ftp://joe~:(*)%46@ftp.gnome.org:2020/start", "ftp", "ftp.gnome.org", 2020, -1 },
|
|
{ "ftp://[fec0::abcd]/start", "ftp", "fec0::abcd", 8080, -1 },
|
|
{ "ftp://[fec0::abcd]:999/start", "ftp", "fec0::abcd", 999, -1 },
|
|
{ "ftp://joe%x-@ftp.gnome.org:2020/start", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "http://[fec0::abcd%em1]/start", "http", "fec0::abcd%em1", 8080, -1 },
|
|
{ "http://[fec0::abcd%25em1]/start", "http", "fec0::abcd%em1", 8080, -1 },
|
|
{ "http://[fec0::abcd%10]/start", "http", "fec0::abcd%10", 8080, -1 },
|
|
{ "http://[fec0::abcd%25em%31]/start", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "ftp://ftp.gnome.org/start?foo=bar@baz", "ftp", "ftp.gnome.org", 8080, -1 }
|
|
};
|
|
|
|
static void
|
|
test_parse_uri (gconstpointer d)
|
|
{
|
|
const ParseTest *test = d;
|
|
GNetworkAddress *address;
|
|
GError *error;
|
|
|
|
error = NULL;
|
|
address = (GNetworkAddress*)g_network_address_parse_uri (test->input, 8080, &error);
|
|
|
|
if (address)
|
|
{
|
|
g_assert_cmpstr (g_network_address_get_scheme (address), ==, test->scheme);
|
|
g_assert_cmpstr (g_network_address_get_hostname (address), ==, test->hostname);
|
|
g_assert_cmpint (g_network_address_get_port (address), ==, test->port);
|
|
g_assert_no_error (error);
|
|
}
|
|
else
|
|
g_assert_error (error, G_IO_ERROR, test->error_code);
|
|
|
|
if (address)
|
|
g_object_unref (address);
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
static ParseTest host_tests[] =
|
|
{
|
|
{ "www.gnome.org", NULL, "www.gnome.org", 1234, -1 },
|
|
{ "www.gnome.org:8080", NULL, "www.gnome.org", 8080, -1 },
|
|
{ "[2001:db8::1]", NULL, "2001:db8::1", 1234, -1 },
|
|
{ "[2001:db8::1]:888", NULL, "2001:db8::1", 888, -1 },
|
|
{ "[2001:db8::1%em1]", NULL, "2001:db8::1%em1", 1234, -1 },
|
|
{ "[hostname", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "[hostnam]e", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "hostname:", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "hostname:-1", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT },
|
|
{ "hostname:9999999", NULL, NULL, 0, G_IO_ERROR_INVALID_ARGUMENT }
|
|
};
|
|
|
|
static void
|
|
test_parse_host (gconstpointer d)
|
|
{
|
|
const ParseTest *test = d;
|
|
GNetworkAddress *address;
|
|
GError *error;
|
|
|
|
error = NULL;
|
|
address = (GNetworkAddress*)g_network_address_parse (test->input, 1234, &error);
|
|
|
|
if (address)
|
|
{
|
|
g_assert_null (g_network_address_get_scheme (address));
|
|
g_assert_cmpstr (g_network_address_get_hostname (address), ==, test->hostname);
|
|
g_assert_cmpint (g_network_address_get_port (address), ==, test->port);
|
|
g_assert_no_error (error);
|
|
}
|
|
else
|
|
{
|
|
g_assert_error (error, G_IO_ERROR, test->error_code);
|
|
}
|
|
|
|
if (address)
|
|
g_object_unref (address);
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
typedef struct {
|
|
const gchar *input;
|
|
gboolean valid_parse, valid_resolve, valid_ip;
|
|
} ResolveTest;
|
|
|
|
static ResolveTest address_tests[] = {
|
|
{ "192.168.1.2", TRUE, TRUE, TRUE },
|
|
{ "fe80::42", TRUE, TRUE, TRUE },
|
|
|
|
/* GResolver accepts this by ignoring the scope ID. This was not
|
|
* intentional, but it's best to not "fix" it at this point.
|
|
*/
|
|
{ "fe80::42%1", TRUE, TRUE, FALSE },
|
|
|
|
/* g_network_address_parse() accepts these, but they are not
|
|
* (just) IP addresses.
|
|
*/
|
|
{ "192.168.1.2:80", TRUE, FALSE, FALSE },
|
|
{ "[fe80::42]", TRUE, FALSE, FALSE },
|
|
{ "[fe80::42]:80", TRUE, FALSE, FALSE },
|
|
|
|
/* These should not be considered IP addresses by anyone. */
|
|
{ "192.168.258", FALSE, FALSE, FALSE },
|
|
{ "192.11010306", FALSE, FALSE, FALSE },
|
|
{ "3232235778", FALSE, FALSE, FALSE },
|
|
{ "0300.0250.0001.0001", FALSE, FALSE, FALSE },
|
|
{ "0xC0.0xA8.0x01.0x02", FALSE, FALSE, FALSE },
|
|
{ "0xc0.0xa8.0x01.0x02", FALSE, FALSE, FALSE },
|
|
{ "0xc0a80102", FALSE, FALSE, FALSE }
|
|
};
|
|
|
|
static void
|
|
test_resolve_address (gconstpointer d)
|
|
{
|
|
const ResolveTest *test = d;
|
|
GSocketConnectable *connectable;
|
|
GSocketAddressEnumerator *addr_enum;
|
|
GSocketAddress *addr;
|
|
GError *error = NULL;
|
|
|
|
g_assert_cmpint (test->valid_ip, ==, g_hostname_is_ip_address (test->input));
|
|
|
|
connectable = g_network_address_parse (test->input, 1234, &error);
|
|
g_assert_no_error (error);
|
|
|
|
addr_enum = g_socket_connectable_enumerate (connectable);
|
|
addr = g_socket_address_enumerator_next (addr_enum, NULL, &error);
|
|
g_object_unref (addr_enum);
|
|
g_object_unref (connectable);
|
|
|
|
if (addr)
|
|
{
|
|
g_assert_true (test->valid_parse);
|
|
g_assert_true (G_IS_INET_SOCKET_ADDRESS (addr));
|
|
g_object_unref (addr);
|
|
}
|
|
else
|
|
{
|
|
g_assert_false (test->valid_parse);
|
|
g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Technically this should be in a GResolver test program, but we don't
|
|
* have one of those since it's mostly impossible to test programmatically.
|
|
* So it goes here so it can share the tests.
|
|
*/
|
|
static void
|
|
test_resolve_address_gresolver (gconstpointer d)
|
|
{
|
|
const ResolveTest *test = d;
|
|
GResolver *resolver;
|
|
GList *addrs;
|
|
GInetAddress *iaddr;
|
|
GError *error = NULL;
|
|
|
|
resolver = g_resolver_get_default ();
|
|
addrs = g_resolver_lookup_by_name (resolver, test->input, NULL, &error);
|
|
g_object_unref (resolver);
|
|
|
|
if (addrs)
|
|
{
|
|
g_assert_true (test->valid_resolve);
|
|
g_assert_cmpint (g_list_length (addrs), ==, 1);
|
|
|
|
iaddr = addrs->data;
|
|
g_assert_true (G_IS_INET_ADDRESS (iaddr));
|
|
|
|
g_object_unref (iaddr);
|
|
g_list_free (addrs);
|
|
}
|
|
else
|
|
{
|
|
g_assert_false (test->valid_resolve);
|
|
|
|
if (!test->valid_parse)
|
|
{
|
|
/* GResolver should have rejected the address internally, in
|
|
* which case we're guaranteed to get G_RESOLVER_ERROR_NOT_FOUND.
|
|
*/
|
|
g_assert_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
|
|
}
|
|
else
|
|
{
|
|
/* If GResolver didn't reject the string itself, then we
|
|
* might have attempted to send it over the network. If that
|
|
* attempt succeeded, we'd get back NOT_FOUND, but if
|
|
* there's no network available we might have gotten some
|
|
* other error instead.
|
|
*/
|
|
}
|
|
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#define SCOPE_ID_TEST_ADDR "fe80::42"
|
|
#define SCOPE_ID_TEST_PORT 99
|
|
|
|
#if defined (HAVE_IF_INDEXTONAME) && defined (HAVE_IF_NAMETOINDEX)
|
|
static char SCOPE_ID_TEST_IFNAME[IF_NAMESIZE];
|
|
static int SCOPE_ID_TEST_INDEX;
|
|
#else
|
|
#define SCOPE_ID_TEST_IFNAME "1"
|
|
#define SCOPE_ID_TEST_INDEX 1
|
|
#endif
|
|
|
|
static void
|
|
find_ifname_and_index (void)
|
|
{
|
|
if (SCOPE_ID_TEST_INDEX != 0)
|
|
return;
|
|
|
|
#if defined (HAVE_IF_INDEXTONAME) && defined (HAVE_IF_NAMETOINDEX)
|
|
SCOPE_ID_TEST_INDEX = if_nametoindex ("lo");
|
|
if (SCOPE_ID_TEST_INDEX != 0)
|
|
{
|
|
g_strlcpy (SCOPE_ID_TEST_IFNAME, "lo", sizeof (SCOPE_ID_TEST_IFNAME));
|
|
return;
|
|
}
|
|
|
|
for (SCOPE_ID_TEST_INDEX = 1; SCOPE_ID_TEST_INDEX < 1024; SCOPE_ID_TEST_INDEX++) {
|
|
if (if_indextoname (SCOPE_ID_TEST_INDEX, SCOPE_ID_TEST_IFNAME))
|
|
break;
|
|
}
|
|
g_assert_cmpstr (SCOPE_ID_TEST_IFNAME, !=, "");
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
test_scope_id (GSocketConnectable *addr)
|
|
{
|
|
GSocketAddressEnumerator *addr_enum;
|
|
GSocketAddress *saddr;
|
|
GInetSocketAddress *isaddr;
|
|
GInetAddress *iaddr;
|
|
char *tostring;
|
|
GError *error = NULL;
|
|
|
|
addr_enum = g_socket_connectable_enumerate (addr);
|
|
saddr = g_socket_address_enumerator_next (addr_enum, NULL, &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_assert (saddr != NULL);
|
|
g_assert (G_IS_INET_SOCKET_ADDRESS (saddr));
|
|
|
|
isaddr = G_INET_SOCKET_ADDRESS (saddr);
|
|
g_assert_cmpint (g_inet_socket_address_get_scope_id (isaddr), ==, SCOPE_ID_TEST_INDEX);
|
|
g_assert_cmpint (g_inet_socket_address_get_port (isaddr), ==, SCOPE_ID_TEST_PORT);
|
|
|
|
iaddr = g_inet_socket_address_get_address (isaddr);
|
|
tostring = g_inet_address_to_string (iaddr);
|
|
g_assert_cmpstr (tostring, ==, SCOPE_ID_TEST_ADDR);
|
|
g_free (tostring);
|
|
|
|
g_object_unref (saddr);
|
|
saddr = g_socket_address_enumerator_next (addr_enum, NULL, &error);
|
|
g_assert_no_error (error);
|
|
g_assert (saddr == NULL);
|
|
|
|
g_object_unref (addr_enum);
|
|
}
|
|
|
|
static void
|
|
test_host_scope_id (void)
|
|
{
|
|
GSocketConnectable *addr;
|
|
char *str;
|
|
|
|
find_ifname_and_index ();
|
|
|
|
str = g_strdup_printf ("%s%%%s", SCOPE_ID_TEST_ADDR, SCOPE_ID_TEST_IFNAME);
|
|
addr = g_network_address_new (str, SCOPE_ID_TEST_PORT);
|
|
g_free (str);
|
|
|
|
test_scope_id (addr);
|
|
g_object_unref (addr);
|
|
}
|
|
|
|
static void
|
|
test_uri_scope_id (void)
|
|
{
|
|
GSocketConnectable *addr;
|
|
char *uri;
|
|
GError *error = NULL;
|
|
|
|
find_ifname_and_index ();
|
|
|
|
uri = g_strdup_printf ("http://[%s%%%s]:%d/foo",
|
|
SCOPE_ID_TEST_ADDR,
|
|
SCOPE_ID_TEST_IFNAME,
|
|
SCOPE_ID_TEST_PORT);
|
|
addr = g_network_address_parse_uri (uri, 0, &error);
|
|
g_free (uri);
|
|
g_assert_no_error (error);
|
|
|
|
test_scope_id (addr);
|
|
g_object_unref (addr);
|
|
|
|
uri = g_strdup_printf ("http://[%s%%25%s]:%d/foo",
|
|
SCOPE_ID_TEST_ADDR,
|
|
SCOPE_ID_TEST_IFNAME,
|
|
SCOPE_ID_TEST_PORT);
|
|
addr = g_network_address_parse_uri (uri, 0, &error);
|
|
g_free (uri);
|
|
g_assert_no_error (error);
|
|
|
|
test_scope_id (addr);
|
|
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[])
|
|
{
|
|
gint i;
|
|
gchar *path;
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_add_func ("/network-address/basic", test_basic);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (host_tests); i++)
|
|
{
|
|
path = g_strdup_printf ("/network-address/parse-host/%d", i);
|
|
g_test_add_data_func (path, &host_tests[i], test_parse_host);
|
|
g_free (path);
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (uri_tests); i++)
|
|
{
|
|
path = g_strdup_printf ("/network-address/parse-uri/%d", i);
|
|
g_test_add_data_func (path, &uri_tests[i], test_parse_uri);
|
|
g_free (path);
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (address_tests); i++)
|
|
{
|
|
path = g_strdup_printf ("/network-address/resolve-address/%d", i);
|
|
g_test_add_data_func (path, &address_tests[i], test_resolve_address);
|
|
g_free (path);
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (address_tests); i++)
|
|
{
|
|
path = g_strdup_printf ("/gresolver/resolve-address/%d", i);
|
|
g_test_add_data_func (path, &address_tests[i], test_resolve_address_gresolver);
|
|
g_free (path);
|
|
}
|
|
|
|
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 ();
|
|
}
|