(root)/
glibc-2.38/
sunrpc/
tst-udp-timeout.c
       1  /* Test timeout handling in the UDP client.
       2     Copyright (C) 2017-2023 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  #include <stdlib.h>
      33  
      34  static pid_t server_pid;
      35  
      36  /* Test data serialization and deserialization.   */
      37  
      38  struct test_query
      39  {
      40    uint32_t a;
      41    uint32_t b;
      42    uint32_t timeout_ms;
      43    uint32_t wait_for_seq;
      44    uint32_t garbage_packets;
      45  };
      46  
      47  static bool_t
      48  xdr_test_query (XDR *xdrs, void *data, ...)
      49  {
      50    struct test_query *p = data;
      51    return xdr_uint32_t (xdrs, &p->a)
      52      && xdr_uint32_t (xdrs, &p->b)
      53      && xdr_uint32_t (xdrs, &p->timeout_ms)
      54      && xdr_uint32_t (xdrs, &p->wait_for_seq)
      55      && xdr_uint32_t (xdrs, &p->garbage_packets);
      56  }
      57  
      58  struct test_response
      59  {
      60    uint32_t seq;
      61    uint32_t sum;
      62  };
      63  
      64  static bool_t
      65  xdr_test_response (XDR *xdrs, void *data, ...)
      66  {
      67    struct test_response *p = data;
      68    return xdr_uint32_t (xdrs, &p->seq)
      69      && xdr_uint32_t (xdrs, &p->sum);
      70  }
      71  
      72  /* Implementation of the test server.  */
      73  
      74  enum
      75    {
      76      /* RPC parameters, chosen at random.  */
      77      PROGNUM = 15717,
      78      VERSNUM = 13689,
      79  
      80      /* Main RPC operation.  */
      81      PROC_ADD = 1,
      82  
      83      /* Reset the sequence number.  */
      84      PROC_RESET_SEQ,
      85  
      86      /* Request process termination.  */
      87      PROC_EXIT,
      88  
      89      /* Special exit status to mark successful processing.  */
      90      EXIT_MARKER = 55,
      91    };
      92  
      93  static void
      94  server_dispatch (struct svc_req *request, SVCXPRT *transport)
      95  {
      96    /* Query sequence number.  */
      97    static uint32_t seq = 0;
      98    ++seq;
      99  
     100    if (test_verbose)
     101      printf ("info: server_dispatch seq=%u rq_proc=%lu\n",
     102              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 wait_for_seq=%u"
     116                    " garbage_packets=%u\n",
     117                    query.a, query.b, query.timeout_ms, query.wait_for_seq,
     118                    query.garbage_packets);
     119  
     120          if (seq < query.wait_for_seq)
     121            {
     122              /* No response at this point.  */
     123              if (test_verbose)
     124                printf ("  skipped response\n");
     125              break;
     126            }
     127  
     128          if (query.garbage_packets > 0)
     129            {
     130              int per_packet_timeout;
     131              if (query.timeout_ms > 0)
     132                per_packet_timeout
     133                  = query.timeout_ms * 1000 / query.garbage_packets;
     134              else
     135                per_packet_timeout = 0;
     136  
     137              char buf[20];
     138              memset (&buf, 0xc0, sizeof (buf));
     139              for (int i = 0; i < query.garbage_packets; ++i)
     140                {
     141                  /* 13 is relatively prime to 20 = sizeof (buf) + 1, so
     142                     the len variable will cover the entire interval
     143                     [0, 20] if query.garbage_packets is sufficiently
     144                     large.  */
     145                  size_t len = (i * 13 + 1) % (sizeof (buf) + 1);
     146                  TEST_VERIFY (sendto (transport->xp_sock,
     147                                       buf, len, MSG_NOSIGNAL,
     148                                       (struct sockaddr *) &transport->xp_raddr,
     149                                       transport->xp_addrlen) == len);
     150                  if (per_packet_timeout > 0)
     151                    usleep (per_packet_timeout);
     152                }
     153            }
     154          else if (query.timeout_ms > 0)
     155            usleep (query.timeout_ms * 1000);
     156  
     157          struct test_response response =
     158            {
     159              .seq = seq,
     160              .sum = query.a + query.b,
     161            };
     162          TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
     163                                      (void *) &response));
     164        }
     165        break;
     166  
     167      case PROC_RESET_SEQ:
     168        seq = 0;
     169        TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
     170        break;
     171  
     172      case PROC_EXIT:
     173        TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
     174        _exit (EXIT_MARKER);
     175        break;
     176  
     177      default:
     178        FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
     179        break;
     180      }
     181  }
     182  
     183  /* Function to be called before exit to make sure the
     184     server process is properly killed.  */
     185  static void
     186  kill_server (void)
     187  {
     188    kill (server_pid, SIGTERM);
     189  }
     190  
     191  /* Implementation of the test client.  */
     192  
     193  static struct test_response
     194  test_call (CLIENT *clnt, int proc, struct test_query query,
     195             struct timeval timeout)
     196  {
     197    if (test_verbose)
     198      printf ("info: test_call proc=%d timeout=%lu.%06lu\n",
     199              proc, (unsigned long) timeout.tv_sec,
     200              (unsigned long) timeout.tv_usec);
     201    struct test_response response;
     202    TEST_VERIFY_EXIT (clnt_call (clnt, proc,
     203                                 xdr_test_query, (void *) &query,
     204                                 xdr_test_response, (void *) &response,
     205                                 timeout)
     206                      == RPC_SUCCESS);
     207    return response;
     208  }
     209  
     210  static void
     211  test_call_timeout (CLIENT *clnt, int proc, struct test_query query,
     212                     struct timeval timeout)
     213  {
     214    struct test_response response;
     215    TEST_VERIFY (clnt_call (clnt, proc,
     216                            xdr_test_query, (void *) &query,
     217                            xdr_test_response, (void *) &response,
     218                            timeout)
     219                 == RPC_TIMEDOUT);
     220  }
     221  
     222  /* Complete one regular RPC call to drain the server socket
     223     buffer.  Resets the sequence number.  */
     224  static void
     225  test_call_flush (CLIENT *clnt)
     226  {
     227    /* This needs a longer timeout to flush out all pending requests.
     228       The choice of 5 seconds is larger than the per-response timeouts
     229       requested via the timeout_ms field.  */
     230    if (test_verbose)
     231      printf ("info: flushing pending queries\n");
     232    TEST_VERIFY_EXIT (clnt_call (clnt, PROC_RESET_SEQ,
     233                                 (xdrproc_t) xdr_void, NULL,
     234                                 (xdrproc_t) xdr_void, NULL,
     235                                 ((struct timeval) { 5, 0 }))
     236                      == RPC_SUCCESS);
     237  }
     238  
     239  /* Return the number seconds since an arbitrary point in time.  */
     240  static double
     241  get_ticks (void)
     242  {
     243    {
     244      struct timespec ts;
     245      if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
     246        return ts.tv_sec + ts.tv_nsec * 1e-9;
     247    }
     248    {
     249      struct timeval tv;
     250      TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
     251      return tv.tv_sec + tv.tv_usec * 1e-6;
     252    }
     253  }
     254  
     255  static void
     256  test_udp_server (int port)
     257  {
     258    struct sockaddr_in sin =
     259      {
     260        .sin_family = AF_INET,
     261        .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
     262        .sin_port = htons (port)
     263      };
     264    int sock = RPC_ANYSOCK;
     265  
     266    /* The client uses a 1.5 second timeout for retries.  The timeouts
     267       are arbitrary, but chosen so that there is a substantial gap
     268       between them, but the total time spent waiting is not too
     269       large.  */
     270    CLIENT *clnt = clntudp_create (&sin, PROGNUM, VERSNUM,
     271                                   (struct timeval) { 1, 500 * 1000 },
     272                                   &sock);
     273    TEST_VERIFY_EXIT (clnt != NULL);
     274  
     275    /* Basic call/response test.  */
     276    struct test_response response = test_call
     277      (clnt, PROC_ADD,
     278       (struct test_query) { .a = 17, .b = 4 },
     279       (struct timeval) { 3, 0 });
     280    TEST_VERIFY (response.sum == 21);
     281    TEST_VERIFY (response.seq == 1);
     282  
     283    /* Check that garbage packets do not interfere with timeout
     284       processing.  */
     285    double before = get_ticks ();
     286    response = test_call
     287      (clnt, PROC_ADD,
     288       (struct test_query) {
     289         .a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21,
     290       },
     291       (struct timeval) { 3, 0 });
     292    TEST_VERIFY (response.sum == 23);
     293    TEST_VERIFY (response.seq == 2);
     294    double after = get_ticks ();
     295    if (test_verbose)
     296      printf ("info: 21 garbage packets took %f seconds\n", after - before);
     297    /* Expected timeout is 0.5 seconds.  Add some slack for rounding errors and
     298       in case process scheduling delays processing the query or response, but
     299       do not accept a retry (which would happen at 1.5 seconds).  */
     300    TEST_VERIFY (0.45 <= after - before);
     301    TEST_VERIFY (after - before < 1.2);
     302    test_call_flush (clnt);
     303  
     304    /* Check that missing a response introduces a 1.5 second timeout, as
     305       requested when calling clntudp_create.  */
     306    before = get_ticks ();
     307    response = test_call
     308      (clnt, PROC_ADD,
     309       (struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 },
     310       (struct timeval) { 3, 0 });
     311    TEST_VERIFY (response.sum == 210);
     312    TEST_VERIFY (response.seq == 2);
     313    after = get_ticks ();
     314    if (test_verbose)
     315      printf ("info: skipping one response took %f seconds\n",
     316              after - before);
     317    /* Expected timeout is 1.5 seconds.  Do not accept a second retry
     318       (which would happen at 3 seconds).  */
     319    TEST_VERIFY (1.45 <= after - before);
     320    TEST_VERIFY (after - before < 2.9);
     321    test_call_flush (clnt);
     322  
     323    /* Check that the overall timeout wins against the per-query
     324       timeout.  */
     325    before = get_ticks ();
     326    test_call_timeout
     327      (clnt, PROC_ADD,
     328       (struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 },
     329       (struct timeval) { 0, 750 * 1000 });
     330    after = get_ticks ();
     331    if (test_verbose)
     332      printf ("info: 0.75 second timeout took %f seconds\n",
     333              after - before);
     334    TEST_VERIFY (0.70 <= after - before);
     335    TEST_VERIFY (after - before < 1.4);
     336    test_call_flush (clnt);
     337  
     338    for (int with_garbage = 0; with_garbage < 2; ++with_garbage)
     339      {
     340        /* Check that no response at all causes the client to bail out.  */
     341        before = get_ticks ();
     342        test_call_timeout
     343          (clnt, PROC_ADD,
     344           (struct test_query) {
     345             .a = 170, .b = 40, .timeout_ms = 1200,
     346             .garbage_packets = with_garbage * 21
     347           },
     348           (struct timeval) { 0, 750 * 1000 });
     349        after = get_ticks ();
     350        if (test_verbose)
     351          printf ("info: test_udp_server: 0.75 second timeout took %f seconds"
     352                  " (garbage %d)\n",
     353                  after - before, with_garbage);
     354        TEST_VERIFY (0.70 <= after - before);
     355        TEST_VERIFY (after - before < 1.4);
     356        test_call_flush (clnt);
     357  
     358        /* As above, but check the total timeout.  */
     359        before = get_ticks ();
     360        test_call_timeout
     361          (clnt, PROC_ADD,
     362           (struct test_query) {
     363             .a = 170, .b = 40, .timeout_ms = 3000,
     364             .garbage_packets = with_garbage * 30
     365           },
     366           (struct timeval) { 2, 500 * 1000 });
     367        after = get_ticks ();
     368        if (test_verbose)
     369          printf ("info: test_udp_server: 2.5 second timeout took %f seconds"
     370                  " (garbage %d)\n",
     371                  after - before, with_garbage);
     372        TEST_VERIFY (2.45 <= after - before);
     373        TEST_VERIFY (after - before < 3.0);
     374        test_call_flush (clnt);
     375      }
     376  
     377    TEST_VERIFY_EXIT (clnt_call (clnt, PROC_EXIT,
     378                                 (xdrproc_t) xdr_void, NULL,
     379                                 (xdrproc_t) xdr_void, NULL,
     380                                 ((struct timeval) { 5, 0 }))
     381                      == RPC_SUCCESS);
     382    clnt_destroy (clnt);
     383  }
     384  
     385  static int
     386  do_test (void)
     387  {
     388    support_become_root ();
     389    support_enter_network_namespace ();
     390  
     391    SVCXPRT *transport = svcudp_create (RPC_ANYSOCK);
     392    TEST_VERIFY_EXIT (transport != NULL);
     393    TEST_VERIFY (svc_register (transport, PROGNUM, VERSNUM, server_dispatch, 0));
     394  
     395    server_pid = xfork ();
     396    if (server_pid == 0)
     397      {
     398        svc_run ();
     399        FAIL_EXIT1 ("supposed to be unreachable");
     400      }
     401    atexit (kill_server);
     402    test_udp_server (transport->xp_port);
     403  
     404    int status;
     405    xwaitpid (server_pid, &status, 0);
     406    TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
     407  
     408    SVC_DESTROY (transport);
     409    return 0;
     410  }
     411  
     412  /* The minimum run time is around 17 seconds.  */
     413  #define TIMEOUT 25
     414  #include <support/test-driver.c>