aix, ibmi: handle server hang when remote sends TCP RST (#3482)
Workaround getsockname() not working for a TCP handle that has received RST from the remote. Co-authored-by: Jameson Nash <vtjnash@gmail.com>
This commit is contained in:
parent
df63ee1a1d
commit
5ec89b8c50
@ -574,6 +574,7 @@ if(LIBUV_BUILD_TESTS)
|
||||
test/test-tcp-open.c
|
||||
test/test-tcp-read-stop.c
|
||||
test/test-tcp-read-stop-start.c
|
||||
test/test-tcp-rst.c
|
||||
test/test-tcp-shutdown-after-write.c
|
||||
test/test-tcp-try-write.c
|
||||
test/test-tcp-try-write-error.c
|
||||
|
||||
@ -266,6 +266,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
|
||||
test/test-tcp-open.c \
|
||||
test/test-tcp-read-stop.c \
|
||||
test/test-tcp-read-stop-start.c \
|
||||
test/test-tcp-rst.c \
|
||||
test/test-tcp-shutdown-after-write.c \
|
||||
test/test-tcp-unexpected-read.c \
|
||||
test/test-tcp-oob.c \
|
||||
|
||||
@ -277,7 +277,6 @@ void uv__tcp_close(uv_tcp_t* handle);
|
||||
size_t uv__thread_stack_size(void);
|
||||
void uv__udp_close(uv_udp_t* handle);
|
||||
void uv__udp_finish_close(uv_udp_t* handle);
|
||||
uv_handle_type uv__handle_type(int fd);
|
||||
FILE* uv__open_file(const char* path);
|
||||
int uv__getpwuid_r(uv_passwd_t* pwd);
|
||||
int uv__search_path(const char* prog, char* buf, size_t* buflen);
|
||||
|
||||
@ -319,7 +319,7 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) {
|
||||
if (handle->accepted_fd == -1)
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
else
|
||||
return uv__handle_type(handle->accepted_fd);
|
||||
return uv_guess_handle(handle->accepted_fd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -964,49 +964,6 @@ static void uv__write_callbacks(uv_stream_t* stream) {
|
||||
}
|
||||
|
||||
|
||||
uv_handle_type uv__handle_type(int fd) {
|
||||
struct sockaddr_storage ss;
|
||||
socklen_t sslen;
|
||||
socklen_t len;
|
||||
int type;
|
||||
|
||||
memset(&ss, 0, sizeof(ss));
|
||||
sslen = sizeof(ss);
|
||||
|
||||
if (getsockname(fd, (struct sockaddr*)&ss, &sslen))
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
|
||||
len = sizeof type;
|
||||
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len))
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
|
||||
if (type == SOCK_STREAM) {
|
||||
#if defined(_AIX) || defined(__DragonFly__)
|
||||
/* on AIX/DragonFly the getsockname call returns an empty sa structure
|
||||
* for sockets of type AF_UNIX. For all other types it will
|
||||
* return a properly filled in structure.
|
||||
*/
|
||||
if (sslen == 0)
|
||||
return UV_NAMED_PIPE;
|
||||
#endif
|
||||
switch (ss.ss_family) {
|
||||
case AF_UNIX:
|
||||
return UV_NAMED_PIPE;
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
return UV_TCP;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == SOCK_DGRAM &&
|
||||
(ss.ss_family == AF_INET || ss.ss_family == AF_INET6))
|
||||
return UV_UDP;
|
||||
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
static void uv__stream_eof(uv_stream_t* stream, const uv_buf_t* buf) {
|
||||
stream->flags |= UV_HANDLE_READ_EOF;
|
||||
stream->flags &= ~UV_HANDLE_READING;
|
||||
|
||||
@ -331,7 +331,7 @@ int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) {
|
||||
|
||||
|
||||
uv_handle_type uv_guess_handle(uv_file file) {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_storage ss;
|
||||
struct stat s;
|
||||
socklen_t len;
|
||||
int type;
|
||||
@ -342,8 +342,23 @@ uv_handle_type uv_guess_handle(uv_file file) {
|
||||
if (isatty(file))
|
||||
return UV_TTY;
|
||||
|
||||
if (fstat(file, &s))
|
||||
if (fstat(file, &s)) {
|
||||
#if defined(__PASE__)
|
||||
// On ibmi receiving RST from TCP instead of FIN immediately puts fd into
|
||||
// an error state. fstat will return EINVAL, getsockname will also return
|
||||
// EINVAL, even if sockaddr_storage is valid. (If file does not refer to a
|
||||
// socket, ENOTSOCK is returned instead.)
|
||||
// In such cases, we will permit the user to open the connection as uv_tcp
|
||||
// still, so that the user can get immediately notified of the error in
|
||||
// their read callback and close this fd.
|
||||
len = sizeof(ss);
|
||||
if (getsockname(file, (struct sockaddr*) &ss, &len)) {
|
||||
if (errno == EINVAL)
|
||||
return UV_TCP;
|
||||
}
|
||||
#endif
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
}
|
||||
|
||||
if (S_ISREG(s.st_mode))
|
||||
return UV_FILE;
|
||||
@ -357,16 +372,28 @@ uv_handle_type uv_guess_handle(uv_file file) {
|
||||
if (!S_ISSOCK(s.st_mode))
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
|
||||
len = sizeof(ss);
|
||||
if (getsockname(file, (struct sockaddr*) &ss, &len)) {
|
||||
#if defined(_AIX)
|
||||
// On aix receiving RST from TCP instead of FIN immediately puts fd into
|
||||
// an error state. In such case getsockname will return EINVAL, even if
|
||||
// sockaddr_storage is valid.
|
||||
// In such cases, we will permit the user to open the connection as uv_tcp
|
||||
// still, so that the user can get immediately notified of the error in
|
||||
// their read callback and close this fd.
|
||||
if (errno == EINVAL) {
|
||||
return UV_TCP;
|
||||
}
|
||||
#endif
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
}
|
||||
|
||||
len = sizeof(type);
|
||||
if (getsockopt(file, SOL_SOCKET, SO_TYPE, &type, &len))
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
|
||||
len = sizeof(sa);
|
||||
if (getsockname(file, &sa, &len))
|
||||
return UV_UNKNOWN_HANDLE;
|
||||
|
||||
if (type == SOCK_DGRAM)
|
||||
if (sa.sa_family == AF_INET || sa.sa_family == AF_INET6)
|
||||
if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6)
|
||||
return UV_UDP;
|
||||
|
||||
if (type == SOCK_STREAM) {
|
||||
@ -379,9 +406,9 @@ uv_handle_type uv_guess_handle(uv_file file) {
|
||||
return UV_NAMED_PIPE;
|
||||
#endif /* defined(_AIX) || defined(__DragonFly__) */
|
||||
|
||||
if (sa.sa_family == AF_INET || sa.sa_family == AF_INET6)
|
||||
if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6)
|
||||
return UV_TCP;
|
||||
if (sa.sa_family == AF_UNIX)
|
||||
if (ss.ss_family == AF_UNIX)
|
||||
return UV_NAMED_PIPE;
|
||||
}
|
||||
|
||||
|
||||
@ -147,6 +147,7 @@ TEST_DECLARE (tcp_write_to_half_open_connection)
|
||||
TEST_DECLARE (tcp_unexpected_read)
|
||||
TEST_DECLARE (tcp_read_stop)
|
||||
TEST_DECLARE (tcp_read_stop_start)
|
||||
TEST_DECLARE (tcp_rst)
|
||||
TEST_DECLARE (tcp_bind6_error_addrinuse)
|
||||
TEST_DECLARE (tcp_bind6_error_addrnotavail)
|
||||
TEST_DECLARE (tcp_bind6_error_fault)
|
||||
@ -723,6 +724,9 @@ TASK_LIST_START
|
||||
|
||||
TEST_ENTRY (tcp_read_stop_start)
|
||||
|
||||
TEST_ENTRY (tcp_rst)
|
||||
TEST_HELPER (tcp_rst, tcp4_echo_server)
|
||||
|
||||
TEST_ENTRY (tcp_bind6_error_addrinuse)
|
||||
TEST_ENTRY (tcp_bind6_error_addrnotavail)
|
||||
TEST_ENTRY (tcp_bind6_error_fault)
|
||||
|
||||
111
test/test-tcp-rst.c
Normal file
111
test/test-tcp-rst.c
Normal file
@ -0,0 +1,111 @@
|
||||
/* Copyright libuv project and contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "uv.h"
|
||||
#include "task.h"
|
||||
|
||||
static uv_tcp_t tcp;
|
||||
static uv_connect_t connect_req;
|
||||
static uv_shutdown_t shutdown_req;
|
||||
static uv_buf_t qbuf;
|
||||
static int called_alloc_cb;
|
||||
static int called_connect_cb;
|
||||
static int called_shutdown_cb;
|
||||
static int called_close_cb;
|
||||
|
||||
|
||||
static void close_cb(uv_handle_t* handle) {
|
||||
ASSERT(handle == (uv_handle_t*) &tcp);
|
||||
called_close_cb++;
|
||||
}
|
||||
|
||||
|
||||
static void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
|
||||
buf->base = malloc(size);
|
||||
buf->len = size;
|
||||
called_alloc_cb++;
|
||||
}
|
||||
|
||||
|
||||
static void read_cb(uv_stream_t* t, ssize_t nread, const uv_buf_t* buf) {
|
||||
ASSERT_PTR_EQ((uv_tcp_t*) t, &tcp);
|
||||
ASSERT_EQ(nread, UV_ECONNRESET);
|
||||
|
||||
int fd;
|
||||
ASSERT_EQ(0, uv_fileno((uv_handle_t*) t, &fd));
|
||||
uv_handle_type type = uv_guess_handle(fd);
|
||||
ASSERT_EQ(type, UV_TCP);
|
||||
|
||||
uv_close((uv_handle_t *) t, close_cb);
|
||||
free(buf->base);
|
||||
}
|
||||
|
||||
|
||||
static void connect_cb(uv_connect_t *req, int status) {
|
||||
ASSERT_EQ(status, 0);
|
||||
ASSERT_PTR_EQ(req, &connect_req);
|
||||
|
||||
/* Start reading from the connection so we receive the RST in uv__read. */
|
||||
ASSERT_EQ(0, uv_read_start((uv_stream_t*) &tcp, alloc_cb, read_cb));
|
||||
|
||||
/* Write 'QSH' to receive RST from the echo server. */
|
||||
ASSERT_EQ(qbuf.len, uv_try_write((uv_stream_t*) &tcp, &qbuf, 1));
|
||||
|
||||
called_connect_cb++;
|
||||
ASSERT_EQ(called_shutdown_cb, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This test has a client which connects to the echo_server and receives TCP
|
||||
* RST. Test checks that uv_guess_handle still works on a reset TCP handle.
|
||||
*/
|
||||
TEST_IMPL(tcp_rst) {
|
||||
#ifndef _WIN32
|
||||
struct sockaddr_in server_addr;
|
||||
int r;
|
||||
|
||||
qbuf.base = "QSH";
|
||||
qbuf.len = 3;
|
||||
|
||||
ASSERT_EQ(0, uv_ip4_addr("127.0.0.1", TEST_PORT, &server_addr));
|
||||
r = uv_tcp_init(uv_default_loop(), &tcp);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
r = uv_tcp_connect(&connect_req,
|
||||
&tcp,
|
||||
(const struct sockaddr*) &server_addr,
|
||||
connect_cb);
|
||||
ASSERT_EQ(r, 0);
|
||||
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
|
||||
ASSERT_EQ(called_alloc_cb, 1);
|
||||
ASSERT_EQ(called_connect_cb, 1);
|
||||
ASSERT_EQ(called_shutdown_cb, 0);
|
||||
ASSERT_EQ(called_close_cb, 1);
|
||||
|
||||
MAKE_VALGRIND_HAPPY();
|
||||
return 0;
|
||||
#else
|
||||
RETURN_SKIP("Unix only test");
|
||||
#endif
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user