123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /* Test timeout handling in 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;
- uint32_t wait_for_seq;
- uint32_t garbage_packets;
- };
- 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)
- && xdr_uint32_t (xdrs, &p->wait_for_seq)
- && xdr_uint32_t (xdrs, &p->garbage_packets);
- }
- struct test_response
- {
- 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->seq)
- && xdr_uint32_t (xdrs, &p->sum);
- }
- /* Implementation of the test server. */
- enum
- {
- /* RPC parameters, chosen at random. */
- PROGNUM = 15717,
- VERSNUM = 13689,
- /* Main RPC operation. */
- PROC_ADD = 1,
- /* Reset the sequence number. */
- PROC_RESET_SEQ,
- /* Request process termination. */
- PROC_EXIT,
- /* Special exit status to mark successful processing. */
- EXIT_MARKER = 55,
- };
- static void
- server_dispatch (struct svc_req *request, SVCXPRT *transport)
- {
- /* Query sequence number. */
- static uint32_t seq = 0;
- ++seq;
- if (test_verbose)
- printf ("info: server_dispatch seq=%u rq_proc=%lu\n",
- 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 wait_for_seq=%u"
- " garbage_packets=%u\n",
- query.a, query.b, query.timeout_ms, query.wait_for_seq,
- query.garbage_packets);
- if (seq < query.wait_for_seq)
- {
- /* No response at this point. */
- if (test_verbose)
- printf (" skipped response\n");
- break;
- }
- if (query.garbage_packets > 0)
- {
- int per_packet_timeout;
- if (query.timeout_ms > 0)
- per_packet_timeout
- = query.timeout_ms * 1000 / query.garbage_packets;
- else
- per_packet_timeout = 0;
- char buf[20];
- memset (&buf, 0xc0, sizeof (buf));
- for (int i = 0; i < query.garbage_packets; ++i)
- {
- /* 13 is relatively prime to 20 = sizeof (buf) + 1, so
- the len variable will cover the entire interval
- [0, 20] if query.garbage_packets is sufficiently
- large. */
- size_t len = (i * 13 + 1) % (sizeof (buf) + 1);
- TEST_VERIFY (sendto (transport->xp_sock,
- buf, len, MSG_NOSIGNAL,
- (struct sockaddr *) &transport->xp_raddr,
- transport->xp_addrlen) == len);
- if (per_packet_timeout > 0)
- usleep (per_packet_timeout);
- }
- }
- else if (query.timeout_ms > 0)
- usleep (query.timeout_ms * 1000);
- struct test_response response =
- {
- .seq = seq,
- .sum = query.a + query.b,
- };
- TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
- (void *) &response));
- }
- break;
- case PROC_RESET_SEQ:
- seq = 0;
- TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
- break;
- case PROC_EXIT:
- 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;
- }
- }
- /* Implementation of the test client. */
- static struct test_response
- test_call (CLIENT *clnt, int proc, struct test_query query,
- struct timeval timeout)
- {
- if (test_verbose)
- printf ("info: test_call proc=%d timeout=%lu.%06lu\n",
- proc, (unsigned long) timeout.tv_sec,
- (unsigned long) timeout.tv_usec);
- struct test_response response;
- TEST_VERIFY_EXIT (clnt_call (clnt, proc,
- xdr_test_query, (void *) &query,
- xdr_test_response, (void *) &response,
- timeout)
- == RPC_SUCCESS);
- return response;
- }
- static void
- test_call_timeout (CLIENT *clnt, int proc, struct test_query query,
- struct timeval timeout)
- {
- struct test_response response;
- TEST_VERIFY (clnt_call (clnt, proc,
- xdr_test_query, (void *) &query,
- xdr_test_response, (void *) &response,
- timeout)
- == RPC_TIMEDOUT);
- }
- /* Complete one regular RPC call to drain the server socket
- buffer. Resets the sequence number. */
- static void
- test_call_flush (CLIENT *clnt)
- {
- /* This needs a longer timeout to flush out all pending requests.
- The choice of 5 seconds is larger than the per-response timeouts
- requested via the timeout_ms field. */
- if (test_verbose)
- printf ("info: flushing pending queries\n");
- TEST_VERIFY_EXIT (clnt_call (clnt, PROC_RESET_SEQ,
- (xdrproc_t) xdr_void, NULL,
- (xdrproc_t) xdr_void, NULL,
- ((struct timeval) { 5, 0 }))
- == RPC_SUCCESS);
- }
- /* 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 void
- test_udp_server (int port)
- {
- struct sockaddr_in sin =
- {
- .sin_family = AF_INET,
- .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
- .sin_port = htons (port)
- };
- int sock = RPC_ANYSOCK;
- /* The client uses a 1.5 second timeout for retries. The timeouts
- are arbitrary, but chosen so that there is a substantial gap
- between them, but the total time spent waiting is not too
- large. */
- CLIENT *clnt = clntudp_create (&sin, PROGNUM, VERSNUM,
- (struct timeval) { 1, 500 * 1000 },
- &sock);
- TEST_VERIFY_EXIT (clnt != NULL);
- /* Basic call/response test. */
- struct test_response response = test_call
- (clnt, PROC_ADD,
- (struct test_query) { .a = 17, .b = 4 },
- (struct timeval) { 3, 0 });
- TEST_VERIFY (response.sum == 21);
- TEST_VERIFY (response.seq == 1);
- /* Check that garbage packets do not interfere with timeout
- processing. */
- double before = get_ticks ();
- response = test_call
- (clnt, PROC_ADD,
- (struct test_query) {
- .a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21,
- },
- (struct timeval) { 3, 0 });
- TEST_VERIFY (response.sum == 23);
- TEST_VERIFY (response.seq == 2);
- double after = get_ticks ();
- if (test_verbose)
- printf ("info: 21 garbage packets took %f seconds\n", after - before);
- /* Expected timeout is 0.5 seconds. Add some slack in case process
- scheduling delays processing the query or response, but do not
- accept a retry (which would happen at 1.5 seconds). */
- TEST_VERIFY (0.5 <= after - before);
- TEST_VERIFY (after - before < 1.2);
- test_call_flush (clnt);
- /* Check that missing a response introduces a 1.5 second timeout, as
- requested when calling clntudp_create. */
- before = get_ticks ();
- response = test_call
- (clnt, PROC_ADD,
- (struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 },
- (struct timeval) { 3, 0 });
- TEST_VERIFY (response.sum == 210);
- TEST_VERIFY (response.seq == 2);
- after = get_ticks ();
- if (test_verbose)
- printf ("info: skipping one response took %f seconds\n",
- after - before);
- /* Expected timeout is 1.5 seconds. Do not accept a second retry
- (which would happen at 3 seconds). */
- TEST_VERIFY (1.5 <= after - before);
- TEST_VERIFY (after - before < 2.9);
- test_call_flush (clnt);
- /* Check that the overall timeout wins against the per-query
- timeout. */
- before = get_ticks ();
- test_call_timeout
- (clnt, PROC_ADD,
- (struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 },
- (struct timeval) { 0, 750 * 1000 });
- after = get_ticks ();
- if (test_verbose)
- printf ("info: 0.75 second timeout took %f seconds\n",
- after - before);
- TEST_VERIFY (0.75 <= after - before);
- TEST_VERIFY (after - before < 1.4);
- test_call_flush (clnt);
- for (int with_garbage = 0; with_garbage < 2; ++with_garbage)
- {
- /* Check that no response at all causes the client to bail out. */
- before = get_ticks ();
- test_call_timeout
- (clnt, PROC_ADD,
- (struct test_query) {
- .a = 170, .b = 40, .timeout_ms = 1200,
- .garbage_packets = with_garbage * 21
- },
- (struct timeval) { 0, 750 * 1000 });
- after = get_ticks ();
- if (test_verbose)
- printf ("info: test_udp_server: 0.75 second timeout took %f seconds"
- " (garbage %d)\n",
- after - before, with_garbage);
- TEST_VERIFY (0.75 <= after - before);
- TEST_VERIFY (after - before < 1.4);
- test_call_flush (clnt);
- /* As above, but check the total timeout. */
- before = get_ticks ();
- test_call_timeout
- (clnt, PROC_ADD,
- (struct test_query) {
- .a = 170, .b = 40, .timeout_ms = 3000,
- .garbage_packets = with_garbage * 30
- },
- (struct timeval) { 2, 500 * 1000 });
- after = get_ticks ();
- if (test_verbose)
- printf ("info: test_udp_server: 2.5 second timeout took %f seconds"
- " (garbage %d)\n",
- after - before, with_garbage);
- TEST_VERIFY (2.5 <= after - before);
- TEST_VERIFY (after - before < 3.0);
- test_call_flush (clnt);
- }
- TEST_VERIFY_EXIT (clnt_call (clnt, PROC_EXIT,
- (xdrproc_t) xdr_void, NULL,
- (xdrproc_t) xdr_void, NULL,
- ((struct timeval) { 5, 0 }))
- == RPC_SUCCESS);
- clnt_destroy (clnt);
- }
- static int
- do_test (void)
- {
- support_become_root ();
- support_enter_network_namespace ();
- SVCXPRT *transport = svcudp_create (RPC_ANYSOCK);
- TEST_VERIFY_EXIT (transport != NULL);
- TEST_VERIFY (svc_register (transport, PROGNUM, VERSNUM, server_dispatch, 0));
- pid_t pid = xfork ();
- if (pid == 0)
- {
- svc_run ();
- FAIL_EXIT1 ("supposed to be unreachable");
- }
- test_udp_server (transport->xp_port);
- int status;
- xwaitpid (pid, &status, 0);
- TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
- SVC_DESTROY (transport);
- return 0;
- }
- /* The minimum run time is around 17 seconds. */
- #define TIMEOUT 25
- #include <support/test-driver.c>
|