diff --git a/CMakeLists.txt b/CMakeLists.txt index e21bc49c..56763122 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ set(uv_test_sources test/test-tcp-close-accept.c test/test-tcp-close-while-connecting.c test/test-tcp-close.c + test/test-tcp-close-reset.c test/test-tcp-connect-error-after-write.c test/test-tcp-connect-error.c test/test-tcp-connect-timeout.c diff --git a/Makefile.am b/Makefile.am index f0ebe18a..7d380c51 100644 --- a/Makefile.am +++ b/Makefile.am @@ -260,6 +260,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tcp-close-accept.c \ test/test-tcp-close-while-connecting.c \ test/test-tcp-close.c \ + test/test-tcp-close-reset.c \ test/test-tcp-create-socket-early.c \ test/test-tcp-connect-error-after-write.c \ test/test-tcp-connect-error.c \ diff --git a/docs/src/tcp.rst b/docs/src/tcp.rst index d20a6362..bcb163ea 100644 --- a/docs/src/tcp.rst +++ b/docs/src/tcp.rst @@ -113,3 +113,13 @@ API mapping .. seealso:: The :c:type:`uv_stream_t` API functions also apply. + +.. c:function:: int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) + + Resets a TCP connection by sending a RST packet. This is accomplished by + setting the `SO_LINGER` socket option with a linger interval of zero and + then calling :c:func:`uv_close`. + Due to some platform inconsistencies, mixing of :c:func:`uv_shutdown` and + :c:func:`uv_tcp_close_reset` calls is not allowed. + + .. versionadded:: 1.32.0 diff --git a/include/uv.h b/include/uv.h index f71767b6..ffafaa8e 100644 --- a/include/uv.h +++ b/include/uv.h @@ -559,6 +559,7 @@ UV_EXTERN int uv_tcp_getsockname(const uv_tcp_t* handle, UV_EXTERN int uv_tcp_getpeername(const uv_tcp_t* handle, struct sockaddr* name, int* namelen); +UV_EXTERN int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb); UV_EXTERN int uv_tcp_connect(uv_connect_t* req, uv_tcp_t* handle, const struct sockaddr* addr, diff --git a/src/unix/tcp.c b/src/unix/tcp.c index 8cedcd60..fa660f13 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -308,6 +308,23 @@ int uv_tcp_getpeername(const uv_tcp_t* handle, } +int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { + int fd; + struct linger l = { 1, 0 }; + + /* Disallow setting SO_LINGER to zero due to some platform inconsistencies */ + if (handle->flags & UV_HANDLE_SHUTTING) + return UV_EINVAL; + + fd = uv__stream_fd(handle); + if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) + return UV__ERR(errno); + + uv_close((uv_handle_t*) handle, close_cb); + return 0; +} + + int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) { static int single_accept = -1; unsigned long flags; diff --git a/src/win/stream.c b/src/win/stream.c index 7656627e..46a0709a 100644 --- a/src/win/stream.c +++ b/src/win/stream.c @@ -198,8 +198,10 @@ int uv_try_write(uv_stream_t* stream, int uv_shutdown(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb) { uv_loop_t* loop = handle->loop; - if (!(handle->flags & UV_HANDLE_WRITABLE)) { - return UV_EPIPE; + if (!(handle->flags & UV_HANDLE_WRITABLE) || + handle->flags & UV_HANDLE_SHUTTING || + uv__is_closing(handle)) { + return UV_ENOTCONN; } UV_REQ_INIT(req, UV_SHUTDOWN); @@ -207,6 +209,7 @@ int uv_shutdown(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb) { req->cb = cb; handle->flags &= ~UV_HANDLE_WRITABLE; + handle->flags |= UV_HANDLE_SHUTTING; handle->stream.conn.shutdown_req = req; handle->reqs_pending++; REGISTER_HANDLE_REQ(loop, handle, req); diff --git a/src/win/tcp.c b/src/win/tcp.c index f2cb5271..81e48136 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -549,6 +549,21 @@ static void uv_tcp_queue_read(uv_loop_t* loop, uv_tcp_t* handle) { } +int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { + struct linger l = { 1, 0 }; + + /* Disallow setting SO_LINGER to zero due to some platform inconsistencies */ + if (handle->flags & UV_HANDLE_SHUTTING) + return UV_EINVAL; + + if (0 != setsockopt(handle->socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) + return uv_translate_sys_error(WSAGetLastError()); + + uv_close((uv_handle_t*) handle, close_cb); + return 0; +} + + int uv_tcp_listen(uv_tcp_t* handle, int backlog, uv_connection_cb cb) { unsigned int i, simultaneous_accepts; uv_tcp_accept_t* req; diff --git a/test/test-list.h b/test/test-list.h index 4ceeff80..930bb6af 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -112,6 +112,10 @@ TEST_DECLARE (tcp_connect_error_fault) TEST_DECLARE (tcp_connect_timeout) TEST_DECLARE (tcp_close_while_connecting) TEST_DECLARE (tcp_close) +TEST_DECLARE (tcp_close_reset_accepted) +TEST_DECLARE (tcp_close_reset_accepted_after_shutdown) +TEST_DECLARE (tcp_close_reset_client) +TEST_DECLARE (tcp_close_reset_client_after_shutdown) TEST_DECLARE (tcp_create_early) TEST_DECLARE (tcp_create_early_bad_bind) TEST_DECLARE (tcp_create_early_bad_domain) @@ -624,6 +628,10 @@ TASK_LIST_START TEST_ENTRY (tcp_connect_timeout) TEST_ENTRY (tcp_close_while_connecting) TEST_ENTRY (tcp_close) + TEST_ENTRY (tcp_close_reset_accepted) + TEST_ENTRY (tcp_close_reset_accepted_after_shutdown) + TEST_ENTRY (tcp_close_reset_client) + TEST_ENTRY (tcp_close_reset_client_after_shutdown) TEST_ENTRY (tcp_create_early) TEST_ENTRY (tcp_create_early_bad_bind) TEST_ENTRY (tcp_create_early_bad_domain) diff --git a/test/test-tcp-bind-error.c b/test/test-tcp-bind-error.c index 1456d081..f95efd9f 100644 --- a/test/test-tcp-bind-error.c +++ b/test/test-tcp-bind-error.c @@ -239,11 +239,7 @@ TEST_IMPL(tcp_bind_writable_flags) { r = uv_write(&write_req, (uv_stream_t*) &server, &buf, 1, NULL); ASSERT(r == UV_EPIPE); r = uv_shutdown(&shutdown_req, (uv_stream_t*) &server, NULL); -#ifdef _WIN32 - ASSERT(r == UV_EPIPE); -#else ASSERT(r == UV_ENOTCONN); -#endif r = uv_read_start((uv_stream_t*) &server, NULL, NULL); ASSERT(r == UV_ENOTCONN); diff --git a/test/test-tcp-close-reset.c b/test/test-tcp-close-reset.c new file mode 100644 index 00000000..8ce15ef2 --- /dev/null +++ b/test/test-tcp-close-reset.c @@ -0,0 +1,289 @@ +/* Copyright libuv project 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" + +#include +#include /* memset */ + +static uv_loop_t* loop; +static uv_tcp_t tcp_server; +static uv_tcp_t tcp_client; +static uv_tcp_t tcp_accepted; +static uv_connect_t connect_req; +static uv_shutdown_t shutdown_req; +static uv_write_t write_reqs[4]; + +static int client_close; +static int shutdown_before_close; + +static int write_cb_called; +static int close_cb_called; +static int shutdown_cb_called; + +static void connect_cb(uv_connect_t* req, int status); +static void write_cb(uv_write_t* req, int status); +static void close_cb(uv_handle_t* handle); +static void shutdown_cb(uv_shutdown_t* req, int status); + +static int read_size; + + +static void do_write(uv_tcp_t* handle) { + uv_buf_t buf; + int i, r; + + buf = uv_buf_init("PING", 4); + for (i = 0; i < ARRAY_SIZE(write_reqs); i++) { + r = uv_write(&write_reqs[i], (uv_stream_t*) handle, &buf, 1, write_cb); + ASSERT(r == 0); + } +} + + +static void do_close(uv_tcp_t* handle) { + 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 { + ASSERT(0 == uv_tcp_close_reset(handle, close_cb)); + ASSERT(UV_ENOTCONN == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); + } + + uv_close((uv_handle_t*) &tcp_server, NULL); +} + +static void alloc_cb(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + static char slab[1024]; + buf->base = slab; + buf->len = sizeof(slab); +} + +static void read_cb2(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + ASSERT((uv_tcp_t*)stream == &tcp_client); + if (nread == UV_EOF) + uv_close((uv_handle_t*) stream, NULL); +} + + +static void connect_cb(uv_connect_t* conn_req, int status) { + ASSERT(conn_req == &connect_req); + uv_read_start((uv_stream_t*) &tcp_client, alloc_cb, read_cb2); + do_write(&tcp_client); + if (client_close) + do_close(&tcp_client); +} + + +static void write_cb(uv_write_t* req, int status) { + /* write callbacks should run before the close callback */ + ASSERT(close_cb_called == 0); + ASSERT(req->handle == (uv_stream_t*)&tcp_client); + write_cb_called++; +} + + +static void close_cb(uv_handle_t* handle) { + if (client_close) + ASSERT(handle == (uv_handle_t*) &tcp_client); + else + ASSERT(handle == (uv_handle_t*) &tcp_accepted); + + close_cb_called++; +} + +static void shutdown_cb(uv_shutdown_t* req, int status) { + if (client_close) + ASSERT(req->handle == (uv_stream_t*) &tcp_client); + else + ASSERT(req->handle == (uv_stream_t*) &tcp_accepted); + + shutdown_cb_called++; +} + + +static void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + ASSERT((uv_tcp_t*)stream == &tcp_accepted); + if (nread < 0) { + uv_close((uv_handle_t*) stream, NULL); + } else { + read_size += nread; + if (read_size == 16 && client_close == 0) + do_close(&tcp_accepted); + } +} + + +static void connection_cb(uv_stream_t* server, int status) { + ASSERT(status == 0); + + ASSERT(0 == uv_tcp_init(loop, &tcp_accepted)); + ASSERT(0 == uv_accept(server, (uv_stream_t*) &tcp_accepted)); + + uv_read_start((uv_stream_t*) &tcp_accepted, alloc_cb, read_cb); +} + + +static void start_server(uv_loop_t* loop, uv_tcp_t* handle) { + struct sockaddr_in addr; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_init(loop, handle); + ASSERT(r == 0); + + r = uv_tcp_bind(handle, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)handle, 128, connection_cb); + ASSERT(r == 0); +} + + +static void do_connect(uv_loop_t* loop, uv_tcp_t* tcp_client) { + struct sockaddr_in addr; + int r; + + ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_init(loop, tcp_client); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, + tcp_client, + (const struct sockaddr*) &addr, + connect_cb); + ASSERT(r == 0); +} + + +/* Check that pending write requests have their callbacks + * invoked when the handle is closed. + */ +TEST_IMPL(tcp_close_reset_client) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 1; + shutdown_before_close = 0; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 1); + ASSERT(shutdown_cb_called == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_client_after_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 1; + shutdown_before_close = 1; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_accepted) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 0; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 1); + ASSERT(shutdown_cb_called == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + +TEST_IMPL(tcp_close_reset_accepted_after_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 1; + + do_connect(loop, &tcp_client); + + ASSERT(write_cb_called == 0); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(write_cb_called == 4); + ASSERT(close_cb_called == 0); + ASSERT(shutdown_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test.gyp b/test/test.gyp index 9be9fe15..60792ad6 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -109,6 +109,7 @@ 'test-tcp-bind6-error.c', 'test-tcp-close.c', 'test-tcp-close-accept.c', + 'test-tcp-close-reset.c', 'test-tcp-close-while-connecting.c', 'test-tcp-create-socket-early.c', 'test-tcp-connect-error-after-write.c',