/* Test non-blocking use of the UDP client. Copyright (C) 2017-2018 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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