From cc1c1912ca119c56ecdc0e4564720a01bd53d47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Mon, 17 Sep 2012 12:15:12 +0200 Subject: [PATCH] unix, windows: add uv_tcp_open and uv_udp_open --- include/uv.h | 10 +++ src/unix/tcp.c | 7 ++ src/unix/udp.c | 68 ++++++++++++++--- src/win/tcp.c | 15 ++++ src/win/udp.c | 22 ++++++ test/test-list.h | 8 ++ test/test-tcp-open.c | 175 +++++++++++++++++++++++++++++++++++++++++++ test/test-udp-open.c | 154 +++++++++++++++++++++++++++++++++++++ uv.gyp | 2 + 9 files changed, 449 insertions(+), 12 deletions(-) create mode 100644 test/test-tcp-open.c create mode 100644 test/test-udp-open.c diff --git a/include/uv.h b/include/uv.h index 3ac37a23..d2d16292 100644 --- a/include/uv.h +++ b/include/uv.h @@ -599,6 +599,11 @@ struct uv_tcp_s { UV_EXTERN int uv_tcp_init(uv_loop_t*, uv_tcp_t* handle); +/* + * Opens an existing file descriptor or SOCKET as a tcp handle. + */ +UV_EXTERN int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock); + /* Enable/disable Nagle's algorithm. */ UV_EXTERN int uv_tcp_nodelay(uv_tcp_t* handle, int enable); @@ -703,6 +708,11 @@ struct uv_udp_send_s { */ UV_EXTERN int uv_udp_init(uv_loop_t*, uv_udp_t* handle); +/* + * Opens an existing file descriptor or SOCKET as a udp handle. + */ +UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); + /* * Bind to a IPv4 address and port. * diff --git a/src/unix/tcp.c b/src/unix/tcp.c index 186150be..364314ef 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -153,6 +153,13 @@ int uv__tcp_bind6(uv_tcp_t* handle, struct sockaddr_in6 addr) { } +int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock) { + return uv__stream_open((uv_stream_t*)handle, + sock, + UV_STREAM_READABLE | UV_STREAM_WRITABLE); +} + + int uv_tcp_getsockname(uv_tcp_t* handle, struct sockaddr* name, int* namelen) { socklen_t socklen; diff --git a/src/unix/udp.c b/src/unix/udp.c index c4966874..6f5a3f14 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -316,17 +316,15 @@ static int uv__bind(uv_udp_t* handle, goto out; } - /* Check for already active socket. */ - if (handle->fd != -1) { - uv__set_artificial_error(handle->loop, UV_EALREADY); - goto out; - } - - if ((fd = uv__socket(domain, SOCK_DGRAM, 0)) == -1) { - uv__set_sys_error(handle->loop, errno); - goto out; + if (handle->fd == -1) { + if ((fd = uv__socket(domain, SOCK_DGRAM, 0)) == -1) { + uv__set_sys_error(handle->loop, errno); + goto out; + } + handle->fd = fd; } + fd = handle->fd; yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { uv__set_sys_error(handle->loop, errno); @@ -367,12 +365,13 @@ static int uv__bind(uv_udp_t* handle, goto out; } - handle->fd = fd; status = 0; out: - if (status) - close(fd); + if (status) { + close(handle->fd); + handle->fd = -1; + } errno = saved_errno; return status; @@ -486,6 +485,51 @@ int uv__udp_bind6(uv_udp_t* handle, struct sockaddr_in6 addr, unsigned flags) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + int saved_errno; + int status; + int yes; + + saved_errno = errno; + status = -1; + + /* Check for already active socket. */ + if (handle->fd != -1) { + uv__set_artificial_error(handle->loop, UV_EALREADY); + goto out; + } + + yes = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { + uv__set_sys_error(handle->loop, errno); + goto out; + } + + /* On the BSDs, SO_REUSEADDR lets you reuse an address that's in the TIME_WAIT + * state (i.e. was until recently tied to a socket) while SO_REUSEPORT lets + * multiple processes bind to the same address. Yes, it's something of a + * misnomer but then again, SO_REUSEADDR was already taken. + * + * None of the above applies to Linux: SO_REUSEADDR implies SO_REUSEPORT on + * Linux and hence it does not have SO_REUSEPORT at all. + */ +#ifdef SO_REUSEPORT + yes = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof yes) == -1) { + uv__set_sys_error(handle->loop, errno); + goto out; + } +#endif + + handle->fd = sock; + status = 0; + +out: + errno = saved_errno; + return status; +} + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership) { diff --git a/src/win/tcp.c b/src/win/tcp.c index 1be1d186..ff7b27b8 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -1378,3 +1378,18 @@ void uv_tcp_close(uv_loop_t* loop, uv_tcp_t* tcp) { uv_want_endgame(tcp->loop, (uv_handle_t*)tcp); } } + + +int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock) { + /* Make the socket non-inheritable */ + if (!SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0)) { + uv__set_sys_error(handle->loop, GetLastError()); + return -1; + } + + if (uv_tcp_set_socket(handle->loop, handle, sock, 0) == -1) { + return -1; + } + + return 0; +} diff --git a/src/win/udp.c b/src/win/udp.c index 2436799b..56b46239 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -653,6 +653,28 @@ int uv_udp_set_broadcast(uv_udp_t* handle, int value) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + int r; + DWORD yes = 1; + + if (uv_udp_set_socket(handle->loop, handle, sock) == -1) { + return -1; + } + + r = setsockopt(handle->socket, + SOL_SOCKET, + SO_REUSEADDR, + (char*) &yes, + sizeof yes); + if (r == SOCKET_ERROR) { + uv__set_sys_error(handle->loop, WSAGetLastError()); + return -1; + } + + return 0; +} + + #define SOCKOPT_SETTER(name, option4, option6, validate) \ int uv_udp_set_##name(uv_udp_t* handle, int value) { \ DWORD optval = (DWORD) value; \ diff --git a/test/test-list.h b/test/test-list.h index 1423f806..ce70c1f9 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -40,6 +40,7 @@ TEST_DECLARE (pipe_ping_pong) TEST_DECLARE (delayed_accept) TEST_DECLARE (multiple_listen) TEST_DECLARE (tcp_writealot) +TEST_DECLARE (tcp_open) TEST_DECLARE (tcp_connect_error_after_write) TEST_DECLARE (tcp_shutdown_after_write) TEST_DECLARE (tcp_bind_error_addrinuse) @@ -69,6 +70,7 @@ TEST_DECLARE (udp_dgram_too_big) TEST_DECLARE (udp_dual_stack) TEST_DECLARE (udp_ipv6_only) TEST_DECLARE (udp_options) +TEST_DECLARE (udp_open) TEST_DECLARE (pipe_bind_error_addrinuse) TEST_DECLARE (pipe_bind_error_addrnotavail) TEST_DECLARE (pipe_bind_error_inval) @@ -237,6 +239,9 @@ TASK_LIST_START TEST_ENTRY (tcp_writealot) TEST_HELPER (tcp_writealot, tcp4_echo_server) + TEST_ENTRY (tcp_open) + TEST_HELPER (tcp_open, tcp4_echo_server) + TEST_ENTRY (tcp_shutdown_after_write) TEST_HELPER (tcp_shutdown_after_write, tcp4_echo_server) @@ -271,6 +276,9 @@ TASK_LIST_START TEST_ENTRY (udp_multicast_join) TEST_ENTRY (udp_multicast_ttl) + TEST_ENTRY (udp_open) + TEST_HELPER (udp_open, udp4_echo_server) + TEST_ENTRY (pipe_bind_error_addrinuse) TEST_ENTRY (pipe_bind_error_addrnotavail) TEST_ENTRY (pipe_bind_error_inval) diff --git a/test/test-tcp-open.c b/test/test-tcp-open.c new file mode 100644 index 00000000..48188d24 --- /dev/null +++ b/test/test-tcp-open.c @@ -0,0 +1,175 @@ +/* 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 +#include + +#ifndef _WIN32 +# include +#endif + +static int shutdown_cb_called = 0; +static int connect_cb_called = 0; +static int write_cb_called = 0; +static int close_cb_called = 0; + +static uv_connect_t connect_req; +static uv_shutdown_t shutdown_req; +static uv_write_t write_req; + + +static void startup(void) { +#ifdef _WIN32 + struct WSAData wsa_data; + int r = WSAStartup(MAKEWORD(2, 2), &wsa_data); + ASSERT(r == 0); +#endif +} + + +static uv_os_sock_t create_tcp_socket(void) { + uv_os_sock_t sock; + int r; + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); +#ifdef _WIN32 + ASSERT(sock != INVALID_SOCKET); +#else + ASSERT(sock >= 0); +#endif + +#ifndef _WIN32 + { + /* Allow reuse of the port. */ + int yes = 1; + r = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + ASSERT(r == 0); + } +#endif + + return sock; +} + + +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char slab[65536]; + ASSERT(suggested_size <= sizeof slab); + return uv_buf_init(slab, sizeof slab); +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle != NULL); + close_cb_called++; +} + + +static void shutdown_cb(uv_shutdown_t* req, int status) { + ASSERT(req == &shutdown_req); + ASSERT(status == 0); + + /* Now we wait for the EOF */ + shutdown_cb_called++; +} + + +static void read_cb(uv_stream_t* tcp, ssize_t nread, uv_buf_t buf) { + ASSERT(tcp != NULL); + + if (nread >= 0) { + ASSERT(nread == 4); + ASSERT(memcmp("PING", buf.base, nread) == 0); + } + else { + ASSERT(uv_last_error(uv_default_loop()).code == UV_EOF); + printf("GOT EOF\n"); + uv_close((uv_handle_t*)tcp, close_cb); + } +} + + +static void write_cb(uv_write_t* req, int status) { + ASSERT(req != NULL); + + if (status) { + uv_err_t err = uv_last_error(uv_default_loop()); + fprintf(stderr, "uv_write error: %s\n", uv_strerror(err)); + ASSERT(0); + } + + write_cb_called++; +} + + +static void connect_cb(uv_connect_t* req, int status) { + uv_buf_t buf = uv_buf_init("PING", 4); + uv_stream_t* stream; + int r; + + ASSERT(req == &connect_req); + ASSERT(status == 0); + + stream = req->handle; + connect_cb_called++; + + r = uv_write(&write_req, stream, &buf, 1, write_cb); + ASSERT(r == 0); + + /* Shutdown on drain. */ + r = uv_shutdown(&shutdown_req, stream, shutdown_cb); + ASSERT(r == 0); + + /* Start reading */ + r = uv_read_start(stream, alloc_cb, read_cb); + ASSERT(r == 0); +} + + +TEST_IMPL(tcp_open) { + struct sockaddr_in addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + uv_tcp_t client; + uv_os_sock_t sock; + int r; + + startup(); + sock = create_tcp_socket(); + + r = uv_tcp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = uv_tcp_open(&client, sock); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, &client, addr, connect_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop()); + + ASSERT(shutdown_cb_called == 1); + ASSERT(connect_cb_called == 1); + ASSERT(write_cb_called == 1); + ASSERT(close_cb_called == 1); + + return 0; +} diff --git a/test/test-udp-open.c b/test/test-udp-open.c new file mode 100644 index 00000000..9168549e --- /dev/null +++ b/test/test-udp-open.c @@ -0,0 +1,154 @@ +/* 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 +#include + +#ifndef _WIN32 +# include +#endif + +static int send_cb_called = 0; +static int close_cb_called = 0; + +static uv_udp_send_t send_req; + + +static void startup(void) { +#ifdef _WIN32 + struct WSAData wsa_data; + int r = WSAStartup(MAKEWORD(2, 2), &wsa_data); + ASSERT(r == 0); +#endif +} + + +static uv_os_sock_t create_udp_socket(void) { + uv_os_sock_t sock; + int r; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); +#ifdef _WIN32 + ASSERT(sock != INVALID_SOCKET); +#else + ASSERT(sock >= 0); +#endif + +#ifndef _WIN32 + { + /* Allow reuse of the port. */ + int yes = 1; + r = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + ASSERT(r == 0); + } +#endif + + return sock; +} + + +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char slab[65536]; + ASSERT(suggested_size <= sizeof slab); + return uv_buf_init(slab, sizeof slab); +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle != NULL); + close_cb_called++; +} + + +static void recv_cb(uv_udp_t* handle, + ssize_t nread, + uv_buf_t buf, + struct sockaddr* addr, + unsigned flags) { + int r; + + if (nread < 0) { + ASSERT(0 && "unexpected error"); + } + + if (nread == 0) { + /* Returning unused buffer */ + /* Don't count towards sv_recv_cb_called */ + ASSERT(addr == NULL); + return; + } + + ASSERT(flags == 0); + + ASSERT(addr != NULL); + ASSERT(nread == 4); + ASSERT(memcmp("PING", buf.base, nread) == 0); + + r = uv_udp_recv_stop(handle); + ASSERT(r == 0); + + uv_close((uv_handle_t*) handle, close_cb); +} + + +static void send_cb(uv_udp_send_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == 0); + + send_cb_called++; +} + + +TEST_IMPL(udp_open) { + struct sockaddr_in addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + uv_buf_t buf = uv_buf_init("PING", 4); + uv_udp_t client; + uv_os_sock_t sock; + int r; + + startup(); + sock = create_udp_socket(); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = uv_udp_open(&client, sock); + ASSERT(r == 0); + + r = uv_udp_bind(&client, addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&client, alloc_cb, recv_cb); + ASSERT(r == 0); + + r = uv_udp_send(&send_req, &client, &buf, 1, addr, send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop()); + + ASSERT(send_cb_called == 1); + ASSERT(close_cb_called == 1); + + return 0; +} diff --git a/uv.gyp b/uv.gyp index a5dbf153..ba5ca318 100644 --- a/uv.gyp +++ b/uv.gyp @@ -305,6 +305,7 @@ 'test/test-tcp-connect-error.c', 'test/test-tcp-connect-timeout.c', 'test/test-tcp-connect6-error.c', + 'test/test-tcp-open.c', 'test/test-tcp-write-error.c', 'test/test-tcp-write-to-half-open-connection.c', 'test/test-tcp-writealot.c', @@ -318,6 +319,7 @@ 'test/test-tty.c', 'test/test-udp-dgram-too-big.c', 'test/test-udp-ipv6.c', + 'test/test-udp-open.c', 'test/test-udp-options.c', 'test/test-udp-send-and-recv.c', 'test/test-udp-multicast-join.c',