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
xdr_test_query(XDR * xdrs,void * data,...)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
xdr_test_response(XDR * xdrs,void * data,...)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
server_dispatch(struct svc_req * request,SVCXPRT * transport)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
get_ticks(void)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
do_test(void)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.  */
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 ("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);
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 ("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>
334