From 3d2c820a4efe3954a77b539bb56e7398263069d3 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 30 Oct 2013 09:36:47 +0100 Subject: [PATCH] linux: don't turn on SO_REUSEPORT socket option On the BSDs, SO_REUSEPORT is pretty much SO_REUSEADDR with some special casing for IP multicast. When two processes (that don't do multicast) bind to the same address, only the last one receives traffic. It allows one to "steal" the bound address from another process. (Both processes have to enable SO_REUSEPORT though, so it only works in a cooperative setting.) On Linux however, it enables port sharing, not stealing - both processes receive a share of the traffic. This is a desirable trait but pre-3.9 kernels don't support the socket option and a libuv program therefore behaves differently with older kernels or on another platform. This is a back-port of commit 9d60f1e from the master branch. Fixes joyent/node#6454. --- src/unix/udp.c | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/unix/udp.c b/src/unix/udp.c index 3fb8af932..ea437a3ef 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -286,6 +286,31 @@ static void uv__udp_sendmsg(uv_loop_t* loop, } +/* On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but with some additional + * refinements for programs that use multicast. + * + * Linux as of 3.9 has a SO_REUSEPORT socket option but with semantics that + * are different from the BSDs: it _shares_ the port rather than steal it + * from the current listener. While useful, it's not something we can emulate + * on other platforms so we don't enable it. + */ +static int uv__set_reuse(int fd) { + int yes; + +#if defined(SO_REUSEPORT) && !defined(__linux__) + yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))) + return -errno; +#else + yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) + return -errno; +#endif + + return 0; +} + + static int uv__bind(uv_udp_t* handle, int domain, struct sockaddr* addr, @@ -293,6 +318,7 @@ static int uv__bind(uv_udp_t* handle, unsigned flags) { int saved_errno; int status; + int err; int yes; int fd; @@ -321,28 +347,12 @@ static int uv__bind(uv_udp_t* handle, } fd = handle->io_watcher.fd; - yes = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { - uv__set_sys_error(handle->loop, errno); + err = uv__set_reuse(fd); + if (err) { + uv__set_sys_error(handle->loop, -err); 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(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof yes) == -1) { - uv__set_sys_error(handle->loop, errno); - goto out; - } -#endif - if (flags & UV_UDP_IPV6ONLY) { #ifdef IPV6_V6ONLY yes = 1;