sunrpc: Improvements for UDP client timeout handling [BZ #20257]

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.
This commit is contained in:
Florian Weimer 2017-02-28 15:28:45 +01:00
parent 37fb019cb0
commit cf0bd2f73b
10 changed files with 1346 additions and 58 deletions

View File

@ -1,3 +1,26 @@
2017-02-28 Florian Weimer <fweimer@redhat.com>
[BZ #20257]
* inet/Makefile (routines): Add deadline.
(tests-static): Add tst-deadline.
* inet/net-internal.h (struct deadline_current_time)
(__deadline_current_time, struct deadline, __deadline_is_infinite)
(__deadline_elapsed, __deadline_first, __deadline_from_timeval)
(__deadline_to_ms, __is_timeval_valid_timeout): Declare.
* inet/deadline.c: New file.
* inet/tst-deadline.c: Likewise.
* sunrpc/Makefile (tests): Add tst-udp-nonblocking,
tst-udp-timeout, tst-udp-garbage.
(tst-udp-nonblocking, tst-udp-timeout): Link against libc.so
explicitly.
(tst-udp-garbage): Likewise. Also link against thread library.
* sunrpc/clnt_udp.c (struct cu_data): Mention in comment that the
struct layout is part of the ABI.
(clntudp_call): Rework timeout handling.
* sunrpc/tst-udp-garbage.c: New file.
* sunrpc/tst-udp-nonblocking.c: Likewise.
* sunrpc/tst-udp-timeout.c: Likewise.
2017-02-28 Florian Weimer <fweimer@redhat.com>
[BZ #5010]

View File

@ -45,14 +45,18 @@ routines := htonl htons \
in6_addr getnameinfo if_index ifaddrs inet6_option \
getipv4sourcefilter setipv4sourcefilter \
getsourcefilter setsourcefilter inet6_opt inet6_rth \
inet6_scopeid_pton
inet6_scopeid_pton deadline
aux := check_pf check_native ifreq
tests := htontest test_ifindex tst-ntoa tst-ether_aton tst-network \
tst-gethnm test-ifaddrs bug-if1 test-inet6_opt tst-ether_line \
tst-getni1 tst-getni2 tst-inet6_rth tst-checks tst-checks-posix \
tst-sockaddr tst-inet6_scopeid_pton test-hnto-types
tst-sockaddr tst-inet6_scopeid_pton test-hnto-types tst-deadline
# tst-deadline must be linked statically so that we can access
# internal functions.
tests-static += tst-deadline
include ../Rules

122
inet/deadline.c Normal file
View File

@ -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 (&current_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;
}

View File

@ -20,11 +20,100 @@
#define _NET_INTERNAL_H 1
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h>
int __inet6_scopeid_pton (const struct in6_addr *address,
const char *scope, uint32_t *result)
internal_function attribute_hidden;
libc_hidden_proto (__inet6_scopeid_pton)
/* Deadline handling for enforcing timeouts.
Code should call __deadline_current_time to obtain the current time
and cache it locally. The cache needs updating after every
long-running or potentially blocking operation. Deadlines relative
to the current time can be computed using __deadline_from_timeval.
The deadlines may have to be recomputed in response to certain
events (such as an incoming packet), but they are absolute (not
relative to the current time). A timeout suitable for use with the
poll function can be computed from such a deadline using
__deadline_to_ms.
The fields in the structs defined belowed should only be used
within the implementation. */
/* Cache of the current time. Used to compute deadlines from relative
timeouts and vice versa. */
struct deadline_current_time
{
struct timespec current;
};
/* Return the current time. Terminates the process if the current
time is not available. */
struct deadline_current_time __deadline_current_time (void)
internal_function attribute_hidden;
/* Computed absolute deadline. */
struct deadline
{
struct timespec absolute;
};
/* For internal use only. */
static inline bool
__deadline_is_infinite (struct deadline deadline)
{
return deadline.absolute.tv_nsec < 0;
}
/* Return true if the current time is at the deadline or past it. */
static inline bool
__deadline_elapsed (struct deadline_current_time current,
struct deadline deadline)
{
return !__deadline_is_infinite (deadline)
&& (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 the deadline which occurs first. */
static inline struct deadline
__deadline_first (struct deadline left, struct deadline right)
{
if (__deadline_is_infinite (right)
|| left.absolute.tv_sec < right.absolute.tv_sec
|| (left.absolute.tv_sec == right.absolute.tv_sec
&& left.absolute.tv_nsec < right.absolute.tv_nsec))
return left;
else
return right;
}
/* Add TV to the current time and return it. Returns a special
infinite absolute deadline on overflow. */
struct deadline __deadline_from_timeval (struct deadline_current_time,
struct timeval tv)
internal_function attribute_hidden;
/* Compute the number of milliseconds until the specified deadline,
from the current time in the argument. The result is mainly for
use with poll. If the deadline has already passed, return 0. If
the result would overflow an int, return INT_MAX. */
int __deadline_to_ms (struct deadline_current_time, struct deadline)
internal_function attribute_hidden;
/* Return true if TV.tv_sec is non-negative and TV.tv_usec is in the
interval [0, 999999]. */
static inline bool
__is_timeval_valid_timeout (struct timeval tv)
{
return tv.tv_sec >= 0 && tv.tv_usec >= 0 && tv.tv_usec < 1000 * 1000;
}
#endif /* _NET_INTERNAL_H */

188
inet/tst-deadline.c Normal file
View File

@ -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>

View File

@ -93,12 +93,13 @@ rpcgen-objs = rpc_main.o rpc_hout.o rpc_cout.o rpc_parse.o \
extra-objs = $(rpcgen-objs) $(addprefix cross-,$(rpcgen-objs))
others += rpcgen
tests = tst-xdrmem tst-xdrmem2 test-rpcent tst-udp-error
tests = tst-xdrmem tst-xdrmem2 test-rpcent tst-udp-error tst-udp-timeout \
tst-udp-nonblocking
xtests := tst-getmyaddr
ifeq ($(have-thread-library),yes)
xtests += thrsvc
tests += tst-svc_register
tests += tst-svc_register tst-udp-garbage
endif
ifeq ($(run-built-tests),yes)
@ -238,3 +239,8 @@ $(rpcgen-tests): $(objpfx)%.out: %.x $(objpfx)rpcgen
$(built-program-cmd) -c $< -o $@; \
$(evaluate-test)
endif
$(objpfx)tst-udp-timeout: $(common-objpfx)linkobj/libc.so
$(objpfx)tst-udp-nonblocking: $(common-objpfx)linkobj/libc.so
$(objpfx)tst-udp-garbage: \
$(common-objpfx)linkobj/libc.so $(shared-thread-library)

View File

@ -55,6 +55,7 @@
#endif
#include <kernel-features.h>
#include <inet/net-internal.h>
extern u_long _create_xid (void);
@ -80,7 +81,9 @@ static const struct clnt_ops udp_ops =
};
/*
* Private data kept per client handle
* Private data kept per client handle. This private struct is
* unfortunately part of the ABI; ypbind contains a copy of it and
* accesses it through CLIENT::cl_private field.
*/
struct cu_data
{
@ -278,28 +281,38 @@ clntudp_call (/* client handle */
int inlen;
socklen_t fromlen;
struct pollfd fd;
int milliseconds = (cu->cu_wait.tv_sec * 1000) +
(cu->cu_wait.tv_usec / 1000);
struct sockaddr_in from;
struct rpc_msg reply_msg;
XDR reply_xdrs;
struct timeval time_waited;
bool_t ok;
int nrefreshes = 2; /* number of times to refresh cred */
struct timeval timeout;
int anyup; /* any network interface up */
struct deadline_current_time current_time = __deadline_current_time ();
struct deadline total_deadline; /* Determined once by overall timeout. */
struct deadline response_deadline; /* Determined anew for each query. */
/* Choose the timeout value. For non-sending usage (xargs == NULL),
the total deadline does not matter, only cu->cu_wait is used
below. */
if (xargs != NULL)
{
struct timeval tv;
if (cu->cu_total.tv_usec == -1)
{
timeout = utimeout; /* use supplied timeout */
}
/* Use supplied timeout. */
tv = utimeout;
else
{
timeout = cu->cu_total; /* use default timeout */
/* Use default timeout. */
tv = cu->cu_total;
if (!__is_timeval_valid_timeout (tv))
return (cu->cu_error.re_status = RPC_TIMEDOUT);
total_deadline = __deadline_from_timeval (current_time, tv);
}
time_waited.tv_sec = 0;
time_waited.tv_usec = 0;
/* Guard against bad timeout specification. */
if (!__is_timeval_valid_timeout (cu->cu_wait))
return (cu->cu_error.re_status = RPC_TIMEDOUT);
call_again:
xdrs = &(cu->cu_outxdrs);
if (xargs == NULL)
@ -325,27 +338,46 @@ send_again:
return (cu->cu_error.re_status = RPC_CANTSEND);
}
/*
* Hack to provide rpc-based message passing
*/
if (timeout.tv_sec == 0 && timeout.tv_usec == 0)
{
return (cu->cu_error.re_status = RPC_TIMEDOUT);
}
/* sendto may have blocked, so recompute the current time. */
current_time = __deadline_current_time ();
get_reply:
/*
* sub-optimal code appears here because we have
* some clock time to spare while the packets are in flight.
* (We assume that this is actually only executed once.)
*/
response_deadline = __deadline_from_timeval (current_time, cu->cu_wait);
reply_msg.acpted_rply.ar_verf = _null_auth;
reply_msg.acpted_rply.ar_results.where = resultsp;
reply_msg.acpted_rply.ar_results.proc = xresults;
fd.fd = cu->cu_sock;
fd.events = POLLIN;
anyup = 0;
/* Per-response retry loop. current_time must be up-to-date at the
top of the loop. */
for (;;)
{
int milliseconds;
if (xargs != NULL)
{
if (__deadline_elapsed (current_time, total_deadline))
/* Overall timeout expired. */
return (cu->cu_error.re_status = RPC_TIMEDOUT);
milliseconds = __deadline_to_ms
(current_time, __deadline_first (total_deadline,
response_deadline));
if (milliseconds == 0)
/* Per-query timeout expired. */
goto send_again;
}
else
{
/* xatgs == NULL. Collect a response without sending a
query. In this mode, we need to ignore the total
deadline. */
milliseconds = __deadline_to_ms (current_time, response_deadline);
if (milliseconds == 0)
/* Cannot send again, so bail out. */
return (cu->cu_error.re_status = RPC_CANTSEND);
}
switch (__poll (&fd, 1, milliseconds))
{
@ -356,27 +388,10 @@ send_again:
if (!anyup)
return (cu->cu_error.re_status = RPC_CANTRECV);
}
time_waited.tv_sec += cu->cu_wait.tv_sec;
time_waited.tv_usec += cu->cu_wait.tv_usec;
while (time_waited.tv_usec >= 1000000)
{
time_waited.tv_sec++;
time_waited.tv_usec -= 1000000;
}
if ((time_waited.tv_sec < timeout.tv_sec) ||
((time_waited.tv_sec == timeout.tv_sec) &&
(time_waited.tv_usec < timeout.tv_usec)))
goto send_again;
return (cu->cu_error.re_status = RPC_TIMEDOUT);
/*
* buggy in other cases because time_waited is not being
* updated.
*/
goto next_response;
case -1:
if (errno == EINTR)
continue;
goto next_response;
cu->cu_error.re_errno = errno;
return (cu->cu_error.re_status = RPC_CANTRECV);
}
@ -440,20 +455,22 @@ send_again:
if (inlen < 0)
{
if (errno == EWOULDBLOCK)
continue;
goto next_response;
cu->cu_error.re_errno = errno;
return (cu->cu_error.re_status = RPC_CANTRECV);
}
if (inlen < 4)
continue;
/* see if reply transaction id matches sent id.
Don't do this if we only wait for a replay */
if (xargs != NULL
&& memcmp (cu->cu_inbuf, cu->cu_outbuf, sizeof (u_int32_t)) != 0)
continue;
/* we now assume we have the proper reply */
/* Accept the response if the packet is sufficiently long and
the transaction ID matches the query (if available). */
if (inlen >= 4
&& (xargs == NULL
|| memcmp (cu->cu_inbuf, cu->cu_outbuf,
sizeof (u_int32_t)) == 0))
break;
next_response:
/* Update the current time because poll and recvmsg waited for
an unknown time. */
current_time = __deadline_current_time ();
}
/*

104
sunrpc/tst-udp-garbage.c Normal file
View File

@ -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>

View File

@ -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>

402
sunrpc/tst-udp-timeout.c Normal file
View File

@ -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>