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.
This commit is contained in:
Ben Noordhuis 2013-08-24 15:09:57 +02:00
parent e47e6b2afa
commit 17452cd0e2

View File

@ -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;