Blame sunrpc/tst-udp-timeout.c

Packit 6c4009
/* Test timeout handling in the UDP client.
Packit 6c4009
   Copyright (C) 2017-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <netinet/in.h>
Packit 6c4009
#include <rpc/clnt.h>
Packit 6c4009
#include <rpc/svc.h>
Packit 6c4009
#include <stdbool.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <support/check.h>
Packit 6c4009
#include <support/namespace.h>
Packit 6c4009
#include <support/test-driver.h>
Packit 6c4009
#include <support/xsocket.h>
Packit 6c4009
#include <support/xunistd.h>
Packit 6c4009
#include <sys/socket.h>
Packit 6c4009
#include <time.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
Packit 6c4009
/* Test data serialization and deserialization.   */
Packit 6c4009
Packit 6c4009
struct test_query
Packit 6c4009
{
Packit 6c4009
  uint32_t a;
Packit 6c4009
  uint32_t b;
Packit 6c4009
  uint32_t timeout_ms;
Packit 6c4009
  uint32_t wait_for_seq;
Packit 6c4009
  uint32_t garbage_packets;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
static bool_t
Packit 6c4009
xdr_test_query (XDR *xdrs, void *data, ...)
Packit 6c4009
{
Packit 6c4009
  struct test_query *p = data;
Packit 6c4009
  return xdr_uint32_t (xdrs, &p->a)
Packit 6c4009
    && xdr_uint32_t (xdrs, &p->b)
Packit 6c4009
    && xdr_uint32_t (xdrs, &p->timeout_ms)
Packit 6c4009
    && xdr_uint32_t (xdrs, &p->wait_for_seq)
Packit 6c4009
    && xdr_uint32_t (xdrs, &p->garbage_packets);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct test_response
Packit 6c4009
{
Packit 6c4009
  uint32_t seq;
Packit 6c4009
  uint32_t sum;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
static bool_t
Packit 6c4009
xdr_test_response (XDR *xdrs, void *data, ...)
Packit 6c4009
{
Packit 6c4009
  struct test_response *p = data;
Packit 6c4009
  return xdr_uint32_t (xdrs, &p->seq)
Packit 6c4009
    && xdr_uint32_t (xdrs, &p->sum);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Implementation of the test server.  */
Packit 6c4009
Packit 6c4009
enum
Packit 6c4009
  {
Packit 6c4009
    /* RPC parameters, chosen at random.  */
Packit 6c4009
    PROGNUM = 15717,
Packit 6c4009
    VERSNUM = 13689,
Packit 6c4009
Packit 6c4009
    /* Main RPC operation.  */
Packit 6c4009
    PROC_ADD = 1,
Packit 6c4009
Packit 6c4009
    /* Reset the sequence number.  */
Packit 6c4009
    PROC_RESET_SEQ,
Packit 6c4009
Packit 6c4009
    /* Request process termination.  */
Packit 6c4009
    PROC_EXIT,
Packit 6c4009
Packit 6c4009
    /* Special exit status to mark successful processing.  */
Packit 6c4009
    EXIT_MARKER = 55,
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
server_dispatch (struct svc_req *request, SVCXPRT *transport)
Packit 6c4009
{
Packit 6c4009
  /* Query sequence number.  */
Packit 6c4009
  static uint32_t seq = 0;
Packit 6c4009
  ++seq;
Packit 6c4009
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: server_dispatch seq=%u rq_proc=%lu\n",
Packit 6c4009
            seq, request->rq_proc);
Packit 6c4009
Packit 6c4009
  switch (request->rq_proc)
Packit 6c4009
    {
Packit 6c4009
    case PROC_ADD:
Packit 6c4009
      {
Packit 6c4009
        struct test_query query;
Packit 6c4009
        memset (&query, 0xc0, sizeof (query));
Packit 6c4009
        TEST_VERIFY_EXIT
Packit 6c4009
          (svc_getargs (transport, xdr_test_query,
Packit 6c4009
                        (void *) &query));
Packit 6c4009
Packit 6c4009
        if (test_verbose)
Packit 6c4009
          printf ("  a=%u b=%u timeout_ms=%u wait_for_seq=%u"
Packit 6c4009
                  " garbage_packets=%u\n",
Packit 6c4009
                  query.a, query.b, query.timeout_ms, query.wait_for_seq,
Packit 6c4009
                  query.garbage_packets);
Packit 6c4009
Packit 6c4009
        if (seq < query.wait_for_seq)
Packit 6c4009
          {
Packit 6c4009
            /* No response at this point.  */
Packit 6c4009
            if (test_verbose)
Packit 6c4009
              printf ("  skipped response\n");
Packit 6c4009
            break;
Packit 6c4009
          }
Packit 6c4009
Packit 6c4009
        if (query.garbage_packets > 0)
Packit 6c4009
          {
Packit 6c4009
            int per_packet_timeout;
Packit 6c4009
            if (query.timeout_ms > 0)
Packit 6c4009
              per_packet_timeout
Packit 6c4009
                = query.timeout_ms * 1000 / query.garbage_packets;
Packit 6c4009
            else
Packit 6c4009
              per_packet_timeout = 0;
Packit 6c4009
Packit 6c4009
            char buf[20];
Packit 6c4009
            memset (&buf, 0xc0, sizeof (buf));
Packit 6c4009
            for (int i = 0; i < query.garbage_packets; ++i)
Packit 6c4009
              {
Packit 6c4009
                /* 13 is relatively prime to 20 = sizeof (buf) + 1, so
Packit 6c4009
                   the len variable will cover the entire interval
Packit 6c4009
                   [0, 20] if query.garbage_packets is sufficiently
Packit 6c4009
                   large.  */
Packit 6c4009
                size_t len = (i * 13 + 1) % (sizeof (buf) + 1);
Packit 6c4009
                TEST_VERIFY (sendto (transport->xp_sock,
Packit 6c4009
                                     buf, len, MSG_NOSIGNAL,
Packit 6c4009
                                     (struct sockaddr *) &transport->xp_raddr,
Packit 6c4009
                                     transport->xp_addrlen) == len);
Packit 6c4009
                if (per_packet_timeout > 0)
Packit 6c4009
                  usleep (per_packet_timeout);
Packit 6c4009
              }
Packit 6c4009
          }
Packit 6c4009
        else if (query.timeout_ms > 0)
Packit 6c4009
          usleep (query.timeout_ms * 1000);
Packit 6c4009
Packit 6c4009
        struct test_response response =
Packit 6c4009
          {
Packit 6c4009
            .seq = seq,
Packit 6c4009
            .sum = query.a + query.b,
Packit 6c4009
          };
Packit 6c4009
        TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
Packit 6c4009
                                    (void *) &response));
Packit 6c4009
      }
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case PROC_RESET_SEQ:
Packit 6c4009
      seq = 0;
Packit 6c4009
      TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case PROC_EXIT:
Packit 6c4009
      TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
Packit 6c4009
      _exit (EXIT_MARKER);
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    default:
Packit 6c4009
      FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
Packit 6c4009
      break;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Implementation of the test client.  */
Packit 6c4009
Packit 6c4009
static struct test_response
Packit 6c4009
test_call (CLIENT *clnt, int proc, struct test_query query,
Packit 6c4009
           struct timeval timeout)
Packit 6c4009
{
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: test_call proc=%d timeout=%lu.%06lu\n",
Packit 6c4009
            proc, (unsigned long) timeout.tv_sec,
Packit 6c4009
            (unsigned long) timeout.tv_usec);
Packit 6c4009
  struct test_response response;
Packit 6c4009
  TEST_VERIFY_EXIT (clnt_call (clnt, proc,
Packit 6c4009
                               xdr_test_query, (void *) &query,
Packit 6c4009
                               xdr_test_response, (void *) &response,
Packit 6c4009
                               timeout)
Packit 6c4009
                    == RPC_SUCCESS);
Packit 6c4009
  return response;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
test_call_timeout (CLIENT *clnt, int proc, struct test_query query,
Packit 6c4009
                   struct timeval timeout)
Packit 6c4009
{
Packit 6c4009
  struct test_response response;
Packit 6c4009
  TEST_VERIFY (clnt_call (clnt, proc,
Packit 6c4009
                          xdr_test_query, (void *) &query,
Packit 6c4009
                          xdr_test_response, (void *) &response,
Packit 6c4009
                          timeout)
Packit 6c4009
               == RPC_TIMEDOUT);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Complete one regular RPC call to drain the server socket
Packit 6c4009
   buffer.  Resets the sequence number.  */
Packit 6c4009
static void
Packit 6c4009
test_call_flush (CLIENT *clnt)
Packit 6c4009
{
Packit 6c4009
  /* This needs a longer timeout to flush out all pending requests.
Packit 6c4009
     The choice of 5 seconds is larger than the per-response timeouts
Packit 6c4009
     requested via the timeout_ms field.  */
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: flushing pending queries\n");
Packit 6c4009
  TEST_VERIFY_EXIT (clnt_call (clnt, PROC_RESET_SEQ,
Packit 6c4009
                               (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                               (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                               ((struct timeval) { 5, 0 }))
Packit 6c4009
                    == RPC_SUCCESS);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Return the number seconds since an arbitrary point in time.  */
Packit 6c4009
static double
Packit 6c4009
get_ticks (void)
Packit 6c4009
{
Packit 6c4009
  {
Packit 6c4009
    struct timespec ts;
Packit 6c4009
    if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
Packit 6c4009
      return ts.tv_sec + ts.tv_nsec * 1e-9;
Packit 6c4009
  }
Packit 6c4009
  {
Packit 6c4009
    struct timeval tv;
Packit 6c4009
    TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
Packit 6c4009
    return tv.tv_sec + tv.tv_usec * 1e-6;
Packit 6c4009
  }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
test_udp_server (int port)
Packit 6c4009
{
Packit 6c4009
  struct sockaddr_in sin =
Packit 6c4009
    {
Packit 6c4009
      .sin_family = AF_INET,
Packit 6c4009
      .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
Packit 6c4009
      .sin_port = htons (port)
Packit 6c4009
    };
Packit 6c4009
  int sock = RPC_ANYSOCK;
Packit 6c4009
Packit 6c4009
  /* The client uses a 1.5 second timeout for retries.  The timeouts
Packit 6c4009
     are arbitrary, but chosen so that there is a substantial gap
Packit 6c4009
     between them, but the total time spent waiting is not too
Packit 6c4009
     large.  */
Packit 6c4009
  CLIENT *clnt = clntudp_create (&sin, PROGNUM, VERSNUM,
Packit 6c4009
                                 (struct timeval) { 1, 500 * 1000 },
Packit 6c4009
                                 &sock);
Packit 6c4009
  TEST_VERIFY_EXIT (clnt != NULL);
Packit 6c4009
Packit 6c4009
  /* Basic call/response test.  */
Packit 6c4009
  struct test_response response = test_call
Packit 6c4009
    (clnt, PROC_ADD,
Packit 6c4009
     (struct test_query) { .a = 17, .b = 4 },
Packit 6c4009
     (struct timeval) { 3, 0 });
Packit 6c4009
  TEST_VERIFY (response.sum == 21);
Packit 6c4009
  TEST_VERIFY (response.seq == 1);
Packit 6c4009
Packit 6c4009
  /* Check that garbage packets do not interfere with timeout
Packit 6c4009
     processing.  */
Packit 6c4009
  double before = get_ticks ();
Packit 6c4009
  response = test_call
Packit 6c4009
    (clnt, PROC_ADD,
Packit 6c4009
     (struct test_query) {
Packit 6c4009
       .a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21,
Packit 6c4009
     },
Packit 6c4009
     (struct timeval) { 3, 0 });
Packit 6c4009
  TEST_VERIFY (response.sum == 23);
Packit 6c4009
  TEST_VERIFY (response.seq == 2);
Packit 6c4009
  double after = get_ticks ();
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: 21 garbage packets took %f seconds\n", after - before);
Packit 6c4009
  /* Expected timeout is 0.5 seconds.  Add some slack in case process
Packit 6c4009
     scheduling delays processing the query or response, but do not
Packit 6c4009
     accept a retry (which would happen at 1.5 seconds).  */
Packit 6c4009
  TEST_VERIFY (0.5 <= after - before);
Packit 6c4009
  TEST_VERIFY (after - before < 1.2);
Packit 6c4009
  test_call_flush (clnt);
Packit 6c4009
Packit 6c4009
  /* Check that missing a response introduces a 1.5 second timeout, as
Packit 6c4009
     requested when calling clntudp_create.  */
Packit 6c4009
  before = get_ticks ();
Packit 6c4009
  response = test_call
Packit 6c4009
    (clnt, PROC_ADD,
Packit 6c4009
     (struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 },
Packit 6c4009
     (struct timeval) { 3, 0 });
Packit 6c4009
  TEST_VERIFY (response.sum == 210);
Packit 6c4009
  TEST_VERIFY (response.seq == 2);
Packit 6c4009
  after = get_ticks ();
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: skipping one response took %f seconds\n",
Packit 6c4009
            after - before);
Packit 6c4009
  /* Expected timeout is 1.5 seconds.  Do not accept a second retry
Packit 6c4009
     (which would happen at 3 seconds).  */
Packit 6c4009
  TEST_VERIFY (1.5 <= after - before);
Packit 6c4009
  TEST_VERIFY (after - before < 2.9);
Packit 6c4009
  test_call_flush (clnt);
Packit 6c4009
Packit 6c4009
  /* Check that the overall timeout wins against the per-query
Packit 6c4009
     timeout.  */
Packit 6c4009
  before = get_ticks ();
Packit 6c4009
  test_call_timeout
Packit 6c4009
    (clnt, PROC_ADD,
Packit 6c4009
     (struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 },
Packit 6c4009
     (struct timeval) { 0, 750 * 1000 });
Packit 6c4009
  after = get_ticks ();
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: 0.75 second timeout took %f seconds\n",
Packit 6c4009
            after - before);
Packit 6c4009
  TEST_VERIFY (0.75 <= after - before);
Packit 6c4009
  TEST_VERIFY (after - before < 1.4);
Packit 6c4009
  test_call_flush (clnt);
Packit 6c4009
Packit 6c4009
  for (int with_garbage = 0; with_garbage < 2; ++with_garbage)
Packit 6c4009
    {
Packit 6c4009
      /* Check that no response at all causes the client to bail out.  */
Packit 6c4009
      before = get_ticks ();
Packit 6c4009
      test_call_timeout
Packit 6c4009
        (clnt, PROC_ADD,
Packit 6c4009
         (struct test_query) {
Packit 6c4009
           .a = 170, .b = 40, .timeout_ms = 1200,
Packit 6c4009
           .garbage_packets = with_garbage * 21
Packit 6c4009
         },
Packit 6c4009
         (struct timeval) { 0, 750 * 1000 });
Packit 6c4009
      after = get_ticks ();
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: test_udp_server: 0.75 second timeout took %f seconds"
Packit 6c4009
                " (garbage %d)\n",
Packit 6c4009
                after - before, with_garbage);
Packit 6c4009
      TEST_VERIFY (0.75 <= after - before);
Packit 6c4009
      TEST_VERIFY (after - before < 1.4);
Packit 6c4009
      test_call_flush (clnt);
Packit 6c4009
Packit 6c4009
      /* As above, but check the total timeout.  */
Packit 6c4009
      before = get_ticks ();
Packit 6c4009
      test_call_timeout
Packit 6c4009
        (clnt, PROC_ADD,
Packit 6c4009
         (struct test_query) {
Packit 6c4009
           .a = 170, .b = 40, .timeout_ms = 3000,
Packit 6c4009
           .garbage_packets = with_garbage * 30
Packit 6c4009
         },
Packit 6c4009
         (struct timeval) { 2, 500 * 1000 });
Packit 6c4009
      after = get_ticks ();
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: test_udp_server: 2.5 second timeout took %f seconds"
Packit 6c4009
                " (garbage %d)\n",
Packit 6c4009
                after - before, with_garbage);
Packit 6c4009
      TEST_VERIFY (2.5 <= after - before);
Packit 6c4009
      TEST_VERIFY (after - before < 3.0);
Packit 6c4009
      test_call_flush (clnt);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  TEST_VERIFY_EXIT (clnt_call (clnt, PROC_EXIT,
Packit 6c4009
                               (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                               (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                               ((struct timeval) { 5, 0 }))
Packit 6c4009
                    == RPC_SUCCESS);
Packit 6c4009
  clnt_destroy (clnt);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  support_become_root ();
Packit 6c4009
  support_enter_network_namespace ();
Packit 6c4009
Packit 6c4009
  SVCXPRT *transport = svcudp_create (RPC_ANYSOCK);
Packit 6c4009
  TEST_VERIFY_EXIT (transport != NULL);
Packit 6c4009
  TEST_VERIFY (svc_register (transport, PROGNUM, VERSNUM, server_dispatch, 0));
Packit 6c4009
Packit 6c4009
  pid_t pid = xfork ();
Packit 6c4009
  if (pid == 0)
Packit 6c4009
    {
Packit 6c4009
      svc_run ();
Packit 6c4009
      FAIL_EXIT1 ("supposed to be unreachable");
Packit 6c4009
    }
Packit 6c4009
  test_udp_server (transport->xp_port);
Packit 6c4009
Packit 6c4009
  int status;
Packit 6c4009
  xwaitpid (pid, &status, 0);
Packit 6c4009
  TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
Packit 6c4009
Packit 6c4009
  SVC_DESTROY (transport);
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* The minimum run time is around 17 seconds.  */
Packit 6c4009
#define TIMEOUT 25
Packit 6c4009
#include <support/test-driver.c>