diff options
author | Brad King <brad.king@kitware.com> | 2023-05-30 13:38:38 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2023-05-30 13:39:43 (GMT) |
commit | a6c9b53273eb3ef8b4d9a0a2a0fc6a4822211b9d (patch) | |
tree | 23bfdb5c7a2254a82571a4acb78b5a25c1641898 /Utilities/cmcurl/lib/cf-socket.c | |
parent | 7f1abf62e12a261a2fc91a2d527b5c4a30e25d41 (diff) | |
parent | 80cb6a512119ea6f8f8cf480c78e1e32d494e6ca (diff) | |
download | CMake-a6c9b53273eb3ef8b4d9a0a2a0fc6a4822211b9d.zip CMake-a6c9b53273eb3ef8b4d9a0a2a0fc6a4822211b9d.tar.gz CMake-a6c9b53273eb3ef8b4d9a0a2a0fc6a4822211b9d.tar.bz2 |
Merge branch 'upstream-curl' into update-curl
* upstream-curl:
curl 2023-05-30 (7ab9d437)
Diffstat (limited to 'Utilities/cmcurl/lib/cf-socket.c')
-rw-r--r-- | Utilities/cmcurl/lib/cf-socket.c | 339 |
1 files changed, 167 insertions, 172 deletions
diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c index 6d9ace4..960979b 100644 --- a/Utilities/cmcurl/lib/cf-socket.c +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -54,6 +54,7 @@ #endif #include "urldata.h" +#include "bufq.h" #include "sendf.h" #include "if2ip.h" #include "strerror.h" @@ -79,6 +80,22 @@ #include "memdebug.h" +#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(WIN32) +/* It makes support for IPv4-mapped IPv6 addresses. + * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; + * Windows Vista and later: default is on; + * DragonFly BSD: acts like off, and dummy setting; + * OpenBSD and earlier Windows: unsupported. + * Linux: controlled by /proc/sys/net/ipv6/bindv6only. + */ +static void set_ipv6_v6only(curl_socket_t sockfd, int on) +{ + (void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); +} +#else +#define set_ipv6_v6only(x,y) +#endif + static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) { #if defined(TCP_NODELAY) @@ -195,6 +212,10 @@ tcpkeepalive(struct Curl_easy *data, } } +/** + * Assign the address `ai` to the Curl_sockaddr_ex `dest` and + * set the transport used. + */ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, const struct Curl_addrinfo *ai, int transport) @@ -224,7 +245,7 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, dest->addrlen = ai->ai_addrlen; if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) - dest->addrlen = sizeof(struct Curl_sockaddr_storage); + dest->addrlen = sizeof(struct Curl_sockaddr_storage); memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen); } @@ -700,8 +721,11 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) return rc; } -CURLcode Curl_socket_connect_result(struct Curl_easy *data, - const char *ipaddress, int error) +/** + * Determine the curl code for a socket connect() == -1 with errno. + */ +static CURLcode socket_connect_result(struct Curl_easy *data, + const char *ipaddress, int error) { char buffer[STRERROR_LEN]; @@ -729,29 +753,20 @@ CURLcode Curl_socket_connect_result(struct Curl_easy *data, } } -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -struct io_buffer { - char *bufr; - size_t allc; /* size of the current allocation */ - size_t head; /* bufr index for next read */ - size_t tail; /* bufr index for next write */ -}; - -static void io_buffer_reset(struct io_buffer *iob) -{ - if(iob->bufr) - free(iob->bufr); - memset(iob, 0, sizeof(*iob)); -} -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ +/* We have a recv buffer to enhance reads with len < NW_SMALL_READS. + * This happens often on TLS connections where the TLS implementation + * tries to read the head of a TLS record, determine the length of the + * full record and then make a subsequent read for that. + * On large reads, we will not fill the buffer to avoid the double copy. */ +#define NW_RECV_CHUNK_SIZE (64 * 1024) +#define NW_RECV_CHUNKS 1 +#define NW_SMALL_READS (1024) struct cf_socket_ctx { int transport; struct Curl_sockaddr_ex addr; /* address to connect to */ curl_socket_t sock; /* current attempt socket */ -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - struct io_buffer recv_buffer; -#endif + struct bufq recvbuf; /* used when `buffer_recv` is set */ char r_ip[MAX_IPADR_LEN]; /* remote IP as string */ int r_port; /* remote port number */ char l_ip[MAX_IPADR_LEN]; /* local IP as string */ @@ -763,6 +778,7 @@ struct cf_socket_ctx { BIT(got_first_byte); /* if first byte was received */ BIT(accepted); /* socket was accepted, not connected */ BIT(active); + BIT(buffer_recv); }; static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, @@ -773,6 +789,56 @@ 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); +} + +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; + } + } + DEBUGF(LOG_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu) -> %d, err=%d", + len, (int)nread, *err)); + return nread; } static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -786,14 +852,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) * closed it) and we just forget about it. */ if(ctx->sock == cf->conn->sock[cf->sockindex]) { - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, active)", - (int)ctx->sock)); + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T + ", active)", ctx->sock)); socket_close(data, cf->conn, !ctx->accepted, ctx->sock); cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; } else { - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at " - "conn->sock[], discarding", (int)ctx->sock)); + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T + ") no longer at conn->sock[], discarding", ctx->sock)); /* TODO: we do not want this to happen. Need to check which * code is messing with conn->sock[cf->sockindex] */ } @@ -803,15 +869,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) } else { /* this is our local socket, we did never publish it */ - DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, not active)", - (int)ctx->sock)); + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T + ", not active)", ctx->sock)); sclose(ctx->sock); ctx->sock = CURL_SOCKET_BAD; } -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - io_buffer_reset(&ctx->recv_buffer); -#endif + 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)); } @@ -825,6 +890,7 @@ static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) cf_socket_close(cf, data); DEBUGF(LOG_CF(data, cf, "destroy")); + Curl_bufq_free(&ctx->recvbuf); free(ctx); cf->ctx = NULL; } @@ -901,8 +967,10 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, goto out; #ifdef ENABLE_IPV6 - if(ctx->addr.family == AF_INET6) + if(ctx->addr.family == AF_INET6) { + set_ipv6_v6only(ctx->sock, 0); ipmsg = " Trying [%s]:%d..."; + } else #endif ipmsg = " Trying %s:%d..."; @@ -975,7 +1043,8 @@ out: ctx->connected_at = Curl_now(); cf->connected = TRUE; } - DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%d", result, ctx->sock)); + DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%" CURL_FORMAT_SOCKET_T, + result, ctx->sock)); return result; } @@ -1016,7 +1085,8 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, #elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (void *)&optval, sizeof(optval)) < 0) - infof(data, "Failed to enable TCP Fast Open on fd %d", ctx->sock); + infof(data, "Failed to enable TCP Fast Open on fd %" + CURL_FORMAT_SOCKET_T, ctx->sock); rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); #elif defined(MSG_FASTOPEN) /* old Linux */ @@ -1065,7 +1135,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, /* Connect TCP socket */ rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen); if(-1 == rc) { - result = Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO); + result = socket_connect_result(data, ctx->r_ip, SOCKERRNO); goto out; } } @@ -1151,89 +1221,16 @@ static int cf_socket_get_select_socks(struct Curl_cfilter *cf, return rc; } -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - -static CURLcode pre_receive_plain(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_socket_ctx *ctx = cf->ctx; - struct io_buffer * const iob = &ctx->recv_buffer; - - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. However, skip this, if buffer is already full. */ - if((cf->conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 && - cf->conn->recv[cf->sockindex] == Curl_conn_recv && - (!iob->bufr || (iob->allc > iob->tail))) { - const int readymask = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD, - CURL_SOCKET_BAD, 0); - if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) { - size_t bytestorecv = iob->allc - iob->tail; - ssize_t nread; - /* Have some incoming data */ - if(!iob->bufr) { - /* Use buffer double default size for intermediate buffer */ - iob->allc = 2 * data->set.buffer_size; - iob->bufr = malloc(iob->allc); - if(!iob->bufr) - return CURLE_OUT_OF_MEMORY; - iob->tail = 0; - iob->head = 0; - bytestorecv = iob->allc; - } - - nread = sread(ctx->sock, iob->bufr + iob->tail, bytestorecv); - if(nread > 0) - iob->tail += (size_t)nread; - } - } - return CURLE_OK; -} - -static ssize_t get_pre_recved(struct Curl_cfilter *cf, char *buf, size_t len) -{ - struct cf_socket_ctx *ctx = cf->ctx; - struct io_buffer * const iob = &ctx->recv_buffer; - size_t copysize; - if(!iob->bufr) - return 0; - - DEBUGASSERT(iob->allc > 0); - DEBUGASSERT(iob->tail <= iob->allc); - DEBUGASSERT(iob->head <= iob->tail); - /* Check and process data that already received and storied in internal - intermediate buffer */ - if(iob->tail > iob->head) { - copysize = CURLMIN(len, iob->tail - iob->head); - memcpy(buf, iob->bufr + iob->head, copysize); - iob->head += copysize; - } - else - copysize = 0; /* buffer was allocated, but nothing was received */ - - /* Free intermediate buffer if it has no unprocessed data */ - if(iob->head == iob->tail) - io_buffer_reset(iob); - - return (ssize_t)copysize; -} -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ - static bool cf_socket_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; int readable; -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - if(ctx->recv_buffer.bufr && ctx->recv_buffer.allc && - ctx->recv_buffer.tail > ctx->recv_buffer.head) - return TRUE; -#endif - (void)data; + if(!Curl_bufq_is_empty(&ctx->recvbuf)) + return TRUE; + readable = SOCKET_READABLE(ctx->sock, 0); return (readable > 0 && (readable & CURL_CSELECT_IN)); } @@ -1246,19 +1243,6 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, ssize_t nwritten; *err = CURLE_OK; - -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. */ - if(pre_receive_plain(cf, data)) { - *err = CURLE_OUT_OF_MEMORY; - return -1; - } -#endif - fdsave = cf->conn->sock[cf->sockindex]; cf->conn->sock[cf->sockindex] = ctx->sock; @@ -1315,47 +1299,50 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, *err = CURLE_OK; -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - /* Check and return data that already received and storied in internal - intermediate buffer */ - nread = get_pre_recved(cf, buf, len); - if(nread > 0) { - *err = CURLE_OK; - return nread; - } -#endif - fdsave = cf->conn->sock[cf->sockindex]; cf->conn->sock[cf->sockindex] = ctx->sock; - 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; + if(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) { + DEBUGF(LOG_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. */ + DEBUGF(LOG_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 { + DEBUGF(LOG_CF(data, cf, "buffered %zd additional bytes", nwritten)); + nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); + } } else { - char buffer[STRERROR_LEN]; - failf(data, "Recv failure: %s", - Curl_strerror(sockerr, buffer, sizeof(buffer))); - data->state.os_errno = sockerr; - *err = CURLE_RECV_ERROR; + nread = nw_in_read(&rctx, (unsigned char *)buf, len, err); } } +out: DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, *err)); if(nread > 0 && !ctx->got_first_byte) { @@ -1411,6 +1398,11 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) conn_set_primary_ip(cf, data); set_local_ip(cf, data); Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + /* 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; } @@ -1577,12 +1569,13 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); if(-1 == rc) { - return Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO); + return socket_connect_result(data, ctx->r_ip, SOCKERRNO); } set_local_ip(cf, data); - DEBUGF(LOG_CF(data, cf, "%s socket %d connected: [%s:%d] -> [%s:%d]", - (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", - ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port)); + DEBUGF(LOG_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T + " connected: [%s:%d] -> [%s:%d]", + (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", + ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port)); (void)curlx_nonblock(ctx->sock, TRUE); switch(ctx->addr.family) { @@ -1623,10 +1616,6 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, result = cf_socket_open(cf, data); if(result) { DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), open failed -> %d", result)); - if(ctx->sock != CURL_SOCKET_BAD) { - socket_close(data, cf->conn, TRUE, ctx->sock); - ctx->sock = CURL_SOCKET_BAD; - } goto out; } @@ -1634,12 +1623,13 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, result = cf_udp_setup_quic(cf, data); if(result) goto out; - DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d (%s:%d)", + DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%" + CURL_FORMAT_SOCKET_T " (%s:%d)", ctx->sock, ctx->l_ip, ctx->l_port)); } else { - DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d " - "(unconnected)", ctx->sock)); + DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%" + CURL_FORMAT_SOCKET_T " (unconnected)", ctx->sock)); } *done = TRUE; cf->connected = TRUE; @@ -1811,7 +1801,8 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, ctx->active = TRUE; ctx->connected_at = Curl_now(); cf->connected = TRUE; - DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%d)", (int)ctx->sock)); + DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%" + CURL_FORMAT_SOCKET_T ")", ctx->sock)); out: if(result) { @@ -1875,13 +1866,17 @@ CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, ctx->accepted = TRUE; ctx->connected_at = Curl_now(); cf->connected = TRUE; - DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%d, remote=%s port=%d)", - (int)ctx->sock, ctx->r_ip, ctx->r_port)); + DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%" CURL_FORMAT_SOCKET_T + ", remote=%s port=%d)", + ctx->sock, ctx->r_ip, ctx->r_port)); return CURLE_OK; } -bool Curl_cf_is_socket(struct Curl_cfilter *cf) +/** + * Return TRUE iff `cf` is a socket filter. + */ +static bool cf_is_socket(struct Curl_cfilter *cf) { return cf && (cf->cft == &Curl_cft_tcp || cf->cft == &Curl_cft_udp || @@ -1896,7 +1891,7 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, const char **pr_ip_str, int *pr_port, const char **pl_ip_str, int *pl_port) { - if(Curl_cf_is_socket(cf) && cf->ctx) { + if(cf_is_socket(cf) && cf->ctx) { struct cf_socket_ctx *ctx = cf->ctx; if(psock) |