diff --git a/Makefile.am b/Makefile.am index 2093ac23..68dd8ae3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -159,6 +159,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-close-fd.c \ test/test-close-order.c \ test/test-condvar.c \ + test/test-connect-unspecified.c \ test/test-connection-fail.c \ test/test-cwd-and-chdir.c \ test/test-default-loop-close.c \ diff --git a/docs/src/tcp.rst b/docs/src/tcp.rst index a1a58245..e761b460 100644 --- a/docs/src/tcp.rst +++ b/docs/src/tcp.rst @@ -102,7 +102,14 @@ API and an uninitialized :c:type:`uv_connect_t`. `addr` should point to an initialized ``struct sockaddr_in`` or ``struct sockaddr_in6``. + On Windows if the `addr` is initialized to point to an unspecified address + (``0.0.0.0`` or ``::``) it will be changed to point to ``localhost``. + This is done to match the behavior of Linux systems. + The callback is made when the connection has been established or when a connection error happened. + .. versionchanged:: 1.19.0 added ``0.0.0.0`` and ``::`` to ``localhost`` + mapping + .. seealso:: The :c:type:`uv_stream_t` API functions also apply. diff --git a/docs/src/udp.rst b/docs/src/udp.rst index 8704a099..81488285 100644 --- a/docs/src/udp.rst +++ b/docs/src/udp.rst @@ -243,6 +243,10 @@ API with :c:func:`uv_udp_bind` it will be bound to 0.0.0.0 (the "all interfaces" IPv4 address) and a random port number. + On Windows if the `addr` is initialized to point to an unspecified address + (``0.0.0.0`` or ``::``) it will be changed to point to ``localhost``. + This is done to match the behavior of Linux systems. + :param req: UDP request handle. Need not be initialized. :param handle: UDP handle. Should have been initialized with @@ -259,6 +263,9 @@ API :returns: 0 on success, or an error code < 0 on failure. + .. versionchanged:: 1.19.0 added ``0.0.0.0`` and ``::`` to ``localhost`` + mapping + .. c:function:: int uv_udp_try_send(uv_udp_t* handle, const uv_buf_t bufs[], unsigned int nbufs, const struct sockaddr* addr) Same as :c:func:`uv_udp_send`, but won't queue a send request if it can't diff --git a/src/win/tcp.c b/src/win/tcp.c index e63a63e7..fd6efbaf 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -747,10 +747,15 @@ static int uv_tcp_try_connect(uv_connect_t* req, uv_connect_cb cb) { uv_loop_t* loop = handle->loop; const struct sockaddr* bind_addr; + struct sockaddr_storage converted; BOOL success; DWORD bytes; int err; + err = uv__convert_to_localhost_if_unspecified(addr, &converted); + if (err) + return err; + if (handle->delayed_error) { return handle->delayed_error; } @@ -782,12 +787,12 @@ static int uv_tcp_try_connect(uv_connect_t* req, memset(&req->u.io.overlapped, 0, sizeof(req->u.io.overlapped)); success = handle->tcp.conn.func_connectex(handle->socket, - addr, - addrlen, - NULL, - 0, - &bytes, - &req->u.io.overlapped); + (const struct sockaddr*) &converted, + addrlen, + NULL, + 0, + &bytes, + &req->u.io.overlapped); if (UV_SUCCEEDED_WITHOUT_IOCP(success)) { /* Process the req without IOCP. */ diff --git a/src/win/udp.c b/src/win/udp.c index 21348f37..cd1d0e07 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -923,10 +923,15 @@ int uv__udp_try_send(uv_udp_t* handle, unsigned int addrlen) { DWORD bytes; const struct sockaddr* bind_addr; + struct sockaddr_storage converted; int err; assert(nbufs > 0); + err = uv__convert_to_localhost_if_unspecified(addr, &converted); + if (err) + return err; + /* Already sending a message.*/ if (handle->send_queue_count != 0) return UV_EAGAIN; @@ -948,7 +953,7 @@ int uv__udp_try_send(uv_udp_t* handle, nbufs, &bytes, 0, - addr, + (const struct sockaddr*) &converted, addrlen, NULL, NULL); diff --git a/src/win/winsock.c b/src/win/winsock.c index e86d76b1..7cfa90f8 100644 --- a/src/win/winsock.c +++ b/src/win/winsock.c @@ -559,3 +559,31 @@ int WSAAPI uv_msafd_poll(SOCKET socket, AFD_POLL_INFO* info_in, return SOCKET_ERROR; } } + +int uv__convert_to_localhost_if_unspecified(const struct sockaddr* addr, + struct sockaddr_storage* storage) { + struct sockaddr_in* dest4; + struct sockaddr_in6* dest6; + + if (addr == NULL) + return UV_EINVAL; + + switch (addr->sa_family) { + case AF_INET: + dest4 = (struct sockaddr_in*) storage; + memcpy(dest4, addr, sizeof(*dest4)); + if (dest4->sin_addr.s_addr == 0) + dest4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + return 0; + case AF_INET6: + dest6 = (struct sockaddr_in6*) storage; + memcpy(dest6, addr, sizeof(*dest6)); + if (memcmp(&dest6->sin6_addr, + &uv_addr_ip6_any_.sin6_addr, + sizeof(uv_addr_ip6_any_.sin6_addr)) == 0) + dest6->sin6_addr = (struct in6_addr) IN6ADDR_LOOPBACK_INIT; + return 0; + default: + return UV_EINVAL; + } +} diff --git a/src/win/winsock.h b/src/win/winsock.h index 7c007ab4..7ecb755b 100644 --- a/src/win/winsock.h +++ b/src/win/winsock.h @@ -187,4 +187,7 @@ typedef struct _IP_ADAPTER_UNICAST_ADDRESS_LH { #endif +int uv__convert_to_localhost_if_unspecified(const struct sockaddr* addr, + struct sockaddr_storage* storage); + #endif /* UV_WIN_WINSOCK_H_ */ diff --git a/test/test-connect-unspecified.c b/test/test-connect-unspecified.c new file mode 100644 index 00000000..04e1c8a5 --- /dev/null +++ b/test/test-connect-unspecified.c @@ -0,0 +1,61 @@ +/* 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" + +static void connect_4(uv_connect_t* req, int status) { + ASSERT(status != UV_EADDRNOTAVAIL); +} + +static void connect_6(uv_connect_t* req, int status) { + ASSERT(status != UV_EADDRNOTAVAIL); +} + +TEST_IMPL(connect_unspecified) { + uv_loop_t* loop; + uv_tcp_t socket4; + struct sockaddr_in addr4; + uv_connect_t connect4; + uv_tcp_t socket6; + struct sockaddr_in6 addr6; + uv_connect_t connect6; + + loop = uv_default_loop(); + + ASSERT(uv_tcp_init(loop, &socket4) == 0); + ASSERT(uv_ip4_addr("0.0.0.0", TEST_PORT, &addr4) == 0); + ASSERT(uv_tcp_connect(&connect4, + &socket4, + (const struct sockaddr*) &addr4, + connect_4) == 0); + + ASSERT(uv_tcp_init(loop, &socket6) == 0); + ASSERT(uv_ip6_addr("::", TEST_PORT, &addr6) == 0); + ASSERT(uv_tcp_connect(&connect6, + &socket6, + (const struct sockaddr*) &addr6, + connect_6) == 0); + + ASSERT(uv_run(loop, UV_RUN_DEFAULT) == 0); + + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index c1a17803..6af1b64f 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -55,6 +55,7 @@ TEST_DECLARE (tty_file) TEST_DECLARE (tty_pty) TEST_DECLARE (stdio_over_pipes) TEST_DECLARE (ip6_pton) +TEST_DECLARE (connect_unspecified) TEST_DECLARE (ipc_listen_before_write) TEST_DECLARE (ipc_listen_after_write) #ifndef _WIN32 @@ -468,6 +469,7 @@ TASK_LIST_START TEST_ENTRY (tty_pty) TEST_ENTRY (stdio_over_pipes) TEST_ENTRY (ip6_pton) + TEST_ENTRY (connect_unspecified) TEST_ENTRY (ipc_listen_before_write) TEST_ENTRY (ipc_listen_after_write) #ifndef _WIN32 diff --git a/uv.gyp b/uv.gyp index fbb384a4..46606c5b 100644 --- a/uv.gyp +++ b/uv.gyp @@ -366,6 +366,7 @@ 'test/test-callback-order.c', 'test/test-close-fd.c', 'test/test-close-order.c', + 'test/test-connect-unspecified.c', 'test/test-connection-fail.c', 'test/test-cwd-and-chdir.c', 'test/test-default-loop-close.c',