diff --git a/docs/src/udp.rst b/docs/src/udp.rst index 5f225e5cd..39fe77f5e 100644 --- a/docs/src/udp.rst +++ b/docs/src/udp.rst @@ -173,6 +173,22 @@ API .. versionadded:: 1.7.0 .. versionchanged:: 1.37.0 added the `UV_UDP_RECVMMSG` flag. +.. c:function:: int uv_udp_open_ex(uv_udp_t* handle, uv_os_sock_t sock, unsigned int flags) + + Opens an existing file descriptor or Windows SOCKET as a UDP handle. + + :param handle: UDP handle. Should have been initialized with + :c:func:`uv_udp_init`. + + :param sock: An existing socket to associate with the handle. + + :param flags: Flags that control socket behavior, + ``UV_UDP_REUSEADDR``, and ``UV_UDP_REUSEPORT`` are supported. + + :returns: 0 on success, or an error code < 0 on failure. + + .. versionadded:: 1.52.0 + .. c:function:: int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) Opens an existing file descriptor or Windows SOCKET as a UDP handle. @@ -189,6 +205,10 @@ API The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid datagram socket. + Internally sets the SO_REUSEADDR socket option unconditionally. This + means the reuse flag is always enabled, regardless of user intent. For + more control use :c:func:`uv_udp_open_ex`. + .. c:function:: int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int flags) Bind the UDP handle to an IP address and port. diff --git a/include/uv.h b/include/uv.h index f788c040f..d1a77c749 100644 --- a/include/uv.h +++ b/include/uv.h @@ -746,6 +746,9 @@ struct uv_udp_send_s { UV_EXTERN int uv_udp_init(uv_loop_t*, uv_udp_t* handle); UV_EXTERN int uv_udp_init_ex(uv_loop_t*, uv_udp_t* handle, unsigned int flags); UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); +UV_EXTERN int uv_udp_open_ex(uv_udp_t* handle, + uv_os_sock_t sock, + unsigned int flags); UV_EXTERN int uv_udp_bind(uv_udp_t* handle, const struct sockaddr* addr, unsigned int flags); diff --git a/src/unix/udp.c b/src/unix/udp.c index 5f8d86f57..f66b00a72 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -897,9 +897,13 @@ int uv_udp_using_recvmmsg(const uv_udp_t* handle) { } -int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { +int uv_udp_open_ex(uv_udp_t* handle, uv_os_sock_t sock, unsigned int flags) { int err; + /* Check for bad flags. */ + if (flags & ~(UV_UDP_REUSEADDR | UV_UDP_REUSEPORT)) + return UV_EINVAL; + /* Check for already active socket. */ if (handle->io_watcher.fd != -1) return UV_EBUSY; @@ -911,9 +915,17 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { if (err) return err; - err = uv__sock_reuseaddr(sock); - if (err) - return err; + if (flags & UV_UDP_REUSEADDR) { + err = uv__sock_reuseaddr(sock); + if (err) + return err; + } + + if (flags & UV_UDP_REUSEPORT) { + err = uv__sock_reuseport(sock); + if (err) + return err; + } handle->io_watcher.fd = sock; if (uv__udp_is_connected(handle)) @@ -923,6 +935,15 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + /* + * Keep backward compatibility, always set REUSEADDR. + * Refs: https://github.com/libuv/libuv/issues/4551 + */ + return uv_udp_open_ex(handle, sock, UV_UDP_REUSEADDR); +} + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, diff --git a/src/win/udp.c b/src/win/udp.c index 33b4acc2b..08e358a2c 100644 --- a/src/win/udp.c +++ b/src/win/udp.c @@ -204,7 +204,7 @@ static int uv__udp_maybe_bind(uv_udp_t* handle, * so we just return an error directly when UV_UDP_REUSEPORT is requested * for binding the socket. */ if (flags & UV_UDP_REUSEPORT) - return ERROR_NOT_SUPPORTED; + return UV_ENOTSUP; if ((flags & UV_UDP_IPV6ONLY) && addr->sa_family != AF_INET6) { /* UV_UDP_IPV6ONLY is supported only for IPV6 sockets */ @@ -908,11 +908,15 @@ int uv__udp_is_bound(uv_udp_t* handle) { } -int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { +int uv_udp_open_ex(uv_udp_t* handle, uv_os_sock_t sock, unsigned int flags) { WSAPROTOCOL_INFOW protocol_info; int opt_len; int err; + /* Check for bad flags. */ + if (flags & ~(UV_UDP_REUSEADDR | UV_UDP_REUSEPORT)) + return UV_EINVAL; + /* Detect the address family of the socket. */ opt_len = (int) sizeof protocol_info; if (getsockopt(sock, @@ -930,6 +934,25 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { if (err) return uv_translate_sys_error(err); + /* There is no SO_REUSEPORT on Windows, Windows only knows SO_REUSEADDR. + * so we just return an error directly when UV_UDP_REUSEPORT is requested + * for binding the socket. */ + if (flags & UV_UDP_REUSEPORT) + return UV_ENOTSUP; + + if (flags & UV_UDP_REUSEADDR) { + DWORD yes = 1; + /* Set SO_REUSEADDR on the socket. */ + if (setsockopt(handle->socket, + SOL_SOCKET, + SO_REUSEADDR, + (char*) &yes, + sizeof yes) == SOCKET_ERROR) { + err = WSAGetLastError(); + return uv_translate_sys_error(err); + } + } + if (uv__udp_is_bound(handle)) handle->flags |= UV_HANDLE_BOUND; @@ -940,6 +963,11 @@ int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + return uv_udp_open_ex(handle, sock, 0); +} + + #define SOCKOPT_SETTER(name, option4, option6, validate) \ int uv_udp_set_##name(uv_udp_t* handle, int value) { \ DWORD optval = (DWORD) value; \