1 | /* Test non-blocking use of 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 | |
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 (format: "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 (format: " a=%u b=%u timeout_ms=%u\n" , |
116 | query.a, query.b, query.timeout_ms); |
117 | |
118 | usleep (useconds: 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 (format: " 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, tp: &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 (format: "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 (format: "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. */ |
252 | *((uint32_t *) (cu->cu_outbuf)) = servers[i].xid - 1; |
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. */ |
260 | query.timeout_ms = 750; |
261 | else |
262 | query.timeout_ms = 1500; |
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 (format: "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 (format: "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 (format: "info: send/receive took %f seconds\n" , |
291 | after_pings - before_pings); |
292 | /* Expected timeout is 0.75 seconds. */ |
293 | TEST_VERIFY (0.70 <= after_pings - before_pings); |
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 (format: "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 (format: "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: &status, flags: 0); |
327 | TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER); |
328 | } |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | #include <support/test-driver.c> |
334 | |