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