From patchwork Thu Feb 9 15:36:28 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 19195 Received: (qmail 21975 invoked by alias); 9 Feb 2017 15:36:44 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 21908 invoked by uid 89); 9 Feb 2017 15:36:39 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.8 required=5.0 tests=BAYES_50, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=123456789, structs, refresh, 556 X-HELO: mx1.redhat.com Date: Thu, 09 Feb 2017 16:36:28 +0100 To: libc-alpha@sourceware.org Subject: [PATCH] sunrpc: Improvements for UDP client timeout handling [BZ #20257] User-Agent: Heirloom mailx 12.5 7/5/10 MIME-Version: 1.0 Message-Id: <20170209153628.12AFC4025DC0A@oldenburg.str.redhat.com> From: fweimer@redhat.com (Florian Weimer) 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. 2017-02-09 Florian Weimer [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-timeout, tst-udp-garbage. (tst-udp-timeout): Link against libc.so explicitly. (tst-udp-garbage): Likewise. Also link against thread library. * sunrpc/clnt_udp.c (clntudp_call): Rework timeout handling. * sunrpc/tst-udp-garbage.c: New file. * sunrpc/tst-udp-timeout.c: Likewise. diff --git a/inet/Makefile b/inet/Makefile index 010792a..6a7d3e0 100644 --- a/inet/Makefile +++ b/inet/Makefile @@ -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 diff --git a/inet/deadline.c b/inet/deadline.c new file mode 100644 index 0000000..c1fa415 --- /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 + . */ + +#include + +#include +#include +#include +#include +#include + +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; +} diff --git a/inet/net-internal.h b/inet/net-internal.h index 087597e..adab90c 100644 --- a/inet/net-internal.h +++ b/inet/net-internal.h @@ -20,11 +20,100 @@ #define _NET_INTERNAL_H 1 #include +#include #include +#include 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 to updated 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 */ diff --git a/inet/tst-deadline.c b/inet/tst-deadline.c new file mode 100644 index 0000000..ed04345 --- /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 + . */ + +#include +#include +#include +#include +#include + +/* 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 diff --git a/sunrpc/Makefile b/sunrpc/Makefile index 0249e10..28c5514 100644 --- a/sunrpc/Makefile +++ b/sunrpc/Makefile @@ -98,7 +98,7 @@ xtests := tst-getmyaddr ifeq ($(have-thread-library),yes) xtests += thrsvc -tests += tst-svc_register +tests += tst-svc_register tst-udp-timeout tst-udp-garbage endif ifeq ($(run-built-tests),yes) @@ -238,3 +238,7 @@ $(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-garbage: \ + $(common-objpfx)linkobj/libc.so $(shared-thread-library) diff --git a/sunrpc/clnt_udp.c b/sunrpc/clnt_udp.c index 1de25cb..b7ff6e1 100644 --- a/sunrpc/clnt_udp.c +++ b/sunrpc/clnt_udp.c @@ -55,6 +55,7 @@ #endif #include +#include extern u_long _create_xid (void); @@ -278,28 +279,35 @@ 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 */ - 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. */ + { + 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) @@ -325,27 +333,31 @@ 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 (;;) { + if (__deadline_elapsed (current_time, total_deadline)) + /* Overall timeout expired. */ + return (cu->cu_error.re_status = RPC_TIMEDOUT); + int milliseconds = __deadline_to_ms + (current_time, __deadline_first (total_deadline, response_deadline)); + if (milliseconds == 0) + /* Per-query timeout expired. */ + goto send_again; + switch (__poll (&fd, 1, milliseconds)) { @@ -356,27 +368,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 +435,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 (); } /* diff --git a/sunrpc/tst-udp-garbage.c b/sunrpc/tst-udp-garbage.c new file mode 100644 index 0000000..95cf49e --- /dev/null +++ b/sunrpc/tst-udp-garbage.c @@ -0,0 +1,93 @@ +/* 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; + 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); + } + + xpthread_detach (xpthread_create (NULL, garbage_sender_thread, NULL)); + + int client_fd = RPC_ANYSOCK; + CLIENT *clnt = clntudp_create (&server_address, 1, 2, + (struct timeval) { .tv_sec = 1, }, + &client_fd); + if (clnt == NULL) + FAIL_EXIT1 ("clntudp_create: %m"); + + clnt_call (clnt, 3, (xdrproc_t) xdr_void, NULL, (xdrproc_t) xdr_void, NULL, + ((struct timeval) { .tv_sec = 1, })); + + return 0; +} + +#include diff --git a/sunrpc/tst-udp-timeout.c b/sunrpc/tst-udp-timeout.c new file mode 100644 index 0000000..2c27784 --- /dev/null +++ b/sunrpc/tst-udp-timeout.c @@ -0,0 +1,384 @@ +/* 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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. */ + 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) + { + 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)); + + if (request->rq_proc == PROC_EXIT) + _exit (EXIT_MARKER); + } + 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 long timeout to flush out all pending requests. */ + 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); +} + +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. */ + 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 (); + printf ("info: 21 garbage packets took %f seconds\n", after - before); + 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); + 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); + 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; +} + +#include