mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-30 10:45:40 +03:00 
			
		
		
		
	This commit fixes various aspects in the UDP client timeout handling. Timeouts are now applied in a more consistent fashion. Discarded UDP packets no longer prevent the timeout from happening at all.
		
			
				
	
	
		
			403 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Test timeout handling in the UDP client.
 | |
|    Copyright (C) 2017 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, 300 * 1000 });
 | |
|       after = get_ticks ();
 | |
|       if (test_verbose)
 | |
|         printf ("info: test_udp_server: 2.3 second timeout took %f seconds"
 | |
|                 " (garbage %d)\n",
 | |
|                 after - before, with_garbage);
 | |
|       TEST_VERIFY (2.3 <= 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>
 |