diff --git a/Makefile.am b/Makefile.am index 12d1c4d0..41833c98 100644 --- a/Makefile.am +++ b/Makefile.am @@ -200,6 +200,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-timer-from-check.c \ test/test-timer.c \ test/test-tty.c \ + test/test-udp-bind.c \ test/test-udp-dgram-too-big.c \ test/test-udp-ipv6.c \ test/test-udp-multicast-interface.c \ diff --git a/include/uv.h b/include/uv.h index d4685e88..9e3e1c33 100644 --- a/include/uv.h +++ b/include/uv.h @@ -846,7 +846,15 @@ enum uv_udp_flags { * Indicates message was truncated because read buffer was too small. The * remainder was discarded by the OS. Used in uv_udp_recv_cb. */ - UV_UDP_PARTIAL = 2 + UV_UDP_PARTIAL = 2, + /* Indicates if SO_REUSEADDR will be set when binding the handle. + * This sets the SO_REUSEPORT socket flag on the BSDs and OS X. On other + * UNIX platforms, it sets the SO_REUSEADDR flag. What that means is that + * multiple threads or processes can bind to the same address without error + * (provided they all set the flag) but only the last one to bind will receive + * any traffic, in effect "stealing" the port from the previous listener. + */ + UV_UDP_REUSEADDR = 4 }; /* @@ -921,18 +929,11 @@ UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); * handle UDP handle. Should have been initialized with `uv_udp_init`. * addr struct sockaddr_in or struct sockaddr_in6 with the address and * port to bind to. - * flags Unused. + * flags Indicate how the socket will be bound, UV_UDP_IPV6ONLY and + * UV_UDP_REUSEADDR are supported. * * Returns: * 0 on success, or an error code < 0 on failure. - * - * This sets the SO_REUSEPORT socket flag on the BSDs and OS X. On other - * UNIX platforms, it sets the SO_REUSEADDR flag. What that means is that - * multiple threads or processes can bind to the same address without error - * (provided they all set the flag) but only the last one to bind will receive - * any traffic, in effect "stealing" the port from the previous listener. - * This behavior is something of an anomaly and may be replaced by an explicit - * opt-in mechanism in future versions of libuv. */ UV_EXTERN int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, diff --git a/src/unix/udp.c b/src/unix/udp.c index 50fa7350..003be583 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -42,7 +42,9 @@ static void uv__udp_run_pending(uv_udp_t* handle); static void uv__udp_io(uv_loop_t* loop, uv__io_t* w, unsigned int revents); static void uv__udp_recvmsg(uv_loop_t* loop, uv__io_t* w, unsigned int revents); static void uv__udp_sendmsg(uv_loop_t* loop, uv__io_t* w, unsigned int revents); -static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain); +static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, + int domain, + unsigned int flags); void uv__udp_close(uv_udp_t* handle) { @@ -308,7 +310,7 @@ int uv__udp_bind(uv_udp_t* handle, fd = -1; /* Check for bad flags. */ - if (flags & ~UV_UDP_IPV6ONLY) + if (flags & ~(UV_UDP_IPV6ONLY | UV_UDP_REUSEADDR)) return -EINVAL; /* Cannot set IPv6-only mode on non-IPv6 socket. */ @@ -323,9 +325,11 @@ int uv__udp_bind(uv_udp_t* handle, handle->io_watcher.fd = fd; } - err = uv__set_reuse(fd); - if (err) - goto out; + if (flags & UV_UDP_REUSEADDR) { + err = uv__set_reuse(fd); + if (err) + goto out; + } if (flags & UV_UDP_IPV6ONLY) { #ifdef IPV6_V6ONLY @@ -354,7 +358,9 @@ out: } -static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain) { +static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, + int domain, + unsigned int flags) { unsigned char taddr[sizeof(struct sockaddr_in6)]; socklen_t addrlen; @@ -387,7 +393,7 @@ static int uv__udp_maybe_deferred_bind(uv_udp_t* handle, int domain) { abort(); } - return uv__udp_bind(handle, (const struct sockaddr*) &taddr, addrlen, 0); + return uv__udp_bind(handle, (const struct sockaddr*) &taddr, addrlen, flags); } @@ -402,7 +408,7 @@ int uv__udp_send(uv_udp_send_t* req, assert(nbufs > 0); - err = uv__udp_maybe_deferred_bind(handle, addr->sa_family); + err = uv__udp_maybe_deferred_bind(handle, addr->sa_family, 0); if (err) return err; @@ -547,15 +553,23 @@ int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership) { + int err; struct sockaddr_in addr4; struct sockaddr_in6 addr6; - if (uv_ip4_addr(multicast_addr, 0, &addr4) == 0) + if (uv_ip4_addr(multicast_addr, 0, &addr4) == 0) { + err = uv__udp_maybe_deferred_bind(handle, AF_INET, UV_UDP_REUSEADDR); + if (err) + return err; return uv__udp_set_membership4(handle, &addr4, interface_addr, membership); - else if (uv_ip6_addr(multicast_addr, 0, &addr6) == 0) + } else if (uv_ip6_addr(multicast_addr, 0, &addr6) == 0) { + err = uv__udp_maybe_deferred_bind(handle, AF_INET6, UV_UDP_REUSEADDR); + if (err) + return err; return uv__udp_set_membership6(handle, &addr6, interface_addr, membership); - else + } else { return -EINVAL; + } } @@ -610,6 +624,7 @@ int uv_udp_set_multicast_loop(uv_udp_t* handle, int on) { } int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) { + int err; struct sockaddr_storage addr_st; struct sockaddr_in* addr4; struct sockaddr_in6* addr6; @@ -630,6 +645,9 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) } if (addr_st.ss_family == AF_INET) { + err = uv__udp_maybe_deferred_bind(handle, AF_INET, UV_UDP_REUSEADDR); + if (err) + return err; if (setsockopt(handle->io_watcher.fd, IPPROTO_IP, IP_MULTICAST_IF, @@ -638,6 +656,9 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) return -errno; } } else if (addr_st.ss_family == AF_INET6) { + err = uv__udp_maybe_deferred_bind(handle, AF_INET6, UV_UDP_REUSEADDR); + if (err) + return err; if (setsockopt(handle->io_watcher.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, @@ -682,7 +703,7 @@ int uv__udp_recv_start(uv_udp_t* handle, if (uv__io_active(&handle->io_watcher, UV__POLLIN)) return -EALREADY; /* FIXME(bnoordhuis) Should be -EBUSY. */ - err = uv__udp_maybe_deferred_bind(handle, AF_INET); + err = uv__udp_maybe_deferred_bind(handle, AF_INET, 0); if (err) return err; diff --git a/src/win/udp.c b/src/win/udp.c index 80958404..8719fa7f 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -62,15 +62,6 @@ static int uv_udp_set_socket(uv_loop_t* loop, uv_udp_t* handle, SOCKET socket, assert(handle->socket == INVALID_SOCKET); - /* Set SO_REUSEADDR on the socket. */ - if (setsockopt(socket, - SOL_SOCKET, - SO_REUSEADDR, - (char*) &yes, - sizeof yes) == SOCKET_ERROR) { - return WSAGetLastError(); - } - /* Set the socket to nonblocking mode */ if (ioctlsocket(socket, FIONBIO, &yes) == SOCKET_ERROR) { return WSAGetLastError(); @@ -168,14 +159,17 @@ void uv_udp_endgame(uv_loop_t* loop, uv_udp_t* handle) { } -static int uv_udp_try_bind(uv_udp_t* handle, - const struct sockaddr* addr, - unsigned int addrlen, - unsigned int flags) { +static int uv_udp_maybe_bind(uv_udp_t* handle, + const struct sockaddr* addr, + unsigned int addrlen, + unsigned int flags) { int r; int err; DWORD no = 0; + if (handle->flags & UV_HANDLE_BOUND) + return 0; + if ((flags & UV_UDP_IPV6ONLY) && addr->sa_family != AF_INET6) { /* UV_UDP_IPV6ONLY is supported only for IPV6 sockets */ return ERROR_INVALID_PARAMETER; @@ -193,6 +187,20 @@ static int uv_udp_try_bind(uv_udp_t* handle, return err; } + if (flags & UV_UDP_REUSEADDR) { + DWORD yes = 1; + /* Set SO_REUSEADDR on the socket. */ + if (setsockopt(sock, + SOL_SOCKET, + SO_REUSEADDR, + (char*) &yes, + sizeof yes) == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(sock); + return err; + } + } + if (addr->sa_family == AF_INET6) handle->flags |= UV_HANDLE_IPV6; } @@ -324,14 +332,12 @@ int uv__udp_recv_start(uv_udp_t* handle, uv_alloc_cb alloc_cb, return WSAEALREADY; } - if (!(handle->flags & UV_HANDLE_BOUND)) { - err = uv_udp_try_bind(handle, + err = uv_udp_maybe_bind(handle, (const struct sockaddr*) &uv_addr_ip4_any_, sizeof(uv_addr_ip4_any_), 0); - if (err) - return err; - } + if (err) + return err; handle->flags |= UV_HANDLE_READING; INCREASE_ACTIVE_COUNT(loop, handle); @@ -544,14 +550,12 @@ static int uv__udp_set_membership4(uv_udp_t* handle, return UV_EINVAL; /* If the socket is unbound, bind to inaddr_any. */ - if (!(handle->flags & UV_HANDLE_BOUND)) { - err = uv_udp_try_bind(handle, + err = uv_udp_maybe_bind(handle, (const struct sockaddr*) &uv_addr_ip4_any_, sizeof(uv_addr_ip4_any_), - 0); - if (err) - return uv_translate_sys_error(err); - } + UV_UDP_REUSEADDR); + if (err) + return uv_translate_sys_error(err); memset(&mreq, 0, sizeof mreq); @@ -598,15 +602,13 @@ int uv__udp_set_membership6(uv_udp_t* handle, if ((handle->flags & UV_HANDLE_BOUND) && !(handle->flags & UV_HANDLE_IPV6)) return UV_EINVAL; - if (!(handle->flags & UV_HANDLE_BOUND)) { - err = uv_udp_try_bind(handle, + err = uv_udp_maybe_bind(handle, (const struct sockaddr*) &uv_addr_ip6_any_, sizeof(uv_addr_ip6_any_), - 0); + UV_UDP_REUSEADDR); - if (err) - return uv_translate_sys_error(err); - } + if (err) + return uv_translate_sys_error(err); memset(&mreq, 0, sizeof(mreq)); @@ -665,16 +667,6 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) struct sockaddr_in* addr4; struct sockaddr_in6* addr6; - /* If the socket is unbound, bind to inaddr_any. */ - if (!(handle->flags & UV_HANDLE_BOUND)) { - err = uv_udp_try_bind(handle, - (const struct sockaddr*) &uv_addr_ip4_any_, - sizeof(uv_addr_ip4_any_), - 0); - if (err) - return uv_translate_sys_error(err); - } - addr4 = (struct sockaddr_in*) &addr_st; addr6 = (struct sockaddr_in6*) &addr_st; @@ -696,6 +688,12 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) } if (addr_st.ss_family == AF_INET) { + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip4_any_, + sizeof(uv_addr_ip4_any_), + UV_UDP_REUSEADDR); + if (err) + return uv_translate_sys_error(err); if (setsockopt(handle->socket, IPPROTO_IP, IP_MULTICAST_IF, @@ -704,6 +702,12 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) return uv_translate_sys_error(WSAGetLastError()); } } else if (addr_st.ss_family == AF_INET6) { + err = uv_udp_maybe_bind(handle, + (const struct sockaddr*) &uv_addr_ip6_any_, + sizeof(uv_addr_ip6_any_), + UV_UDP_REUSEADDR); + if (err) + return uv_translate_sys_error(err); if (setsockopt(handle->socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, @@ -725,14 +729,12 @@ int uv_udp_set_broadcast(uv_udp_t* handle, int value) { int err; /* If the socket is unbound, bind to inaddr_any. */ - if (!(handle->flags & UV_HANDLE_BOUND)) { - err = uv_udp_try_bind(handle, + err = uv_udp_maybe_bind(handle, (const struct sockaddr*) &uv_addr_ip4_any_, sizeof(uv_addr_ip4_any_), 0); - if (err) - return uv_translate_sys_error(err); - } + if (err) + return uv_translate_sys_error(err); if (setsockopt(handle->socket, SOL_SOCKET, @@ -779,14 +781,12 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { } \ \ /* If the socket is unbound, bind to inaddr_any. */ \ - if (!(handle->flags & UV_HANDLE_BOUND)) { \ - err = uv_udp_try_bind(handle, \ + err = uv_udp_maybe_bind(handle, \ (const struct sockaddr*) &uv_addr_ip4_any_, \ sizeof(uv_addr_ip4_any_), \ 0); \ - if (err) \ - return uv_translate_sys_error(err); \ - } \ + if (err) \ + return uv_translate_sys_error(err); \ \ if (!(handle->flags & UV_HANDLE_IPV6)) { \ /* Set IPv4 socket option */ \ @@ -842,7 +842,7 @@ int uv__udp_bind(uv_udp_t* handle, unsigned int flags) { int err; - err = uv_udp_try_bind(handle, addr, addrlen, flags); + err = uv_udp_maybe_bind(handle, addr, addrlen, flags); if (err) return uv_translate_sys_error(err); @@ -871,7 +871,7 @@ int uv__udp_send(uv_udp_send_t* req, } else { abort(); } - err = uv_udp_try_bind(handle, bind_addr, addrlen, 0); + err = uv_udp_maybe_bind(handle, bind_addr, addrlen, 0); if (err) return uv_translate_sys_error(err); } diff --git a/test/test-list.h b/test/test-list.h index 7d365a37..f44d9f96 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -79,6 +79,8 @@ TEST_DECLARE (tcp_bind6_error_addrnotavail) TEST_DECLARE (tcp_bind6_error_fault) TEST_DECLARE (tcp_bind6_error_inval) TEST_DECLARE (tcp_bind6_localhost_ok) +TEST_DECLARE (udp_bind) +TEST_DECLARE (udp_bind_reuseaddr) TEST_DECLARE (udp_send_and_recv) TEST_DECLARE (udp_multicast_join) TEST_DECLARE (udp_multicast_join6) @@ -349,6 +351,8 @@ TASK_LIST_START TEST_ENTRY (tcp_bind6_error_inval) TEST_ENTRY (tcp_bind6_localhost_ok) + TEST_ENTRY (udp_bind) + TEST_ENTRY (udp_bind_reuseaddr) TEST_ENTRY (udp_send_and_recv) TEST_ENTRY (udp_dgram_too_big) TEST_ENTRY (udp_dual_stack) diff --git a/test/test-udp-bind.c b/test/test-udp-bind.c new file mode 100644 index 00000000..a1e080ee --- /dev/null +++ b/test/test-udp-bind.c @@ -0,0 +1,93 @@ +/* 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 + + +TEST_IMPL(udp_bind) { + struct sockaddr_in addr; + uv_loop_t* loop; + uv_udp_t h1, h2; + int r; + + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + loop = uv_default_loop(); + + r = uv_udp_init(loop, &h1); + ASSERT(r == 0); + + r = uv_udp_init(loop, &h2); + ASSERT(r == 0); + + r = uv_udp_bind(&h1, (const struct sockaddr*) &addr, 0); + ASSERT(r == 0); + + r = uv_udp_bind(&h2, (const struct sockaddr*) &addr, 0); + ASSERT(r == UV_EADDRINUSE); + + uv_close((uv_handle_t*) &h1, NULL); + uv_close((uv_handle_t*) &h2, NULL); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} + + +TEST_IMPL(udp_bind_reuseaddr) { + struct sockaddr_in addr; + uv_loop_t* loop; + uv_udp_t h1, h2; + int r; + + ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + loop = uv_default_loop(); + + r = uv_udp_init(loop, &h1); + ASSERT(r == 0); + + r = uv_udp_init(loop, &h2); + ASSERT(r == 0); + + r = uv_udp_bind(&h1, (const struct sockaddr*) &addr, UV_UDP_REUSEADDR); + ASSERT(r == 0); + + r = uv_udp_bind(&h2, (const struct sockaddr*) &addr, UV_UDP_REUSEADDR); + ASSERT(r == 0); + + uv_close((uv_handle_t*) &h1, NULL); + uv_close((uv_handle_t*) &h2, NULL); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT(r == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/test/test-watcher-cross-stop.c b/test/test-watcher-cross-stop.c index c701dd2f..9284a129 100644 --- a/test/test-watcher-cross-stop.c +++ b/test/test-watcher-cross-stop.c @@ -73,7 +73,9 @@ TEST_IMPL(watcher_cross_stop) { for (i = 0; i < ARRAY_SIZE(sockets); i++) { ASSERT(0 == uv_udp_init(loop, &sockets[i])); - ASSERT(0 == uv_udp_bind(&sockets[i], (const struct sockaddr*) &addr, 0)); + ASSERT(0 == uv_udp_bind(&sockets[i], + (const struct sockaddr*) &addr, + UV_UDP_REUSEADDR)); ASSERT(0 == uv_udp_recv_start(&sockets[i], alloc_cb, recv_cb)); ASSERT(0 == uv_udp_send(&reqs[i], &sockets[i], diff --git a/uv.gyp b/uv.gyp index f277e842..5425dd63 100644 --- a/uv.gyp +++ b/uv.gyp @@ -383,6 +383,7 @@ 'test/test-timer-from-check.c', 'test/test-timer.c', 'test/test-tty.c', + 'test/test-udp-bind.c', 'test/test-udp-dgram-too-big.c', 'test/test-udp-ipv6.c', 'test/test-udp-open.c',