diff --git a/CMakeLists.txt b/CMakeLists.txt index af89db2d..6b2b8df8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -600,6 +600,7 @@ if(LIBUV_BUILD_TESTS) test/test-pipe-connect-prepare.c test/test-pipe-getsockname.c test/test-pipe-pending-instances.c + test/test-pipe-reject.c test/test-pipe-sendmsg.c test/test-pipe-server-close.c test/test-pipe-set-fchmod.c @@ -637,6 +638,8 @@ if(LIBUV_BUILD_TESTS) test/test-tcp-bind-error.c test/test-tcp-bind6-error.c test/test-tcp-close-accept.c + test/test-tcp-reject.c + test/test-tcp-reject-accept.c test/test-tcp-close-after-read-timeout.c test/test-tcp-close-while-connecting.c test/test-tcp-close.c diff --git a/Makefile.am b/Makefile.am index 9b9e6be7..63a252e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -226,6 +226,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-pipe-connect-prepare.c \ test/test-pipe-getsockname.c \ test/test-pipe-pending-instances.c \ + test/test-pipe-reject.c \ test/test-pipe-sendmsg.c \ test/test-pipe-server-close.c \ test/test-pipe-close-stdout-read-stdin.c \ @@ -264,6 +265,8 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tcp-bind-error.c \ test/test-tcp-bind6-error.c \ test/test-tcp-close-accept.c \ + test/test-tcp-reject.c \ + test/test-tcp-reject-accept.c \ test/test-tcp-close-while-connecting.c \ test/test-tcp-close-after-read-timeout.c \ test/test-tcp-close.c \ diff --git a/docs/src/stream.rst b/docs/src/stream.rst index 0b42c4b3..db0e663d 100644 --- a/docs/src/stream.rst +++ b/docs/src/stream.rst @@ -133,6 +133,19 @@ API .. note:: `server` and `client` must be handles running on the same loop. +.. c:function:: int uv_reject(uv_stream_t* server) + + This call is used in conjunction with :c:func:`uv_listen` to accept and + immediately discard incoming connections. Call this function after receiving + a :c:type:`uv_connection_cb`. + + When the :c:type:`uv_connection_cb` callback is called it is guaranteed that + this function will complete successfully the first time. If you attempt to + use it more than once, it may fail. It is suggested to only call this + function once per :c:type:`uv_connection_cb` call. As :c:type:`uv_accept`. + + .. versionadded:: REPLACEME + .. c:function:: int uv_read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb) Read data from an incoming stream. The :c:type:`uv_read_cb` callback will diff --git a/include/uv.h b/include/uv.h index 938e998f..034b25c8 100644 --- a/include/uv.h +++ b/include/uv.h @@ -543,6 +543,7 @@ UV_EXTERN size_t uv_stream_get_write_queue_size(const uv_stream_t* stream); UV_EXTERN int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb); UV_EXTERN int uv_accept(uv_stream_t* server, uv_stream_t* client); +UV_EXTERN int uv_reject(uv_stream_t* server); UV_EXTERN int uv_read_start(uv_stream_t*, uv_alloc_cb alloc_cb, diff --git a/src/unix/stream.c b/src/unix/stream.c index 18763b47..548523d0 100644 --- a/src/unix/stream.c +++ b/src/unix/stream.c @@ -533,6 +533,54 @@ void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { } +static void uv__process_queued_fds(uv_stream_t* server) { + /* Process queued fds */ + uv__stream_queued_fds_t* queued_fds; + + queued_fds = server->queued_fds; + + /* Read first */ + server->accepted_fd = queued_fds->fds[0]; + + /* All read, free */ + assert(queued_fds->offset > 0); + if (--queued_fds->offset == 0) { + uv__free(queued_fds); + server->queued_fds = NULL; + } else { + /* Shift rest */ + memmove(queued_fds->fds, + queued_fds->fds + 1, + queued_fds->offset * sizeof(*queued_fds->fds)); + } +} + + +int uv_reject(uv_stream_t* server) { + if (server->accepted_fd == -1) + return UV_EAGAIN; + + switch (server->type) { + case UV_NAMED_PIPE: + case UV_UDP: + case UV_TCP: + uv__close(server->accepted_fd); /* Simply close the fd */ + break; + default: + return UV_EINVAL; + } + + /* Process queued fds if needed */ + if (server->queued_fds != NULL) { + uv__process_queued_fds(server); + } else { + server->accepted_fd = -1; + uv__io_start(server->loop, &server->io_watcher, POLLIN); + } + + return 0; +} + int uv_accept(uv_stream_t* server, uv_stream_t* client) { int err; @@ -569,30 +617,12 @@ int uv_accept(uv_stream_t* server, uv_stream_t* client) { client->flags |= UV_HANDLE_BOUND; done: - /* Process queued fds */ + /* Process queued fds if needed */ if (server->queued_fds != NULL) { - uv__stream_queued_fds_t* queued_fds; - - queued_fds = server->queued_fds; - - /* Read first */ - server->accepted_fd = queued_fds->fds[0]; - - /* All read, free */ - assert(queued_fds->offset > 0); - if (--queued_fds->offset == 0) { - uv__free(queued_fds); - server->queued_fds = NULL; - } else { - /* Shift rest */ - memmove(queued_fds->fds, - queued_fds->fds + 1, - queued_fds->offset * sizeof(*queued_fds->fds)); - } + uv__process_queued_fds(server); } else { server->accepted_fd = -1; - if (err == 0) - uv__io_start(server->loop, &server->io_watcher, POLLIN); + uv__io_start(server->loop, &server->io_watcher, POLLIN); } return err; } diff --git a/src/win/stream.c b/src/win/stream.c index a53a10b0..89f0ccbb 100644 --- a/src/win/stream.c +++ b/src/win/stream.c @@ -48,6 +48,46 @@ int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb) { } +int uv_reject(uv_stream_t* server) { + uv_tcp_accept_t* tcp_req; + uv_pipe_accept_t* pipe_req; + + if (server == NULL) { + return UV_EINVAL; + } + + switch (server->type) { + case UV_NAMED_PIPE: + pipe_req = ((uv_pipe_t*)server)->pipe.serv.pending_accepts; + + if (!pipe_req || pipe_req->pipeHandle == INVALID_HANDLE_VALUE) { + return UV_EAGAIN; + } + + CloseHandle(pipe_req->pipeHandle); + pipe_req->pipeHandle = INVALID_HANDLE_VALUE; + ((uv_pipe_t*)server)->pipe.serv.pending_accepts = pipe_req->next_pending; + pipe_req->next_pending = NULL; + break; + case UV_TCP: + tcp_req = ((uv_tcp_t*)server)->tcp.serv.pending_accepts; + + if (!tcp_req || tcp_req->accept_socket == INVALID_SOCKET) { + return UV_EAGAIN; + } + + closesocket(tcp_req->accept_socket); + tcp_req->accept_socket = INVALID_SOCKET; + ((uv_tcp_t*)server)->tcp.serv.pending_accepts = tcp_req->next_pending; + tcp_req->next_pending = NULL; + break; + default: + return UV_EINVAL; + } + return 0; +} + + int uv_accept(uv_stream_t* server, uv_stream_t* client) { int err; diff --git a/test/test-list.h b/test/test-list.h index 24dbcdd7..eb91fdd0 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -154,6 +154,8 @@ 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_reject) +TEST_DECLARE (tcp_reject_accept) TEST_DECLARE (tcp_reuseport) TEST_DECLARE (tcp_rst) TEST_DECLARE (tcp_bind6_error_addrinuse) @@ -214,6 +216,7 @@ TEST_DECLARE (pipe_getsockname_autobind) TEST_DECLARE (pipe_getsockname_blocking) TEST_DECLARE (pipe_pending_instances) TEST_DECLARE (pipe_sendmsg) +TEST_DECLARE (pipe_reject) TEST_DECLARE (pipe_server_close) TEST_DECLARE (connection_fail) TEST_DECLARE (connection_fail_doesnt_auto_close) @@ -771,6 +774,8 @@ TASK_LIST_START TEST_ENTRY (tcp_close_accept) TEST_ENTRY (tcp_oob) #endif + TEST_ENTRY (tcp_reject) + TEST_ENTRY (tcp_reject_accept) TEST_ENTRY (tcp_flags) TEST_ENTRY (tcp_write_to_half_open_connection) TEST_ENTRY (tcp_unexpected_read) @@ -842,6 +847,7 @@ TASK_LIST_START TEST_ENTRY (pipe_getsockname_blocking) TEST_ENTRY (pipe_pending_instances) TEST_ENTRY (pipe_sendmsg) + TEST_ENTRY (pipe_reject) TEST_ENTRY (connection_fail) TEST_ENTRY (connection_fail_doesnt_auto_close) diff --git a/test/test-pipe-reject.c b/test/test-pipe-reject.c new file mode 100644 index 00000000..4f0ad581 --- /dev/null +++ b/test/test-pipe-reject.c @@ -0,0 +1,110 @@ +/* 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 "task.h" +#include "uv.h" + +typedef struct { + uv_pipe_t pipe_handle; + uv_connect_t conn_req; +} client_t; +static uv_pipe_t server_handle; +static int connect_cb_called; +static int do_accept_called = 0; +static int connection_cb_called = 0; +static uv_pipe_t connections[2]; +static client_t clients[2]; + +static void connect_cb(uv_connect_t* _, int status) { + ASSERT_OK(status); + connect_cb_called++; +} + +static void connection_pipe_cb(uv_stream_t* server, int status) { + int r; + uv_pipe_t* conn; + + conn = &connections[connection_cb_called]; + ASSERT_OK(status); + + if (connection_cb_called == 0) { + ASSERT_OK(uv_reject(server)); + + r = uv_pipe_init(server->loop, conn, 0); + ASSERT_OK(r); + + r = uv_accept(server, (uv_stream_t*)conn); + ASSERT(r == UV_EAGAIN); + } else { + r = uv_pipe_init(server->loop, conn, 0); + ASSERT_OK(r); + + r = uv_accept(server, (uv_stream_t*)conn); + ASSERT_OK(r); + + do_accept_called++; + } + + /* After accepting the second client, close the server handle */ + if (do_accept_called == 1) { + uv_close((uv_handle_t*) &server_handle, NULL); + } + + connection_cb_called++; +} + +TEST_IMPL(pipe_reject) { + + int r; + uv_loop_t* loop; + + loop = uv_default_loop(); + + r = uv_pipe_init(loop, &server_handle, 0); + ASSERT_OK(r); + + r = uv_pipe_bind(&server_handle, TEST_PIPENAME); + ASSERT_OK(r); + + r = uv_listen((uv_stream_t*)&server_handle, 128, connection_pipe_cb); + ASSERT_OK(r); + + r = uv_pipe_init(loop, &clients[0].pipe_handle, 0); + ASSERT_OK(r); + uv_pipe_connect(&clients[0].conn_req, + &clients[0].pipe_handle, + TEST_PIPENAME, + connect_cb); + + r = uv_pipe_init(loop, &clients[1].pipe_handle, 0); + ASSERT_OK(r); + uv_pipe_connect(&clients[1].conn_req, + &clients[1].pipe_handle, + TEST_PIPENAME, + connect_cb); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT_EQ(2, connection_cb_called); + ASSERT_EQ(2, connect_cb_called); + ASSERT_EQ(1, do_accept_called); + return 0; +} diff --git a/test/test-tcp-reject-accept.c b/test/test-tcp-reject-accept.c new file mode 100644 index 00000000..a58b7ba2 --- /dev/null +++ b/test/test-tcp-reject-accept.c @@ -0,0 +1,137 @@ +/* Copyright Joyent, Inc. and other Node 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 + +static int connection_cb_called = 0; +static int do_accept_called = 0; +static int close_cb_called = 0; +static int connect_cb_called = 0; +static uv_tcp_t connections[2]; +uv_tcp_t* server; + +static void close_cb(uv_handle_t* handle) { + ASSERT_NOT_NULL(handle); + close_cb_called++; +} + +static void connection_cb(uv_stream_t* tcp, int status) { + int r; + uv_tcp_t* conn; + + conn = &connections[connection_cb_called]; + ASSERT_OK(status); + + if (connection_cb_called == 0) { + ASSERT_OK(uv_reject(tcp)); + + r = uv_tcp_init(tcp->loop, conn); + ASSERT_OK(r); + + r = uv_accept(tcp, (uv_stream_t*)conn); + ASSERT(r == UV_EAGAIN); + } else { + r = uv_tcp_init(uv_default_loop(), conn); + ASSERT_OK(r); + + r = uv_accept((uv_stream_t*)tcp, (uv_stream_t*)conn); + ASSERT_OK(r); + + do_accept_called++; + + uv_close((uv_handle_t*)conn, close_cb); + } + + /* After accepting the second client, close the server handle */ + if (do_accept_called == 1) { + uv_close((uv_handle_t*)tcp, close_cb); + } + + connection_cb_called++; +} + +static void start_server(void) { + struct sockaddr_in addr; + server = malloc(sizeof *server); + int r; + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + ASSERT_NOT_NULL(server); + + r = uv_tcp_init(uv_default_loop(), server); + ASSERT_OK(r); + r = uv_tcp_bind(server, (const struct sockaddr*) &addr, 0); + ASSERT_OK(r); + + r = uv_listen((uv_stream_t*)server, 128, connection_cb); + ASSERT_OK(r); +} + +static void connect_cb(uv_connect_t* req, int status) { + ASSERT_NOT_NULL(req); + ASSERT_OK(status); + connect_cb_called++; + free(req); +} + +static void client_connect(uv_tcp_t* client) { + struct sockaddr_in addr; + int r; + uv_connect_t* connect_req = malloc(sizeof *connect_req); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + ASSERT_NOT_NULL(client); + ASSERT_NOT_NULL(connect_req); + + ASSERT_OK(uv_tcp_init(uv_default_loop(), client)); + + r = uv_tcp_connect(connect_req, + client, + (const struct sockaddr*) &addr, + connect_cb); + ASSERT_OK(r); +} + +TEST_IMPL(tcp_reject_accept) { + uv_tcp_t client1; + uv_tcp_t client2; + + start_server(); + + ASSERT_OK(uv_tcp_init(uv_default_loop(), &client1)); + ASSERT_OK(uv_tcp_init(uv_default_loop(), &client2)); + + client_connect(&client1); + client_connect(&client2); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + ASSERT_EQ(2, connection_cb_called); + ASSERT_EQ(1, do_accept_called); + ASSERT_EQ(2, connect_cb_called); + ASSERT_EQ(2, close_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} diff --git a/test/test-tcp-reject.c b/test/test-tcp-reject.c new file mode 100644 index 00000000..c9f41be2 --- /dev/null +++ b/test/test-tcp-reject.c @@ -0,0 +1,83 @@ +/* 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 "task.h" +#include "uv.h" + +static uv_tcp_t server; +static uv_tcp_t client; +static uv_tcp_t conn; +static int connection_cb_called; +static int connect_cb_called; + +static void connect_cb(uv_connect_t* _, int status) { + ASSERT_OK(status); + connect_cb_called++; +} + +static void connection_cb(uv_stream_t* tcp, int status) { + ASSERT_OK(status); + connection_cb_called++; + + /* Reject the connection */ + ASSERT_OK(uv_reject(tcp)); + + /* The server should not have accepted the connection */ + int r = uv_tcp_init(uv_default_loop(), &conn); + r = uv_accept((uv_stream_t*)tcp, (uv_stream_t*)&conn); + ASSERT(r == UV_EAGAIN); + + /* Close the server */ + uv_close((uv_handle_t*) &server, NULL); +} + +static void start_server(void) { + struct sockaddr_in addr; + + ASSERT_OK(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + ASSERT_OK(uv_tcp_init(uv_default_loop(), &server)); + ASSERT_OK(uv_tcp_bind(&server, (struct sockaddr*) &addr, 0)); + ASSERT_OK(uv_listen((uv_stream_t*) &server, 128, connection_cb)); +} + +TEST_IMPL(tcp_reject) { + uv_connect_t connect_req; + struct sockaddr_in addr; + + start_server(); + + ASSERT_OK(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + ASSERT_OK(uv_tcp_init(uv_default_loop(), &client)); + ASSERT_OK(uv_tcp_connect(&connect_req, + &client, + (struct sockaddr*) &addr, + connect_cb)); + + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + ASSERT_EQ(1, connection_cb_called); + ASSERT_EQ(1, connect_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +}