mirror of
https://sourceware.org/git/glibc.git
synced 2025-07-30 22:43:12 +03:00
sunrpc: Improvements for UDP client timeout handling [BZ #20257]
This commit fixes various aspects in the UDP client timeout handling. Timeouts are now applied in a more consistent fashion. Discarded UDP packets no longer prevent the timeout from happening at all.
This commit is contained in:
23
ChangeLog
23
ChangeLog
@ -1,3 +1,26 @@
|
|||||||
|
2017-02-28 Florian Weimer <fweimer@redhat.com>
|
||||||
|
|
||||||
|
[BZ #20257]
|
||||||
|
* inet/Makefile (routines): Add deadline.
|
||||||
|
(tests-static): Add tst-deadline.
|
||||||
|
* inet/net-internal.h (struct deadline_current_time)
|
||||||
|
(__deadline_current_time, struct deadline, __deadline_is_infinite)
|
||||||
|
(__deadline_elapsed, __deadline_first, __deadline_from_timeval)
|
||||||
|
(__deadline_to_ms, __is_timeval_valid_timeout): Declare.
|
||||||
|
* inet/deadline.c: New file.
|
||||||
|
* inet/tst-deadline.c: Likewise.
|
||||||
|
* sunrpc/Makefile (tests): Add tst-udp-nonblocking,
|
||||||
|
tst-udp-timeout, tst-udp-garbage.
|
||||||
|
(tst-udp-nonblocking, tst-udp-timeout): Link against libc.so
|
||||||
|
explicitly.
|
||||||
|
(tst-udp-garbage): Likewise. Also link against thread library.
|
||||||
|
* sunrpc/clnt_udp.c (struct cu_data): Mention in comment that the
|
||||||
|
struct layout is part of the ABI.
|
||||||
|
(clntudp_call): Rework timeout handling.
|
||||||
|
* sunrpc/tst-udp-garbage.c: New file.
|
||||||
|
* sunrpc/tst-udp-nonblocking.c: Likewise.
|
||||||
|
* sunrpc/tst-udp-timeout.c: Likewise.
|
||||||
|
|
||||||
2017-02-28 Florian Weimer <fweimer@redhat.com>
|
2017-02-28 Florian Weimer <fweimer@redhat.com>
|
||||||
|
|
||||||
[BZ #5010]
|
[BZ #5010]
|
||||||
|
@ -45,14 +45,18 @@ routines := htonl htons \
|
|||||||
in6_addr getnameinfo if_index ifaddrs inet6_option \
|
in6_addr getnameinfo if_index ifaddrs inet6_option \
|
||||||
getipv4sourcefilter setipv4sourcefilter \
|
getipv4sourcefilter setipv4sourcefilter \
|
||||||
getsourcefilter setsourcefilter inet6_opt inet6_rth \
|
getsourcefilter setsourcefilter inet6_opt inet6_rth \
|
||||||
inet6_scopeid_pton
|
inet6_scopeid_pton deadline
|
||||||
|
|
||||||
aux := check_pf check_native ifreq
|
aux := check_pf check_native ifreq
|
||||||
|
|
||||||
tests := htontest test_ifindex tst-ntoa tst-ether_aton tst-network \
|
tests := htontest test_ifindex tst-ntoa tst-ether_aton tst-network \
|
||||||
tst-gethnm test-ifaddrs bug-if1 test-inet6_opt tst-ether_line \
|
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-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
|
include ../Rules
|
||||||
|
|
||||||
|
122
inet/deadline.c
Normal file
122
inet/deadline.c
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/* Computing deadlines for timeouts.
|
||||||
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <net-internal.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
struct deadline_current_time internal_function
|
||||||
|
__deadline_current_time (void)
|
||||||
|
{
|
||||||
|
struct deadline_current_time result;
|
||||||
|
if (__clock_gettime (CLOCK_MONOTONIC, &result.current) != 0)
|
||||||
|
{
|
||||||
|
struct timeval current_tv;
|
||||||
|
if (__gettimeofday (¤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;
|
||||||
|
}
|
@ -20,11 +20,100 @@
|
|||||||
#define _NET_INTERNAL_H 1
|
#define _NET_INTERNAL_H 1
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
int __inet6_scopeid_pton (const struct in6_addr *address,
|
int __inet6_scopeid_pton (const struct in6_addr *address,
|
||||||
const char *scope, uint32_t *result)
|
const char *scope, uint32_t *result)
|
||||||
internal_function attribute_hidden;
|
internal_function attribute_hidden;
|
||||||
libc_hidden_proto (__inet6_scopeid_pton)
|
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 */
|
#endif /* _NET_INTERNAL_H */
|
||||||
|
188
inet/tst-deadline.c
Normal file
188
inet/tst-deadline.c
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/* Tests for computing deadlines for timeouts.
|
||||||
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <inet/net-internal.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
|
||||||
|
/* Find the maximum value which can be represented in a time_t. */
|
||||||
|
static time_t
|
||||||
|
time_t_max (void)
|
||||||
|
{
|
||||||
|
_Static_assert (0 > (time_t) -1, "time_t is signed");
|
||||||
|
uintmax_t current = 1;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
uintmax_t next = current * 2;
|
||||||
|
/* This cannot happen because time_t is signed. */
|
||||||
|
TEST_VERIFY_EXIT (next > current);
|
||||||
|
++next;
|
||||||
|
if ((time_t) next < 0 || next != (uintmax_t) (time_t) next)
|
||||||
|
/* Value cannot be represented in time_t. Return the previous
|
||||||
|
value. */
|
||||||
|
return current;
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
struct deadline_current_time current_time = __deadline_current_time ();
|
||||||
|
TEST_VERIFY (current_time.current.tv_sec >= 0);
|
||||||
|
current_time = __deadline_current_time ();
|
||||||
|
/* Due to CLOCK_MONOTONIC, either seconds or nanoseconds are
|
||||||
|
greater than zero. This is also true for the gettimeofday
|
||||||
|
fallback. */
|
||||||
|
TEST_VERIFY (current_time.current.tv_sec >= 0);
|
||||||
|
TEST_VERIFY (current_time.current.tv_sec > 0
|
||||||
|
|| current_time.current.tv_nsec > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check basic computations of deadlines. */
|
||||||
|
struct deadline_current_time current_time = { { 1, 123456789 } };
|
||||||
|
struct deadline deadline = __deadline_from_timeval
|
||||||
|
(current_time, (struct timeval) { 0, 1 });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 123457789);
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1);
|
||||||
|
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
(current_time, ((struct timeval) { 0, 2 }));
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 123458789);
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1);
|
||||||
|
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
(current_time, ((struct timeval) { 1, 0 }));
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 2);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 123456789);
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000);
|
||||||
|
|
||||||
|
/* Check if timeouts are correctly rounded up to the next
|
||||||
|
millisecond. */
|
||||||
|
for (int i = 0; i < 999999; ++i)
|
||||||
|
{
|
||||||
|
++current_time.current.tv_nsec;
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A full millisecond has elapsed, so the time to the deadline is
|
||||||
|
now less than 1000. */
|
||||||
|
++current_time.current.tv_nsec;
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 999);
|
||||||
|
|
||||||
|
/* Check __deadline_to_ms carry-over. */
|
||||||
|
current_time = (struct deadline_current_time) { { 9, 123456789 } };
|
||||||
|
deadline = (struct deadline) { { 10, 122456789 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 999);
|
||||||
|
deadline = (struct deadline) { { 10, 122456790 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000);
|
||||||
|
deadline = (struct deadline) { { 10, 123456788 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000);
|
||||||
|
deadline = (struct deadline) { { 10, 123456789 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 1000);
|
||||||
|
|
||||||
|
/* Check __deadline_to_ms overflow. */
|
||||||
|
deadline = (struct deadline) { { INT_MAX - 1, 1 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == INT_MAX);
|
||||||
|
|
||||||
|
/* Check __deadline_to_ms for elapsed deadlines. */
|
||||||
|
current_time = (struct deadline_current_time) { { 9, 123456789 } };
|
||||||
|
deadline.absolute = current_time.current;
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0);
|
||||||
|
current_time = (struct deadline_current_time) { { 9, 123456790 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0);
|
||||||
|
current_time = (struct deadline_current_time) { { 10, 0 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0);
|
||||||
|
current_time = (struct deadline_current_time) { { 10, 123456788 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0);
|
||||||
|
current_time = (struct deadline_current_time) { { 10, 123456789 } };
|
||||||
|
TEST_VERIFY (__deadline_to_ms (current_time, deadline) == 0);
|
||||||
|
|
||||||
|
/* Check carry-over in __deadline_from_timeval. */
|
||||||
|
current_time = (struct deadline_current_time) { { 9, 998000001 } };
|
||||||
|
for (int i = 0; i < 2000; ++i)
|
||||||
|
{
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
(current_time, (struct timeval) { 1, i });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 10);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 998000001 + i * 1000);
|
||||||
|
}
|
||||||
|
for (int i = 2000; i < 3000; ++i)
|
||||||
|
{
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
(current_time, (struct timeval) { 2, i });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 12);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 1 + (i - 2000) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check infinite deadlines. */
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
((struct deadline_current_time) { { 0, 1000 * 1000 * 1000 - 1000 } },
|
||||||
|
(struct timeval) { time_t_max (), 1 });
|
||||||
|
TEST_VERIFY (__deadline_is_infinite (deadline));
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
((struct deadline_current_time) { { 0, 1000 * 1000 * 1000 - 1001 } },
|
||||||
|
(struct timeval) { time_t_max (), 1 });
|
||||||
|
TEST_VERIFY (!__deadline_is_infinite (deadline));
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
((struct deadline_current_time)
|
||||||
|
{ { time_t_max (), 1000 * 1000 * 1000 - 1000 } },
|
||||||
|
(struct timeval) { 0, 1 });
|
||||||
|
TEST_VERIFY (__deadline_is_infinite (deadline));
|
||||||
|
deadline = __deadline_from_timeval
|
||||||
|
((struct deadline_current_time)
|
||||||
|
{ { time_t_max () / 2 + 1, 0 } },
|
||||||
|
(struct timeval) { time_t_max () / 2 + 1, 0 });
|
||||||
|
TEST_VERIFY (__deadline_is_infinite (deadline));
|
||||||
|
|
||||||
|
/* Check __deadline_first behavior. */
|
||||||
|
deadline = __deadline_first
|
||||||
|
((struct deadline) { { 1, 2 } },
|
||||||
|
(struct deadline) { { 1, 3 } });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 2);
|
||||||
|
deadline = __deadline_first
|
||||||
|
((struct deadline) { { 1, 3 } },
|
||||||
|
(struct deadline) { { 1, 2 } });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 2);
|
||||||
|
deadline = __deadline_first
|
||||||
|
((struct deadline) { { 1, 2 } },
|
||||||
|
(struct deadline) { { 2, 1 } });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 2);
|
||||||
|
deadline = __deadline_first
|
||||||
|
((struct deadline) { { 1, 2 } },
|
||||||
|
(struct deadline) { { 2, 4 } });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 2);
|
||||||
|
deadline = __deadline_first
|
||||||
|
((struct deadline) { { 2, 4 } },
|
||||||
|
(struct deadline) { { 1, 2 } });
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_sec == 1);
|
||||||
|
TEST_VERIFY (deadline.absolute.tv_nsec == 2);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <support/test-driver.c>
|
@ -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))
|
extra-objs = $(rpcgen-objs) $(addprefix cross-,$(rpcgen-objs))
|
||||||
others += rpcgen
|
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
|
xtests := tst-getmyaddr
|
||||||
|
|
||||||
ifeq ($(have-thread-library),yes)
|
ifeq ($(have-thread-library),yes)
|
||||||
xtests += thrsvc
|
xtests += thrsvc
|
||||||
tests += tst-svc_register
|
tests += tst-svc_register tst-udp-garbage
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(run-built-tests),yes)
|
ifeq ($(run-built-tests),yes)
|
||||||
@ -238,3 +239,8 @@ $(rpcgen-tests): $(objpfx)%.out: %.x $(objpfx)rpcgen
|
|||||||
$(built-program-cmd) -c $< -o $@; \
|
$(built-program-cmd) -c $< -o $@; \
|
||||||
$(evaluate-test)
|
$(evaluate-test)
|
||||||
endif
|
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)
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <kernel-features.h>
|
#include <kernel-features.h>
|
||||||
|
#include <inet/net-internal.h>
|
||||||
|
|
||||||
extern u_long _create_xid (void);
|
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
|
struct cu_data
|
||||||
{
|
{
|
||||||
@ -278,28 +281,38 @@ clntudp_call (/* client handle */
|
|||||||
int inlen;
|
int inlen;
|
||||||
socklen_t fromlen;
|
socklen_t fromlen;
|
||||||
struct pollfd fd;
|
struct pollfd fd;
|
||||||
int milliseconds = (cu->cu_wait.tv_sec * 1000) +
|
|
||||||
(cu->cu_wait.tv_usec / 1000);
|
|
||||||
struct sockaddr_in from;
|
struct sockaddr_in from;
|
||||||
struct rpc_msg reply_msg;
|
struct rpc_msg reply_msg;
|
||||||
XDR reply_xdrs;
|
XDR reply_xdrs;
|
||||||
struct timeval time_waited;
|
|
||||||
bool_t ok;
|
bool_t ok;
|
||||||
int nrefreshes = 2; /* number of times to refresh cred */
|
int nrefreshes = 2; /* number of times to refresh cred */
|
||||||
struct timeval timeout;
|
|
||||||
int anyup; /* any network interface up */
|
int anyup; /* any network interface up */
|
||||||
|
|
||||||
if (cu->cu_total.tv_usec == -1)
|
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 = utimeout; /* use supplied timeout */
|
struct timeval tv;
|
||||||
}
|
if (cu->cu_total.tv_usec == -1)
|
||||||
else
|
/* Use supplied timeout. */
|
||||||
{
|
tv = utimeout;
|
||||||
timeout = cu->cu_total; /* use default timeout */
|
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;
|
/* Guard against bad timeout specification. */
|
||||||
time_waited.tv_usec = 0;
|
if (!__is_timeval_valid_timeout (cu->cu_wait))
|
||||||
|
return (cu->cu_error.re_status = RPC_TIMEDOUT);
|
||||||
|
|
||||||
call_again:
|
call_again:
|
||||||
xdrs = &(cu->cu_outxdrs);
|
xdrs = &(cu->cu_outxdrs);
|
||||||
if (xargs == NULL)
|
if (xargs == NULL)
|
||||||
@ -325,27 +338,46 @@ send_again:
|
|||||||
return (cu->cu_error.re_status = RPC_CANTSEND);
|
return (cu->cu_error.re_status = RPC_CANTSEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* sendto may have blocked, so recompute the current time. */
|
||||||
* Hack to provide rpc-based message passing
|
current_time = __deadline_current_time ();
|
||||||
*/
|
|
||||||
if (timeout.tv_sec == 0 && timeout.tv_usec == 0)
|
|
||||||
{
|
|
||||||
return (cu->cu_error.re_status = RPC_TIMEDOUT);
|
|
||||||
}
|
|
||||||
get_reply:
|
get_reply:
|
||||||
/*
|
response_deadline = __deadline_from_timeval (current_time, cu->cu_wait);
|
||||||
* 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.)
|
|
||||||
*/
|
|
||||||
reply_msg.acpted_rply.ar_verf = _null_auth;
|
reply_msg.acpted_rply.ar_verf = _null_auth;
|
||||||
reply_msg.acpted_rply.ar_results.where = resultsp;
|
reply_msg.acpted_rply.ar_results.where = resultsp;
|
||||||
reply_msg.acpted_rply.ar_results.proc = xresults;
|
reply_msg.acpted_rply.ar_results.proc = xresults;
|
||||||
fd.fd = cu->cu_sock;
|
fd.fd = cu->cu_sock;
|
||||||
fd.events = POLLIN;
|
fd.events = POLLIN;
|
||||||
anyup = 0;
|
anyup = 0;
|
||||||
|
|
||||||
|
/* Per-response retry loop. current_time must be up-to-date at the
|
||||||
|
top of the loop. */
|
||||||
for (;;)
|
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))
|
switch (__poll (&fd, 1, milliseconds))
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -356,27 +388,10 @@ send_again:
|
|||||||
if (!anyup)
|
if (!anyup)
|
||||||
return (cu->cu_error.re_status = RPC_CANTRECV);
|
return (cu->cu_error.re_status = RPC_CANTRECV);
|
||||||
}
|
}
|
||||||
|
goto next_response;
|
||||||
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.
|
|
||||||
*/
|
|
||||||
case -1:
|
case -1:
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
continue;
|
goto next_response;
|
||||||
cu->cu_error.re_errno = errno;
|
cu->cu_error.re_errno = errno;
|
||||||
return (cu->cu_error.re_status = RPC_CANTRECV);
|
return (cu->cu_error.re_status = RPC_CANTRECV);
|
||||||
}
|
}
|
||||||
@ -440,20 +455,22 @@ send_again:
|
|||||||
if (inlen < 0)
|
if (inlen < 0)
|
||||||
{
|
{
|
||||||
if (errno == EWOULDBLOCK)
|
if (errno == EWOULDBLOCK)
|
||||||
continue;
|
goto next_response;
|
||||||
cu->cu_error.re_errno = errno;
|
cu->cu_error.re_errno = errno;
|
||||||
return (cu->cu_error.re_status = RPC_CANTRECV);
|
return (cu->cu_error.re_status = RPC_CANTRECV);
|
||||||
}
|
}
|
||||||
if (inlen < 4)
|
/* Accept the response if the packet is sufficiently long and
|
||||||
continue;
|
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.
|
next_response:
|
||||||
Don't do this if we only wait for a replay */
|
/* Update the current time because poll and recvmsg waited for
|
||||||
if (xargs != NULL
|
an unknown time. */
|
||||||
&& memcmp (cu->cu_inbuf, cu->cu_outbuf, sizeof (u_int32_t)) != 0)
|
current_time = __deadline_current_time ();
|
||||||
continue;
|
|
||||||
/* we now assume we have the proper reply */
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
104
sunrpc/tst-udp-garbage.c
Normal file
104
sunrpc/tst-udp-garbage.c
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/* Test that garbage packets do not affect timeout handling.
|
||||||
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <rpc/clnt.h>
|
||||||
|
#include <rpc/svc.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/namespace.h>
|
||||||
|
#include <support/xsocket.h>
|
||||||
|
#include <support/xthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* Descriptor for the server UDP socket. */
|
||||||
|
static int server_fd;
|
||||||
|
|
||||||
|
static void *
|
||||||
|
garbage_sender_thread (void *unused)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
struct sockaddr_storage sa;
|
||||||
|
socklen_t salen = sizeof (sa);
|
||||||
|
char buf[1];
|
||||||
|
if (recvfrom (server_fd, buf, sizeof (buf), 0,
|
||||||
|
(struct sockaddr *) &sa, &salen) < 0)
|
||||||
|
FAIL_EXIT1 ("recvfrom: %m");
|
||||||
|
|
||||||
|
/* Send garbage packets indefinitely. */
|
||||||
|
buf[0] = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
/* sendto can fail if the client closed the socket. */
|
||||||
|
if (sendto (server_fd, buf, sizeof (buf), 0,
|
||||||
|
(struct sockaddr *) &sa, salen) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Wait a bit, to avoid burning too many CPU cycles in a
|
||||||
|
tight loop. The wait period must be much shorter than
|
||||||
|
the client timeouts configured below. */
|
||||||
|
usleep (50 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
support_become_root ();
|
||||||
|
support_enter_network_namespace ();
|
||||||
|
|
||||||
|
server_fd = xsocket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
|
||||||
|
struct sockaddr_in server_address =
|
||||||
|
{
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = htonl (INADDR_LOOPBACK),
|
||||||
|
};
|
||||||
|
xbind (server_fd,
|
||||||
|
(struct sockaddr *) &server_address, sizeof (server_address));
|
||||||
|
{
|
||||||
|
socklen_t sinlen = sizeof (server_address);
|
||||||
|
xgetsockname (server_fd, (struct sockaddr *) &server_address, &sinlen);
|
||||||
|
TEST_VERIFY (sizeof (server_address) == sinlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Garbage packet source. */
|
||||||
|
xpthread_detach (xpthread_create (NULL, garbage_sender_thread, NULL));
|
||||||
|
|
||||||
|
/* Test client. Use an arbitrary timeout of one second, which is
|
||||||
|
much longer than the garbage packet interval, but still
|
||||||
|
reasonably short, so that the test completes quickly. */
|
||||||
|
int client_fd = RPC_ANYSOCK;
|
||||||
|
CLIENT *clnt = clntudp_create (&server_address,
|
||||||
|
1, 2, /* Arbitrary RPC endpoint numbers. */
|
||||||
|
(struct timeval) { 1, 0 },
|
||||||
|
&client_fd);
|
||||||
|
if (clnt == NULL)
|
||||||
|
FAIL_EXIT1 ("clntudp_create: %m");
|
||||||
|
|
||||||
|
TEST_VERIFY (clnt_call (clnt, 3, /* Arbitrary RPC procedure number. */
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
((struct timeval) { 1, 0 })));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <support/test-driver.c>
|
333
sunrpc/tst-udp-nonblocking.c
Normal file
333
sunrpc/tst-udp-nonblocking.c
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/* Test non-blocking use of the UDP client.
|
||||||
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <rpc/clnt.h>
|
||||||
|
#include <rpc/svc.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/namespace.h>
|
||||||
|
#include <support/test-driver.h>
|
||||||
|
#include <support/xsocket.h>
|
||||||
|
#include <support/xunistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* Test data serialization and deserialization. */
|
||||||
|
|
||||||
|
struct test_query
|
||||||
|
{
|
||||||
|
uint32_t a;
|
||||||
|
uint32_t b;
|
||||||
|
uint32_t timeout_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool_t
|
||||||
|
xdr_test_query (XDR *xdrs, void *data, ...)
|
||||||
|
{
|
||||||
|
struct test_query *p = data;
|
||||||
|
return xdr_uint32_t (xdrs, &p->a)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->b)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct test_response
|
||||||
|
{
|
||||||
|
uint32_t server_id;
|
||||||
|
uint32_t seq;
|
||||||
|
uint32_t sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool_t
|
||||||
|
xdr_test_response (XDR *xdrs, void *data, ...)
|
||||||
|
{
|
||||||
|
struct test_response *p = data;
|
||||||
|
return xdr_uint32_t (xdrs, &p->server_id)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->seq)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of the test server. */
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* Number of test servers to run. */
|
||||||
|
SERVER_COUNT = 3,
|
||||||
|
|
||||||
|
/* RPC parameters, chosen at random. */
|
||||||
|
PROGNUM = 8242,
|
||||||
|
VERSNUM = 19654,
|
||||||
|
|
||||||
|
/* Main RPC operation. */
|
||||||
|
PROC_ADD = 1,
|
||||||
|
|
||||||
|
/* Request process termination. */
|
||||||
|
PROC_EXIT,
|
||||||
|
|
||||||
|
/* Special exit status to mark successful processing. */
|
||||||
|
EXIT_MARKER = 55,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set by the parent process to tell test servers apart. */
|
||||||
|
static int server_id;
|
||||||
|
|
||||||
|
/* Implementation of the test server. */
|
||||||
|
static void
|
||||||
|
server_dispatch (struct svc_req *request, SVCXPRT *transport)
|
||||||
|
{
|
||||||
|
/* Query sequence number. */
|
||||||
|
static uint32_t seq = 0;
|
||||||
|
++seq;
|
||||||
|
static bool proc_add_seen;
|
||||||
|
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: server_dispatch server_id=%d seq=%u rq_proc=%lu\n",
|
||||||
|
server_id, seq, request->rq_proc);
|
||||||
|
|
||||||
|
switch (request->rq_proc)
|
||||||
|
{
|
||||||
|
case PROC_ADD:
|
||||||
|
{
|
||||||
|
struct test_query query;
|
||||||
|
memset (&query, 0xc0, sizeof (query));
|
||||||
|
TEST_VERIFY_EXIT
|
||||||
|
(svc_getargs (transport, xdr_test_query,
|
||||||
|
(void *) &query));
|
||||||
|
|
||||||
|
if (test_verbose)
|
||||||
|
printf (" a=%u b=%u timeout_ms=%u\n",
|
||||||
|
query.a, query.b, query.timeout_ms);
|
||||||
|
|
||||||
|
usleep (query.timeout_ms * 1000);
|
||||||
|
|
||||||
|
struct test_response response =
|
||||||
|
{
|
||||||
|
.server_id = server_id,
|
||||||
|
.seq = seq,
|
||||||
|
.sum = query.a + query.b,
|
||||||
|
};
|
||||||
|
TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
|
||||||
|
(void *) &response));
|
||||||
|
if (test_verbose)
|
||||||
|
printf (" server id %d response seq=%u sent\n", server_id, seq);
|
||||||
|
proc_add_seen = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROC_EXIT:
|
||||||
|
TEST_VERIFY (proc_add_seen);
|
||||||
|
TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
|
||||||
|
_exit (EXIT_MARKER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the number seconds since an arbitrary point in time. */
|
||||||
|
static double
|
||||||
|
get_ticks (void)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
|
||||||
|
return ts.tv_sec + ts.tv_nsec * 1e-9;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
|
||||||
|
return tv.tv_sec + tv.tv_usec * 1e-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
support_become_root ();
|
||||||
|
support_enter_network_namespace ();
|
||||||
|
|
||||||
|
/* Information about the test servers. */
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
SVCXPRT *transport;
|
||||||
|
struct sockaddr_in address;
|
||||||
|
pid_t pid;
|
||||||
|
uint32_t xid;
|
||||||
|
} servers[SERVER_COUNT];
|
||||||
|
|
||||||
|
/* Spawn the test servers. */
|
||||||
|
for (int i = 0; i < SERVER_COUNT; ++i)
|
||||||
|
{
|
||||||
|
servers[i].transport = svcudp_create (RPC_ANYSOCK);
|
||||||
|
TEST_VERIFY_EXIT (servers[i].transport != NULL);
|
||||||
|
servers[i].address = (struct sockaddr_in)
|
||||||
|
{
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = htonl (INADDR_LOOPBACK),
|
||||||
|
.sin_port = htons (servers[i].transport->xp_port),
|
||||||
|
};
|
||||||
|
servers[i].xid = 0xabcd0101 + i;
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: setting up server %d xid=%x on port %d\n",
|
||||||
|
i, servers[i].xid, servers[i].transport->xp_port);
|
||||||
|
|
||||||
|
server_id = i;
|
||||||
|
servers[i].pid = xfork ();
|
||||||
|
if (servers[i].pid == 0)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (svc_register (servers[i].transport,
|
||||||
|
PROGNUM, VERSNUM, server_dispatch, 0));
|
||||||
|
svc_run ();
|
||||||
|
FAIL_EXIT1 ("supposed to be unreachable");
|
||||||
|
}
|
||||||
|
/* We need to close the socket so that we do not accidentally
|
||||||
|
consume the request. */
|
||||||
|
TEST_VERIFY (close (servers[i].transport->xp_sock) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The following code mirrors what ypbind does. */
|
||||||
|
|
||||||
|
/* Copied from clnt_udp.c (like ypbind). */
|
||||||
|
struct cu_data
|
||||||
|
{
|
||||||
|
int cu_sock;
|
||||||
|
bool_t cu_closeit;
|
||||||
|
struct sockaddr_in cu_raddr;
|
||||||
|
int cu_rlen;
|
||||||
|
struct timeval cu_wait;
|
||||||
|
struct timeval cu_total;
|
||||||
|
struct rpc_err cu_error;
|
||||||
|
XDR cu_outxdrs;
|
||||||
|
u_int cu_xdrpos;
|
||||||
|
u_int cu_sendsz;
|
||||||
|
char *cu_outbuf;
|
||||||
|
u_int cu_recvsz;
|
||||||
|
char cu_inbuf[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
int client_socket = xsocket (AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
|
||||||
|
CLIENT *clnt = clntudp_create (&servers[0].address, PROGNUM, VERSNUM,
|
||||||
|
/* 5 seconds per-response timeout. */
|
||||||
|
((struct timeval) { 5, 0 }),
|
||||||
|
&client_socket);
|
||||||
|
TEST_VERIFY (clnt != NULL);
|
||||||
|
clnt->cl_auth = authunix_create_default ();
|
||||||
|
{
|
||||||
|
struct timeval zero = { 0, 0 };
|
||||||
|
TEST_VERIFY (clnt_control (clnt, CLSET_TIMEOUT, (void *) &zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Poke at internal data structures (like ypbind). */
|
||||||
|
struct cu_data *cu = (struct cu_data *) clnt->cl_private;
|
||||||
|
|
||||||
|
/* Send a ping to each server. */
|
||||||
|
double before_pings = get_ticks ();
|
||||||
|
for (int i = 0; i < SERVER_COUNT; ++i)
|
||||||
|
{
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: sending server %d ping\n", i);
|
||||||
|
/* Reset the xid because it is changed by each invocation of
|
||||||
|
clnt_call. Subtract one to compensate for the xid update
|
||||||
|
during the call. */
|
||||||
|
*((u_int32_t *) (cu->cu_outbuf)) = servers[i].xid - 1;
|
||||||
|
cu->cu_raddr = servers[i].address;
|
||||||
|
|
||||||
|
struct test_query query = { .a = 100, .b = i + 1 };
|
||||||
|
if (i == 1)
|
||||||
|
/* Shorter timeout to prefer this server. These timeouts must
|
||||||
|
be much shorter than the 5-second per-response timeout
|
||||||
|
configured with clntudp_create. */
|
||||||
|
query.timeout_ms = 700;
|
||||||
|
else
|
||||||
|
query.timeout_ms = 1400;
|
||||||
|
struct test_response response = { 0 };
|
||||||
|
/* NB: Do not check the return value. The server reply will
|
||||||
|
prove that the call worked. */
|
||||||
|
double before_one_ping = get_ticks ();
|
||||||
|
clnt_call (clnt, PROC_ADD,
|
||||||
|
xdr_test_query, (void *) &query,
|
||||||
|
xdr_test_response, (void *) &response,
|
||||||
|
((struct timeval) { 0, 0 }));
|
||||||
|
double after_one_ping = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: non-blocking send took %f seconds\n",
|
||||||
|
after_one_ping - before_one_ping);
|
||||||
|
/* clnt_call should return immediately. Accept some delay in
|
||||||
|
case the process is descheduled. */
|
||||||
|
TEST_VERIFY (after_one_ping - before_one_ping < 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collect the non-blocking response. */
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: collecting response\n");
|
||||||
|
struct test_response response = { 0 };
|
||||||
|
TEST_VERIFY
|
||||||
|
(clnt_call (clnt, PROC_ADD, NULL, NULL,
|
||||||
|
xdr_test_response, (void *) &response,
|
||||||
|
((struct timeval) { 0, 0 })) == RPC_SUCCESS);
|
||||||
|
double after_pings = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: send/receive took %f seconds\n",
|
||||||
|
after_pings - before_pings);
|
||||||
|
/* Expected timeout is 0.7 seconds. */
|
||||||
|
TEST_VERIFY (0.7 <= after_pings - before_pings);
|
||||||
|
TEST_VERIFY (after_pings - before_pings < 1.2);
|
||||||
|
|
||||||
|
uint32_t xid;
|
||||||
|
memcpy (&xid, &cu->cu_inbuf, sizeof (xid));
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: non-blocking response: xid=%x server_id=%u seq=%u sum=%u\n",
|
||||||
|
xid, response.server_id, response.seq, response.sum);
|
||||||
|
/* Check that the reply from the preferred server was used. */
|
||||||
|
TEST_VERIFY (servers[1].xid == xid);
|
||||||
|
TEST_VERIFY (response.server_id == 1);
|
||||||
|
TEST_VERIFY (response.seq == 1);
|
||||||
|
TEST_VERIFY (response.sum == 102);
|
||||||
|
|
||||||
|
auth_destroy (clnt->cl_auth);
|
||||||
|
clnt_destroy (clnt);
|
||||||
|
|
||||||
|
for (int i = 0; i < SERVER_COUNT; ++i)
|
||||||
|
{
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: requesting server %d termination\n", i);
|
||||||
|
client_socket = RPC_ANYSOCK;
|
||||||
|
clnt = clntudp_create (&servers[i].address, PROGNUM, VERSNUM,
|
||||||
|
((struct timeval) { 5, 0 }),
|
||||||
|
&client_socket);
|
||||||
|
TEST_VERIFY_EXIT (clnt != NULL);
|
||||||
|
TEST_VERIFY (clnt_call (clnt, PROC_EXIT,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
((struct timeval) { 3, 0 })) == RPC_SUCCESS);
|
||||||
|
clnt_destroy (clnt);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
xwaitpid (servers[i].pid, &status, 0);
|
||||||
|
TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <support/test-driver.c>
|
402
sunrpc/tst-udp-timeout.c
Normal file
402
sunrpc/tst-udp-timeout.c
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
/* Test timeout handling in the UDP client.
|
||||||
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <rpc/clnt.h>
|
||||||
|
#include <rpc/svc.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/namespace.h>
|
||||||
|
#include <support/test-driver.h>
|
||||||
|
#include <support/xsocket.h>
|
||||||
|
#include <support/xunistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* Test data serialization and deserialization. */
|
||||||
|
|
||||||
|
struct test_query
|
||||||
|
{
|
||||||
|
uint32_t a;
|
||||||
|
uint32_t b;
|
||||||
|
uint32_t timeout_ms;
|
||||||
|
uint32_t wait_for_seq;
|
||||||
|
uint32_t garbage_packets;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool_t
|
||||||
|
xdr_test_query (XDR *xdrs, void *data, ...)
|
||||||
|
{
|
||||||
|
struct test_query *p = data;
|
||||||
|
return xdr_uint32_t (xdrs, &p->a)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->b)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->timeout_ms)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->wait_for_seq)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->garbage_packets);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct test_response
|
||||||
|
{
|
||||||
|
uint32_t seq;
|
||||||
|
uint32_t sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool_t
|
||||||
|
xdr_test_response (XDR *xdrs, void *data, ...)
|
||||||
|
{
|
||||||
|
struct test_response *p = data;
|
||||||
|
return xdr_uint32_t (xdrs, &p->seq)
|
||||||
|
&& xdr_uint32_t (xdrs, &p->sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of the test server. */
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* RPC parameters, chosen at random. */
|
||||||
|
PROGNUM = 15717,
|
||||||
|
VERSNUM = 13689,
|
||||||
|
|
||||||
|
/* Main RPC operation. */
|
||||||
|
PROC_ADD = 1,
|
||||||
|
|
||||||
|
/* Reset the sequence number. */
|
||||||
|
PROC_RESET_SEQ,
|
||||||
|
|
||||||
|
/* Request process termination. */
|
||||||
|
PROC_EXIT,
|
||||||
|
|
||||||
|
/* Special exit status to mark successful processing. */
|
||||||
|
EXIT_MARKER = 55,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_dispatch (struct svc_req *request, SVCXPRT *transport)
|
||||||
|
{
|
||||||
|
/* Query sequence number. */
|
||||||
|
static uint32_t seq = 0;
|
||||||
|
++seq;
|
||||||
|
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: server_dispatch seq=%u rq_proc=%lu\n",
|
||||||
|
seq, request->rq_proc);
|
||||||
|
|
||||||
|
switch (request->rq_proc)
|
||||||
|
{
|
||||||
|
case PROC_ADD:
|
||||||
|
{
|
||||||
|
struct test_query query;
|
||||||
|
memset (&query, 0xc0, sizeof (query));
|
||||||
|
TEST_VERIFY_EXIT
|
||||||
|
(svc_getargs (transport, xdr_test_query,
|
||||||
|
(void *) &query));
|
||||||
|
|
||||||
|
if (test_verbose)
|
||||||
|
printf (" a=%u b=%u timeout_ms=%u wait_for_seq=%u"
|
||||||
|
" garbage_packets=%u\n",
|
||||||
|
query.a, query.b, query.timeout_ms, query.wait_for_seq,
|
||||||
|
query.garbage_packets);
|
||||||
|
|
||||||
|
if (seq < query.wait_for_seq)
|
||||||
|
{
|
||||||
|
/* No response at this point. */
|
||||||
|
if (test_verbose)
|
||||||
|
printf (" skipped response\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.garbage_packets > 0)
|
||||||
|
{
|
||||||
|
int per_packet_timeout;
|
||||||
|
if (query.timeout_ms > 0)
|
||||||
|
per_packet_timeout
|
||||||
|
= query.timeout_ms * 1000 / query.garbage_packets;
|
||||||
|
else
|
||||||
|
per_packet_timeout = 0;
|
||||||
|
|
||||||
|
char buf[20];
|
||||||
|
memset (&buf, 0xc0, sizeof (buf));
|
||||||
|
for (int i = 0; i < query.garbage_packets; ++i)
|
||||||
|
{
|
||||||
|
/* 13 is relatively prime to 20 = sizeof (buf) + 1, so
|
||||||
|
the len variable will cover the entire interval
|
||||||
|
[0, 20] if query.garbage_packets is sufficiently
|
||||||
|
large. */
|
||||||
|
size_t len = (i * 13 + 1) % (sizeof (buf) + 1);
|
||||||
|
TEST_VERIFY (sendto (transport->xp_sock,
|
||||||
|
buf, len, MSG_NOSIGNAL,
|
||||||
|
(struct sockaddr *) &transport->xp_raddr,
|
||||||
|
transport->xp_addrlen) == len);
|
||||||
|
if (per_packet_timeout > 0)
|
||||||
|
usleep (per_packet_timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (query.timeout_ms > 0)
|
||||||
|
usleep (query.timeout_ms * 1000);
|
||||||
|
|
||||||
|
struct test_response response =
|
||||||
|
{
|
||||||
|
.seq = seq,
|
||||||
|
.sum = query.a + query.b,
|
||||||
|
};
|
||||||
|
TEST_VERIFY (svc_sendreply (transport, xdr_test_response,
|
||||||
|
(void *) &response));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROC_RESET_SEQ:
|
||||||
|
seq = 0;
|
||||||
|
TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROC_EXIT:
|
||||||
|
TEST_VERIFY (svc_sendreply (transport, (xdrproc_t) xdr_void, NULL));
|
||||||
|
_exit (EXIT_MARKER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
FAIL_EXIT1 ("invalid rq_proc value: %lu", request->rq_proc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation of the test client. */
|
||||||
|
|
||||||
|
static struct test_response
|
||||||
|
test_call (CLIENT *clnt, int proc, struct test_query query,
|
||||||
|
struct timeval timeout)
|
||||||
|
{
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: test_call proc=%d timeout=%lu.%06lu\n",
|
||||||
|
proc, (unsigned long) timeout.tv_sec,
|
||||||
|
(unsigned long) timeout.tv_usec);
|
||||||
|
struct test_response response;
|
||||||
|
TEST_VERIFY_EXIT (clnt_call (clnt, proc,
|
||||||
|
xdr_test_query, (void *) &query,
|
||||||
|
xdr_test_response, (void *) &response,
|
||||||
|
timeout)
|
||||||
|
== RPC_SUCCESS);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_call_timeout (CLIENT *clnt, int proc, struct test_query query,
|
||||||
|
struct timeval timeout)
|
||||||
|
{
|
||||||
|
struct test_response response;
|
||||||
|
TEST_VERIFY (clnt_call (clnt, proc,
|
||||||
|
xdr_test_query, (void *) &query,
|
||||||
|
xdr_test_response, (void *) &response,
|
||||||
|
timeout)
|
||||||
|
== RPC_TIMEDOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Complete one regular RPC call to drain the server socket
|
||||||
|
buffer. Resets the sequence number. */
|
||||||
|
static void
|
||||||
|
test_call_flush (CLIENT *clnt)
|
||||||
|
{
|
||||||
|
/* This needs a longer timeout to flush out all pending requests.
|
||||||
|
The choice of 5 seconds is larger than the per-response timeouts
|
||||||
|
requested via the timeout_ms field. */
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: flushing pending queries\n");
|
||||||
|
TEST_VERIFY_EXIT (clnt_call (clnt, PROC_RESET_SEQ,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
((struct timeval) { 5, 0 }))
|
||||||
|
== RPC_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the number seconds since an arbitrary point in time. */
|
||||||
|
static double
|
||||||
|
get_ticks (void)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
|
||||||
|
return ts.tv_sec + ts.tv_nsec * 1e-9;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
TEST_VERIFY_EXIT (gettimeofday (&tv, NULL) == 0);
|
||||||
|
return tv.tv_sec + tv.tv_usec * 1e-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_udp_server (int port)
|
||||||
|
{
|
||||||
|
struct sockaddr_in sin =
|
||||||
|
{
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = htonl (INADDR_LOOPBACK),
|
||||||
|
.sin_port = htons (port)
|
||||||
|
};
|
||||||
|
int sock = RPC_ANYSOCK;
|
||||||
|
|
||||||
|
/* The client uses a 1.5 second timeout for retries. The timeouts
|
||||||
|
are arbitrary, but chosen so that there is a substantial gap
|
||||||
|
between them, but the total time spent waiting is not too
|
||||||
|
large. */
|
||||||
|
CLIENT *clnt = clntudp_create (&sin, PROGNUM, VERSNUM,
|
||||||
|
(struct timeval) { 1, 500 * 1000 },
|
||||||
|
&sock);
|
||||||
|
TEST_VERIFY_EXIT (clnt != NULL);
|
||||||
|
|
||||||
|
/* Basic call/response test. */
|
||||||
|
struct test_response response = test_call
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) { .a = 17, .b = 4 },
|
||||||
|
(struct timeval) { 3, 0 });
|
||||||
|
TEST_VERIFY (response.sum == 21);
|
||||||
|
TEST_VERIFY (response.seq == 1);
|
||||||
|
|
||||||
|
/* Check that garbage packets do not interfere with timeout
|
||||||
|
processing. */
|
||||||
|
double before = get_ticks ();
|
||||||
|
response = test_call
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) {
|
||||||
|
.a = 19, .b = 4, .timeout_ms = 500, .garbage_packets = 21,
|
||||||
|
},
|
||||||
|
(struct timeval) { 3, 0 });
|
||||||
|
TEST_VERIFY (response.sum == 23);
|
||||||
|
TEST_VERIFY (response.seq == 2);
|
||||||
|
double after = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: 21 garbage packets took %f seconds\n", after - before);
|
||||||
|
/* Expected timeout is 0.5 seconds. Add some slack in case process
|
||||||
|
scheduling delays processing the query or response, but do not
|
||||||
|
accept a retry (which would happen at 1.5 seconds). */
|
||||||
|
TEST_VERIFY (0.5 <= after - before);
|
||||||
|
TEST_VERIFY (after - before < 1.2);
|
||||||
|
test_call_flush (clnt);
|
||||||
|
|
||||||
|
/* Check that missing a response introduces a 1.5 second timeout, as
|
||||||
|
requested when calling clntudp_create. */
|
||||||
|
before = get_ticks ();
|
||||||
|
response = test_call
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) { .a = 170, .b = 40, .wait_for_seq = 2 },
|
||||||
|
(struct timeval) { 3, 0 });
|
||||||
|
TEST_VERIFY (response.sum == 210);
|
||||||
|
TEST_VERIFY (response.seq == 2);
|
||||||
|
after = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: skipping one response took %f seconds\n",
|
||||||
|
after - before);
|
||||||
|
/* Expected timeout is 1.5 seconds. Do not accept a second retry
|
||||||
|
(which would happen at 3 seconds). */
|
||||||
|
TEST_VERIFY (1.5 <= after - before);
|
||||||
|
TEST_VERIFY (after - before < 2.9);
|
||||||
|
test_call_flush (clnt);
|
||||||
|
|
||||||
|
/* Check that the overall timeout wins against the per-query
|
||||||
|
timeout. */
|
||||||
|
before = get_ticks ();
|
||||||
|
test_call_timeout
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) { .a = 170, .b = 41, .wait_for_seq = 2 },
|
||||||
|
(struct timeval) { 0, 750 * 1000 });
|
||||||
|
after = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: 0.75 second timeout took %f seconds\n",
|
||||||
|
after - before);
|
||||||
|
TEST_VERIFY (0.75 <= after - before);
|
||||||
|
TEST_VERIFY (after - before < 1.4);
|
||||||
|
test_call_flush (clnt);
|
||||||
|
|
||||||
|
for (int with_garbage = 0; with_garbage < 2; ++with_garbage)
|
||||||
|
{
|
||||||
|
/* Check that no response at all causes the client to bail out. */
|
||||||
|
before = get_ticks ();
|
||||||
|
test_call_timeout
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) {
|
||||||
|
.a = 170, .b = 40, .timeout_ms = 1200,
|
||||||
|
.garbage_packets = with_garbage * 21
|
||||||
|
},
|
||||||
|
(struct timeval) { 0, 750 * 1000 });
|
||||||
|
after = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: test_udp_server: 0.75 second timeout took %f seconds"
|
||||||
|
" (garbage %d)\n",
|
||||||
|
after - before, with_garbage);
|
||||||
|
TEST_VERIFY (0.75 <= after - before);
|
||||||
|
TEST_VERIFY (after - before < 1.4);
|
||||||
|
test_call_flush (clnt);
|
||||||
|
|
||||||
|
/* As above, but check the total timeout. */
|
||||||
|
before = get_ticks ();
|
||||||
|
test_call_timeout
|
||||||
|
(clnt, PROC_ADD,
|
||||||
|
(struct test_query) {
|
||||||
|
.a = 170, .b = 40, .timeout_ms = 3000,
|
||||||
|
.garbage_packets = with_garbage * 30
|
||||||
|
},
|
||||||
|
(struct timeval) { 2, 300 * 1000 });
|
||||||
|
after = get_ticks ();
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: test_udp_server: 2.3 second timeout took %f seconds"
|
||||||
|
" (garbage %d)\n",
|
||||||
|
after - before, with_garbage);
|
||||||
|
TEST_VERIFY (2.3 <= after - before);
|
||||||
|
TEST_VERIFY (after - before < 3.0);
|
||||||
|
test_call_flush (clnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_VERIFY_EXIT (clnt_call (clnt, PROC_EXIT,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
(xdrproc_t) xdr_void, NULL,
|
||||||
|
((struct timeval) { 5, 0 }))
|
||||||
|
== RPC_SUCCESS);
|
||||||
|
clnt_destroy (clnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
support_become_root ();
|
||||||
|
support_enter_network_namespace ();
|
||||||
|
|
||||||
|
SVCXPRT *transport = svcudp_create (RPC_ANYSOCK);
|
||||||
|
TEST_VERIFY_EXIT (transport != NULL);
|
||||||
|
TEST_VERIFY (svc_register (transport, PROGNUM, VERSNUM, server_dispatch, 0));
|
||||||
|
|
||||||
|
pid_t pid = xfork ();
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
svc_run ();
|
||||||
|
FAIL_EXIT1 ("supposed to be unreachable");
|
||||||
|
}
|
||||||
|
test_udp_server (transport->xp_port);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
xwaitpid (pid, &status, 0);
|
||||||
|
TEST_VERIFY (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_MARKER);
|
||||||
|
|
||||||
|
SVC_DESTROY (transport);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The minimum run time is around 17 seconds. */
|
||||||
|
#define TIMEOUT 25
|
||||||
|
#include <support/test-driver.c>
|
Reference in New Issue
Block a user