curl/lib/socketpair.c
Viktor Szakats 879a1514c3
socket: introduce SOCK_EAGAIN() and use it
To contain the logic of checking for both `EWOULDBLOCK` and/or `EAGAIN`
depending on platform/availability. Also to avoid checking for both if
they mapp to the same value, and to avoid PP guards around use.

This also ensures `EAGAIN` is consistently not checked on Windows, where
headers defined it, but `SOCKERRNO` never returns it, because curl maps
it to `WSAGetLastError()`.

If they map to the same value, checking them both in an `if` expression
trips GCC warning `-Wlogical-op` (the same way it triggers duplicate
case value error in `switch`).

Also:
- replace two `switch()` statements with the new macro.
- tests/server/sws: make two outliers use the new macro that were only
  checking for `EWOULDBLOCK` before this patch, in `connect_to()`.
- move variables to the left-side of expressions, where missing.
- rustls: use a variant of this macro that uses raw `EWOULDBLOCK`.
  Tried tracing it back to the origins, but I couldn't figure out if
  this is working as expected on all supported Windows versions in
  Rust. It seems to be using `GetLastError()`, according to
  https://docs.rs/system_error/0.2.0/system_error/, which would be
  probably incorrect.

Notes:
- it's probably a good idea to assign `SOCKERRNO` to a variable before
  passing it to this macro.

Cherry-picked from #21893

Closes #21992
2026-06-12 23:27:23 +02:00

364 lines
8.5 KiB
C

/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "curl_setup.h"
#include "socketpair.h"
#include "urldata.h"
#include "rand.h"
#include "curlx/nonblock.h"
#ifndef CURL_DISABLE_SOCKETPAIR
/* choose implementation */
#ifdef USE_EVENTFD
#include <sys/eventfd.h>
static int wakeup_eventfd(curl_socket_t socks[2], bool nonblocking)
{
int efd = eventfd(0, nonblocking ? EFD_CLOEXEC | EFD_NONBLOCK : EFD_CLOEXEC);
if(efd == -1) {
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
socks[0] = socks[1] = efd;
return 0;
}
#elif defined(HAVE_PIPE)
#ifdef HAVE_FCNTL
#include <fcntl.h>
#endif
static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking)
{
#ifdef HAVE_PIPE2
int flags = nonblocking ? O_NONBLOCK | O_CLOEXEC : O_CLOEXEC;
if(pipe2(socks, flags))
return -1;
#else
if(pipe(socks))
return -1;
#ifdef HAVE_FCNTL
if(fcntl(socks[0], F_SETFD, FD_CLOEXEC) ||
fcntl(socks[1], F_SETFD, FD_CLOEXEC)) {
sclose(socks[0]);
sclose(socks[1]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
#endif
if(nonblocking) {
if(curlx_nonblock(socks[0], TRUE) < 0 ||
curlx_nonblock(socks[1], TRUE) < 0) {
sclose(socks[0]);
sclose(socks[1]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
}
#endif
return 0;
}
#elif defined(HAVE_SOCKETPAIR) /* !USE_EVENTFD && !HAVE_PIPE */
#ifndef USE_UNIX_SOCKETS
#error "unsupported Unix domain and socketpair build combo"
#endif
static int wakeup_socketpair(curl_socket_t socks[2], bool nonblocking)
{
int type = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
type |= SOCK_CLOEXEC;
#endif
#ifdef SOCK_NONBLOCK
if(nonblocking)
type |= SOCK_NONBLOCK;
#endif
if(CURL_SOCKETPAIR(AF_UNIX, type, 0, socks))
return -1;
#ifndef SOCK_NONBLOCK
if(nonblocking) {
if(curlx_nonblock(socks[0], TRUE) < 0 ||
curlx_nonblock(socks[1], TRUE) < 0) {
sclose(socks[0]);
sclose(socks[1]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
}
#endif
#ifdef USE_SO_NOSIGPIPE
if(Curl_sock_nosigpipe(socks[1]) < 0) {
sclose(socks[0]);
sclose(socks[1]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
#endif /* USE_SO_NOSIGPIPE */
return 0;
}
#else /* !USE_EVENTFD && !HAVE_PIPE && !HAVE_SOCKETPAIR */
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h> /* for IPPROTO_TCP */
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifndef INADDR_LOOPBACK
#define INADDR_LOOPBACK 0x7f000001
#endif
#include "select.h" /* for Curl_poll */
static int wakeup_inet(curl_socket_t socks[2], bool nonblocking)
{
union {
struct sockaddr_in inaddr;
struct sockaddr addr;
} a;
curl_socket_t listener;
curl_socklen_t addrlen = sizeof(a.inaddr);
int reuse = 1;
struct pollfd pfd[1];
listener = CURL_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listener == CURL_SOCKET_BAD)
return -1;
memset(&a, 0, sizeof(a));
a.inaddr.sin_family = AF_INET;
a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.inaddr.sin_port = 0;
socks[0] = socks[1] = CURL_SOCKET_BAD;
#if defined(_WIN32) || defined(__CYGWIN__)
/* do not set SO_REUSEADDR on Windows */
(void)reuse;
#ifdef SO_EXCLUSIVEADDRUSE
{
int exclusive = 1;
if(setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
(char *)&exclusive, (curl_socklen_t)sizeof(exclusive)) == -1)
goto error;
}
#endif
#else
if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
(char *)&reuse, (curl_socklen_t)sizeof(reuse)) == -1)
goto error;
#endif
if(bind(listener, &a.addr, sizeof(a.inaddr)) == -1)
goto error;
if(getsockname(listener, &a.addr, &addrlen) == -1 ||
addrlen < (int)sizeof(a.inaddr))
goto error;
if(listen(listener, 1) == -1)
goto error;
socks[0] = CURL_SOCKET(AF_INET, SOCK_STREAM, 0);
if(socks[0] == CURL_SOCKET_BAD)
goto error;
if(connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1)
goto error;
/* use non-blocking accept to make sure we do not block forever */
if(curlx_nonblock(listener, TRUE) < 0)
goto error;
pfd[0].fd = listener;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
(void)Curl_poll(pfd, 1, 1000); /* one second */
socks[1] = CURL_ACCEPT(listener, NULL, NULL);
if(socks[1] == CURL_SOCKET_BAD)
goto error;
else {
struct curltime start = curlx_now();
char rnd[9];
char check[sizeof(rnd)];
char *p = &check[0];
size_t s = sizeof(check);
if(Curl_rand(NULL, (unsigned char *)rnd, sizeof(rnd)))
goto error;
/* write data to the socket */
swrite(socks[0], rnd, sizeof(rnd));
/* verify that we read the correct data */
do {
ssize_t nread;
pfd[0].fd = socks[1];
pfd[0].events = POLLIN;
pfd[0].revents = 0;
(void)Curl_poll(pfd, 1, 1000); /* one second */
nread = sread(socks[1], p, s);
if(nread == -1) {
int sockerr = SOCKERRNO;
/* Do not block forever */
if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000))
goto error;
if(SOCK_EAGAIN(sockerr)
#ifndef USE_WINSOCK
|| (sockerr == SOCKEINTR) || (sockerr == SOCKEINPROGRESS)
#endif
) {
continue;
}
goto error;
}
s -= nread;
if(s) {
p += nread;
continue;
}
if(memcmp(rnd, check, sizeof(check)))
goto error;
break;
} while(1);
}
if(nonblocking)
if(curlx_nonblock(socks[0], TRUE) < 0 ||
curlx_nonblock(socks[1], TRUE) < 0)
goto error;
#ifdef USE_SO_NOSIGPIPE
if(Curl_sock_nosigpipe(socks[1]) < 0)
goto error;
#endif
sclose(listener);
return 0;
error:
sclose(listener);
sclose(socks[0]);
sclose(socks[1]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
return -1;
}
#endif /* choose implementation */
int Curl_wakeup_init(curl_socket_t socks[2], bool nonblocking)
{
#ifdef USE_EVENTFD
return wakeup_eventfd(socks, nonblocking);
#elif defined(HAVE_PIPE)
return wakeup_pipe(socks, nonblocking);
#elif defined(HAVE_SOCKETPAIR)
return wakeup_socketpair(socks, nonblocking);
#else
return wakeup_inet(socks, nonblocking);
#endif
}
#if defined(USE_EVENTFD) || defined(HAVE_PIPE)
#define wakeup_write write
#define wakeup_read read
#define wakeup_close close
#else /* !USE_EVENTFD && !HAVE_PIPE */
#define wakeup_write swrite
#define wakeup_read sread
#define wakeup_close sclose
#endif
int Curl_wakeup_signal(curl_socket_t socks[2])
{
int err = 0;
#ifdef USE_EVENTFD
const uint64_t buf[1] = { 1 };
#else
const char buf[1] = { 1 };
#endif
while(1) {
err = 0;
if(wakeup_write(socks[1], buf, sizeof(buf)) < 0) {
err = SOCKERRNO;
#ifndef USE_WINSOCK
if(err == SOCKEINTR)
continue;
#endif
if(SOCK_EAGAIN(err))
err = 0; /* wakeup is already ongoing */
}
break;
}
return err;
}
CURLcode Curl_wakeup_consume(curl_socket_t socks[2], bool all)
{
char buf[64];
ssize_t rc;
CURLcode result = CURLE_OK;
do {
rc = wakeup_read(socks[0], buf, sizeof(buf));
if(!rc)
break;
else if(rc < 0) {
int sockerr = SOCKERRNO;
#ifndef USE_WINSOCK
if(sockerr == SOCKEINTR)
continue;
#endif
if(SOCK_EAGAIN(sockerr))
break;
result = CURLE_READ_ERROR;
break;
}
} while(all);
return result;
}
void Curl_wakeup_destroy(curl_socket_t socks[2])
{
#ifndef USE_EVENTFD
if(socks[1] != CURL_SOCKET_BAD)
wakeup_close(socks[1]);
#endif
if(socks[0] != CURL_SOCKET_BAD)
wakeup_close(socks[0]);
socks[0] = socks[1] = CURL_SOCKET_BAD;
}
#endif /* !CURL_DISABLE_SOCKETPAIR */