1/* Test timeout handling in the UDP client.
2 Copyright (C) 2017-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <netinet/in.h>
20#include <rpc/clnt.h>
21#include <rpc/svc.h>
22#include <stdbool.h>
23#include <string.h>
24#include <support/check.h>
25#include <support/namespace.h>
26#include <support/test-driver.h>
27#include <support/xsocket.h>
28#include <support/xunistd.h>
29#include <sys/socket.h>
30#include <time.h>
31#include <unistd.h>
32#include <stdlib.h>
33
34static pid_t server_pid;
35
36/* Test data serialization and deserialization. */
37
38struct test_query
39{
40 uint32_t a;
41 uint32_t b;
42 uint32_t timeout_ms;
43 uint32_t wait_for_seq;
44 uint32_t garbage_packets;
45};
46
47static bool_t
48xdr_test_query (XDR *xdrs, void *data, ...)
49{
50 struct test_query *p = data;
51 return xdr_uint32_t (xdrs, &p->a)
52 && xdr_uint32_t (xdrs, &p->b)
53 && xdr_uint32_t (xdrs, &p->timeout_ms)
54 && xdr_uint32_t (xdrs, &p->wait_for_seq)
55 && xdr_uint32_t (xdrs, &p->garbage_packets);
56}
57
58struct test_response
59{
60 uint32_t seq;
61 uint32_t sum;
62};
63
64static bool_t
65xdr_test_response (XDR *xdrs, void *data, ...)
66{
67 struct test_response *p = data;
68 return xdr_uint32_t (xdrs, &p->seq)
69 && xdr_uint32_t (xdrs, &p->sum);
70}
71
72/* Implementation of the test server. */
73
74enum
75 {
76 /* RPC parameters, chosen at random. */
77 PROGNUM = 15717,
78 VERSNUM = 13689,
79
80 /* Main RPC operation. */
81 PROC_ADD = 1,
82
83 /* Reset the sequence number. */
84 PROC_RESET_SEQ,
85
86 /* Request process termination. */
87 PROC_EXIT,
88
89 /* Special exit status to mark successful processing. */
90 EXIT_MARKER = 55,
91 };
92
93static void
94server_dispatch (struct svc_req *request, SVCXPRT *transport)
95{
96 /* Query sequence number. */
97 static uint32_t seq = 0;
98 ++seq;
99
100 if (test_verbose)
101 printf (format: "info: server_dispatch seq=%u rq_proc=%lu\n",
102 seq, request->rq_proc);
103
104 switch (request->rq_proc)
105 {
106 case PROC_ADD:
107 {
108 struct test_query query;
109 memset (&query, 0xc0, sizeof (query));
110 TEST_VERIFY_EXIT
111 (svc_getargs (transport, xdr_test_query,
112 (void *) &query));
113
114 if (test_verbose)
115 printf (format: " a=%u b=%u timeout_ms=%u wait_for_seq=%u"
116 " garbage_packets=%u\n",
117 query.a, query.b, query.timeout_ms, query.wait_for_seq,
118 query.garbage_packets);
119
120 if (seq < query.wait_for_seq)
121 {
122 /* No response at this point. */
123 if (test_verbose)
124 printf (format: " skipped response\n");
125 break;
126 }
127
128 if (query.garbage_packets > 0)
129 {
130 int per_packet_timeout;
131 if (query.timeout_ms > 0)
132 per_packet_timeout
133 = query.timeout_ms * 1000 / query.garbage_packets;
134 else
135 per_packet_timeout = 0;
136
137 char buf[20];
138 memset (&buf, 0xc0, sizeof (buf));
139 for (int i = 0; i < query.garbage_packets; ++i)
140 {
141 /* 13 is relatively prime to 20 = sizeof (buf) + 1, so
142 the len variable will cover the entire interval
143 [0, 20] if query.garbage_packets is sufficiently
144 large. */
145 size_t len = (i * 13 + 1) % (sizeof (buf) + 1);
146 TEST_VERIFY (sendto (transport->xp_sock,
147 buf, len, MSG_NOSIGNAL,
148 (struct sockaddr *) &transport->xp_raddr,
149 transport->xp_addrlen) == len);
150 if (per_packet_timeout > 0)
151 usleep (useconds: per_packet_timeout);
152 }
153 }
154 else if (query.timeout_ms > 0)
155 usleep (useconds: query.timeout_ms * 1000);
156
157 struct test_response response =
158 {
159 .seq = seq,
160 .sum = query.a + query.b,
161 };
162 TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
163 (void *) &response));
164 }
165 break;
166
167 case PROC_RESET_SEQ:
168 seq = 0;
169 TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
170 break;
171
172 case PROC_EXIT:
173 TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
174 _exit (EXIT_MARKER);
175 break;
176
177 default:
178 FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
179 break;
180 }
181}
182
183/* Function to be called before exit to make sure the
184 server process is properly killed. */
185static void
186kill_server (void)
187{
188 kill (pid: server_pid, SIGTERM);
189}
190
191/* Implementation of the test client. */
192
193static struct test_response
194test_call (CLIENT *clnt, int proc, struct test_query query,
195 struct timeval timeout)
196{
197 if (test_verbose)
198 printf (format: "info: test_call proc=%d timeout=%lu.%06lu\n",
199 proc, (unsigned long) timeout.tv_sec,
200 (unsigned long) timeout.tv_usec);
201 struct test_response response;
202 TEST_VERIFY_EXIT (clnt_call (clnt, proc,
203 xdr_test_query, (void *) &query,
204 xdr_test_response, (void *) &response,
205 timeout)
206 == RPC_SUCCESS);
207 return response;
208}
209
210static void
211test_call_timeout (CLIENT *clnt, int proc, struct test_query query,
212 struct timeval timeout)
213{
214 struct test_response response;
215 TEST_VERIFY (clnt_call (clnt, proc,
216 xdr_test_query, (void *) &query,
217 xdr_test_response, (void *) &response,
218 timeout)
219 == RPC_TIMEDOUT);
220}
221
222/* Complete one regular RPC call to drain the server socket
223 buffer. Resets the sequence number. */
224static void
225test_call_flush (CLIENT *clnt)
226{
227 /* This needs a longer timeout to flush out all pending requests.
228 The choice of 5 seconds is larger than the per-response timeouts
229 requested via the timeout_ms field. */
230 if (test_verbose)
231 printf (format: "info: flushing pending queries\n");
232 TEST_VERIFY_EXIT (clnt_call (clnt, PROC_RESET_SEQ,
233 (xdrproc_t) xdr_void, NULL,
234 (xdrproc_t) xdr_void, NULL,
235 ((struct timeval) { 5, 0 }))
236 == RPC_SUCCESS);
237}
238
239/* Return the number seconds since an arbitrary point in time. */
240static double
241get_ticks (void)
242{
243 {
244 struct timespec ts;
245 if (clock_gettime (CLOCK_MONOTONIC, tp: &ts) == 0)
246 return ts.tv_sec + ts.tv_nsec * 1e-9;
247 }
248 {
249 struct timeval tv;
250 TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
251 return tv.tv_sec + tv.tv_usec * 1e-6;
252 }
253}
254
255static void
256test_udp_server (int port)
257{
258 struct sockaddr_in sin =
259 {
260 .sin_family = AF_INET,
261 .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
262 .sin_port = htons (port)
263 };
264 int sock = RPC_ANYSOCK;
265
266 /* The client uses a 1.5 second timeout for retries. The timeouts
267 are arbitrary, but chosen so that there is a substantial gap
268 between them, but the total time spent waiting is not too
269 large. */
270 CLIENT *clnt = clntudp_create (&sin, PROGNUM, VERSNUM,
271 (struct timeval) { 1, 500 * 1000 },
272 &sock);
273 TEST_VERIFY_EXIT (clnt != NULL);
274
275 /* Basic call/response test. */
276 struct test_response response = test_call
277 (clnt, proc: PROC_ADD,
278 query: (struct test_query) { .a = 17, .b = 4 },
279 timeout: (struct timeval) { 3, 0 });
280 TEST_VERIFY (response.sum == 21);
281 TEST_VERIFY (response.seq == 1);
282
283 /* Check that garbage packets do not interfere with timeout
284 processing. */
285 double before = get_ticks ();
286 response = test_call
287 (clnt, proc: PROC_ADD,
288 query: (struct test_query) {
289 .a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21,
290 },
291 timeout: (struct timeval) { 3, 0 });
292 TEST_VERIFY (response.sum == 23);
293 TEST_VERIFY (response.seq == 2);
294 double after = get_ticks ();
295 if (test_verbose)
296 printf (format: "info: 21 garbage packets took %f seconds\n", after - before);
297 /* Expected timeout is 0.5 seconds. Add some slack for rounding errors and
298 in case process scheduling delays processing the query or response, but
299 do not accept a retry (which would happen at 1.5 seconds). */
300 TEST_VERIFY (0.45 <= after - before);
301 TEST_VERIFY (after - before < 1.2);
302 test_call_flush (clnt);
303
304 /* Check that missing a response introduces a 1.5 second timeout, as
305 requested when calling clntudp_create. */
306 before = get_ticks ();
307 response = test_call
308 (clnt, proc: PROC_ADD,
309 query: (struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 },
310 timeout: (struct timeval) { 3, 0 });
311 TEST_VERIFY (response.sum == 210);
312 TEST_VERIFY (response.seq == 2);
313 after = get_ticks ();
314 if (test_verbose)
315 printf (format: "info: skipping one response took %f seconds\n",
316 after - before);
317 /* Expected timeout is 1.5 seconds. Do not accept a second retry
318 (which would happen at 3 seconds). */
319 TEST_VERIFY (1.45 <= after - before);
320 TEST_VERIFY (after - before < 2.9);
321 test_call_flush (clnt);
322
323 /* Check that the overall timeout wins against the per-query
324 timeout. */
325 before = get_ticks ();
326 test_call_timeout
327 (clnt, proc: PROC_ADD,
328 query: (struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 },
329 timeout: (struct timeval) { 0, 750 * 1000 });
330 after = get_ticks ();
331 if (test_verbose)
332 printf (format: "info: 0.75 second timeout took %f seconds\n",
333 after - before);
334 TEST_VERIFY (0.70 <= after - before);
335 TEST_VERIFY (after - before < 1.4);
336 test_call_flush (clnt);
337
338 for (int with_garbage = 0; with_garbage < 2; ++with_garbage)
339 {
340 /* Check that no response at all causes the client to bail out. */
341 before = get_ticks ();
342 test_call_timeout
343 (clnt, proc: PROC_ADD,
344 query: (struct test_query) {
345 .a = 170, .b = 40, .timeout_ms = 1200,
346 .garbage_packets = with_garbage * 21
347 },
348 timeout: (struct timeval) { 0, 750 * 1000 });
349 after = get_ticks ();
350 if (test_verbose)
351 printf (format: "info: test_udp_server: 0.75 second timeout took %f seconds"
352 " (garbage %d)\n",
353 after - before, with_garbage);
354 TEST_VERIFY (0.70 <= after - before);
355 TEST_VERIFY (after - before < 1.4);
356 test_call_flush (clnt);
357
358 /* As above, but check the total timeout. */
359 before = get_ticks ();
360 test_call_timeout
361 (clnt, proc: PROC_ADD,
362 query: (struct test_query) {
363 .a = 170, .b = 40, .timeout_ms = 3000,
364 .garbage_packets = with_garbage * 30
365 },
366 timeout: (struct timeval) { 2, 500 * 1000 });
367 after = get_ticks ();
368 if (test_verbose)
369 printf (format: "info: test_udp_server: 2.5 second timeout took %f seconds"
370 " (garbage %d)\n",
371 after - before, with_garbage);
372 TEST_VERIFY (2.45 <= after - before);
373 TEST_VERIFY (after - before < 3.0);
374 test_call_flush (clnt);
375 }
376
377 TEST_VERIFY_EXIT (clnt_call (clnt, PROC_EXIT,
378 (xdrproc_t) xdr_void, NULL,
379 (xdrproc_t) xdr_void, NULL,
380 ((struct timeval) { 5, 0 }))
381 == RPC_SUCCESS);
382 clnt_destroy (clnt);
383}
384
385static int
386do_test (void)
387{
388 support_become_root ();
389 support_enter_network_namespace ();
390
391 SVCXPRT *transport = svcudp_create (RPC_ANYSOCK);
392 TEST_VERIFY_EXIT (transport != NULL);
393 TEST_VERIFY (svc_register (transport, PROGNUM, VERSNUM, server_dispatch, 0));
394
395 server_pid = xfork ();
396 if (server_pid == 0)
397 {
398 svc_run ();
399 FAIL_EXIT1 ("supposed to be unreachable");
400 }
401 atexit (func: kill_server);
402 test_udp_server (port: transport->xp_port);
403
404 int status;
405 xwaitpid (server_pid, status: &status, flags: 0);
406 TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
407
408 SVC_DESTROY (transport);
409 return 0;
410}
411
412/* The minimum run time is around 17 seconds. */
413#define TIMEOUT 25
414#include <support/test-driver.c>
415

source code of glibc/sunrpc/tst-udp-timeout.c