123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- /* Test non-blocking use of the UDP client.
- Copyright (C) 2017-2019 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <http://www.gnu.org/licenses/>. */
- #include <netinet/in.h>
- #include <rpc/clnt.h>
- #include <rpc/svc.h>
- #include <stdbool.h>
- #include <string.h>
- #include <support/check.h>
- #include <support/namespace.h>
- #include <support/test-driver.h>
- #include <support/xsocket.h>
- #include <support/xunistd.h>
- #include <sys/socket.h>
- #include <time.h>
- #include <unistd.h>
- /* Test data serialization and deserialization. */
- struct test_query
- {
- uint32_t a;
- uint32_t b;
- uint32_t timeout_ms;
- };
- static bool_t
- xdr_test_query (XDR *xdrs, void *data, ...)
- {
- struct test_query *p = data;
- return xdr_uint32_t (xdrs, &p->a)
- && xdr_uint32_t (xdrs, &p->b)
- && xdr_uint32_t (xdrs, &p->timeout_ms);
- }
- struct test_response
- {
- uint32_t server_id;
- uint32_t seq;
- uint32_t sum;
- };
- static bool_t
- xdr_test_response (XDR *xdrs, void *data, ...)
- {
- struct test_response *p = data;
- return xdr_uint32_t (xdrs, &p->server_id)
- && xdr_uint32_t (xdrs, &p->seq)
- && xdr_uint32_t (xdrs, &p->sum);
- }
- /* Implementation of the test server. */
- enum
- {
- /* Number of test servers to run. */
- SERVER_COUNT = 3,
- /* RPC parameters, chosen at random. */
- PROGNUM = 8242,
- VERSNUM = 19654,
- /* Main RPC operation. */
- PROC_ADD = 1,
- /* Request process termination. */
- PROC_EXIT,
- /* Special exit status to mark successful processing. */
- EXIT_MARKER = 55,
- };
- /* Set by the parent process to tell test servers apart. */
- static int server_id;
- /* Implementation of the test server. */
- static void
- server_dispatch (struct svc_req *request, SVCXPRT *transport)
- {
- /* Query sequence number. */
- static uint32_t seq = 0;
- ++seq;
- static bool proc_add_seen;
- if (test_verbose)
- printf ("info: server_dispatch server_id=%d seq=%u rq_proc=%lu\n",
- server_id, seq, request->rq_proc);
- switch (request->rq_proc)
- {
- case PROC_ADD:
- {
- struct test_query query;
- memset (&query, 0xc0, sizeof (query));
- TEST_VERIFY_EXIT
- (svc_getargs (transport, xdr_test_query,
- (void *) &query));
- if (test_verbose)
- printf (" a=%u b=%u timeout_ms=%u\n",
- query.a, query.b, query.timeout_ms);
- usleep (query.timeout_ms * 1000);
- struct test_response response =
- {
- .server_id = server_id,
- .seq = seq,
- .sum = query.a + query.b,
- };
- TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
- (void *) &response));
- if (test_verbose)
- printf (" server id %d response seq=%u sent\n", server_id, seq);
- proc_add_seen = true;
- }
- break;
- case PROC_EXIT:
- TEST_VERIFY (proc_add_seen);
- TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
- _exit (EXIT_MARKER);
- break;
- default:
- FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
- break;
- }
- }
- /* Return the number seconds since an arbitrary point in time. */
- static double
- get_ticks (void)
- {
- {
- struct timespec ts;
- if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
- return ts.tv_sec + ts.tv_nsec * 1e-9;
- }
- {
- struct timeval tv;
- TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
- return tv.tv_sec + tv.tv_usec * 1e-6;
- }
- }
- static int
- do_test (void)
- {
- support_become_root ();
- support_enter_network_namespace ();
- /* Information about the test servers. */
- struct
- {
- SVCXPRT *transport;
- struct sockaddr_in address;
- pid_t pid;
- uint32_t xid;
- } servers[SERVER_COUNT];
- /* Spawn the test servers. */
- for (int i = 0; i < SERVER_COUNT; ++i)
- {
- servers[i].transport = svcudp_create (RPC_ANYSOCK);
- TEST_VERIFY_EXIT (servers[i].transport != NULL);
- servers[i].address = (struct sockaddr_in)
- {
- .sin_family = AF_INET,
- .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
- .sin_port = htons (servers[i].transport->xp_port),
- };
- servers[i].xid = 0xabcd0101 + i;
- if (test_verbose)
- printf ("info: setting up server %d xid=%x on port %d\n",
- i, servers[i].xid, servers[i].transport->xp_port);
- server_id = i;
- servers[i].pid = xfork ();
- if (servers[i].pid == 0)
- {
- TEST_VERIFY (svc_register (servers[i].transport,
- PROGNUM, VERSNUM, server_dispatch, 0));
- svc_run ();
- FAIL_EXIT1 ("supposed to be unreachable");
- }
- /* We need to close the socket so that we do not accidentally
- consume the request. */
- TEST_VERIFY (close (servers[i].transport->xp_sock) == 0);
- }
- /* The following code mirrors what ypbind does. */
- /* Copied from clnt_udp.c (like ypbind). */
- struct cu_data
- {
- int cu_sock;
- bool_t cu_closeit;
- struct sockaddr_in cu_raddr;
- int cu_rlen;
- struct timeval cu_wait;
- struct timeval cu_total;
- struct rpc_err cu_error;
- XDR cu_outxdrs;
- u_int cu_xdrpos;
- u_int cu_sendsz;
- char *cu_outbuf;
- u_int cu_recvsz;
- char cu_inbuf[1];
- };
- int client_socket = xsocket (AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
- CLIENT *clnt = clntudp_create (&servers[0].address, PROGNUM, VERSNUM,
- /* 5 seconds per-response timeout. */
- ((struct timeval) { 5, 0 }),
- &client_socket);
- TEST_VERIFY (clnt != NULL);
- clnt->cl_auth = authunix_create_default ();
- {
- struct timeval zero = { 0, 0 };
- TEST_VERIFY (clnt_control (clnt, CLSET_TIMEOUT, (void *) &zero));
- }
- /* Poke at internal data structures (like ypbind). */
- struct cu_data *cu = (struct cu_data *) clnt->cl_private;
- /* Send a ping to each server. */
- double before_pings = get_ticks ();
- for (int i = 0; i < SERVER_COUNT; ++i)
- {
- if (test_verbose)
- printf ("info: sending server %d ping\n", i);
- /* Reset the xid because it is changed by each invocation of
- clnt_call. Subtract one to compensate for the xid update
- during the call. */
- *((uint32_t *) (cu->cu_outbuf)) = servers[i].xid - 1;
- cu->cu_raddr = servers[i].address;
- struct test_query query = { .a = 100, .b = i + 1 };
- if (i == 1)
- /* Shorter timeout to prefer this server. These timeouts must
- be much shorter than the 5-second per-response timeout
- configured with clntudp_create. */
- query.timeout_ms = 750;
- else
- query.timeout_ms = 1500;
- struct test_response response = { 0 };
- /* NB: Do not check the return value. The server reply will
- prove that the call worked. */
- double before_one_ping = get_ticks ();
- clnt_call (clnt, PROC_ADD,
- xdr_test_query, (void *) &query,
- xdr_test_response, (void *) &response,
- ((struct timeval) { 0, 0 }));
- double after_one_ping = get_ticks ();
- if (test_verbose)
- printf ("info: non-blocking send took %f seconds\n",
- after_one_ping - before_one_ping);
- /* clnt_call should return immediately. Accept some delay in
- case the process is descheduled. */
- TEST_VERIFY (after_one_ping - before_one_ping < 0.3);
- }
- /* Collect the non-blocking response. */
- if (test_verbose)
- printf ("info: collecting response\n");
- struct test_response response = { 0 };
- TEST_VERIFY
- (clnt_call (clnt, PROC_ADD, NULL, NULL,
- xdr_test_response, (void *) &response,
- ((struct timeval) { 0, 0 })) == RPC_SUCCESS);
- double after_pings = get_ticks ();
- if (test_verbose)
- printf ("info: send/receive took %f seconds\n",
- after_pings - before_pings);
- /* Expected timeout is 0.75 seconds. */
- TEST_VERIFY (0.75 <= after_pings - before_pings);
- TEST_VERIFY (after_pings - before_pings < 1.2);
- uint32_t xid;
- memcpy (&xid, &cu->cu_inbuf, sizeof (xid));
- if (test_verbose)
- printf ("info: non-blocking response: xid=%x server_id=%u seq=%u sum=%u\n",
- xid, response.server_id, response.seq, response.sum);
- /* Check that the reply from the preferred server was used. */
- TEST_VERIFY (servers[1].xid == xid);
- TEST_VERIFY (response.server_id == 1);
- TEST_VERIFY (response.seq == 1);
- TEST_VERIFY (response.sum == 102);
- auth_destroy (clnt->cl_auth);
- clnt_destroy (clnt);
- for (int i = 0; i < SERVER_COUNT; ++i)
- {
- if (test_verbose)
- printf ("info: requesting server %d termination\n", i);
- client_socket = RPC_ANYSOCK;
- clnt = clntudp_create (&servers[i].address, PROGNUM, VERSNUM,
- ((struct timeval) { 5, 0 }),
- &client_socket);
- TEST_VERIFY_EXIT (clnt != NULL);
- TEST_VERIFY (clnt_call (clnt, PROC_EXIT,
- (xdrproc_t) xdr_void, NULL,
- (xdrproc_t) xdr_void, NULL,
- ((struct timeval) { 3, 0 })) == RPC_SUCCESS);
- clnt_destroy (clnt);
- int status;
- xwaitpid (servers[i].pid, &status, 0);
- TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
- }
- return 0;
- }
- #include <support/test-driver.c>
|