mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-01 23:13:40 +02:00
gnetworkaddress: Implement parallel ipv4 and ipv6 dns lookups
As RFC 8305 recommends we can start multiple DNS queries in parallel to more quickly make an initial response, especially when one is particularly slow/broken.
This commit is contained in:
committed by
Patrick Griffis
parent
d6afa6c988
commit
35e41862c2
@@ -1,4 +1,5 @@
|
||||
#include "config.h"
|
||||
#include "mock-resolver.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gnetworking.h>
|
||||
@@ -416,6 +417,8 @@ test_loopback_sync (void)
|
||||
typedef struct {
|
||||
GList/*<owned GSocketAddress> */ *addrs; /* owned */
|
||||
GMainLoop *loop; /* owned */
|
||||
guint delay_ms;
|
||||
gint expected_error_code;
|
||||
} AsyncData;
|
||||
|
||||
static void
|
||||
@@ -430,7 +433,14 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
|
||||
data = user_data;
|
||||
|
||||
a = g_socket_address_enumerator_next_finish (enumerator, result, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
if (data->expected_error_code)
|
||||
{
|
||||
g_assert_error (error, G_IO_ERROR, data->expected_error_code);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
else
|
||||
g_assert_no_error (error);
|
||||
|
||||
if (a == NULL)
|
||||
{
|
||||
@@ -443,6 +453,9 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
|
||||
g_assert (G_IS_INET_SOCKET_ADDRESS (a));
|
||||
data->addrs = g_list_prepend (data->addrs, a);
|
||||
|
||||
if (data->delay_ms)
|
||||
g_usleep (data->delay_ms * 1000);
|
||||
|
||||
g_socket_address_enumerator_next_async (enumerator, NULL,
|
||||
got_addr, user_data);
|
||||
}
|
||||
@@ -515,6 +528,338 @@ test_to_string (void)
|
||||
g_object_unref (addr);
|
||||
}
|
||||
|
||||
static int
|
||||
sort_addresses (gconstpointer a, gconstpointer b)
|
||||
{
|
||||
GSocketFamily family_a = g_inet_address_get_family (G_INET_ADDRESS (a));
|
||||
GSocketFamily family_b = g_inet_address_get_family (G_INET_ADDRESS (b));
|
||||
|
||||
if (family_a == family_b)
|
||||
return 0;
|
||||
else if (family_a == G_SOCKET_FAMILY_IPV4)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
sort_socket_addresses (gconstpointer a, gconstpointer b)
|
||||
{
|
||||
GInetAddress *addr_a = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (a));
|
||||
GInetAddress *addr_b = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (b));
|
||||
return sort_addresses (addr_a, addr_b);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_list_matches_expected (GList *result, GList *expected)
|
||||
{
|
||||
g_assert_cmpint (g_list_length (result), ==, g_list_length (expected));
|
||||
|
||||
/* Sort by ipv4 first which matches the expected list */
|
||||
result = g_list_sort (result, sort_socket_addresses);
|
||||
|
||||
for (; result != NULL; result = result->next, expected = expected->next)
|
||||
{
|
||||
GInetAddress *address = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (result->data));
|
||||
g_assert_true (g_inet_address_equal (address, expected->data));
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
MockResolver *mock_resolver;
|
||||
GResolver *original_resolver;
|
||||
GList *input_ipv4_results;
|
||||
GList *input_ipv6_results;
|
||||
GList *input_all_results;
|
||||
GSocketConnectable *addr;
|
||||
GSocketAddressEnumerator *enumerator;
|
||||
GMainLoop *loop;
|
||||
} HappyEyeballsFixture;
|
||||
|
||||
static void
|
||||
happy_eyeballs_setup (HappyEyeballsFixture *fixture,
|
||||
gconstpointer data)
|
||||
{
|
||||
static const char * const ipv4_address_strings[] = { "1.1.1.1", "2.2.2.2" };
|
||||
static const char * const ipv6_address_strings[] = { "ff::11", "ff::22" };
|
||||
gsize i;
|
||||
|
||||
fixture->original_resolver = g_resolver_get_default ();
|
||||
fixture->mock_resolver = mock_resolver_new ();
|
||||
g_resolver_set_default (G_RESOLVER (fixture->mock_resolver));
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (ipv4_address_strings); ++i)
|
||||
{
|
||||
GInetAddress *ipv4_addr = g_inet_address_new_from_string (ipv4_address_strings[i]);
|
||||
GInetAddress *ipv6_addr = g_inet_address_new_from_string (ipv6_address_strings[i]);
|
||||
fixture->input_ipv4_results = g_list_append (fixture->input_ipv4_results, ipv4_addr);
|
||||
fixture->input_ipv6_results = g_list_append (fixture->input_ipv6_results, ipv6_addr);
|
||||
fixture->input_all_results = g_list_append (fixture->input_all_results, ipv4_addr);
|
||||
fixture->input_all_results = g_list_append (fixture->input_all_results, ipv6_addr);
|
||||
}
|
||||
fixture->input_all_results = g_list_sort (fixture->input_all_results, sort_addresses);
|
||||
mock_resolver_set_ipv4_results (fixture->mock_resolver, fixture->input_ipv4_results);
|
||||
mock_resolver_set_ipv6_results (fixture->mock_resolver, fixture->input_ipv6_results);
|
||||
|
||||
fixture->addr = g_network_address_new ("test.fake", 80);
|
||||
fixture->enumerator = g_socket_connectable_enumerate (fixture->addr);
|
||||
|
||||
fixture->loop = g_main_loop_new (NULL, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
happy_eyeballs_teardown (HappyEyeballsFixture *fixture,
|
||||
gconstpointer data)
|
||||
{
|
||||
g_object_unref (fixture->addr);
|
||||
g_object_unref (fixture->enumerator);
|
||||
g_resolver_free_addresses (fixture->input_all_results);
|
||||
g_list_free (fixture->input_ipv4_results);
|
||||
g_list_free (fixture->input_ipv6_results);
|
||||
g_resolver_set_default (fixture->original_resolver);
|
||||
g_object_unref (fixture->original_resolver);
|
||||
g_object_unref (fixture->mock_resolver);
|
||||
g_main_loop_unref (fixture->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_basic (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
|
||||
data.delay_ms = 10;
|
||||
data.loop = fixture->loop;
|
||||
|
||||
/* This just tests in the common case it gets all results */
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_all_results);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_slow_ipv4 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
|
||||
/* If ipv4 dns response is a bit slow we just don't get them */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 25);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_ipv6_results);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_slow_ipv6 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
|
||||
/* If ipv6 is a bit slow it waits for them */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 25);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_all_results);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_very_slow_ipv6 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
|
||||
/* If ipv6 is very slow we don't get them */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 200);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_ipv4_results);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_slow_connection_and_ipv4 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
|
||||
/* Even if the dns response is slow we still get them if our connection attempts
|
||||
* take long enough. */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
data.delay_ms = 500;
|
||||
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 200);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_all_results);
|
||||
/* Note that interleaving will not happen here because ipv6 was used before ipv4
|
||||
* responded */
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_ipv6_error (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv6_error;
|
||||
|
||||
/* If ipv6 fails we still get ipv4. */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
|
||||
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_ipv4_results);
|
||||
|
||||
g_error_free (ipv6_error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_ipv4_error (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv4_error;
|
||||
|
||||
/* If ipv4 fails we still get ipv6. */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
|
||||
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
assert_list_matches_expected (data.addrs, fixture->input_ipv6_results);
|
||||
|
||||
g_error_free (ipv4_error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_both_error (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv4_error, *ipv6_error;
|
||||
|
||||
/* If both fail we get an error. */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
|
||||
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
|
||||
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
|
||||
|
||||
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
|
||||
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
g_assert_null (data.addrs);
|
||||
|
||||
g_error_free (ipv4_error);
|
||||
g_error_free (ipv6_error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_both_error_delays_1 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv4_error, *ipv6_error;
|
||||
|
||||
/* The same with some different timings */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
|
||||
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
|
||||
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
|
||||
|
||||
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
|
||||
mock_resolver_set_ipv4_delay_ms (fixture->mock_resolver, 25);
|
||||
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
g_assert_null (data.addrs);
|
||||
|
||||
g_error_free (ipv4_error);
|
||||
g_error_free (ipv6_error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_both_error_delays_2 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv4_error, *ipv6_error;
|
||||
|
||||
/* The same with some different timings */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
|
||||
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
|
||||
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
|
||||
|
||||
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
|
||||
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
|
||||
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 25);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
g_assert_null (data.addrs);
|
||||
|
||||
g_error_free (ipv4_error);
|
||||
g_error_free (ipv6_error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_happy_eyeballs_both_error_delays_3 (HappyEyeballsFixture *fixture,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
AsyncData data = { 0 };
|
||||
GError *ipv4_error, *ipv6_error;
|
||||
|
||||
/* The same with some different timings */
|
||||
|
||||
data.loop = fixture->loop;
|
||||
data.expected_error_code = G_IO_ERROR_TIMED_OUT;
|
||||
ipv4_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv4 Broken");
|
||||
ipv6_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "IPv6 Broken");
|
||||
|
||||
mock_resolver_set_ipv4_error (fixture->mock_resolver, ipv4_error);
|
||||
mock_resolver_set_ipv6_error (fixture->mock_resolver, ipv6_error);
|
||||
mock_resolver_set_ipv6_delay_ms (fixture->mock_resolver, 200);
|
||||
|
||||
g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
|
||||
g_main_loop_run (fixture->loop);
|
||||
|
||||
g_assert_null (data.addrs);
|
||||
|
||||
g_error_free (ipv4_error);
|
||||
g_error_free (ipv6_error);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
@@ -560,5 +905,28 @@ main (int argc, char *argv[])
|
||||
g_test_add_func ("/network-address/loopback/async", test_loopback_async);
|
||||
g_test_add_func ("/network-address/to-string", test_to_string);
|
||||
|
||||
g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_basic, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/slow-ipv4", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_slow_ipv4, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/slow-ipv6", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_slow_ipv6, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/very-slow-ipv6", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_very_slow_ipv6, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/slow-connection-and-ipv4", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_slow_connection_and_ipv4, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/ipv6-error", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_ipv6_error, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/ipv4-error", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_ipv4_error, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/both-error", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_both_error, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/both-error-delays-1", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_1, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/both-error-delays-2", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_2, happy_eyeballs_teardown);
|
||||
g_test_add ("/network-address/happy-eyeballs/both-error-delays-3", HappyEyeballsFixture, NULL,
|
||||
happy_eyeballs_setup, test_happy_eyeballs_both_error_delays_3, happy_eyeballs_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
Reference in New Issue
Block a user