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:
V-for-Vasili 2022-03-10 18:53:04 -05:00 committed by GitHub
parent df63ee1a1d
commit 5ec89b8c50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 154 additions and 54 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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
View 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
}