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:
parent
e47e6b2afa
commit
17452cd0e2
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user