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