From f5e4d85cd25889f4afb3ddbc1649fe34438ef517 Mon Sep 17 00:00:00 2001 From: Stacey Marshall Date: Mon, 11 Jul 2022 17:29:25 +0100 Subject: [PATCH] unix,tcp: allow EINVAL errno from setsockopt in uv_tcp_close_reset() (#3662) Some setsockopt() implememantations may return with errno of EINVAL when the socket has been shut down already, as documented in the Open Group Specifications Issue 7, 2018. When this happens, reset errno and continue to mark the socket closed and handle any callback. --- src/unix/tcp.c | 12 ++++++++-- test/test-list.h | 2 ++ test/test-tcp-close-reset.c | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/unix/tcp.c b/src/unix/tcp.c index f1040e98..73fc657a 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -321,8 +321,16 @@ int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { return UV_EINVAL; fd = uv__stream_fd(handle); - if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) - return UV__ERR(errno); + if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) { + if (errno == EINVAL) { + /* Open Group Specifications Issue 7, 2018 edition states that + * EINVAL may mean the socket has been shut down already. + * Behavior observed on Solaris, illumos and macOS. */ + errno = 0; + } else { + return UV__ERR(errno); + } + } uv_close((uv_handle_t*) handle, close_cb); return 0; diff --git a/test/test-list.h b/test/test-list.h index e74c1009..b19c10c7 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -133,6 +133,7 @@ TEST_DECLARE (tcp_close_after_read_timeout) TEST_DECLARE (tcp_close) TEST_DECLARE (tcp_close_reset_accepted) TEST_DECLARE (tcp_close_reset_accepted_after_shutdown) +TEST_DECLARE (tcp_close_reset_accepted_after_socket_shutdown) TEST_DECLARE (tcp_close_reset_client) TEST_DECLARE (tcp_close_reset_client_after_shutdown) TEST_DECLARE (tcp_create_early) @@ -710,6 +711,7 @@ TASK_LIST_START TEST_ENTRY (tcp_close) TEST_ENTRY (tcp_close_reset_accepted) TEST_ENTRY (tcp_close_reset_accepted_after_shutdown) + TEST_ENTRY (tcp_close_reset_accepted_after_socket_shutdown) TEST_ENTRY (tcp_close_reset_client) TEST_ENTRY (tcp_close_reset_client_after_shutdown) TEST_ENTRY (tcp_create_early) diff --git a/test/test-tcp-close-reset.c b/test/test-tcp-close-reset.c index 7ca55c4c..66dfc82e 100644 --- a/test/test-tcp-close-reset.c +++ b/test/test-tcp-close-reset.c @@ -25,6 +25,12 @@ #include #include /* memset */ +#ifdef _WIN32 +# define INVALID_FD (INVALID_HANDLE_VALUE) +#else +# define INVALID_FD (-1) +#endif + static uv_loop_t* loop; static uv_tcp_t tcp_server; static uv_tcp_t tcp_client; @@ -62,9 +68,22 @@ static void do_write(uv_tcp_t* handle) { static void do_close(uv_tcp_t* handle) { + uv_os_fd_t fd; + int r; + if (shutdown_before_close == 1) { ASSERT(0 == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); ASSERT(UV_EINVAL == uv_tcp_close_reset(handle, close_cb)); + } else if (shutdown_before_close == 2) { + r = uv_fileno((const uv_handle_t*) handle, &fd); + ASSERT_EQ(r, 0); + ASSERT_NE(fd, INVALID_FD); +#ifdef _WIN32 + ASSERT_EQ(0, shutdown(fd, SD_BOTH)); +#else + ASSERT_EQ(0, shutdown(fd, SHUT_RDWR)); +#endif + ASSERT_EQ(0, uv_tcp_close_reset(handle, close_cb)); } else { ASSERT(0 == uv_tcp_close_reset(handle, close_cb)); ASSERT(UV_ENOTCONN == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); @@ -288,3 +307,30 @@ TEST_IMPL(tcp_close_reset_accepted_after_shutdown) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(tcp_close_reset_accepted_after_socket_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 2; + + do_connect(loop, &tcp_client); + + ASSERT_EQ(write_cb_called, 0); + ASSERT_EQ(close_cb_called, 0); + ASSERT_EQ(shutdown_cb_called, 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT_EQ(r, 0); + + ASSERT_EQ(write_cb_called, 4); + ASSERT_EQ(close_cb_called, 1); + ASSERT_EQ(shutdown_cb_called, 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +}