mirror of
https://github.com/libevent/libevent.git
synced 2025-01-09 00:56:20 +08:00
Accept SOCK_NONBLOCK/SOCK_CLOEXEC in type argument of socketpair (#1567)
Setting `SOCK_NONBLOCK` and `SOCK_CLOEXEC` in the `type` argument of `socketpair()` is widely supported across UNIX-like OS: Linux, *BSD, Solaris, etc., as is the `socket()`. This will conserve several extra system calls, we should use it where available. ### References - [socketpair(2) on Linux](https://man7.org/linux/man-pages/man2/socketpair.2.html#HISTORY) - [socketpair(2) on FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=socketpair&sektion=2#DESCRIPTION) - [socketpair(2) on DragonFly](https://man.dragonflybsd.org/?command=socketpair§ion=2) - [socketpair(2) on NetBSD](https://man.netbsd.org/socketpair.2#DESCRIPTION) - [socketpair(2) on OpenBSD](https://man.openbsd.org/socketpair.2) - [socketpair(3C) on Solaris](https://docs.oracle.com/cd/E88353_01/html/E37843/socketpair-3c.html) Changelog: - Set SOCK_NONBLOCK and SOCK_CLOEXEC in the type argument of socketpair - Avoid EPROTOTYPE on macOS and OpenBSD - Eliminate the warnings about unused variables - Add some comments
This commit is contained in:
parent
f2b3ce6b55
commit
e66df92cfc
306
evutil.c
306
evutil.c
@ -403,125 +403,6 @@ evutil_win_socketpair(int family, int type, int protocol,
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
evutil_socketpair(int family, int type, int protocol, evutil_socket_t fd[2])
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return evutil_win_socketpair(family, type, protocol, fd);
|
||||
#elif defined(EVENT__HAVE_SOCKETPAIR)
|
||||
return socketpair(family, type, protocol, fd);
|
||||
#else
|
||||
return evutil_ersatz_socketpair_(family, type, protocol, fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
evutil_ersatz_socketpair_(int family, int type, int protocol,
|
||||
evutil_socket_t fd[2])
|
||||
{
|
||||
/* This code is originally from Tor. Used with permission. */
|
||||
|
||||
/* This socketpair does not work when localhost is down. So
|
||||
* it's really not the same thing at all. But it's close enough
|
||||
* for now, and really, when localhost is down sometimes, we
|
||||
* have other problems too.
|
||||
*/
|
||||
#undef ERR
|
||||
#ifdef _WIN32
|
||||
#define ERR(e) WSA##e
|
||||
#else
|
||||
#define ERR(e) e
|
||||
#endif
|
||||
evutil_socket_t listener = -1;
|
||||
evutil_socket_t connector = -1;
|
||||
evutil_socket_t acceptor = -1;
|
||||
struct sockaddr_in listen_addr;
|
||||
struct sockaddr_in connect_addr;
|
||||
ev_socklen_t size;
|
||||
int saved_errno = -1;
|
||||
int family_test;
|
||||
|
||||
family_test = family != AF_INET;
|
||||
#ifdef AF_UNIX
|
||||
family_test = family_test && (family != AF_UNIX);
|
||||
#endif
|
||||
if (protocol || family_test) {
|
||||
EVUTIL_SET_SOCKET_ERROR(ERR(EAFNOSUPPORT));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!fd) {
|
||||
EVUTIL_SET_SOCKET_ERROR(ERR(EINVAL));
|
||||
return -1;
|
||||
}
|
||||
|
||||
listener = socket(AF_INET, type, 0);
|
||||
if (listener < 0)
|
||||
return -1;
|
||||
memset(&listen_addr, 0, sizeof(listen_addr));
|
||||
listen_addr.sin_family = AF_INET;
|
||||
listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
listen_addr.sin_port = 0; /* kernel chooses port. */
|
||||
if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr))
|
||||
== -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (listen(listener, 1) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
|
||||
connector = socket(AF_INET, type, 0);
|
||||
if (connector < 0)
|
||||
goto tidy_up_and_fail;
|
||||
|
||||
memset(&connect_addr, 0, sizeof(connect_addr));
|
||||
|
||||
/* We want to find out the port number to connect to. */
|
||||
size = sizeof(connect_addr);
|
||||
if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof (connect_addr))
|
||||
goto abort_tidy_up_and_fail;
|
||||
if (connect(connector, (struct sockaddr *) &connect_addr,
|
||||
sizeof(connect_addr)) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
|
||||
size = sizeof(listen_addr);
|
||||
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
|
||||
if (acceptor < 0)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof(listen_addr))
|
||||
goto abort_tidy_up_and_fail;
|
||||
/* Now check we are talking to ourself by matching port and host on the
|
||||
two sockets. */
|
||||
if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof (connect_addr)
|
||||
|| listen_addr.sin_family != connect_addr.sin_family
|
||||
|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
|
||||
|| listen_addr.sin_port != connect_addr.sin_port)
|
||||
goto abort_tidy_up_and_fail;
|
||||
evutil_closesocket(listener);
|
||||
fd[0] = connector;
|
||||
fd[1] = acceptor;
|
||||
|
||||
return 0;
|
||||
|
||||
abort_tidy_up_and_fail:
|
||||
saved_errno = ERR(ECONNABORTED);
|
||||
tidy_up_and_fail:
|
||||
if (saved_errno < 0)
|
||||
saved_errno = EVUTIL_SOCKET_ERROR();
|
||||
if (listener != -1)
|
||||
evutil_closesocket(listener);
|
||||
if (connector != -1)
|
||||
evutil_closesocket(connector);
|
||||
if (acceptor != -1)
|
||||
evutil_closesocket(acceptor);
|
||||
|
||||
EVUTIL_SET_SOCKET_ERROR(saved_errno);
|
||||
return -1;
|
||||
#undef ERR
|
||||
}
|
||||
|
||||
int
|
||||
evutil_make_socket_nonblocking(evutil_socket_t fd)
|
||||
{
|
||||
@ -2891,6 +2772,176 @@ evutil_socket_(int domain, int type, int protocol)
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
evutil_socketpair(int family, int type, int protocol, evutil_socket_t fd[2])
|
||||
{
|
||||
int ret = 0;
|
||||
int sock_type = type;
|
||||
(void) sock_type;
|
||||
/* SOCK_NONBLOCK and SOCK_CLOEXEC are UNIX-specific. Therefore, the predefined and
|
||||
* platform-independent macros EVUTIL_SOCK_NONBLOCK and EVUTIL_SOCK_CLOEXEC are used
|
||||
* in type argument as combination while SOCK_NONBLOCK and SOCK_CLOEXEC are used for
|
||||
* distinguishing platforms.
|
||||
*/
|
||||
#ifndef SOCK_NONBLOCK
|
||||
type &= ~EVUTIL_SOCK_NONBLOCK;
|
||||
#endif
|
||||
#ifndef SOCK_CLOEXEC
|
||||
type &= ~EVUTIL_SOCK_CLOEXEC;
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
ret = evutil_win_socketpair(family, type, protocol, fd);
|
||||
#elif defined(EVENT__HAVE_SOCKETPAIR)
|
||||
ret = socketpair(family, type, protocol, fd);
|
||||
#else
|
||||
ret = evutil_ersatz_socketpair_(family, type, protocol, fd);
|
||||
#endif
|
||||
if (ret)
|
||||
return ret;
|
||||
#ifndef SOCK_NONBLOCK
|
||||
if (sock_type & EVUTIL_SOCK_NONBLOCK) {
|
||||
if ((ret = evutil_fast_socket_nonblocking(fd[0]))) {
|
||||
evutil_closesocket(fd[0]);
|
||||
evutil_closesocket(fd[1]);
|
||||
return ret;
|
||||
}
|
||||
if ((ret = evutil_fast_socket_nonblocking(fd[1]))) {
|
||||
evutil_closesocket(fd[0]);
|
||||
evutil_closesocket(fd[1]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifndef SOCK_CLOEXEC
|
||||
if (sock_type & EVUTIL_SOCK_CLOEXEC) {
|
||||
if ((ret = evutil_fast_socket_closeonexec(fd[0]))) {
|
||||
evutil_closesocket(fd[0]);
|
||||
evutil_closesocket(fd[1]);
|
||||
return ret;
|
||||
}
|
||||
if ((ret = evutil_fast_socket_closeonexec(fd[1]))) {
|
||||
evutil_closesocket(fd[0]);
|
||||
evutil_closesocket(fd[1]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
evutil_ersatz_socketpair_(int family, int type, int protocol,
|
||||
evutil_socket_t fd[2])
|
||||
{
|
||||
/* This code is originally from Tor. Used with permission. */
|
||||
|
||||
/* This socketpair does not work when localhost is down. So
|
||||
* it's really not the same thing at all. But it's close enough
|
||||
* for now, and really, when localhost is down sometimes, we
|
||||
* have other problems too.
|
||||
*/
|
||||
#undef ERR
|
||||
#ifdef _WIN32
|
||||
#define ERR(e) WSA##e
|
||||
#else
|
||||
#define ERR(e) e
|
||||
#endif
|
||||
evutil_socket_t listener = -1;
|
||||
evutil_socket_t connector = -1;
|
||||
evutil_socket_t acceptor = -1;
|
||||
struct sockaddr_in listen_addr;
|
||||
struct sockaddr_in connect_addr;
|
||||
ev_socklen_t size;
|
||||
int saved_errno = -1;
|
||||
int family_test;
|
||||
|
||||
family_test = family != AF_INET;
|
||||
#ifdef AF_UNIX
|
||||
family_test = family_test && (family != AF_UNIX);
|
||||
#endif
|
||||
if (protocol || family_test) {
|
||||
EVUTIL_SET_SOCKET_ERROR(ERR(EAFNOSUPPORT));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!fd) {
|
||||
EVUTIL_SET_SOCKET_ERROR(ERR(EINVAL));
|
||||
return -1;
|
||||
}
|
||||
|
||||
listener = socket(AF_INET, type, 0);
|
||||
if (listener < 0)
|
||||
return -1;
|
||||
memset(&listen_addr, 0, sizeof(listen_addr));
|
||||
listen_addr.sin_family = AF_INET;
|
||||
listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
listen_addr.sin_port = 0; /* kernel chooses port. */
|
||||
if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr))
|
||||
== -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (listen(listener, 1) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
|
||||
connector = socket(AF_INET, type, 0);
|
||||
if (connector < 0)
|
||||
goto tidy_up_and_fail;
|
||||
|
||||
memset(&connect_addr, 0, sizeof(connect_addr));
|
||||
|
||||
/* We want to find out the port number to connect to. */
|
||||
size = sizeof(connect_addr);
|
||||
if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof(connect_addr))
|
||||
goto abort_tidy_up_and_fail;
|
||||
if (connect(connector, (struct sockaddr *) &connect_addr,
|
||||
sizeof(connect_addr)) == -1) {
|
||||
/* It's OK for a non-blocking socket to get an EINPROGRESS from connect(). */
|
||||
int err = evutil_socket_geterror(connector);
|
||||
if (!(EVUTIL_ERR_CONNECT_RETRIABLE(err) && type & EVUTIL_SOCK_NONBLOCK))
|
||||
goto tidy_up_and_fail;
|
||||
}
|
||||
|
||||
size = sizeof(listen_addr);
|
||||
do {
|
||||
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
|
||||
} while(acceptor < 0 && EVUTIL_ERR_ACCEPT_RETRIABLE(errno) && type & EVUTIL_SOCK_NONBLOCK);
|
||||
if (acceptor < 0)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof(listen_addr))
|
||||
goto abort_tidy_up_and_fail;
|
||||
/* Now check we are talking to ourself by matching port and host on the
|
||||
two sockets. */
|
||||
if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)
|
||||
goto tidy_up_and_fail;
|
||||
if (size != sizeof (connect_addr)
|
||||
|| listen_addr.sin_family != connect_addr.sin_family
|
||||
|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
|
||||
|| listen_addr.sin_port != connect_addr.sin_port)
|
||||
goto abort_tidy_up_and_fail;
|
||||
evutil_closesocket(listener);
|
||||
fd[0] = connector;
|
||||
fd[1] = acceptor;
|
||||
|
||||
return 0;
|
||||
|
||||
abort_tidy_up_and_fail:
|
||||
saved_errno = ERR(ECONNABORTED);
|
||||
tidy_up_and_fail:
|
||||
if (saved_errno < 0)
|
||||
saved_errno = EVUTIL_SOCKET_ERROR();
|
||||
if (listener != -1)
|
||||
evutil_closesocket(listener);
|
||||
if (connector != -1)
|
||||
evutil_closesocket(connector);
|
||||
if (acceptor != -1)
|
||||
evutil_closesocket(acceptor);
|
||||
|
||||
EVUTIL_SET_SOCKET_ERROR(saved_errno);
|
||||
return -1;
|
||||
#undef ERR
|
||||
}
|
||||
|
||||
/* Internal wrapper around 'accept' or 'accept4' to provide Linux-style
|
||||
* support for syscall-saving methods where available.
|
||||
*
|
||||
@ -2976,20 +3027,11 @@ evutil_make_internal_pipe_(evutil_socket_t fd[2])
|
||||
#else
|
||||
#define LOCAL_SOCKETPAIR_AF AF_UNIX
|
||||
#endif
|
||||
if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, fd) == 0) {
|
||||
if (evutil_fast_socket_nonblocking(fd[0]) < 0 ||
|
||||
evutil_fast_socket_nonblocking(fd[1]) < 0 ||
|
||||
evutil_fast_socket_closeonexec(fd[0]) < 0 ||
|
||||
evutil_fast_socket_closeonexec(fd[1]) < 0) {
|
||||
evutil_closesocket(fd[0]);
|
||||
evutil_closesocket(fd[1]);
|
||||
fd[0] = fd[1] = -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM|EVUTIL_SOCK_CLOEXEC|EVUTIL_SOCK_NONBLOCK, 0, fd)) {
|
||||
fd[0] = fd[1] = -1;
|
||||
return -1;
|
||||
}
|
||||
fd[0] = fd[1] = -1;
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wrapper around eventfd on systems that provide it. Unlike the system
|
||||
|
@ -377,9 +377,14 @@ int evutil_gettime_monotonic(struct evutil_monotonic_timer *timer,
|
||||
|
||||
/** Create two new sockets that are connected to each other.
|
||||
|
||||
On Unix, this simply calls socketpair(). On Windows, it uses the
|
||||
loopback network interface on 127.0.0.1, and only
|
||||
AF_INET,SOCK_STREAM are supported.
|
||||
On Unix, this simply calls socketpair() and creates an unnamed pair of connected sockets
|
||||
in the specified domain, of the specified type, and using the optionally specified protocol.
|
||||
|
||||
On Windows, it will try to use the AF_UNIX to create unix socket pair if available, otherwise
|
||||
it instead uses AF_INET to create socket pair, binding the loopback network interface 127.0.0.1.
|
||||
|
||||
Including the bitwise OR of the EVUTIL_SOCK_NONBLOCK and/or EVUTIL_SOCK_CLOEXEC
|
||||
in the type argument will apply to both file descriptors.
|
||||
|
||||
(This may fail on some Windows hosts where firewall software has cleverly
|
||||
decided to keep 127.0.0.1 from talking to itself.)
|
||||
@ -387,7 +392,7 @@ int evutil_gettime_monotonic(struct evutil_monotonic_timer *timer,
|
||||
Parameters and return values are as for socketpair()
|
||||
*/
|
||||
EVENT2_EXPORT_SYMBOL
|
||||
int evutil_socketpair(int d, int type, int protocol, evutil_socket_t sv[2]);
|
||||
int evutil_socketpair(int domain, int type, int protocol, evutil_socket_t sv[2]);
|
||||
/** Do platform-specific operations as needed to make a socket nonblocking.
|
||||
|
||||
@param sock The socket to make nonblocking
|
||||
|
@ -1237,14 +1237,12 @@ test_evbuffer_add_file(void *ptr)
|
||||
#if defined(EVENT__HAVE_SENDFILE) && defined(__sun__) && defined(__svr4__)
|
||||
/* We need to use a pair of AF_INET sockets, since Solaris
|
||||
doesn't support sendfile() over AF_UNIX. */
|
||||
if (evutil_ersatz_socketpair_(AF_INET, SOCK_STREAM, 0, pair) == -1)
|
||||
if (evutil_ersatz_socketpair_(AF_INET, SOCK_STREAM|EVUTIL_SOCK_NONBLOCK, 0, pair) == -1)
|
||||
tt_abort_msg("ersatz_socketpair failed");
|
||||
#else
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1)
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM|EVUTIL_SOCK_NONBLOCK, 0, pair) == -1)
|
||||
tt_abort_msg("socketpair failed");
|
||||
#endif
|
||||
evutil_make_socket_nonblocking(pair[0]);
|
||||
evutil_make_socket_nonblocking(pair[1]);
|
||||
|
||||
tt_assert(fd != -1);
|
||||
|
||||
@ -2665,7 +2663,7 @@ test_evbuffer_freeze(void *ptr)
|
||||
FREEZE_EQ(r, 0, -1);
|
||||
len = strlen(tmpfilecontent);
|
||||
fd = regress_make_tmpfile(tmpfilecontent, len, &tmpfilename);
|
||||
/* On Windows, if TMP environment variable is corrupted, we may not be
|
||||
/* On Windows, if TMP environment variable is corrupted, we may not be
|
||||
* able create temporary file, just skip it */
|
||||
if (fd < 0)
|
||||
tt_skip();
|
||||
|
@ -286,20 +286,10 @@ basic_test_setup(const struct testcase_t *testcase)
|
||||
}
|
||||
|
||||
if (testcase->flags & TT_NEED_SOCKETPAIR) {
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, spair) == -1) {
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM|EVUTIL_SOCK_NONBLOCK, 0, spair) == -1) {
|
||||
fprintf(stderr, "%s: socketpair\n", __func__);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (evutil_make_socket_nonblocking(spair[0]) == -1) {
|
||||
fprintf(stderr, "fcntl(O_NONBLOCK)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (evutil_make_socket_nonblocking(spair[1]) == -1) {
|
||||
fprintf(stderr, "fcntl(O_NONBLOCK)");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (testcase->flags & TT_NEED_BASE) {
|
||||
if (testcase->flags & TT_LEGACY)
|
||||
|
@ -49,6 +49,8 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "util-internal.h"
|
||||
|
||||
#include "event2/util.h"
|
||||
#include "event2/event.h"
|
||||
#include "event2/event_compat.h"
|
||||
@ -286,13 +288,10 @@ test_bufferevent_zlib(void *arg)
|
||||
infilter_calls = outfilter_calls = readcb_finished = writecb_finished
|
||||
= errorcb_invoked = 0;
|
||||
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
|
||||
if (evutil_socketpair(AF_UNIX, SOCK_STREAM|EVUTIL_SOCK_NONBLOCK, 0, pair) == -1) {
|
||||
tt_abort_perror("socketpair");
|
||||
}
|
||||
|
||||
evutil_make_socket_nonblocking(pair[0]);
|
||||
evutil_make_socket_nonblocking(pair[1]);
|
||||
|
||||
bev1 = bufferevent_socket_new(NULL, pair[0], 0);
|
||||
bev2 = bufferevent_socket_new(NULL, pair[1], 0);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user