Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright (C) 2018 Igalia S.L.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : */
20 : :
21 : : #include <gio/gio.h>
22 : : #include <gio/gnetworking.h> /* For IPV6_V6ONLY */
23 : :
24 : : static void
25 : 1 : on_connected (GObject *source_object,
26 : : GAsyncResult *result,
27 : : gpointer user_data)
28 : : {
29 : : GSocketConnection *conn;
30 : 1 : GError *error = NULL;
31 : :
32 : 1 : conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
33 : 1 : g_assert_no_error (error);
34 : :
35 : 1 : g_object_unref (conn);
36 : 1 : g_main_loop_quit (user_data);
37 : 1 : }
38 : :
39 : : static void
40 : 1 : test_happy_eyeballs (void)
41 : : {
42 : : GSocketClient *client;
43 : : GSocketService *service;
44 : 1 : GError *error = NULL;
45 : : guint16 port;
46 : : GMainLoop *loop;
47 : :
48 : 1 : loop = g_main_loop_new (NULL, FALSE);
49 : :
50 : 1 : service = g_socket_service_new ();
51 : 1 : port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
52 : 1 : g_assert_no_error (error);
53 : 1 : g_socket_service_start (service);
54 : :
55 : : /* All of the magic here actually happens in slow-connect-preload.c
56 : : * which as you would guess is preloaded. So this is just making a
57 : : * normal connection that happens to take 600ms each time. This will
58 : : * trigger the logic to make multiple parallel connections.
59 : : */
60 : 1 : client = g_socket_client_new ();
61 : 1 : g_socket_client_connect_to_host_async (client, "localhost", port, NULL, on_connected, loop);
62 : 1 : g_main_loop_run (loop);
63 : :
64 : 1 : g_main_loop_unref (loop);
65 : 1 : g_object_unref (service);
66 : 1 : g_object_unref (client);
67 : 1 : }
68 : :
69 : : static void
70 : 2 : on_connected_cancelled (GObject *source_object,
71 : : GAsyncResult *result,
72 : : gpointer user_data)
73 : : {
74 : : GSocketConnection *conn;
75 : 2 : GError *error = NULL;
76 : :
77 : 2 : conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);
78 : 2 : g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
79 : 2 : g_assert_null (conn);
80 : :
81 : 2 : g_error_free (error);
82 : 2 : g_main_loop_quit (user_data);
83 : 2 : }
84 : :
85 : : typedef struct
86 : : {
87 : : GCancellable *cancellable;
88 : : gboolean completed;
89 : : } EventCallbackData;
90 : :
91 : : static void
92 : 5 : on_event (GSocketClient *client,
93 : : GSocketClientEvent event,
94 : : GSocketConnectable *connectable,
95 : : GIOStream *connection,
96 : : EventCallbackData *data)
97 : : {
98 : 5 : if (data->cancellable && event == G_SOCKET_CLIENT_CONNECTED)
99 : : {
100 : 1 : g_cancellable_cancel (data->cancellable);
101 : : }
102 : 4 : else if (event == G_SOCKET_CLIENT_COMPLETE)
103 : : {
104 : 2 : data->completed = TRUE;
105 : 2 : g_assert_null (connection);
106 : : }
107 : 5 : }
108 : :
109 : : static void
110 : 1 : test_happy_eyeballs_cancel_delayed (void)
111 : : {
112 : : GSocketClient *client;
113 : : GSocketService *service;
114 : 1 : GError *error = NULL;
115 : : guint16 port;
116 : : GMainLoop *loop;
117 : 1 : EventCallbackData data = { NULL, FALSE };
118 : :
119 : : /* This just tests that cancellation works as expected, still emits the completed signal,
120 : : * and never returns a connection */
121 : :
122 : 1 : loop = g_main_loop_new (NULL, FALSE);
123 : :
124 : 1 : service = g_socket_service_new ();
125 : 1 : port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
126 : 1 : g_assert_no_error (error);
127 : 1 : g_socket_service_start (service);
128 : :
129 : 1 : client = g_socket_client_new ();
130 : 1 : data.cancellable = g_cancellable_new ();
131 : 1 : g_socket_client_connect_to_host_async (client, "localhost", port, data.cancellable, on_connected_cancelled, loop);
132 : 1 : g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
133 : 1 : g_main_loop_run (loop);
134 : :
135 : 1 : g_assert_true (data.completed);
136 : 1 : g_main_loop_unref (loop);
137 : 1 : g_object_unref (service);
138 : 1 : g_object_unref (client);
139 : 1 : g_object_unref (data.cancellable);
140 : 1 : }
141 : :
142 : : static void
143 : 1 : test_happy_eyeballs_cancel_instant (void)
144 : : {
145 : : GSocketClient *client;
146 : : GSocketService *service;
147 : 1 : GError *error = NULL;
148 : : guint16 port;
149 : : GMainLoop *loop;
150 : : GCancellable *cancel;
151 : 1 : EventCallbackData data = { NULL, FALSE };
152 : :
153 : : /* This tests the same things as above, test_happy_eyeballs_cancel_delayed(), but
154 : : * with different timing since it sends an already cancelled cancellable */
155 : :
156 : 1 : loop = g_main_loop_new (NULL, FALSE);
157 : :
158 : 1 : service = g_socket_service_new ();
159 : 1 : port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error);
160 : 1 : g_assert_no_error (error);
161 : 1 : g_socket_service_start (service);
162 : :
163 : 1 : client = g_socket_client_new ();
164 : 1 : cancel = g_cancellable_new ();
165 : 1 : g_cancellable_cancel (cancel);
166 : 1 : g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop);
167 : 1 : g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
168 : 1 : g_main_loop_run (loop);
169 : :
170 : 1 : g_assert_true (data.completed);
171 : 1 : g_main_loop_unref (loop);
172 : 1 : g_object_unref (service);
173 : 1 : g_object_unref (client);
174 : 1 : g_object_unref (cancel);
175 : 1 : }
176 : :
177 : : static void
178 : 1 : async_result_cb (GObject *source,
179 : : GAsyncResult *res,
180 : : gpointer user_data)
181 : : {
182 : 1 : GAsyncResult **result_out = user_data;
183 : :
184 : 1 : g_assert_null (*result_out);
185 : 1 : *result_out = g_object_ref (res);
186 : :
187 : 1 : g_main_context_wakeup (NULL);
188 : 1 : }
189 : :
190 : : static void
191 : 1 : test_connection_failed (void)
192 : : {
193 : 1 : GSocketClient *client = NULL;
194 : 1 : GInetAddress *inet_address = NULL;
195 : 1 : GSocketAddress *address = NULL;
196 : 1 : GSocket *socket = NULL;
197 : : guint16 port;
198 : 1 : GAsyncResult *async_result = NULL;
199 : 1 : GSocketConnection *conn = NULL;
200 : 1 : GError *local_error = NULL;
201 : :
202 : 1 : g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3184");
203 : :
204 : 1 : inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
205 : 1 : address = g_inet_socket_address_new (inet_address, 0);
206 : 1 : g_object_unref (inet_address);
207 : :
208 : 1 : socket = g_socket_new (G_SOCKET_FAMILY_IPV6, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP, &local_error);
209 : 1 : g_assert_no_error (local_error);
210 : 1 : g_socket_set_option (socket, IPPROTO_IPV6, IPV6_V6ONLY, FALSE, NULL);
211 : 1 : g_socket_bind (socket, address, TRUE, &local_error);
212 : 1 : g_assert_no_error (local_error);
213 : :
214 : : /* reserve a port without listening so we know that connecting to it will fail */
215 : 1 : g_object_unref (address);
216 : 1 : address = g_socket_get_local_address (socket, &local_error);
217 : 1 : g_assert_no_error (local_error);
218 : 1 : port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
219 : :
220 : 1 : client = g_socket_client_new ();
221 : : /* Connect to the port we have reserved but do not listen to. Because of the slow connection
222 : : * caused by slow-connect-preload.c and the fact that we try to connect to both IPv4 and IPv6
223 : : * we will in some way exercise the code path in try_next_connection_or_finish() that ends
224 : : * with a call to complete_connection_with_error(). This path previously had a memory leak.
225 : : * Note that the slowness is important, because without it we could bail out already in the
226 : : * address enumeration phase because it finishes when there are no connection attempts in
227 : : * progress. */
228 : 1 : g_socket_client_connect_to_host_async (client, "localhost", port, NULL, async_result_cb, &async_result);
229 : :
230 : 6 : while (async_result == NULL)
231 : 5 : g_main_context_iteration (NULL, TRUE);
232 : :
233 : 1 : conn = g_socket_client_connect_to_uri_finish (client, async_result, &local_error);
234 : 1 : g_assert_nonnull (local_error);
235 : 1 : g_assert_cmpint (local_error->domain, ==, G_IO_ERROR);
236 : 1 : g_assert_null (conn);
237 : 1 : g_clear_error (&local_error);
238 : 1 : g_clear_object (&async_result);
239 : :
240 : 1 : g_clear_object (&client);
241 : 1 : g_clear_object (&socket);
242 : 1 : g_clear_object (&address);
243 : 1 : }
244 : :
245 : : int
246 : 1 : main (int argc, char *argv[])
247 : : {
248 : 1 : g_test_init (&argc, &argv, NULL);
249 : :
250 : 1 : g_test_add_func ("/socket-client/happy-eyeballs/slow", test_happy_eyeballs);
251 : 1 : g_test_add_func ("/socket-client/happy-eyeballs/cancellation/instant", test_happy_eyeballs_cancel_instant);
252 : 1 : g_test_add_func ("/socket-client/happy-eyeballs/cancellation/delayed", test_happy_eyeballs_cancel_delayed);
253 : 1 : g_test_add_func ("/socket-client/connection-fail", test_connection_failed);
254 : :
255 : 1 : return g_test_run ();
256 : : }
|