Branch data Line data Source code
1 : : /* GLib testing framework examples and tests
2 : : *
3 : : * Copyright 2014 Red Hat, Inc.
4 : : * Copyright 2025 GNOME Foundation, Inc.
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public
19 : : * License along with this library; if not, see
20 : : * <http://www.gnu.org/licenses/>.
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <gio/gio.h>
26 : : #include <stdint.h>
27 : :
28 : : /* Override the socket(), bind(), listen() and getsockopt() functions from libc
29 : : * so that we can mock results from them in the tests. The libc implementations
30 : : * are used by default (via `dlsym()`) unless a test sets a callback
31 : : * deliberately.
32 : : *
33 : : * These override functions are used simply because the linker will resolve them
34 : : * before it finds the symbols in libc. This is effectively like `LD_PRELOAD`,
35 : : * except without using an external library for them.
36 : : *
37 : : * This mechanism is intended to be generic and not to force tests in this file
38 : : * to be written in a certain way. Tests are free to override these functions
39 : : * with their own implementations, or leave them as default. Different tests may
40 : : * need to mock these socket functions differently.
41 : : *
42 : : * If a test overrides these functions, it *must* do so at the start of the test
43 : : * (before starting any threads), and *must* clear them to `NULL` at the end of
44 : : * the test. The overrides are not thread-safe and will not be automatically
45 : : * cleared at the end of a test.
46 : : */
47 : : /* FIXME: Not currently supported on macOS as its symbol lookup works
48 : : * differently to Linux. It will likely need something like DYLD_INTERPOSE()
49 : : * from getpwuid-preload.c here to work. At that point, this common code for
50 : : * mocking arbitrary syscalls using dlsym(RTLD_NEXT) should probably be factored
51 : : * out into a set of internal helpers, because various tests do it for various
52 : : * syscalls. */
53 : : #if defined(HAVE_RTLD_NEXT) && !defined(__APPLE__) && !defined(G_OS_WIN32)
54 : : #include <dlfcn.h>
55 : : #include <sys/types.h>
56 : : #include <netinet/in.h>
57 : : #include <netinet/ip.h>
58 : : #include <sys/socket.h>
59 : : #define MOCK_SOCKET_SUPPORTED
60 : : #endif
61 : :
62 : : #ifdef MOCK_SOCKET_SUPPORTED
63 : : static int (*real_socket) (int, int, int);
64 : : static int (*real_bind) (int, const struct sockaddr *, socklen_t);
65 : : static int (*real_listen) (int, int);
66 : : static int (*real_getsockopt) (int, int, int, void *, socklen_t *);
67 : : static int (*mock_socket) (int, int, int);
68 : : static int (*mock_bind) (int, const struct sockaddr *, socklen_t);
69 : : static int (*mock_listen) (int, int);
70 : : static int (*mock_getsockopt) (int, int, int, void *, socklen_t *);
71 : :
72 : : int
73 : : socket (int domain,
74 : : int type,
75 : : int protocol)
76 : : {
77 : 41 : if (real_socket == NULL)
78 : 1 : real_socket = dlsym (RTLD_NEXT, "socket");
79 : :
80 : 41 : return ((mock_socket != NULL) ? mock_socket : real_socket) (domain, type, protocol);
81 : : }
82 : :
83 : : int
84 : : bind (int sockfd,
85 : : const struct sockaddr *addr,
86 : : socklen_t addrlen)
87 : : {
88 : 30 : if (real_bind == NULL)
89 : 1 : real_bind = dlsym (RTLD_NEXT, "bind");
90 : :
91 : 30 : return ((mock_bind != NULL) ? mock_bind : real_bind) (sockfd, addr, addrlen);
92 : : }
93 : :
94 : : int
95 : : listen (int sockfd,
96 : : int backlog)
97 : : {
98 : 26 : if (real_listen == NULL)
99 : 1 : real_listen = dlsym (RTLD_NEXT, "listen");
100 : :
101 : 26 : return ((mock_listen != NULL) ? mock_listen : real_listen) (sockfd, backlog);
102 : : }
103 : :
104 : : int
105 : : getsockopt (int sockfd,
106 : : int level,
107 : : int optname,
108 : : void *optval,
109 : : socklen_t *optlen)
110 : : {
111 : 31 : if (real_getsockopt == NULL)
112 : 1 : real_getsockopt = dlsym (RTLD_NEXT, "getsockopt");
113 : :
114 : 31 : return ((mock_getsockopt != NULL) ? mock_getsockopt : real_getsockopt) (sockfd, level, optname, optval, optlen);
115 : : }
116 : :
117 : : #endif /* MOCK_SOCKET_SUPPORTED */
118 : :
119 : : /* Test event signals. */
120 : : static void
121 : 4 : event_cb (GSocketListener *listener,
122 : : GSocketListenerEvent event,
123 : : GSocket *socket,
124 : : gpointer data)
125 : : {
126 : : static GSocketListenerEvent expected_event = G_SOCKET_LISTENER_BINDING;
127 : 4 : gboolean *success = (gboolean *)data;
128 : :
129 : 4 : g_assert (G_IS_SOCKET_LISTENER (listener));
130 : 4 : g_assert (G_IS_SOCKET (socket));
131 : 4 : g_assert (event == expected_event);
132 : :
133 : 4 : switch (event)
134 : : {
135 : 1 : case G_SOCKET_LISTENER_BINDING:
136 : 1 : expected_event = G_SOCKET_LISTENER_BOUND;
137 : 1 : break;
138 : 1 : case G_SOCKET_LISTENER_BOUND:
139 : 1 : expected_event = G_SOCKET_LISTENER_LISTENING;
140 : 1 : break;
141 : 1 : case G_SOCKET_LISTENER_LISTENING:
142 : 1 : expected_event = G_SOCKET_LISTENER_LISTENED;
143 : 1 : break;
144 : 1 : case G_SOCKET_LISTENER_LISTENED:
145 : 1 : *success = TRUE;
146 : 1 : break;
147 : : }
148 : 4 : }
149 : :
150 : : static void
151 : 1 : test_event_signal (void)
152 : : {
153 : 1 : gboolean success = FALSE;
154 : : GInetAddress *iaddr;
155 : : GSocketAddress *saddr;
156 : : GSocketListener *listener;
157 : 1 : GError *error = NULL;
158 : :
159 : 1 : iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
160 : 1 : saddr = g_inet_socket_address_new (iaddr, 0);
161 : 1 : g_object_unref (iaddr);
162 : :
163 : 1 : listener = g_socket_listener_new ();
164 : :
165 : 1 : g_signal_connect (listener, "event", G_CALLBACK (event_cb), &success);
166 : :
167 : 1 : g_socket_listener_add_address (listener,
168 : : saddr,
169 : : G_SOCKET_TYPE_STREAM,
170 : : G_SOCKET_PROTOCOL_TCP,
171 : : NULL,
172 : : NULL,
173 : : &error);
174 : 1 : g_assert_no_error (error);
175 : 1 : g_assert_true (success);
176 : :
177 : 1 : g_object_unref (saddr);
178 : 1 : g_object_unref (listener);
179 : 1 : }
180 : :
181 : : /* Provide a mock implementation of socket(), listen(), bind() and getsockopt()
182 : : * which use a simple fixed configuration to either force a call to fail with a
183 : : * given errno, or allow it to pass through to the system implementation (which
184 : : * is assumed to succeed). Results are differentiated by protocol (IPv4 or IPv6)
185 : : * but nothing more complex than that.
186 : : *
187 : : * This allows the `listen()` fallback code in
188 : : * `g_socket_listener_add_any_inet_port()` and
189 : : * `g_socket_listener_add_inet_port()` to be tested for different situations
190 : : * where IPv4 and/or IPv6 sockets don’t work. It doesn’t allow the port
191 : : * allocation retry logic to be tested (as it forces all IPv6 `bind()` calls to
192 : : * have the same result), but this means it doesn’t have to do more complex
193 : : * state tracking of fully mocked-up sockets.
194 : : *
195 : : * It also means that the test won’t work on systems which don’t support IPv6,
196 : : * or which have a configuration which causes the first test case (which passes
197 : : * all syscalls through to the system) to fail. On those systems, the test
198 : : * should be skipped rather than the mock made more complex.
199 : : */
200 : : #ifdef MOCK_SOCKET_SUPPORTED
201 : :
202 : : typedef struct {
203 : : gboolean ipv6_socket_supports_ipv4;
204 : : int ipv4_socket_errno; /* 0 for socket() to succeed on the IPv4 socket (i.e. IPv4 sockets are supported) */
205 : : int ipv6_socket_errno; /* similarly */
206 : : int ipv4_bind_errno; /* 0 for bind() to succeed on the IPv4 socket */
207 : : int ipv6_bind_errno; /* similarly */
208 : : int ipv4_listen_errno; /* 0 for listen() to succeed on the IPv4 socket */
209 : : int ipv6_listen_errno; /* similarly */
210 : : } ListenFailuresConfig;
211 : :
212 : : static struct {
213 : : /* Input: */
214 : : ListenFailuresConfig config;
215 : :
216 : : /* State (we only support tracking one socket of each type): */
217 : : int ipv4_socket_fd;
218 : : int ipv6_socket_fd;
219 : : } listen_failures_mock_state;
220 : :
221 : : static int
222 : 30 : listen_failures_socket (int domain,
223 : : int type,
224 : : int protocol)
225 : : {
226 : : int fd;
227 : :
228 : : /* Error out if told to */
229 : 30 : if (domain == AF_INET && listen_failures_mock_state.config.ipv4_socket_errno != 0)
230 : : {
231 : 2 : errno = listen_failures_mock_state.config.ipv4_socket_errno;
232 : 2 : return -1;
233 : : }
234 : 28 : else if (domain == AF_INET6 && listen_failures_mock_state.config.ipv6_socket_errno != 0)
235 : : {
236 : 4 : errno = listen_failures_mock_state.config.ipv6_socket_errno;
237 : 4 : return -1;
238 : : }
239 : 24 : else if (domain != AF_INET && domain != AF_INET6)
240 : : {
241 : : /* we don’t expect to support other socket types */
242 : : g_assert_not_reached ();
243 : : }
244 : :
245 : : /* Pass through to the system, which we require to succeed because we’re only
246 : : * mocking errors and not the full socket stack state */
247 : 24 : fd = real_socket (domain, type, protocol);
248 : 24 : g_assert_no_errno (fd);
249 : :
250 : : /* Track the returned FD for each socket type */
251 : 24 : if (domain == AF_INET)
252 : : {
253 : 6 : g_assert (listen_failures_mock_state.ipv4_socket_fd == 0);
254 : 6 : listen_failures_mock_state.ipv4_socket_fd = fd;
255 : : }
256 : 18 : else if (domain == AF_INET6)
257 : : {
258 : 18 : g_assert (listen_failures_mock_state.ipv6_socket_fd == 0);
259 : 18 : listen_failures_mock_state.ipv6_socket_fd = fd;
260 : : }
261 : :
262 : 24 : return fd;
263 : : }
264 : :
265 : : static int
266 : 24 : listen_failures_bind (int sockfd,
267 : : const struct sockaddr *addr,
268 : : socklen_t addrlen)
269 : : {
270 : : int retval;
271 : :
272 : : /* Error out if told to */
273 : 24 : if (listen_failures_mock_state.ipv4_socket_fd == sockfd &&
274 : 6 : listen_failures_mock_state.config.ipv4_bind_errno != 0)
275 : : {
276 : 0 : errno = listen_failures_mock_state.config.ipv4_bind_errno;
277 : 0 : return -1;
278 : : }
279 : 24 : else if (listen_failures_mock_state.ipv6_socket_fd == sockfd &&
280 : 18 : listen_failures_mock_state.config.ipv6_bind_errno != 0)
281 : : {
282 : 4 : errno = listen_failures_mock_state.config.ipv6_bind_errno;
283 : 4 : return -1;
284 : : }
285 : 20 : else if (listen_failures_mock_state.ipv4_socket_fd != sockfd &&
286 : 14 : listen_failures_mock_state.ipv6_socket_fd != sockfd)
287 : : {
288 : : g_assert_not_reached ();
289 : : }
290 : :
291 : : /* Pass through to the system, which we require to succeed because we’re only
292 : : * mocking errors and not the full socket stack state */
293 : 20 : retval = real_bind (sockfd, addr, addrlen);
294 : 20 : g_assert_no_errno (retval);
295 : :
296 : 20 : return retval;
297 : : }
298 : :
299 : : static int
300 : 20 : listen_failures_listen (int sockfd,
301 : : int backlog)
302 : : {
303 : : int retval;
304 : :
305 : : /* Error out if told to */
306 : 20 : if (listen_failures_mock_state.ipv4_socket_fd == sockfd &&
307 : 6 : listen_failures_mock_state.config.ipv4_listen_errno != 0)
308 : : {
309 : 1 : errno = listen_failures_mock_state.config.ipv4_listen_errno;
310 : 1 : return -1;
311 : : }
312 : 19 : else if (listen_failures_mock_state.ipv6_socket_fd == sockfd &&
313 : 14 : listen_failures_mock_state.config.ipv6_listen_errno != 0)
314 : : {
315 : 6 : errno = listen_failures_mock_state.config.ipv6_listen_errno;
316 : 6 : return -1;
317 : : }
318 : 13 : else if (listen_failures_mock_state.ipv4_socket_fd != sockfd &&
319 : 8 : listen_failures_mock_state.ipv6_socket_fd != sockfd)
320 : : {
321 : : g_assert_not_reached ();
322 : : }
323 : :
324 : : /* Pass through to the system, which we require to succeed because we’re only
325 : : * mocking errors and not the full socket stack state */
326 : 13 : retval = real_listen (sockfd, backlog);
327 : 13 : g_assert_no_errno (retval);
328 : :
329 : 13 : return retval;
330 : : }
331 : :
332 : : static int
333 : 11 : listen_failures_getsockopt (int sockfd,
334 : : int level,
335 : : int optname,
336 : : void *optval,
337 : : socklen_t *optlen)
338 : : {
339 : : /* Mock whether IPv6 sockets claim to support IPv4 */
340 : : #if defined (IPPROTO_IPV6) && defined (IPV6_V6ONLY)
341 : 11 : if (listen_failures_mock_state.ipv6_socket_fd == sockfd &&
342 : 11 : level == IPPROTO_IPV6 && optname == IPV6_V6ONLY)
343 : : {
344 : 11 : int *v6_only = optval;
345 : 11 : *v6_only = !listen_failures_mock_state.config.ipv6_socket_supports_ipv4;
346 : 11 : return 0;
347 : : }
348 : : #endif
349 : :
350 : : /* Don’t assert that the system getsockopt() succeeded, as it could be used
351 : : * in complex ways, and it’s incidental to what we’re actually trying to test. */
352 : 0 : return real_getsockopt (sockfd, level, optname, optval, optlen);
353 : : }
354 : :
355 : : #endif /* MOCK_SOCKET_SUPPORTED */
356 : :
357 : : static void
358 : 1 : test_add_any_inet_port_listen_failures (void)
359 : : {
360 : : #ifdef MOCK_SOCKET_SUPPORTED
361 : : const struct
362 : : {
363 : : ListenFailuresConfig config;
364 : : GQuark expected_error_domain; /* 0 if success is expected */
365 : : int expected_error_code; /* 0 if success is expected */
366 : : }
367 : 6 : test_matrix[] =
368 : : {
369 : : /* If everything works, it should all work: */
370 : : { { TRUE, 0, 0, 0, 0, 0, 0 }, 0, 0 },
371 : : /* If IPv4 sockets are not supported, IPv6 should be used: */
372 : : { { TRUE, EAFNOSUPPORT, 0, 0, 0, 0, 0 }, 0, 0 },
373 : : /* If IPv6 sockets are not supported, IPv4 should be used: */
374 : : { { TRUE, 0, EAFNOSUPPORT, 0, 0, 0, 0, }, 0, 0 },
375 : : /* If no sockets are supported, everything should fail: */
376 : : { { TRUE, EAFNOSUPPORT, EAFNOSUPPORT, 0, 0, 0, 0 },
377 : 1 : G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED },
378 : : /* If binding IPv4 fails, IPv6 should be used: */
379 : : { { TRUE, 0, 0, EADDRINUSE, 0, 0, 0 }, 0, 0 },
380 : : /* If binding IPv6 fails, fail overall (the algorithm is not symmetric): */
381 : : { { TRUE, 0, 0, 0, EADDRINUSE, 0, 0 },
382 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
383 : : /* If binding them both fails, fail overall: */
384 : : { { TRUE, 0, 0, EADDRINUSE, EADDRINUSE, 0, 0 },
385 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
386 : : /* If listening on IPv4 fails, IPv6 should be used: */
387 : : { { TRUE, 0, 0, 0, 0, EADDRINUSE, 0 }, 0, 0 },
388 : : /* If listening on IPv6 fails, IPv4 should be used:
389 : : * FIXME: If the IPv6 socket claims to support IPv4, this currently won’t
390 : : * retry with an IPv4-only socket; see https://gitlab.gnome.org/GNOME/glib/-/issues/3604 */
391 : : { { TRUE, 0, 0, 0, 0, 0, EADDRINUSE },
392 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
393 : : /* If listening on IPv6 fails (and the IPv6 socket doesn’t claim to
394 : : * support IPv4), IPv4 should be used: */
395 : : { { FALSE, 0, 0, 0, 0, 0, EADDRINUSE }, 0, 0 },
396 : : /* If listening on both fails, fail overall: */
397 : : { { TRUE, 0, 0, 0, 0, EADDRINUSE, EADDRINUSE },
398 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
399 : : };
400 : :
401 : : /* Override the socket(), bind(), listen() and getsockopt() functions */
402 : 1 : mock_socket = listen_failures_socket;
403 : 1 : mock_bind = listen_failures_bind;
404 : 1 : mock_listen = listen_failures_listen;
405 : 1 : mock_getsockopt = listen_failures_getsockopt;
406 : :
407 : 1 : g_test_summary ("Test that adding a listening port succeeds if either "
408 : : "listening on IPv4 or IPv6 succeeds");
409 : :
410 : 12 : for (size_t i = 0; i < G_N_ELEMENTS (test_matrix); i++)
411 : : {
412 : 11 : GSocketService *service = NULL;
413 : 11 : GError *local_error = NULL;
414 : : uint16_t port;
415 : :
416 : 11 : g_test_message ("Test %" G_GSIZE_FORMAT, i);
417 : :
418 : : /* Configure the mock socket behaviour. */
419 : 11 : memset (&listen_failures_mock_state, 0, sizeof (listen_failures_mock_state));
420 : 11 : listen_failures_mock_state.config = test_matrix[i].config;
421 : :
422 : : /* Create a GSocketService to test. */
423 : 11 : service = g_socket_service_new ();
424 : 11 : port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &local_error);
425 : :
426 : 11 : if (test_matrix[i].expected_error_domain == 0)
427 : : {
428 : 6 : g_assert_no_error (local_error);
429 : 6 : g_assert_cmpuint (port, !=, 0);
430 : : }
431 : : else
432 : : {
433 : 5 : g_assert_error (local_error, test_matrix[i].expected_error_domain,
434 : : test_matrix[i].expected_error_code);
435 : 5 : g_assert_cmpuint (port, ==, 0);
436 : : }
437 : :
438 : 11 : g_clear_error (&local_error);
439 : 11 : g_socket_listener_close (G_SOCKET_LISTENER (service));
440 : 11 : g_clear_object (&service);
441 : : }
442 : :
443 : : /* Tidy up. */
444 : 1 : mock_socket = NULL;
445 : 1 : mock_bind = NULL;
446 : 1 : mock_listen = NULL;
447 : 1 : mock_getsockopt = NULL;
448 : 1 : memset (&listen_failures_mock_state, 0, sizeof (listen_failures_mock_state));
449 : : #else /* if !MOCK_SOCKET_SUPPORTED */
450 : : g_test_skip ("Mock socket not supported");
451 : : #endif
452 : 1 : }
453 : :
454 : : static void
455 : 1 : test_add_inet_port_listen_failures (void)
456 : : {
457 : : #ifdef MOCK_SOCKET_SUPPORTED
458 : : const struct
459 : : {
460 : : ListenFailuresConfig config;
461 : : GQuark expected_error_domain; /* 0 if success is expected */
462 : : int expected_error_code; /* 0 if success is expected */
463 : : }
464 : 5 : test_matrix[] =
465 : : {
466 : : /* If everything works, it should all work: */
467 : : { { TRUE, 0, 0, 0, 0, 0, 0 }, 0, 0 },
468 : : /* If IPv4 sockets are not supported, IPv6 should be used: */
469 : : { { TRUE, EAFNOSUPPORT, 0, 0, 0, 0, 0 }, 0, 0 },
470 : : /* If IPv6 sockets are not supported, IPv4 should be used: */
471 : : { { TRUE, 0, EAFNOSUPPORT, 0, 0, 0, 0, }, 0, 0 },
472 : : /* If no sockets are supported, everything should fail: */
473 : : { { TRUE, EAFNOSUPPORT, EAFNOSUPPORT, 0, 0, 0, 0 },
474 : 1 : G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED },
475 : : /* If binding IPv4 fails, IPv6 should be used: */
476 : : { { TRUE, 0, 0, EADDRINUSE, 0, 0, 0 }, 0, 0 },
477 : : /* If binding IPv6 fails, fail overall (the algorithm is not symmetric): */
478 : : { { TRUE, 0, 0, 0, EADDRINUSE, 0, 0 },
479 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
480 : : /* If binding them both fails, fail overall: */
481 : : { { TRUE, 0, 0, EADDRINUSE, EADDRINUSE, 0, 0 },
482 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
483 : : /* If listening on IPv4 fails, IPv6 should be used: */
484 : : { { TRUE, 0, 0, 0, 0, EADDRINUSE, 0 }, 0, 0 },
485 : : /* If listening on IPv6 fails, IPv4 should be used: */
486 : : { { TRUE, 0, 0, 0, 0, 0, EADDRINUSE }, 0, 0 },
487 : : /* If listening on IPv6 fails (and the IPv6 socket doesn’t claim to
488 : : * support IPv4), IPv4 should be used: */
489 : : { { FALSE, 0, 0, 0, 0, 0, EADDRINUSE }, 0, 0 },
490 : : /* If listening on both fails, fail overall: */
491 : : { { TRUE, 0, 0, 0, 0, EADDRINUSE, EADDRINUSE },
492 : 1 : G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE },
493 : : };
494 : :
495 : : /* Override the socket(), bind(), listen() and getsockopt() functions */
496 : 1 : mock_socket = listen_failures_socket;
497 : 1 : mock_bind = listen_failures_bind;
498 : 1 : mock_listen = listen_failures_listen;
499 : 1 : mock_getsockopt = listen_failures_getsockopt;
500 : :
501 : 1 : g_test_summary ("Test that adding a listening port succeeds if either "
502 : : "listening on IPv4 or IPv6 succeeds");
503 : :
504 : 12 : for (size_t i = 0; i < G_N_ELEMENTS (test_matrix); i++)
505 : : {
506 : 11 : GSocketService *service = NULL;
507 : 11 : GError *local_error = NULL;
508 : : gboolean retval;
509 : :
510 : 11 : g_test_message ("Test %" G_GSIZE_FORMAT, i);
511 : :
512 : : /* Configure the mock socket behaviour. */
513 : 11 : memset (&listen_failures_mock_state, 0, sizeof (listen_failures_mock_state));
514 : 11 : listen_failures_mock_state.config = test_matrix[i].config;
515 : :
516 : : /* Create a GSocketService to test. */
517 : 11 : service = g_socket_service_new ();
518 : 11 : retval = g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), 4321, NULL, &local_error);
519 : :
520 : 11 : if (test_matrix[i].expected_error_domain == 0)
521 : : {
522 : 7 : g_assert_no_error (local_error);
523 : 7 : g_assert_true (retval);
524 : : }
525 : : else
526 : : {
527 : 4 : g_assert_error (local_error, test_matrix[i].expected_error_domain,
528 : : test_matrix[i].expected_error_code);
529 : 4 : g_assert_false (retval);
530 : : }
531 : :
532 : 11 : g_clear_error (&local_error);
533 : :
534 : 11 : g_socket_listener_close (G_SOCKET_LISTENER (service));
535 : 11 : g_clear_object (&service);
536 : : }
537 : :
538 : : /* Tidy up. */
539 : 1 : mock_socket = NULL;
540 : 1 : mock_bind = NULL;
541 : 1 : mock_listen = NULL;
542 : 1 : mock_getsockopt = NULL;
543 : 1 : memset (&listen_failures_mock_state, 0, sizeof (listen_failures_mock_state));
544 : : #else /* if !MOCK_SOCKET_SUPPORTED */
545 : : g_test_skip ("Mock socket not supported");
546 : : #endif
547 : 1 : }
548 : :
549 : : static void
550 : 10 : async_result_cb (GObject *source_object,
551 : : GAsyncResult *result,
552 : : void *user_data)
553 : : {
554 : 10 : GAsyncResult **result_out = user_data;
555 : :
556 : 10 : g_assert (*result_out == NULL);
557 : 10 : *result_out = g_object_ref (result);
558 : :
559 : 10 : g_main_context_wakeup (NULL);
560 : 10 : }
561 : :
562 : : static gboolean
563 : 2 : any_are_null (const void * const *ptr_array,
564 : : size_t n_elements)
565 : : {
566 : 8 : for (size_t i = 0; i < n_elements; i++)
567 : 7 : if (ptr_array[i] == NULL)
568 : 1 : return TRUE;
569 : :
570 : 1 : return FALSE;
571 : : }
572 : :
573 : : typedef struct
574 : : {
575 : : uint16_t listening_port;
576 : : GSocketClient *client;
577 : : GAsyncResult *result;
578 : : GSocketConnection *connection;
579 : : } AcceptMultiSimultaneouslyClient;
580 : :
581 : : static gboolean
582 : 1 : any_client_results_are_null (const AcceptMultiSimultaneouslyClient *clients,
583 : : size_t n_clients)
584 : : {
585 : 6 : for (size_t i = 0; i < n_clients; i++)
586 : 5 : if (clients[i].result == NULL)
587 : 0 : return TRUE;
588 : :
589 : 1 : return FALSE;
590 : : }
591 : :
592 : : static void
593 : 1 : test_accept_multi_simultaneously (void)
594 : : {
595 : 1 : GSocketListener *listener = NULL;
596 : 1 : GAsyncResult *accept_results[5] = { NULL, };
597 : 1 : AcceptMultiSimultaneouslyClient clients[5] = { { 0, NULL, NULL, NULL }, };
598 : 1 : GSocketConnection *server_connection = NULL;
599 : 1 : GCancellable *cancellable = NULL;
600 : 1 : GError *local_error = NULL;
601 : :
602 : 1 : g_test_summary ("Test that accepting multiple pending connections on the "
603 : : "same GMainContext iteration works");
604 : 1 : g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3739");
605 : :
606 : : G_STATIC_ASSERT (G_N_ELEMENTS (clients) >= 2);
607 : : G_STATIC_ASSERT (G_N_ELEMENTS (accept_results) == G_N_ELEMENTS (clients));
608 : :
609 : 1 : listener = g_socket_listener_new ();
610 : 1 : cancellable = g_cancellable_new ();
611 : :
612 : : /* Listen on several ports at once. */
613 : 6 : for (size_t i = 0; i < G_N_ELEMENTS (clients); i++)
614 : : {
615 : 5 : clients[i].listening_port = g_socket_listener_add_any_inet_port (listener, NULL, &local_error);
616 : 5 : g_assert_no_error (local_error);
617 : : }
618 : :
619 : : /* Start to accept a connection, but don’t iterate the `GMainContext` yet. */
620 : 1 : g_socket_listener_accept_async (listener, cancellable, async_result_cb, &accept_results[0]);
621 : :
622 : : /* Connect to multiple ports before iterating the `GMainContext`, so that
623 : : * multiple sockets are ready in the first iteration. */
624 : 6 : for (size_t i = 0; i < G_N_ELEMENTS (clients); i++)
625 : : {
626 : 5 : clients[i].client = g_socket_client_new ();
627 : 5 : g_socket_client_connect_to_host_async (clients[i].client,
628 : 5 : "localhost", clients[i].listening_port,
629 : 5 : cancellable, async_result_cb, &clients[i].result);
630 : : }
631 : :
632 : 5 : while (accept_results[0] == NULL ||
633 : 1 : any_client_results_are_null (clients, G_N_ELEMENTS (clients)))
634 : 3 : g_main_context_iteration (NULL, TRUE);
635 : :
636 : : /* Exactly one server connection should have been created, because we called
637 : : * g_socket_listener_accept_async() once. */
638 : 1 : server_connection = g_socket_listener_accept_finish (listener, accept_results[0], NULL,
639 : : &local_error);
640 : 1 : g_assert_no_error (local_error);
641 : 1 : g_assert_nonnull (server_connection);
642 : 1 : g_io_stream_close (G_IO_STREAM (server_connection), NULL, NULL);
643 : 1 : g_clear_object (&server_connection);
644 : :
645 : : /* Conversely, all the client connection requests should have succeeded
646 : : * because the kernel will queue them on the server side. */
647 : 6 : for (size_t i = 0; i < G_N_ELEMENTS (clients); i++)
648 : : {
649 : 5 : g_assert_nonnull (clients[i].result);
650 : :
651 : 5 : clients[i].connection = g_socket_client_connect_to_host_finish (clients[i].client,
652 : : clients[i].result,
653 : : &local_error);
654 : 5 : g_assert_no_error (local_error);
655 : 5 : g_assert_nonnull (clients[i].connection);
656 : : }
657 : :
658 : : /* Accept the remaining connections. */
659 : 5 : for (size_t i = 1; i < G_N_ELEMENTS (accept_results); i++)
660 : 4 : g_socket_listener_accept_async (listener, cancellable, async_result_cb, &accept_results[i]);
661 : :
662 : 2 : while (any_are_null ((const void * const *) accept_results, G_N_ELEMENTS (accept_results)))
663 : 1 : g_main_context_iteration (NULL, TRUE);
664 : :
665 : 5 : for (size_t i = 1; i < G_N_ELEMENTS (accept_results); i++)
666 : : {
667 : 4 : server_connection = g_socket_listener_accept_finish (listener, accept_results[i], NULL,
668 : : &local_error);
669 : 4 : g_assert_no_error (local_error);
670 : 4 : g_assert_nonnull (server_connection);
671 : 4 : g_io_stream_close (G_IO_STREAM (server_connection), NULL, NULL);
672 : 4 : g_clear_object (&server_connection);
673 : : }
674 : :
675 : : /* Clean up. */
676 : 1 : g_socket_listener_close (listener);
677 : 1 : g_cancellable_cancel (cancellable);
678 : :
679 : 1 : while (g_main_context_iteration (NULL, FALSE));
680 : :
681 : 6 : for (size_t i = 0; i < G_N_ELEMENTS (clients); i++)
682 : : {
683 : 5 : g_io_stream_close (G_IO_STREAM (clients[i].connection), NULL, NULL);
684 : 5 : g_clear_object (&clients[i].connection);
685 : 5 : g_clear_object (&clients[i].result);
686 : 5 : g_assert_finalize_object (clients[i].client);
687 : : }
688 : :
689 : 6 : for (size_t i = 0; i < G_N_ELEMENTS (accept_results); i++)
690 : 5 : g_clear_object (&accept_results[i]);
691 : :
692 : 1 : g_assert_finalize_object (listener);
693 : 1 : g_clear_object (&cancellable);
694 : 1 : }
695 : :
696 : : int
697 : 1 : main (int argc,
698 : : char *argv[])
699 : : {
700 : 1 : g_test_init (&argc, &argv, NULL);
701 : :
702 : 1 : g_test_add_func ("/socket-listener/event-signal", test_event_signal);
703 : 1 : g_test_add_func ("/socket-listener/accept/multi-simultaneously", test_accept_multi_simultaneously);
704 : 1 : g_test_add_func ("/socket-listener/add-any-inet-port/listen-failures", test_add_any_inet_port_listen_failures);
705 : 1 : g_test_add_func ("/socket-listener/add-inet-port/listen-failures", test_add_inet_port_listen_failures);
706 : :
707 : 1 : return g_test_run();
708 : : }
|