]>
Commit | Line | Data |
---|---|---|
cf0bd2f7 | 1 | /* Test timeout handling in the UDP client. |
6d7e8eda | 2 | Copyright (C) 2017-2023 Free Software Foundation, Inc. |
cf0bd2f7 FW |
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 | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
cf0bd2f7 FW |
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> | |
f34c4d0f MC |
32 | #include <stdlib.h> |
33 | ||
34 | static pid_t server_pid; | |
cf0bd2f7 FW |
35 | |
36 | /* Test data serialization and deserialization. */ | |
37 | ||
38 | struct 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 | ||
47 | static bool_t | |
48 | xdr_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 | ||
58 | struct test_response | |
59 | { | |
60 | uint32_t seq; | |
61 | uint32_t sum; | |
62 | }; | |
63 | ||
64 | static bool_t | |
65 | xdr_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 | ||
74 | enum | |
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 | ||
93 | static void | |
94 | server_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 ("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 (" 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 (" 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 (per_packet_timeout); | |
152 | } | |
153 | } | |
154 | else if (query.timeout_ms > 0) | |
155 | usleep (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 | ||
f34c4d0f MC |
183 | /* Function to be called before exit to make sure the |
184 | server process is properly killed. */ | |
185 | static void | |
186 | kill_server (void) | |
187 | { | |
188 | kill (server_pid, SIGTERM); | |
189 | } | |
190 | ||
cf0bd2f7 FW |
191 | /* Implementation of the test client. */ |
192 | ||
193 | static struct test_response | |
194 | test_call (CLIENT *clnt, int proc, struct test_query query, | |
195 | struct timeval timeout) | |
196 | { | |
197 | if (test_verbose) | |
198 | printf ("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 | ||
210 | static void | |
211 | test_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. */ | |
224 | static void | |
225 | test_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 ("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. */ | |
240 | static double | |
241 | get_ticks (void) | |
242 | { | |
243 | { | |
244 | struct timespec ts; | |
245 | if (clock_gettime (CLOCK_MONOTONIC, &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 | ||
255 | static void | |
256 | test_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_ADD, | |
278 | (struct test_query) { .a = 17, .b = 4 }, | |
279 | (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_ADD, | |
288 | (struct test_query) { | |
289 | .a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21, | |
290 | }, | |
291 | (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 ("info: 21 garbage packets took %f seconds\n", after - before); | |
cbfc1612 ST |
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); | |
cf0bd2f7 FW |
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_ADD, | |
309 | (struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 }, | |
310 | (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 ("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). */ | |
cbfc1612 | 319 | TEST_VERIFY (1.45 <= after - before); |
cf0bd2f7 FW |
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_ADD, | |
328 | (struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 }, | |
329 | (struct timeval) { 0, 750 * 1000 }); | |
330 | after = get_ticks (); | |
331 | if (test_verbose) | |
332 | printf ("info: 0.75 second timeout took %f seconds\n", | |
333 | after - before); | |
cbfc1612 | 334 | TEST_VERIFY (0.70 <= after - before); |
cf0bd2f7 FW |
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_ADD, | |
344 | (struct test_query) { | |
345 | .a = 170, .b = 40, .timeout_ms = 1200, | |
346 | .garbage_packets = with_garbage * 21 | |
347 | }, | |
348 | (struct timeval) { 0, 750 * 1000 }); | |
349 | after = get_ticks (); | |
350 | if (test_verbose) | |
351 | printf ("info: test_udp_server: 0.75 second timeout took %f seconds" | |
352 | " (garbage %d)\n", | |
353 | after - before, with_garbage); | |
cbfc1612 | 354 | TEST_VERIFY (0.70 <= after - before); |
cf0bd2f7 FW |
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_ADD, | |
362 | (struct test_query) { | |
363 | .a = 170, .b = 40, .timeout_ms = 3000, | |
364 | .garbage_packets = with_garbage * 30 | |
365 | }, | |
00c3da43 | 366 | (struct timeval) { 2, 500 * 1000 }); |
cf0bd2f7 FW |
367 | after = get_ticks (); |
368 | if (test_verbose) | |
00c3da43 | 369 | printf ("info: test_udp_server: 2.5 second timeout took %f seconds" |
cf0bd2f7 FW |
370 | " (garbage %d)\n", |
371 | after - before, with_garbage); | |
cbfc1612 | 372 | TEST_VERIFY (2.45 <= after - before); |
cf0bd2f7 FW |
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 | ||
385 | static int | |
386 | do_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 | ||
f34c4d0f MC |
395 | server_pid = xfork (); |
396 | if (server_pid == 0) | |
cf0bd2f7 FW |
397 | { |
398 | svc_run (); | |
399 | FAIL_EXIT1 ("supposed to be unreachable"); | |
400 | } | |
f34c4d0f | 401 | atexit (kill_server); |
cf0bd2f7 FW |
402 | test_udp_server (transport->xp_port); |
403 | ||
404 | int status; | |
f34c4d0f | 405 | xwaitpid (server_pid, &status, 0); |
cf0bd2f7 FW |
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> |