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
35struct test_query
36{
37 uint32_t a;
38 uint32_t b;
39 uint32_t timeout_ms;
40};
41
42static bool_t
43xdr_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
51struct test_response
52{
53 uint32_t server_id;
54 uint32_t seq;
55 uint32_t sum;
56};
57
58static bool_t
59xdr_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
69enum
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. */
89static int server_id;
90
91/* Implementation of the test server. */
92static void
93server_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. */
147static double
148get_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
162static int
163do_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

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