mirror of https://gitee.com/Nocallback/glibc.git
Browse Source
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.fw/bug16145
10 changed files with 1347 additions and 59 deletions
@ -0,0 +1,122 @@ |
|||
/* Computing deadlines for timeouts.
|
|||
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 <net-internal.h> |
|||
|
|||
#include <assert.h> |
|||
#include <limits.h> |
|||
#include <stdio.h> |
|||
#include <stdint.h> |
|||
#include <time.h> |
|||
|
|||
struct deadline_current_time internal_function |
|||
__deadline_current_time (void) |
|||
{ |
|||
struct deadline_current_time result; |
|||
if (__clock_gettime (CLOCK_MONOTONIC, &result.current) != 0) |
|||
{ |
|||
struct timeval current_tv; |
|||
if (__gettimeofday (¤t_tv, NULL) == 0) |
|||
__libc_fatal ("Fatal error: gettimeofday system call failed\n"); |
|||
result.current.tv_sec = current_tv.tv_sec; |
|||
result.current.tv_nsec = current_tv.tv_usec * 1000; |
|||
} |
|||
assert (result.current.tv_sec >= 0); |
|||
return result; |
|||
} |
|||
|
|||
/* A special deadline value for which __deadline_is_infinite is
|
|||
true. */ |
|||
static inline struct deadline |
|||
infinite_deadline (void) |
|||
{ |
|||
return (struct deadline) { { -1, -1 } }; |
|||
} |
|||
|
|||
struct deadline internal_function |
|||
__deadline_from_timeval (struct deadline_current_time current, |
|||
struct timeval tv) |
|||
{ |
|||
assert (__is_timeval_valid_timeout (tv)); |
|||
|
|||
/* Compute second-based deadline. Perform the addition in
|
|||
uintmax_t, which is unsigned, to simply overflow detection. */ |
|||
uintmax_t sec = current.current.tv_sec; |
|||
sec += tv.tv_sec; |
|||
if (sec < (uintmax_t) tv.tv_sec) |
|||
return infinite_deadline (); |
|||
|
|||
/* Compute nanosecond deadline. */ |
|||
int nsec = current.current.tv_nsec + tv.tv_usec * 1000; |
|||
if (nsec >= 1000 * 1000 * 1000) |
|||
{ |
|||
/* Carry nanosecond overflow to seconds. */ |
|||
nsec -= 1000 * 1000 * 1000; |
|||
if (sec + 1 < sec) |
|||
return infinite_deadline (); |
|||
++sec; |
|||
} |
|||
/* This uses a GCC extension, otherwise these casts for detecting
|
|||
overflow would not be defined. */ |
|||
if ((time_t) sec < 0 || sec != (uintmax_t) (time_t) sec) |
|||
return infinite_deadline (); |
|||
|
|||
return (struct deadline) { { sec, nsec } }; |
|||
} |
|||
|
|||
int internal_function |
|||
__deadline_to_ms (struct deadline_current_time current, |
|||
struct deadline deadline) |
|||
{ |
|||
if (__deadline_is_infinite (deadline)) |
|||
return INT_MAX; |
|||
|
|||
if (current.current.tv_sec > deadline.absolute.tv_sec |
|||
|| (current.current.tv_sec == deadline.absolute.tv_sec |
|||
&& current.current.tv_nsec >= deadline.absolute.tv_nsec)) |
|||
return 0; |
|||
time_t sec = deadline.absolute.tv_sec - current.current.tv_sec; |
|||
if (sec >= INT_MAX) |
|||
/* This value will overflow below. */ |
|||
return INT_MAX; |
|||
int nsec = deadline.absolute.tv_nsec - current.current.tv_nsec; |
|||
if (nsec < 0) |
|||
{ |
|||
/* Borrow from the seconds field. */ |
|||
assert (sec > 0); |
|||
--sec; |
|||
nsec += 1000 * 1000 * 1000; |
|||
} |
|||
|
|||
/* Prepare for rounding up to milliseconds. */ |
|||
nsec += 999999; |
|||
if (nsec > 1000 * 1000 * 1000) |
|||
{ |
|||
assert (sec < INT_MAX); |
|||
++sec; |
|||
nsec -= 1000 * 1000 * 1000; |
|||
} |
|||
|
|||
unsigned int msec = nsec / (1000 * 1000); |
|||
if (sec > INT_MAX / 1000) |
|||
return INT_MAX; |
|||
msec += sec * 1000; |
|||
if (msec > INT_MAX) |
|||
return INT_MAX; |
|||
return msec; |
|||
} |
|||
@ -0,0 +1,188 @@ |
|||
/* Tests for computing deadlines for timeouts.
|
|||
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 <inet/net-internal.h> |
|||
#include <limits.h> |
|||
#include <stdbool.h> |
|||
#include <stdint.h> |
|||
#include <support/check.h> |
|||
|
|||
/* Find the maximum value which can be represented in a time_t. */ |
|||
static time_t |
|||
time_t_max (void) |
|||
{ |
|||
_Static_assert (0 > (time_t) -1, "time_t is signed"); |
|||
uintmax_t current = 1; |
|||
while (true) |
|||
{ |
|||
uintmax_t next = current * 2; |
|||
/* This cannot happen because time_t is signed. */ |
|||
TEST_VERIFY_EXIT (next > current); |
|||
++next; |
|||
if ((time_t) next < 0 || next != (uintmax_t) (time_t) next) |
|||
/* Value cannot be represented in time_t. Return the previous
|
|||
value. */ |
|||
return current; |
|||
current = next; |
|||
} |
|||
} |
|||
|
|||
static int |
|||
do_test (void) |
|||
{ |
|||
{ |
|||
struct deadline_current_time current_time = __deadline_current_time (); |
|||
TEST_VERIFY (current_time.current.tv_sec >= 0); |
|||
current_time = __deadline_current_time (); |
|||
/* Due to CLOCK_MONOTONIC, either seconds or nanoseconds are
|
|||
greater than zero. This is also true for the gettimeofday |
|||
fallback. */ |
|||
TEST_VERIFY (current_time.current.tv_sec >= 0); |
|||
TEST_VERIFY (current_time.current.tv_sec > 0 |
|||
|| current_time.current.tv_nsec > 0); |
|||
} |
|||
|
|||
/* Check basic computations of deadlines. */ |
|||
struct deadline_current_time current_time = { { 1, 123456789 } }; |
|||
struct deadline deadline = __deadline_from_timeval |
|||
(current_time, (struct timeval) { 0, 1 }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 123457789); |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1); |
|||
|
|||
deadline = __deadline_from_timeval |
|||
(current_time, ((struct timeval) { 0, 2 })); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 123458789); |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1); |
|||
|
|||
deadline = __deadline_from_timeval |
|||
(current_time, ((struct timeval) { 1, 0 })); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 2); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 123456789); |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000); |
|||
|
|||
/* Check if timeouts are correctly rounded up to the next
|
|||
millisecond. */ |
|||
for (int i = 0; i < 999999; ++i) |
|||
{ |
|||
++current_time.current.tv_nsec; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000); |
|||
} |
|||
|
|||
/* A full millisecond has elapsed, so the time to the deadline is
|
|||
now less than 1000. */ |
|||
++current_time.current.tv_nsec; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 999); |
|||
|
|||
/* Check __deadline_to_ms carry-over. */ |
|||
current_time = (struct deadline_current_time) { { 9, 123456789 } }; |
|||
deadline = (struct deadline) { { 10, 122456789 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 999); |
|||
deadline = (struct deadline) { { 10, 122456790 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000); |
|||
deadline = (struct deadline) { { 10, 123456788 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000); |
|||
deadline = (struct deadline) { { 10, 123456789 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000); |
|||
|
|||
/* Check __deadline_to_ms overflow. */ |
|||
deadline = (struct deadline) { { INT_MAX - 1, 1 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == INT_MAX); |
|||
|
|||
/* Check __deadline_to_ms for elapsed deadlines. */ |
|||
current_time = (struct deadline_current_time) { { 9, 123456789 } }; |
|||
deadline.absolute = current_time.current; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0); |
|||
current_time = (struct deadline_current_time) { { 9, 123456790 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0); |
|||
current_time = (struct deadline_current_time) { { 10, 0 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0); |
|||
current_time = (struct deadline_current_time) { { 10, 123456788 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0); |
|||
current_time = (struct deadline_current_time) { { 10, 123456789 } }; |
|||
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0); |
|||
|
|||
/* Check carry-over in __deadline_from_timeval. */ |
|||
current_time = (struct deadline_current_time) { { 9, 998000001 } }; |
|||
for (int i = 0; i < 2000; ++i) |
|||
{ |
|||
deadline = __deadline_from_timeval |
|||
(current_time, (struct timeval) { 1, i }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 10); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 998000001 + i * 1000); |
|||
} |
|||
for (int i = 2000; i < 3000; ++i) |
|||
{ |
|||
deadline = __deadline_from_timeval |
|||
(current_time, (struct timeval) { 2, i }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 12); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 1 + (i - 2000) * 1000); |
|||
} |
|||
|
|||
/* Check infinite deadlines. */ |
|||
deadline = __deadline_from_timeval |
|||
((struct deadline_current_time) { { 0, 1000 * 1000 * 1000 - 1000 } }, |
|||
(struct timeval) { time_t_max (), 1 }); |
|||
TEST_VERIFY (__deadline_is_infinite (deadline)); |
|||
deadline = __deadline_from_timeval |
|||
((struct deadline_current_time) { { 0, 1000 * 1000 * 1000 - 1001 } }, |
|||
(struct timeval) { time_t_max (), 1 }); |
|||
TEST_VERIFY (!__deadline_is_infinite (deadline)); |
|||
deadline = __deadline_from_timeval |
|||
((struct deadline_current_time) |
|||
{ { time_t_max (), 1000 * 1000 * 1000 - 1000 } }, |
|||
(struct timeval) { 0, 1 }); |
|||
TEST_VERIFY (__deadline_is_infinite (deadline)); |
|||
deadline = __deadline_from_timeval |
|||
((struct deadline_current_time) |
|||
{ { time_t_max () / 2 + 1, 0 } }, |
|||
(struct timeval) { time_t_max () / 2 + 1, 0 }); |
|||
TEST_VERIFY (__deadline_is_infinite (deadline)); |
|||
|
|||
/* Check __deadline_first behavior. */ |
|||
deadline = __deadline_first |
|||
((struct deadline) { { 1, 2 } }, |
|||
(struct deadline) { { 1, 3 } }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 2); |
|||
deadline = __deadline_first |
|||
((struct deadline) { { 1, 3 } }, |
|||
(struct deadline) { { 1, 2 } }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 2); |
|||
deadline = __deadline_first |
|||
((struct deadline) { { 1, 2 } }, |
|||
(struct deadline) { { 2, 1 } }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 2); |
|||
deadline = __deadline_first |
|||
((struct deadline) { { 1, 2 } }, |
|||
(struct deadline) { { 2, 4 } }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 2); |
|||
deadline = __deadline_first |
|||
((struct deadline) { { 2, 4 } }, |
|||
(struct deadline) { { 1, 2 } }); |
|||
TEST_VERIFY (deadline.absolute.tv_sec == 1); |
|||
TEST_VERIFY (deadline.absolute.tv_nsec == 2); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
#include <support/test-driver.c> |
|||
@ -0,0 +1,104 @@ |
|||
/* Test that garbage packets do not affect timeout handling.
|
|||
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 <support/check.h> |
|||
#include <support/namespace.h> |
|||
#include <support/xsocket.h> |
|||
#include <support/xthread.h> |
|||
#include <sys/socket.h> |
|||
#include <unistd.h> |
|||
|
|||
/* Descriptor for the server UDP socket. */ |
|||
static int server_fd; |
|||
|
|||
static void * |
|||
garbage_sender_thread (void *unused) |
|||
{ |
|||
while (true) |
|||
{ |
|||
struct sockaddr_storage sa; |
|||
socklen_t salen = sizeof (sa); |
|||
char buf[1]; |
|||
if (recvfrom (server_fd, buf, sizeof (buf), 0, |
|||
(struct sockaddr *) &sa, &salen) < 0) |
|||
FAIL_EXIT1 ("recvfrom: %m"); |
|||
|
|||
/* Send garbage packets indefinitely. */ |
|||
buf[0] = 0; |
|||
while (true) |
|||
{ |
|||
/* sendto can fail if the client closed the socket. */ |
|||
if (sendto (server_fd, buf, sizeof (buf), 0, |
|||
(struct sockaddr *) &sa, salen) < 0) |
|||
break; |
|||
|
|||
/* Wait a bit, to avoid burning too many CPU cycles in a
|
|||
tight loop. The wait period must be much shorter than |
|||
the client timeouts configured below. */ |
|||
usleep (50 * 1000); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static int |
|||
do_test (void) |
|||
{ |
|||
support_become_root (); |
|||
support_enter_network_namespace (); |
|||
|
|||
server_fd = xsocket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); |
|||
struct sockaddr_in server_address = |
|||
{ |
|||
.sin_family = AF_INET, |
|||
.sin_addr.s_addr = htonl (INADDR_LOOPBACK), |
|||
}; |
|||
xbind (server_fd, |
|||
(struct sockaddr *) &server_address, sizeof (server_address)); |
|||
{ |
|||
socklen_t sinlen = sizeof (server_address); |
|||
xgetsockname (server_fd, (struct sockaddr *) &server_address, &sinlen); |
|||
TEST_VERIFY (sizeof (server_address) == sinlen); |
|||
} |
|||
|
|||
/* Garbage packet source. */ |
|||
xpthread_detach (xpthread_create (NULL, garbage_sender_thread, NULL)); |
|||
|
|||
/* Test client. Use an arbitrary timeout of one second, which is
|
|||
much longer than the garbage packet interval, but still |
|||
reasonably short, so that the test completes quickly. */ |
|||
int client_fd = RPC_ANYSOCK; |
|||
CLIENT *clnt = clntudp_create (&server_address, |
|||
1, 2, /* Arbitrary RPC endpoint numbers. */ |
|||
(struct timeval) { 1, 0 }, |
|||
&client_fd); |
|||
if (clnt == NULL) |
|||
FAIL_EXIT1 ("clntudp_create: %m"); |
|||
|
|||
TEST_VERIFY (clnt_call (clnt, 3, /* Arbitrary RPC procedure number. */ |
|||
(xdrproc_t) xdr_void, NULL, |
|||
(xdrproc_t) xdr_void, NULL, |
|||
((struct timeval) { 1, 0 }))); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
#include <support/test-driver.c> |
|||
@ -0,0 +1,333 @@ |
|||
/* 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> |
|||
@ -0,0 +1,402 @@ |
|||
/* 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> |
|||
Loading…
Reference in new issue