You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1552 lines
50 KiB
1552 lines
50 KiB
commit cf0bd2f73bd65beab613865bba567d7787836888 |
|
Author: Florian Weimer <fweimer@redhat.com> |
|
Date: Tue Feb 28 15:28:45 2017 +0100 |
|
|
|
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. |
|
|
|
Index: b/inet/deadline.c |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/inet/deadline.c |
|
@@ -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; |
|
+} |
|
Index: b/inet/tst-deadline.c |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/inet/tst-deadline.c |
|
@@ -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> |
|
Index: b/sunrpc/Makefile |
|
=================================================================== |
|
--- a/sunrpc/Makefile |
|
+++ b/sunrpc/Makefile |
|
@@ -96,11 +96,13 @@ others += rpcgen |
|
|
|
all: # Make this the default target; it will be defined in Rules. |
|
|
|
-tests = tst-xdrmem tst-xdrmem2 |
|
+tests = tst-xdrmem tst-xdrmem2 tst-udp-timeout \ |
|
+ tst-udp-nonblocking |
|
xtests := tst-getmyaddr |
|
|
|
ifeq ($(have-thread-library),yes) |
|
xtests += thrsvc |
|
+tests += tst-udp-garbage |
|
endif |
|
|
|
headers += $(rpcsvc:%.x=rpcsvc/%.h) |
|
@@ -225,3 +227,8 @@ endif |
|
endif |
|
|
|
$(objpfx)thrsvc: $(common-objpfx)linkobj/libc.so $(shared-thread-library) |
|
+ |
|
+$(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) |
|
Index: b/sunrpc/clnt_udp.c |
|
=================================================================== |
|
--- a/sunrpc/clnt_udp.c |
|
+++ b/sunrpc/clnt_udp.c |
|
@@ -54,6 +54,7 @@ |
|
#endif |
|
|
|
#include <kernel-features.h> |
|
+#include <inet/net-internal.h> |
|
|
|
extern u_long _create_xid (void); |
|
|
|
@@ -79,7 +80,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 |
|
{ |
|
@@ -309,28 +312,38 @@ clntudp_call (cl, proc, xargs, argsp, xr |
|
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 */ |
|
|
|
- if (cu->cu_total.tv_usec == -1) |
|
- { |
|
- timeout = utimeout; /* use supplied timeout */ |
|
- } |
|
- else |
|
- { |
|
- timeout = cu->cu_total; /* use default timeout */ |
|
- } |
|
+ 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) |
|
+ /* Use supplied timeout. */ |
|
+ tv = utimeout; |
|
+ else |
|
+ /* 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); |
|
+ } |
|
+ |
|
+ /* Guard against bad timeout specification. */ |
|
+ if (!__is_timeval_valid_timeout (cu->cu_wait)) |
|
+ return (cu->cu_error.re_status = RPC_TIMEDOUT); |
|
|
|
- time_waited.tv_sec = 0; |
|
- time_waited.tv_usec = 0; |
|
call_again: |
|
xdrs = &(cu->cu_outxdrs); |
|
if (xargs == NULL) |
|
@@ -356,27 +369,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)) |
|
{ |
|
|
|
@@ -387,27 +419,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); |
|
} |
|
@@ -463,20 +478,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; |
|
+ /* 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; |
|
|
|
- /* 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 */ |
|
- break; |
|
+ next_response: |
|
+ /* Update the current time because poll and recvmsg waited for |
|
+ an unknown time. */ |
|
+ current_time = __deadline_current_time (); |
|
} |
|
|
|
/* |
|
Index: b/sunrpc/tst-udp-garbage.c |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/sunrpc/tst-udp-garbage.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> |
|
Index: b/sunrpc/tst-udp-nonblocking.c |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/sunrpc/tst-udp-nonblocking.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> |
|
Index: b/sunrpc/tst-udp-timeout.c |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/sunrpc/tst-udp-timeout.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> |
|
Index: b/inet/net-internal.h |
|
=================================================================== |
|
--- /dev/null |
|
+++ b/inet/net-internal.h |
|
@@ -0,0 +1,112 @@ |
|
+/* Network-related functions for internal library use. |
|
+ Copyright (C) 2016-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/>. */ |
|
+ |
|
+#ifndef _NET_INTERNAL_H |
|
+#define _NET_INTERNAL_H 1 |
|
+ |
|
+#include <stdbool.h> |
|
+#include <stdint.h> |
|
+#include <sys/time.h> |
|
+ |
|
+/* 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 */ |
|
Index: b/inet/Makefile |
|
=================================================================== |
|
--- a/inet/Makefile |
|
+++ b/inet/Makefile |
|
@@ -44,13 +44,18 @@ routines := htonl htons \ |
|
getaliasent_r getaliasent getaliasname getaliasname_r \ |
|
in6_addr getnameinfo if_index ifaddrs inet6_option \ |
|
getipv4sourcefilter setipv4sourcefilter \ |
|
- getsourcefilter setsourcefilter inet6_opt inet6_rth |
|
+ getsourcefilter setsourcefilter inet6_opt inet6_rth \ |
|
+ 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-getni1 tst-getni2 tst-inet6_rth tst-checks tst-deadline |
|
+ |
|
+# tst-deadline must be linked statically so that we can access |
|
+# internal functions. |
|
+tests-static += tst-deadline |
|
|
|
include ../Rules |
|
|
|
|