summary refs log tree commit diff
path: root/inet
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-02-28 15:28:45 +0100
committerFlorian Weimer <fweimer@redhat.com>2017-02-28 15:36:17 +0100
commitcf0bd2f73bd65beab613865bba567d7787836888 (patch)
tree8cde5a2a9547382fac6546c9397aebbb914cb485 /inet
parent37fb019cb02656d0ce0b8d40d56fe8c42f0d1658 (diff)
downloadglibc-cf0bd2f73bd65beab613865bba567d7787836888.tar.gz
glibc-cf0bd2f73bd65beab613865bba567d7787836888.tar.xz
glibc-cf0bd2f73bd65beab613865bba567d7787836888.zip
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.
Diffstat (limited to 'inet')
-rw-r--r--inet/Makefile8
-rw-r--r--inet/deadline.c122
-rw-r--r--inet/net-internal.h89
-rw-r--r--inet/tst-deadline.c188
4 files changed, 405 insertions, 2 deletions
diff --git a/inet/Makefile b/inet/Makefile
index 010792af8f..6a7d3e0664 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 0000000000..c1fa415a39
--- /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 087597ed99..2b2632c7ba 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 0000000000..ed04345c35
--- /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>