summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/cf-socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libcurl/src/cf-socket.c')
-rw-r--r--libs/libcurl/src/cf-socket.c586
1 files changed, 383 insertions, 203 deletions
diff --git a/libs/libcurl/src/cf-socket.c b/libs/libcurl/src/cf-socket.c
index 713499a2e8..bd3acb7e8b 100644
--- a/libs/libcurl/src/cf-socket.c
+++ b/libs/libcurl/src/cf-socket.c
@@ -35,6 +35,9 @@
#elif defined(HAVE_NETINET_TCP_H)
#include <netinet/tcp.h>
#endif
+#ifdef HAVE_NETINET_UDP_H
+#include <netinet/udp.h>
+#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
@@ -53,6 +56,11 @@
#include <inet.h>
#endif
+#ifdef __DragonFly__
+/* Required for __DragonFly_version */
+#include <sys/param.h>
+#endif
+
#include "urldata.h"
#include "bufq.h"
#include "sendf.h"
@@ -73,6 +81,7 @@
#include "multihandle.h"
#include "rand.h"
#include "share.h"
+#include "strdup.h"
#include "version_win32.h"
/* The last 3 #include files should be in this order */
@@ -137,8 +146,12 @@ static void nosigpipe(struct Curl_easy *data,
#define nosigpipe(x,y) Curl_nop_stmt
#endif
-#if defined(__DragonFly__) || defined(USE_WINSOCK)
-/* DragonFlyBSD and Windows use millisecond units */
+#if defined(USE_WINSOCK) || \
+ (defined(__sun) && !defined(TCP_KEEPIDLE)) || \
+ (defined(__DragonFly__) && __DragonFly_version < 500702) || \
+ (defined(_WIN32) && !defined(TCP_KEEPIDLE))
+/* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299
+ * use millisecond units. */
#define KEEPALIVE_FACTOR(x) (x *= 1000)
#else
#define KEEPALIVE_FACTOR(x)
@@ -168,23 +181,50 @@ tcpkeepalive(struct Curl_easy *data,
sockfd, SOCKERRNO);
}
else {
-#if defined(SIO_KEEPALIVE_VALS)
+#if defined(SIO_KEEPALIVE_VALS) /* Windows */
+/* Windows 10, version 1709 (10.0.16299) and later versions */
+#if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
+ optval = curlx_sltosi(data->set.tcp_keepidle);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
+ (const char *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPIDLE on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
+ optval = curlx_sltosi(data->set.tcp_keepintvl);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
+ (const char *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPINTVL on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
+ optval = curlx_sltosi(data->set.tcp_keepcnt);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT,
+ (const char *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPCNT on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
+#else /* Windows < 10.0.16299 */
struct tcp_keepalive vals;
DWORD dummy;
vals.onoff = 1;
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
- vals.keepalivetime = optval;
+ vals.keepalivetime = (u_long)optval;
optval = curlx_sltosi(data->set.tcp_keepintvl);
KEEPALIVE_FACTOR(optval);
- vals.keepaliveinterval = optval;
+ vals.keepaliveinterval = (u_long)optval;
if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals),
NULL, 0, &dummy, NULL, NULL) != 0) {
infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd "
"%" CURL_FORMAT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
-#else
+#endif
+#else /* !Windows */
#ifdef TCP_KEEPIDLE
optval = curlx_sltosi(data->set.tcp_keepidle);
KEEPALIVE_FACTOR(optval);
@@ -204,6 +244,16 @@ tcpkeepalive(struct Curl_easy *data,
"%" CURL_FORMAT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
+#elif defined(TCP_KEEPALIVE_THRESHOLD)
+ /* Solaris <11.4 style */
+ optval = curlx_sltosi(data->set.tcp_keepidle);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
#endif
#ifdef TCP_KEEPINTVL
optval = curlx_sltosi(data->set.tcp_keepintvl);
@@ -214,6 +264,36 @@ tcpkeepalive(struct Curl_easy *data,
"%" CURL_FORMAT_SOCKET_T ": errno %d",
sockfd, SOCKERRNO);
}
+#elif defined(TCP_KEEPALIVE_ABORT_THRESHOLD)
+ /* Solaris <11.4 style */
+ /* TCP_KEEPALIVE_ABORT_THRESHOLD should equal to
+ * TCP_KEEPCNT * TCP_KEEPINTVL on other platforms.
+ * The default value of TCP_KEEPCNT is 9 on Linux,
+ * 8 on *BSD/macOS, 5 or 10 on Windows. We use the
+ * default config for Solaris <11.4 because there is
+ * no default value for TCP_KEEPCNT on Solaris 11.4.
+ *
+ * Note that the consequent probes will not be sent
+ * at equal intervals on Solaris, but will be sent
+ * using the exponential backoff algorithm. */
+ optval = curlx_sltosi(data->set.tcp_keepcnt) *
+ curlx_sltosi(data->set.tcp_keepintvl);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
+#endif
+#ifdef TCP_KEEPCNT
+ optval = curlx_sltosi(data->set.tcp_keepcnt);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPCNT on fd "
+ "%" CURL_FORMAT_SOCKET_T ": errno %d",
+ sockfd, SOCKERRNO);
+ }
#endif
#endif
}
@@ -249,7 +329,7 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
dest->protocol = IPPROTO_UDP;
break;
}
- dest->addrlen = ai->ai_addrlen;
+ dest->addrlen = (unsigned int)ai->ai_addrlen;
if(dest->addrlen > sizeof(struct Curl_sockaddr_storage))
dest->addrlen = sizeof(struct Curl_sockaddr_storage);
@@ -314,7 +394,7 @@ CURLcode Curl_socket_open(struct Curl_easy *data,
struct Curl_sockaddr_ex dummy;
if(!addr)
- /* if the caller doesn't want info back, use a local temp copy */
+ /* if the caller does not want info back, use a local temp copy */
addr = &dummy;
Curl_sock_assign_addr(addr, ai, transport);
@@ -363,14 +443,14 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
Buffer Size
The problem described in this knowledge-base is applied only to pre-Vista
- Windows. Following function trying to detect OS version and skips
+ Windows. Following function trying to detect OS version and skips
SO_SNDBUF adjustment for Windows Vista and above.
*/
#define DETECT_OS_NONE 0
#define DETECT_OS_PREVISTA 1
#define DETECT_OS_VISTA_OR_LATER 2
-void Curl_sndbufset(curl_socket_t sockfd)
+void Curl_sndbuf_init(curl_socket_t sockfd)
{
int val = CURL_MAX_WRITE_SIZE + 32;
int curval = 0;
@@ -395,7 +475,83 @@ void Curl_sndbufset(curl_socket_t sockfd)
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
}
-#endif
+#endif /* USE_WINSOCK */
+
+/*
+ * Curl_parse_interface()
+ *
+ * This is used to parse interface argument in the following formats.
+ * In all the examples, `host` can be an IP address or a hostname.
+ *
+ * <iface_or_host> - can be either an interface name or a host.
+ * if!<iface> - interface name.
+ * host!<host> - hostname.
+ * ifhost!<iface>!<host> - interface name and hostname.
+ *
+ * Parameters:
+ *
+ * input [in] - input string.
+ * len [in] - length of the input string.
+ * dev [in/out] - address where a pointer to newly allocated memory
+ * holding the interface-or-host will be stored upon
+ * completion.
+ * iface [in/out] - address where a pointer to newly allocated memory
+ * holding the interface will be stored upon completion.
+ * host [in/out] - address where a pointer to newly allocated memory
+ * holding the host will be stored upon completion.
+ *
+ * Returns CURLE_OK on success.
+ */
+CURLcode Curl_parse_interface(const char *input, size_t len,
+ char **dev, char **iface, char **host)
+{
+ static const char if_prefix[] = "if!";
+ static const char host_prefix[] = "host!";
+ static const char if_host_prefix[] = "ifhost!";
+
+ DEBUGASSERT(dev);
+ DEBUGASSERT(iface);
+ DEBUGASSERT(host);
+
+ if(strncmp(if_prefix, input, strlen(if_prefix)) == 0) {
+ input += strlen(if_prefix);
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *iface = Curl_memdup0(input, len - strlen(if_prefix));
+ return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+ }
+ if(strncmp(host_prefix, input, strlen(host_prefix)) == 0) {
+ input += strlen(host_prefix);
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *host = Curl_memdup0(input, len - strlen(host_prefix));
+ return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+ }
+ if(strncmp(if_host_prefix, input, strlen(if_host_prefix)) == 0) {
+ const char *host_part;
+ input += strlen(if_host_prefix);
+ len -= strlen(if_host_prefix);
+ host_part = memchr(input, '!', len);
+ if(!host_part || !*(host_part + 1))
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *iface = Curl_memdup0(input, host_part - input);
+ if(!*iface)
+ return CURLE_OUT_OF_MEMORY;
+ ++host_part;
+ *host = Curl_memdup0(host_part, len - (host_part - input));
+ if(!*host) {
+ free(*iface);
+ *iface = NULL;
+ return CURLE_OUT_OF_MEMORY;
+ }
+ return CURLE_OK;
+ }
+
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *dev = Curl_memdup0(input, len);
+ return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+}
#ifndef CURL_DISABLE_BINDLOCAL
static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
@@ -415,6 +571,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
/* how many port numbers to try to bind to, increasing one at a time */
int portnum = data->set.localportrange;
const char *dev = data->set.str[STRING_DEVICE];
+ const char *iface_input = data->set.str[STRING_INTERFACE];
+ const char *host_input = data->set.str[STRING_BINDHOST];
+ const char *iface = iface_input ? iface_input : dev;
+ const char *host = host_input ? host_input : dev;
int error;
#ifdef IP_BIND_ADDRESS_NO_PORT
int on = 1;
@@ -426,83 +586,77 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
/*************************************************************
* Select device to bind socket to
*************************************************************/
- if(!dev && !port)
+ if(!iface && !host && !port)
/* no local kind of binding was requested */
return CURLE_OK;
memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
- if(dev && (strlen(dev)<255) ) {
+ if(iface && (strlen(iface)<255) ) {
char myhost[256] = "";
int done = 0; /* -1 for error, 1 for address found */
- bool is_interface = FALSE;
- bool is_host = FALSE;
- static const char *if_prefix = "if!";
- static const char *host_prefix = "host!";
-
- if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
- dev += strlen(if_prefix);
- is_interface = TRUE;
- }
- else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
- dev += strlen(host_prefix);
- is_host = TRUE;
- }
+ if2ip_result_t if2ip_result = IF2IP_NOT_FOUND;
/* interface */
- if(!is_host) {
#ifdef SO_BINDTODEVICE
- /*
- * This binds the local socket to a particular interface. This will
- * force even requests to other local interfaces to go out the external
- * interface. Only bind to the interface when specified as interface,
- * not just as a hostname or ip address.
- *
- * The interface might be a VRF, eg: vrf-blue, which means it cannot be
- * converted to an IP address and would fail Curl_if2ip. Simply try to
- * use it straight away.
- */
- if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
- dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
- /* This is often "errno 1, error: Operation not permitted" if you're
- * not running as root or another suitable privileged user. If it
- * succeeds it means the parameter was a valid interface and not an IP
- * address. Return immediately.
- */
- infof(data, "socket successfully bound to interface '%s'", dev);
+ /*
+ * This binds the local socket to a particular interface. This will
+ * force even requests to other local interfaces to go out the external
+ * interface. Only bind to the interface when specified as interface,
+ * not just as a hostname or ip address.
+ *
+ * The interface might be a VRF, eg: vrf-blue, which means it cannot be
+ * converted to an IP address and would fail Curl_if2ip. Simply try to
+ * use it straight away.
+ */
+ if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+ iface, (curl_socklen_t)strlen(iface) + 1) == 0) {
+ /* This is often "errno 1, error: Operation not permitted" if you are
+ * not running as root or another suitable privileged user. If it
+ * succeeds it means the parameter was a valid interface and not an IP
+ * address. Return immediately.
+ */
+ if(!host_input) {
+ infof(data, "socket successfully bound to interface '%s'", iface);
return CURLE_OK;
}
+ }
#endif
-
- switch(Curl_if2ip(af,
+ if(!host_input) {
+ /* Discover IP from input device, then bind to it */
+ if2ip_result = Curl_if2ip(af,
#ifdef USE_IPV6
- scope, conn->scope_id,
-#endif
- dev, myhost, sizeof(myhost))) {
- case IF2IP_NOT_FOUND:
- if(is_interface) {
- /* Do not fall back to treating it as a host name */
- failf(data, "Couldn't bind to interface '%s'", dev);
- return CURLE_INTERFACE_FAILED;
- }
- break;
- case IF2IP_AF_NOT_SUPPORTED:
- /* Signal the caller to try another address family if available */
- return CURLE_UNSUPPORTED_PROTOCOL;
- case IF2IP_FOUND:
- is_interface = TRUE;
- /*
- * We now have the numerical IP address in the 'myhost' buffer
- */
- infof(data, "Local Interface %s is ip %s using address family %i",
- dev, myhost, af);
- done = 1;
- break;
- }
+ scope, conn->scope_id,
+#endif
+ iface, myhost, sizeof(myhost));
+ }
+ switch(if2ip_result) {
+ case IF2IP_NOT_FOUND:
+ if(iface_input && !host_input) {
+ /* Do not fall back to treating it as a hostname */
+ char buffer[STRERROR_LEN];
+ data->state.os_errno = error = SOCKERRNO;
+ failf(data, "Couldn't bind to interface '%s' with errno %d: %s",
+ iface, error, Curl_strerror(error, buffer, sizeof(buffer)));
+ return CURLE_INTERFACE_FAILED;
+ }
+ break;
+ case IF2IP_AF_NOT_SUPPORTED:
+ /* Signal the caller to try another address family if available */
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ case IF2IP_FOUND:
+ /*
+ * We now have the numerical IP address in the 'myhost' buffer
+ */
+ host = myhost;
+ infof(data, "Local Interface %s is ip %s using address family %i",
+ iface, host, af);
+ done = 1;
+ break;
}
- if(!is_interface) {
+ if(!iface_input || host_input) {
/*
- * This was not an interface, resolve the name as a host name
+ * This was not an interface, resolve the name as a hostname
* or IP number
*
* Temporarily force name resolution to use only the address type
@@ -519,7 +673,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
conn->ip_version = CURL_IPRESOLVE_V6;
#endif
- rc = Curl_resolv(data, dev, 80, FALSE, &h);
+ rc = Curl_resolv(data, host, 80, FALSE, &h);
if(rc == CURLRESOLV_PENDING)
(void)Curl_resolver_wait_resolv(data, &h);
conn->ip_version = ipver;
@@ -528,7 +682,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
/* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
Curl_printable_address(h->addr, myhost, sizeof(myhost));
infof(data, "Name '%s' family %i resolved to '%s' family %i",
- dev, af, myhost, h->addr->ai_family);
+ host, af, myhost, h->addr->ai_family);
Curl_resolv_unlock(data, h);
if(af != h->addr->ai_family) {
/* bad IP version combo, signal the caller to try another address
@@ -562,7 +716,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
if(scope_ptr) {
/* The "myhost" string either comes from Curl_if2ip or from
Curl_printable_address. The latter returns only numeric scope
- IDs and the former returns none at all. So the scope ID, if
+ IDs and the former returns none at all. So the scope ID, if
present, is known to be numeric */
unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
if(scope_id > UINT_MAX)
@@ -589,8 +743,11 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
/* errorbuf is set false so failf will overwrite any message already in
the error buffer, so the user receives this error message instead of a
generic resolve error. */
+ char buffer[STRERROR_LEN];
data->state.errorbuf = FALSE;
- failf(data, "Couldn't bind to '%s'", dev);
+ data->state.os_errno = error = SOCKERRNO;
+ failf(data, "Couldn't bind to '%s' with errno %d: %s",
+ host, error, Curl_strerror(error, buffer, sizeof(buffer)));
return CURLE_INTERFACE_FAILED;
}
}
@@ -667,8 +824,8 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
* Gisle Vanem could reproduce the former problems with this function, but
* could avoid them by adding this SleepEx() call below:
*
- * "I don't have Rational Quantify, but the hint from his post was
- * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe
+ * "I do not have Rational Quantify, but the hint from his post was
+ * ntdll::NtRemoveIoCompletion(). I would assume the SleepEx (or maybe
* just Sleep(0) would be enough?) would release whatever
* mutex/critical-section the ntdll call is waiting on.
*
@@ -686,14 +843,14 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
err = SOCKERRNO;
#ifdef _WIN32_WCE
- /* Old WinCE versions don't support SO_ERROR */
+ /* Old WinCE versions do not support SO_ERROR */
if(WSAENOPROTOOPT == err) {
SET_SOCKERRNO(0);
err = 0;
}
#endif
#if defined(EBADIOCTL) && defined(__minix)
- /* Minix 3.1.x doesn't support getsockopt on UDP sockets */
+ /* Minix 3.1.x does not support getsockopt on UDP sockets */
if(EBADIOCTL == err) {
SET_SOCKERRNO(0);
err = 0;
@@ -703,7 +860,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
/* we are connected, awesome! */
rc = TRUE;
else
- /* This wasn't a successful connect */
+ /* This was not a successful connect */
rc = FALSE;
if(error)
*error = err;
@@ -765,11 +922,14 @@ struct cf_socket_ctx {
int transport;
struct Curl_sockaddr_ex addr; /* address to connect to */
curl_socket_t sock; /* current attempt socket */
- struct bufq recvbuf; /* used when `buffer_recv` is set */
struct ip_quadruple ip; /* The IP quadruple 2x(addr+port) */
struct curltime started_at; /* when socket was created */
struct curltime connected_at; /* when socket connected/got first byte */
struct curltime first_byte_at; /* when first byte was recvd */
+#ifdef USE_WINSOCK
+ struct curltime last_sndbuf_query_at; /* when SO_SNDBUF last queried */
+ ULONG sndbuf_size; /* the last set SO_SNDBUF size */
+#endif
int error; /* errno of last failure or 0 */
#ifdef DEBUGBUILD
int wblock_percent; /* percent of writes doing EAGAIN */
@@ -781,7 +941,6 @@ struct cf_socket_ctx {
BIT(accepted); /* socket was accepted, not connected */
BIT(sock_connected); /* socket is "connected", e.g. in UDP */
BIT(active);
- BIT(buffer_recv);
};
static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
@@ -792,7 +951,6 @@ static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
ctx->sock = CURL_SOCKET_BAD;
ctx->transport = transport;
Curl_sock_assign_addr(&ctx->addr, ai, transport);
- Curl_bufq_init(&ctx->recvbuf, NW_RECV_CHUNK_SIZE, NW_RECV_CHUNKS);
#ifdef DEBUGBUILD
{
char *p = getenv("CURL_DBG_SOCK_WBLOCK");
@@ -823,56 +981,6 @@ static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
#endif
}
-struct reader_ctx {
- struct Curl_cfilter *cf;
- struct Curl_easy *data;
-};
-
-static ssize_t nw_in_read(void *reader_ctx,
- unsigned char *buf, size_t len,
- CURLcode *err)
-{
- struct reader_ctx *rctx = reader_ctx;
- struct cf_socket_ctx *ctx = rctx->cf->ctx;
- ssize_t nread;
-
- *err = CURLE_OK;
- nread = sread(ctx->sock, buf, len);
-
- if(-1 == nread) {
- int sockerr = SOCKERRNO;
-
- if(
-#ifdef WSAEWOULDBLOCK
- /* This is how Windows does it */
- (WSAEWOULDBLOCK == sockerr)
-#else
- /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
- due to its inability to send off data without blocking. We therefore
- treat both error codes the same here */
- (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr)
-#endif
- ) {
- /* this is just a case of EWOULDBLOCK */
- *err = CURLE_AGAIN;
- nread = -1;
- }
- else {
- char buffer[STRERROR_LEN];
-
- failf(rctx->data, "Recv failure: %s",
- Curl_strerror(sockerr, buffer, sizeof(buffer)));
- rctx->data->state.os_errno = sockerr;
- *err = CURLE_RECV_ERROR;
- nread = -1;
- }
- }
- CURL_TRC_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu, fd=%"
- CURL_FORMAT_SOCKET_T ") -> %d, err=%d",
- len, ctx->sock, (int)nread, *err);
- return nread;
-}
-
static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
@@ -886,9 +994,7 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
ctx->sock = CURL_SOCKET_BAD;
if(ctx->active && cf->sockindex == FIRSTSOCKET)
cf->conn->remote_addr = NULL;
- Curl_bufq_reset(&ctx->recvbuf);
ctx->active = FALSE;
- ctx->buffer_recv = FALSE;
memset(&ctx->started_at, 0, sizeof(ctx->started_at));
memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
}
@@ -896,13 +1002,35 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
cf->connected = FALSE;
}
+static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ if(cf->connected) {
+ struct cf_socket_ctx *ctx = cf->ctx;
+
+ CURL_TRC_CF(data, cf, "cf_socket_shutdown(%" CURL_FORMAT_SOCKET_T
+ ")", ctx->sock);
+ /* On TCP, and when the socket looks well and non-blocking mode
+ * can be enabled, receive dangling bytes before close to avoid
+ * entering RST states unnecessarily. */
+ if(ctx->sock != CURL_SOCKET_BAD &&
+ ctx->transport == TRNSPRT_TCP &&
+ (curlx_nonblock(ctx->sock, TRUE) >= 0)) {
+ unsigned char buf[1024];
+ (void)sread(ctx->sock, buf, sizeof(buf));
+ }
+ }
+ *done = TRUE;
+ return CURLE_OK;
+}
+
static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
cf_socket_close(cf, data);
CURL_TRC_CF(data, cf, "destroy");
- Curl_bufq_free(&ctx->recvbuf);
free(ctx);
cf->ctx = NULL;
}
@@ -949,7 +1077,7 @@ static CURLcode set_remote_ip(struct Curl_cfilter *cf,
struct cf_socket_ctx *ctx = cf->ctx;
/* store remote address and port used in this connection attempt */
- if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen,
+ if(!Curl_addr2string(&ctx->addr.sa_addr, (curl_socklen_t)ctx->addr.addrlen,
ctx->ip.remote_ip, &ctx->ip.remote_port)) {
char buffer[STRERROR_LEN];
@@ -974,7 +1102,20 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
(void)data;
DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD);
ctx->started_at = Curl_now();
+#ifdef SOCK_NONBLOCK
+ /* Do not tuck SOCK_NONBLOCK into socktype when opensocket callback is set
+ * because we would not know how socketype is about to be used in the
+ * callback, SOCK_NONBLOCK might get factored out before calling socket().
+ */
+ if(!data->set.fopensocket)
+ ctx->addr.socktype |= SOCK_NONBLOCK;
+#endif
result = socket_open(data, &ctx->addr, &ctx->sock);
+#ifdef SOCK_NONBLOCK
+ /* Restore the socktype after the socket is created. */
+ if(!data->set.fopensocket)
+ ctx->addr.socktype &= ~SOCK_NONBLOCK;
+#endif
if(result)
goto out;
@@ -1004,7 +1145,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
nosigpipe(data, ctx->sock);
- Curl_sndbufset(ctx->sock);
+ Curl_sndbuf_init(ctx->sock);
if(is_tcp && data->set.tcp_keepalive)
tcpkeepalive(data, ctx->sock);
@@ -1045,8 +1186,27 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
}
#endif
- /* set socket non-blocking */
- (void)curlx_nonblock(ctx->sock, TRUE);
+#ifndef SOCK_NONBLOCK
+ /* Set socket non-blocking, must be a non-blocking socket for
+ * a non-blocking connect. */
+ error = curlx_nonblock(ctx->sock, TRUE);
+ if(error < 0) {
+ result = CURLE_UNSUPPORTED_PROTOCOL;
+ ctx->error = SOCKERRNO;
+ goto out;
+ }
+#else
+ if(data->set.fopensocket) {
+ /* Set socket non-blocking, must be a non-blocking socket for
+ * a non-blocking connect. */
+ error = curlx_nonblock(ctx->sock, TRUE);
+ if(error < 0) {
+ result = CURLE_UNSUPPORTED_PROTOCOL;
+ ctx->error = SOCKERRNO;
+ goto out;
+ }
+ }
+#endif
ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM);
out:
if(result) {
@@ -1114,7 +1274,8 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
#endif
}
else {
- rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+ rc = connect(ctx->sock, &ctx->addr.sa_addr,
+ (curl_socklen_t)ctx->addr.addrlen);
}
return rc;
}
@@ -1257,13 +1418,36 @@ static bool cf_socket_data_pending(struct Curl_cfilter *cf,
int readable;
(void)data;
- if(!Curl_bufq_is_empty(&ctx->recvbuf))
- return TRUE;
-
readable = SOCKET_READABLE(ctx->sock, 0);
return (readable > 0 && (readable & CURL_CSELECT_IN));
}
+#ifdef USE_WINSOCK
+
+#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY
+#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B
+#endif
+
+static void win_update_sndbuf_size(struct cf_socket_ctx *ctx)
+{
+ ULONG ideal;
+ DWORD ideallen;
+ struct curltime n = Curl_now();
+
+ if(Curl_timediff(n, ctx->last_sndbuf_query_at) > 1000) {
+ if(!WSAIoctl(ctx->sock, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0,
+ &ideal, sizeof(ideal), &ideallen, 0, 0) &&
+ ideal != ctx->sndbuf_size &&
+ !setsockopt(ctx->sock, SOL_SOCKET, SO_SNDBUF,
+ (const char *)&ideal, sizeof(ideal))) {
+ ctx->sndbuf_size = ideal;
+ }
+ ctx->last_sndbuf_query_at = n;
+ }
+}
+
+#endif /* USE_WINSOCK */
+
static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
@@ -1336,6 +1520,11 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
}
}
+#if defined(USE_WINSOCK)
+ if(!*err)
+ win_update_sndbuf_size(ctx);
+#endif
+
CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, err=%d",
orig_len, (int)nwritten, *err);
cf->conn->sock[cf->sockindex] = fdsave;
@@ -1346,14 +1535,10 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_socket_ctx *ctx = cf->ctx;
- curl_socket_t fdsave;
ssize_t nread;
*err = CURLE_OK;
- fdsave = cf->conn->sock[cf->sockindex];
- cf->conn->sock[cf->sockindex] = ctx->sock;
-
#ifdef DEBUGBUILD
/* simulate network blocking/partial reads */
if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) {
@@ -1362,9 +1547,7 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
if(c >= ((100-ctx->rblock_percent)*256/100)) {
CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len);
*err = CURLE_AGAIN;
- nread = -1;
- cf->conn->sock[cf->sockindex] = fdsave;
- return nread;
+ return -1;
}
}
if(cf->cft != &Curl_cft_udp && ctx->recv_max && ctx->recv_max < len) {
@@ -1375,54 +1558,42 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
#endif
- if(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) {
- CURL_TRC_CF(data, cf, "recv from buffer");
- nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
- }
- else {
- struct reader_ctx rctx;
-
- rctx.cf = cf;
- rctx.data = data;
-
- /* "small" reads may trigger filling our buffer, "large" reads
- * are probably not worth the additional copy */
- if(ctx->buffer_recv && len < NW_SMALL_READS) {
- ssize_t nwritten;
- nwritten = Curl_bufq_slurp(&ctx->recvbuf, nw_in_read, &rctx, err);
- if(nwritten < 0 && !Curl_bufq_is_empty(&ctx->recvbuf)) {
- /* we have a partial read with an error. need to deliver
- * what we got, return the error later. */
- CURL_TRC_CF(data, cf, "partial read: empty buffer first");
- nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
- }
- else if(nwritten < 0) {
- nread = -1;
- goto out;
- }
- else if(nwritten == 0) {
- /* eof */
- *err = CURLE_OK;
- nread = 0;
- }
- else {
- CURL_TRC_CF(data, cf, "buffered %zd additional bytes", nwritten);
- nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
- }
+ *err = CURLE_OK;
+ nread = sread(ctx->sock, buf, len);
+
+ if(-1 == nread) {
+ int sockerr = SOCKERRNO;
+
+ if(
+#ifdef WSAEWOULDBLOCK
+ /* This is how Windows does it */
+ (WSAEWOULDBLOCK == sockerr)
+#else
+ /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
+ due to its inability to send off data without blocking. We therefore
+ treat both error codes the same here */
+ (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr)
+#endif
+ ) {
+ /* this is just a case of EWOULDBLOCK */
+ *err = CURLE_AGAIN;
}
else {
- nread = nw_in_read(&rctx, (unsigned char *)buf, len, err);
+ char buffer[STRERROR_LEN];
+
+ failf(data, "Recv failure: %s",
+ Curl_strerror(sockerr, buffer, sizeof(buffer)));
+ data->state.os_errno = sockerr;
+ *err = CURLE_RECV_ERROR;
}
}
-out:
CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread,
*err);
if(nread > 0 && !ctx->got_first_byte) {
ctx->first_byte_at = Curl_now();
ctx->got_first_byte = TRUE;
}
- cf->conn->sock[cf->sockindex] = fdsave;
return nread;
}
@@ -1444,11 +1615,6 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
#endif
Curl_persistconninfo(data, cf->conn, &ctx->ip);
- /* buffering is currently disabled by default because we have stalls
- * in parallel transfers where not all buffered data is consumed and no
- * socket events happen.
- */
- ctx->buffer_recv = FALSE;
}
ctx->active = TRUE;
}
@@ -1564,6 +1730,7 @@ struct Curl_cftype Curl_cft_tcp = {
cf_socket_destroy,
cf_tcp_connect,
cf_socket_close,
+ cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
@@ -1608,25 +1775,23 @@ out:
}
static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
- struct Curl_easy *data)
+ struct Curl_easy *data)
{
struct cf_socket_ctx *ctx = cf->ctx;
int rc;
+ int one = 1;
+
+ (void)one;
/* QUIC needs a connected socket, nonblocking */
DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD);
-#if defined(__APPLE__) && defined(USE_OPENSSL_QUIC)
- (void)rc;
- /* On macOS OpenSSL QUIC fails on connected sockets.
- * see: <https://github.com/openssl/openssl/issues/23251> */
-#else
- rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+ rc = connect(ctx->sock, &ctx->addr.sa_addr,
+ (curl_socklen_t)ctx->addr.addrlen);
if(-1 == rc) {
return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO);
}
ctx->sock_connected = TRUE;
-#endif
set_local_ip(cf, data);
CURL_TRC_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T
" connected: [%s:%d] -> [%s:%d]",
@@ -1634,7 +1799,11 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
ctx->sock, ctx->ip.local_ip, ctx->ip.local_port,
ctx->ip.remote_ip, ctx->ip.remote_port);
- (void)curlx_nonblock(ctx->sock, TRUE);
+ /* Currently, cf->ctx->sock is always non-blocking because the only
+ * caller to cf_udp_setup_quic() is cf_udp_connect() that passes the
+ * non-blocking socket created by cf_socket_open() to it. Thus, we
+ * do not need to call curlx_nonblock() in cf_udp_setup_quic() anymore.
+ */
switch(ctx->addr.family) {
#if defined(__linux__) && defined(IP_MTU_DISCOVER)
case AF_INET: {
@@ -1653,6 +1822,14 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
}
#endif
}
+
+#if defined(__linux__) && defined(UDP_GRO) && \
+ (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \
+ ((defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE))
+ (void)setsockopt(ctx->sock, IPPROTO_UDP, UDP_GRO, &one,
+ (socklen_t)sizeof(one));
+#endif
+
return CURLE_OK;
}
@@ -1702,6 +1879,7 @@ struct Curl_cftype Curl_cft_udp = {
cf_socket_destroy,
cf_udp_connect,
cf_socket_close,
+ cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
@@ -1753,6 +1931,7 @@ struct Curl_cftype Curl_cft_unix = {
cf_socket_destroy,
cf_tcp_connect,
cf_socket_close,
+ cf_socket_shutdown,
cf_socket_get_host,
cf_socket_adjust_pollset,
cf_socket_data_pending,
@@ -1817,6 +1996,7 @@ struct Curl_cftype Curl_cft_tcp_accept = {
cf_socket_destroy,
cf_tcp_accept_connect,
cf_socket_close,
+ cf_socket_shutdown,
cf_socket_get_host, /* TODO: not accurate */
cf_socket_adjust_pollset,
cf_socket_data_pending,