Blame sunrpc/tst-udp-nonblocking.c

Packit 6c4009
/* Test non-blocking use of 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
};
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
}
Packit 6c4009
Packit 6c4009
struct test_response
Packit 6c4009
{
Packit 6c4009
  uint32_t server_id;
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->server_id)
Packit 6c4009
    && 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
    /* Number of test servers to run. */
Packit 6c4009
    SERVER_COUNT = 3,
Packit 6c4009
Packit 6c4009
    /* RPC parameters, chosen at random.  */
Packit 6c4009
    PROGNUM = 8242,
Packit 6c4009
    VERSNUM = 19654,
Packit 6c4009
Packit 6c4009
    /* Main RPC operation.  */
Packit 6c4009
    PROC_ADD = 1,
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
/* Set by the parent process to tell test servers apart.  */
Packit 6c4009
static int server_id;
Packit 6c4009
Packit 6c4009
/* Implementation of the test server.  */
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
  static bool proc_add_seen;
Packit 6c4009
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: server_dispatch server_id=%d seq=%u rq_proc=%lu\n",
Packit 6c4009
            server_id, 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\n",
Packit 6c4009
                  query.a, query.b, query.timeout_ms);
Packit 6c4009
Packit 6c4009
        usleep (query.timeout_ms * 1000);
Packit 6c4009
Packit 6c4009
        struct test_response response =
Packit 6c4009
          {
Packit 6c4009
            .server_id = server_id,
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
        if (test_verbose)
Packit 6c4009
          printf ("  server id %d response seq=%u sent\n", server_id, seq);
Packit 6c4009
        proc_add_seen = true;
Packit 6c4009
      }
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case PROC_EXIT:
Packit 6c4009
      TEST_VERIFY (proc_add_seen);
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
/* 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 int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  support_become_root ();
Packit 6c4009
  support_enter_network_namespace ();
Packit 6c4009
Packit 6c4009
  /* Information about the test servers.  */
Packit 6c4009
  struct
Packit 6c4009
  {
Packit 6c4009
    SVCXPRT *transport;
Packit 6c4009
    struct sockaddr_in address;
Packit 6c4009
    pid_t pid;
Packit 6c4009
    uint32_t xid;
Packit 6c4009
  } servers[SERVER_COUNT];
Packit 6c4009
Packit 6c4009
  /* Spawn the test servers.  */
Packit 6c4009
  for (int i = 0; i < SERVER_COUNT; ++i)
Packit 6c4009
    {
Packit 6c4009
      servers[i].transport = svcudp_create (RPC_ANYSOCK);
Packit 6c4009
      TEST_VERIFY_EXIT (servers[i].transport != NULL);
Packit 6c4009
      servers[i].address = (struct sockaddr_in)
Packit 6c4009
        {
Packit 6c4009
          .sin_family = AF_INET,
Packit 6c4009
          .sin_addr.s_addr = htonl (INADDR_LOOPBACK),
Packit 6c4009
          .sin_port = htons (servers[i].transport->xp_port),
Packit 6c4009
        };
Packit 6c4009
      servers[i].xid = 0xabcd0101 + i;
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: setting up server %d xid=%x on port %d\n",
Packit 6c4009
                i, servers[i].xid, servers[i].transport->xp_port);
Packit 6c4009
Packit 6c4009
      server_id = i;
Packit 6c4009
      servers[i].pid = xfork ();
Packit 6c4009
      if (servers[i].pid == 0)
Packit 6c4009
        {
Packit 6c4009
          TEST_VERIFY (svc_register (servers[i].transport,
Packit 6c4009
                                     PROGNUM, VERSNUM, server_dispatch, 0));
Packit 6c4009
          svc_run ();
Packit 6c4009
          FAIL_EXIT1 ("supposed to be unreachable");
Packit 6c4009
        }
Packit 6c4009
      /* We need to close the socket so that we do not accidentally
Packit 6c4009
         consume the request.  */
Packit 6c4009
      TEST_VERIFY (close (servers[i].transport->xp_sock) == 0);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
Packit 6c4009
  /* The following code mirrors what ypbind does.  */
Packit 6c4009
Packit 6c4009
  /* Copied from clnt_udp.c (like ypbind).  */
Packit 6c4009
  struct cu_data
Packit 6c4009
  {
Packit 6c4009
    int cu_sock;
Packit 6c4009
    bool_t cu_closeit;
Packit 6c4009
    struct sockaddr_in cu_raddr;
Packit 6c4009
    int cu_rlen;
Packit 6c4009
    struct timeval cu_wait;
Packit 6c4009
    struct timeval cu_total;
Packit 6c4009
    struct rpc_err cu_error;
Packit 6c4009
    XDR cu_outxdrs;
Packit 6c4009
    u_int cu_xdrpos;
Packit 6c4009
    u_int cu_sendsz;
Packit 6c4009
    char *cu_outbuf;
Packit 6c4009
    u_int cu_recvsz;
Packit 6c4009
    char cu_inbuf[1];
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
  int client_socket = xsocket (AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
Packit 6c4009
  CLIENT *clnt = clntudp_create (&servers[0].address, PROGNUM, VERSNUM,
Packit 6c4009
                                 /* 5 seconds per-response timeout.  */
Packit 6c4009
                                 ((struct timeval) { 5, 0 }),
Packit 6c4009
                                 &client_socket);
Packit 6c4009
  TEST_VERIFY (clnt != NULL);
Packit 6c4009
  clnt->cl_auth = authunix_create_default ();
Packit 6c4009
  {
Packit 6c4009
    struct timeval zero = { 0, 0 };
Packit 6c4009
    TEST_VERIFY (clnt_control (clnt, CLSET_TIMEOUT, (void *) &zero));
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Poke at internal data structures (like ypbind).  */
Packit 6c4009
  struct cu_data *cu = (struct cu_data *) clnt->cl_private;
Packit 6c4009
Packit 6c4009
  /* Send a ping to each server.  */
Packit 6c4009
  double before_pings = get_ticks ();
Packit 6c4009
  for (int i = 0; i < SERVER_COUNT; ++i)
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: sending server %d ping\n", i);
Packit 6c4009
      /* Reset the xid because it is changed by each invocation of
Packit 6c4009
         clnt_call.  Subtract one to compensate for the xid update
Packit 6c4009
         during the call.  */
Packit 6c4009
      *((uint32_t *) (cu->cu_outbuf)) = servers[i].xid - 1;
Packit 6c4009
      cu->cu_raddr = servers[i].address;
Packit 6c4009
Packit 6c4009
      struct test_query query = { .a = 100, .b = i + 1 };
Packit 6c4009
      if (i == 1)
Packit 6c4009
        /* Shorter timeout to prefer this server.  These timeouts must
Packit 6c4009
           be much shorter than the 5-second per-response timeout
Packit 6c4009
           configured with clntudp_create.  */
Packit 6c4009
        query.timeout_ms = 750;
Packit 6c4009
      else
Packit 6c4009
        query.timeout_ms = 1500;
Packit 6c4009
      struct test_response response = { 0 };
Packit 6c4009
      /* NB: Do not check the return value.  The server reply will
Packit 6c4009
         prove that the call worked.  */
Packit 6c4009
      double before_one_ping = get_ticks ();
Packit 6c4009
      clnt_call (clnt, PROC_ADD,
Packit 6c4009
                 xdr_test_query, (void *) &query,
Packit 6c4009
                 xdr_test_response, (void *) &response,
Packit 6c4009
                 ((struct timeval) { 0, 0 }));
Packit 6c4009
      double after_one_ping = get_ticks ();
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: non-blocking send took %f seconds\n",
Packit 6c4009
                after_one_ping - before_one_ping);
Packit 6c4009
      /* clnt_call should return immediately.  Accept some delay in
Packit 6c4009
         case the process is descheduled.  */
Packit 6c4009
      TEST_VERIFY (after_one_ping - before_one_ping < 0.3);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Collect the non-blocking response.  */
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: collecting response\n");
Packit 6c4009
  struct test_response response = { 0 };
Packit 6c4009
  TEST_VERIFY
Packit 6c4009
    (clnt_call (clnt, PROC_ADD, NULL, NULL,
Packit 6c4009
                xdr_test_response, (void *) &response,
Packit 6c4009
                ((struct timeval) { 0, 0 })) == RPC_SUCCESS);
Packit 6c4009
  double after_pings = get_ticks ();
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: send/receive took %f seconds\n",
Packit 6c4009
            after_pings - before_pings);
Packit 6c4009
  /* Expected timeout is 0.75 seconds.  */
Packit 6c4009
  TEST_VERIFY (0.75 <= after_pings - before_pings);
Packit 6c4009
  TEST_VERIFY (after_pings - before_pings < 1.2);
Packit 6c4009
Packit 6c4009
  uint32_t xid;
Packit 6c4009
  memcpy (&xid, &cu->cu_inbuf, sizeof (xid));
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    printf ("info: non-blocking response: xid=%x server_id=%u seq=%u sum=%u\n",
Packit 6c4009
            xid, response.server_id, response.seq, response.sum);
Packit 6c4009
  /* Check that the reply from the preferred server was used.  */
Packit 6c4009
  TEST_VERIFY (servers[1].xid == xid);
Packit 6c4009
  TEST_VERIFY (response.server_id == 1);
Packit 6c4009
  TEST_VERIFY (response.seq == 1);
Packit 6c4009
  TEST_VERIFY (response.sum == 102);
Packit 6c4009
Packit 6c4009
  auth_destroy (clnt->cl_auth);
Packit 6c4009
  clnt_destroy (clnt);
Packit 6c4009
Packit 6c4009
  for (int i = 0; i < SERVER_COUNT; ++i)
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: requesting server %d termination\n", i);
Packit 6c4009
      client_socket = RPC_ANYSOCK;
Packit 6c4009
      clnt = clntudp_create (&servers[i].address, PROGNUM, VERSNUM,
Packit 6c4009
                             ((struct timeval) { 5, 0 }),
Packit 6c4009
                             &client_socket);
Packit 6c4009
      TEST_VERIFY_EXIT (clnt != NULL);
Packit 6c4009
      TEST_VERIFY (clnt_call (clnt, PROC_EXIT,
Packit 6c4009
                              (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                              (xdrproc_t) xdr_void, NULL,
Packit 6c4009
                              ((struct timeval) { 3, 0 })) == RPC_SUCCESS);
Packit 6c4009
      clnt_destroy (clnt);
Packit 6c4009
Packit 6c4009
      int status;
Packit 6c4009
      xwaitpid (servers[i].pid, &status, 0);
Packit 6c4009
      TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#include <support/test-driver.c>