]>
Commit | Line | Data |
---|---|---|
cf0bd2f7 | 1 | /* Test non-blocking use of the UDP client. |
2b778ceb | 2 | Copyright (C) 2017-2021 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 | }; | |
41 | ||
42 | static bool_t | |
43 | xdr_test_query (XDR *xdrs, void *data, ...) | |
44 | { | |
45 | struct test_query *p = data; | |
46 | return xdr_uint32_t (xdrs, &p->a) | |
47 | && xdr_uint32_t (xdrs, &p->b) | |
48 | && xdr_uint32_t (xdrs, &p->timeout_ms); | |
49 | } | |
50 | ||
51 | struct test_response | |
52 | { | |
53 | uint32_t server_id; | |
54 | uint32_t seq; | |
55 | uint32_t sum; | |
56 | }; | |
57 | ||
58 | static bool_t | |
59 | xdr_test_response (XDR *xdrs, void *data, ...) | |
60 | { | |
61 | struct test_response *p = data; | |
62 | return xdr_uint32_t (xdrs, &p->server_id) | |
63 | && xdr_uint32_t (xdrs, &p->seq) | |
64 | && xdr_uint32_t (xdrs, &p->sum); | |
65 | } | |
66 | ||
67 | /* Implementation of the test server. */ | |
68 | ||
69 | enum | |
70 | { | |
71 | /* Number of test servers to run. */ | |
72 | SERVER_COUNT = 3, | |
73 | ||
74 | /* RPC parameters, chosen at random. */ | |
75 | PROGNUM = 8242, | |
76 | VERSNUM = 19654, | |
77 | ||
78 | /* Main RPC operation. */ | |
79 | PROC_ADD = 1, | |
80 | ||
81 | /* Request process termination. */ | |
82 | PROC_EXIT, | |
83 | ||
84 | /* Special exit status to mark successful processing. */ | |
85 | EXIT_MARKER = 55, | |
86 | }; | |
87 | ||
88 | /* Set by the parent process to tell test servers apart. */ | |
89 | static int server_id; | |
90 | ||
91 | /* Implementation of the test server. */ | |
92 | static void | |
93 | server_dispatch (struct svc_req *request, SVCXPRT *transport) | |
94 | { | |
95 | /* Query sequence number. */ | |
96 | static uint32_t seq = 0; | |
97 | ++seq; | |
98 | static bool proc_add_seen; | |
99 | ||
100 | if (test_verbose) | |
101 | printf ("info: server_dispatch server_id=%d seq=%u rq_proc=%lu\n", | |
102 | server_id, 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\n", | |
116 | query.a, query.b, query.timeout_ms); | |
117 | ||
118 | usleep (query.timeout_ms * 1000); | |
119 | ||
120 | struct test_response response = | |
121 | { | |
122 | .server_id = server_id, | |
123 | .seq = seq, | |
124 | .sum = query.a + query.b, | |
125 | }; | |
126 | TEST_VERIFY (svc_sendreply (transport, xdr_test_response, | |
127 | (void *) &response)); | |
128 | if (test_verbose) | |
129 | printf (" server id %d response seq=%u sent\n", server_id, seq); | |
130 | proc_add_seen = true; | |
131 | } | |
132 | break; | |
133 | ||
134 | case PROC_EXIT: | |
135 | TEST_VERIFY (proc_add_seen); | |
136 | TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL)); | |
137 | _exit (EXIT_MARKER); | |
138 | break; | |
139 | ||
140 | default: | |
141 | FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc); | |
142 | break; | |
143 | } | |
144 | } | |
145 | ||
146 | /* Return the number seconds since an arbitrary point in time. */ | |
147 | static double | |
148 | get_ticks (void) | |
149 | { | |
150 | { | |
151 | struct timespec ts; | |
152 | if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0) | |
153 | return ts.tv_sec + ts.tv_nsec * 1e-9; | |
154 | } | |
155 | { | |
156 | struct timeval tv; | |
157 | TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0); | |
158 | return tv.tv_sec + tv.tv_usec * 1e-6; | |
159 | } | |
160 | } | |
161 | ||
162 | static int | |
163 | do_test (void) | |
164 | { | |
165 | support_become_root (); | |
166 | support_enter_network_namespace (); | |
167 | ||
168 | /* Information about the test servers. */ | |
169 | struct | |
170 | { | |
171 | SVCXPRT *transport; | |
172 | struct sockaddr_in address; | |
173 | pid_t pid; | |
174 | uint32_t xid; | |
175 | } servers[SERVER_COUNT]; | |
176 | ||
177 | /* Spawn the test servers. */ | |
178 | for (int i = 0; i < SERVER_COUNT; ++i) | |
179 | { | |
180 | servers[i].transport = svcudp_create (RPC_ANYSOCK); | |
181 | TEST_VERIFY_EXIT (servers[i].transport != NULL); | |
182 | servers[i].address = (struct sockaddr_in) | |
183 | { | |
184 | .sin_family = AF_INET, | |
185 | .sin_addr.s_addr = htonl (INADDR_LOOPBACK), | |
186 | .sin_port = htons (servers[i].transport->xp_port), | |
187 | }; | |
188 | servers[i].xid = 0xabcd0101 + i; | |
189 | if (test_verbose) | |
190 | printf ("info: setting up server %d xid=%x on port %d\n", | |
191 | i, servers[i].xid, servers[i].transport->xp_port); | |
192 | ||
193 | server_id = i; | |
194 | servers[i].pid = xfork (); | |
195 | if (servers[i].pid == 0) | |
196 | { | |
197 | TEST_VERIFY (svc_register (servers[i].transport, | |
198 | PROGNUM, VERSNUM, server_dispatch, 0)); | |
199 | svc_run (); | |
200 | FAIL_EXIT1 ("supposed to be unreachable"); | |
201 | } | |
202 | /* We need to close the socket so that we do not accidentally | |
203 | consume the request. */ | |
204 | TEST_VERIFY (close (servers[i].transport->xp_sock) == 0); | |
205 | } | |
206 | ||
207 | ||
208 | /* The following code mirrors what ypbind does. */ | |
209 | ||
210 | /* Copied from clnt_udp.c (like ypbind). */ | |
211 | struct cu_data | |
212 | { | |
213 | int cu_sock; | |
214 | bool_t cu_closeit; | |
215 | struct sockaddr_in cu_raddr; | |
216 | int cu_rlen; | |
217 | struct timeval cu_wait; | |
218 | struct timeval cu_total; | |
219 | struct rpc_err cu_error; | |
220 | XDR cu_outxdrs; | |
221 | u_int cu_xdrpos; | |
222 | u_int cu_sendsz; | |
223 | char *cu_outbuf; | |
224 | u_int cu_recvsz; | |
225 | char cu_inbuf[1]; | |
226 | }; | |
227 | ||
228 | int client_socket = xsocket (AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); | |
229 | CLIENT *clnt = clntudp_create (&servers[0].address, PROGNUM, VERSNUM, | |
230 | /* 5 seconds per-response timeout. */ | |
231 | ((struct timeval) { 5, 0 }), | |
232 | &client_socket); | |
233 | TEST_VERIFY (clnt != NULL); | |
234 | clnt->cl_auth = authunix_create_default (); | |
235 | { | |
236 | struct timeval zero = { 0, 0 }; | |
237 | TEST_VERIFY (clnt_control (clnt, CLSET_TIMEOUT, (void *) &zero)); | |
238 | } | |
239 | ||
240 | /* Poke at internal data structures (like ypbind). */ | |
241 | struct cu_data *cu = (struct cu_data *) clnt->cl_private; | |
242 | ||
243 | /* Send a ping to each server. */ | |
244 | double before_pings = get_ticks (); | |
245 | for (int i = 0; i < SERVER_COUNT; ++i) | |
246 | { | |
247 | if (test_verbose) | |
248 | printf ("info: sending server %d ping\n", i); | |
249 | /* Reset the xid because it is changed by each invocation of | |
250 | clnt_call. Subtract one to compensate for the xid update | |
251 | during the call. */ | |
d9fee042 | 252 | *((uint32_t *) (cu->cu_outbuf)) = servers[i].xid - 1; |
cf0bd2f7 FW |
253 | cu->cu_raddr = servers[i].address; |
254 | ||
255 | struct test_query query = { .a = 100, .b = i + 1 }; | |
256 | if (i == 1) | |
257 | /* Shorter timeout to prefer this server. These timeouts must | |
258 | be much shorter than the 5-second per-response timeout | |
259 | configured with clntudp_create. */ | |
3bdfd9fb | 260 | query.timeout_ms = 750; |
cf0bd2f7 | 261 | else |
3bdfd9fb | 262 | query.timeout_ms = 1500; |
cf0bd2f7 FW |
263 | struct test_response response = { 0 }; |
264 | /* NB: Do not check the return value. The server reply will | |
265 | prove that the call worked. */ | |
266 | double before_one_ping = get_ticks (); | |
267 | clnt_call (clnt, PROC_ADD, | |
268 | xdr_test_query, (void *) &query, | |
269 | xdr_test_response, (void *) &response, | |
270 | ((struct timeval) { 0, 0 })); | |
271 | double after_one_ping = get_ticks (); | |
272 | if (test_verbose) | |
273 | printf ("info: non-blocking send took %f seconds\n", | |
274 | after_one_ping - before_one_ping); | |
275 | /* clnt_call should return immediately. Accept some delay in | |
276 | case the process is descheduled. */ | |
277 | TEST_VERIFY (after_one_ping - before_one_ping < 0.3); | |
278 | } | |
279 | ||
280 | /* Collect the non-blocking response. */ | |
281 | if (test_verbose) | |
282 | printf ("info: collecting response\n"); | |
283 | struct test_response response = { 0 }; | |
284 | TEST_VERIFY | |
285 | (clnt_call (clnt, PROC_ADD, NULL, NULL, | |
286 | xdr_test_response, (void *) &response, | |
287 | ((struct timeval) { 0, 0 })) == RPC_SUCCESS); | |
288 | double after_pings = get_ticks (); | |
289 | if (test_verbose) | |
290 | printf ("info: send/receive took %f seconds\n", | |
291 | after_pings - before_pings); | |
3bdfd9fb | 292 | /* Expected timeout is 0.75 seconds. */ |
cbfc1612 | 293 | TEST_VERIFY (0.70 <= after_pings - before_pings); |
cf0bd2f7 FW |
294 | TEST_VERIFY (after_pings - before_pings < 1.2); |
295 | ||
296 | uint32_t xid; | |
297 | memcpy (&xid, &cu->cu_inbuf, sizeof (xid)); | |
298 | if (test_verbose) | |
299 | printf ("info: non-blocking response: xid=%x server_id=%u seq=%u sum=%u\n", | |
300 | xid, response.server_id, response.seq, response.sum); | |
301 | /* Check that the reply from the preferred server was used. */ | |
302 | TEST_VERIFY (servers[1].xid == xid); | |
303 | TEST_VERIFY (response.server_id == 1); | |
304 | TEST_VERIFY (response.seq == 1); | |
305 | TEST_VERIFY (response.sum == 102); | |
306 | ||
307 | auth_destroy (clnt->cl_auth); | |
308 | clnt_destroy (clnt); | |
309 | ||
310 | for (int i = 0; i < SERVER_COUNT; ++i) | |
311 | { | |
312 | if (test_verbose) | |
313 | printf ("info: requesting server %d termination\n", i); | |
314 | client_socket = RPC_ANYSOCK; | |
315 | clnt = clntudp_create (&servers[i].address, PROGNUM, VERSNUM, | |
316 | ((struct timeval) { 5, 0 }), | |
317 | &client_socket); | |
318 | TEST_VERIFY_EXIT (clnt != NULL); | |
319 | TEST_VERIFY (clnt_call (clnt, PROC_EXIT, | |
320 | (xdrproc_t) xdr_void, NULL, | |
321 | (xdrproc_t) xdr_void, NULL, | |
322 | ((struct timeval) { 3, 0 })) == RPC_SUCCESS); | |
323 | clnt_destroy (clnt); | |
324 | ||
325 | int status; | |
326 | xwaitpid (servers[i].pid, &status, 0); | |
327 | TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER); | |
328 | } | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
333 | #include <support/test-driver.c> |