From 17452cd0e2327c9d929215bbeabfd79d60f3ca4c Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sat, 24 Aug 2013 15:09:57 +0200 Subject: [PATCH] linux: fix setsockopt(SO_REUSEPORT) error handling Linux as of 3.9 has a SO_REUSEPORT option that is similar but not identical to its BSD counterpart. On the BSDs, it turns on SO_REUSEADDR _and_ makes it possible to share the address and port across processes. On Linux, it "merely" enables fair load distribution - port sharing still requires that you set SO_REUSEADDR. Fair distribution is a desirable trait but not an essential one. We don't know in advance whether the kernel actually supports SO_REUSEPORT so don't treat EINVAL or ENOPROTOOPT as errors. As an aside, on the BSDs we now omit the setsockopt(SO_REUSEADDR) system call because it's implied by SO_REUSEPORT. Fixes #870. --- src/unix/udp.c | 91 +++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/src/unix/udp.c b/src/unix/udp.c index 8c3fc9cd8..4a545d72a 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -280,6 +280,53 @@ static void uv__udp_sendmsg(uv_loop_t* loop, } +/* On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but it also lets you share + * the address and port with other processes. + * + * Linux as of 3.9 has a SO_REUSEPORT socket option but with semantics that + * are different from the BSDs. The address:port sharing part is taken care + * of by SO_REUSEADDR while SO_REUSEPORT enables fair load distribution. (If + * you wonder why you need to explicitly enable that, well, it's complicated.) + * + * Because we cannot rely on SO_REUSEPORT being available on Linux, it's not + * considered an error when the setsockopt() system call fails. Worst case, + * the program has sub-optimal load distribution characteristics but should + * otherwise run fine. + */ +static int uv__set_reuse(int fd) { + int yes; +#if defined(__linux__) + static int no_so_reuseport; + + if (no_so_reuseport) + goto no_so_reuseport; + + yes = 1; + if (setsockopt(fd, SOL_SOCKET, 15 /* SO_REUSEPORT */, &yes, sizeof(yes))) { + if (errno != EINVAL && errno != ENOPROTOOPT) + return -errno; + no_so_reuseport = 1; + } + +no_so_reuseport: + + yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) + return -errno; +#elif defined(SO_REUSEPORT) + 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, @@ -308,27 +355,9 @@ static int uv__bind(uv_udp_t* handle, handle->io_watcher.fd = fd; } - yes = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { - err = -errno; + err = uv__set_reuse(fd); + if (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) { - err = -errno; - goto out; - } -#endif if (flags & UV_UDP_IPV6ONLY) { #ifdef IPV6_V6ONLY @@ -464,29 +493,15 @@ 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 yes; + int err; /* Check for already active socket. */ if (handle->io_watcher.fd != -1) return -EALREADY; /* FIXME(bnoordhuis) Should be -EBUSY. */ - yes = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes)) - return -errno; - - /* 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)) - return -errno; -#endif + err = uv__set_reuse(sock); + if (err) + return err; handle->io_watcher.fd = sock; return 0;