This is the mail archive of the glibc-cvs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

GNU C Library master sources branch master updated. glibc-2.25-75-gcf0bd2f


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU C Library master sources".

The branch, master has been updated
       via  cf0bd2f73bd65beab613865bba567d7787836888 (commit)
      from  37fb019cb02656d0ce0b8d40d56fe8c42f0d1658 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=cf0bd2f73bd65beab613865bba567d7787836888

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.

diff --git a/ChangeLog b/ChangeLog
index edccd36..b3d7ada 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,28 @@
 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]
 	* sunrpc/svc.c (svc_is_mapped): Remove.
 	(svc_unregister): Obtain mapped status while the service is still
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
+   <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;
+}
diff --git a/inet/net-internal.h b/inet/net-internal.h
index 087597e..2b2632c 100644
--- a/inet/net-internal.h
+++ b/inet/net-internal.h
@@ -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 */
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
+   <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>
diff --git a/sunrpc/Makefile b/sunrpc/Makefile
index 0249e10..063a761 100644
--- a/sunrpc/Makefile
+++ b/sunrpc/Makefile
@@ -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)
diff --git a/sunrpc/clnt_udp.c b/sunrpc/clnt_udp.c
index 1de25cb..6ce16eb 100644
--- a/sunrpc/clnt_udp.c
+++ b/sunrpc/clnt_udp.c
@@ -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 */
 
-  if (cu->cu_total.tv_usec == -1)
-    {
-      timeout = utimeout;	/* use supplied timeout */
-    }
-  else
+  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)
     {
-      timeout = cu->cu_total;	/* use default timeout */
+      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);
     }
 
-  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 */
-      break;
+      /* 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 ();
     }
 
   /*
diff --git a/sunrpc/tst-udp-garbage.c b/sunrpc/tst-udp-garbage.c
new file mode 100644
index 0000000..4abda93
--- /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>
diff --git a/sunrpc/tst-udp-nonblocking.c b/sunrpc/tst-udp-nonblocking.c
new file mode 100644
index 0000000..1d6a7f4
--- /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>
diff --git a/sunrpc/tst-udp-timeout.c b/sunrpc/tst-udp-timeout.c
new file mode 100644
index 0000000..db9943a
--- /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>

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                    |   23 +++
 inet/Makefile                |    8 +-
 inet/deadline.c              |  122 +++++++++++++
 inet/net-internal.h          |   89 +++++++++
 inet/tst-deadline.c          |  188 ++++++++++++++++++++
 sunrpc/Makefile              |   10 +-
 sunrpc/clnt_udp.c            |  127 ++++++++------
 sunrpc/tst-udp-garbage.c     |  104 +++++++++++
 sunrpc/tst-udp-nonblocking.c |  333 ++++++++++++++++++++++++++++++++++
 sunrpc/tst-udp-timeout.c     |  402 ++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 1347 insertions(+), 59 deletions(-)
 create mode 100644 inet/deadline.c
 create mode 100644 inet/tst-deadline.c
 create mode 100644 sunrpc/tst-udp-garbage.c
 create mode 100644 sunrpc/tst-udp-nonblocking.c
 create mode 100644 sunrpc/tst-udp-timeout.c


hooks/post-receive
-- 
GNU C Library master sources


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]