summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Eissing <stefan@eissing.org>2022-12-30 09:14:55 +0100
committerDaniel Stenberg <daniel@haxx.se>2022-12-30 16:43:19 +0100
commit71b7e0161032927cdfb4e75ea40f65b8898b3956 (patch)
treedf5d03d631e6d67703d54a011a25fff03ed5867c
parent1c18f8da51f9f37b68fb9422975d32c436e905a0 (diff)
downloadcurl-71b7e0161032927cdfb4e75ea40f65b8898b3956.tar.gz
lib: connect/h2/h3 refactor
Refactoring of connection setup and happy eyeballing. Move nghttp2. ngtcp2, quiche and msh3 into connection filters. - eyeballing cfilter that uses sub-filters for performing parallel connects - socket cfilter for all transport types, including QUIC - QUIC implementations in cfilter, can now participate in eyeballing - connection setup is more dynamic in order to adapt to what filter did really connect. Relevant to see if a SSL filter needs to be added or if SSL has already been provided - HTTP/3 test cases similar to HTTP/2 - multiuse of parallel transfers for HTTP/3, tested for ngtcp2 and quiche - Fix for data attach/detach in VTLS filters that could lead to crashes during parallel transfers. - Eliminating setup() methods in cfilters, no longer needed. - Improving Curl_conn_is_alive() to replace Curl_connalive() and integrated ssl alive checks into cfilter. - Adding CF_CNTRL_CONN_INFO_UPDATE to tell filters to update connection into and persist it at the easy handle. - Several more cfilter related cleanups and moves: - stream_weigth and dependency info is now wrapped in struct Curl_data_priority - Curl_data_priority members depend is available in HTTP2|HTTP3 - Curl_data_priority members depend on NGHTTP2 support - handling init/reset/cleanup of priority part of url.c - data->state.priority same struct, but shallow copy for compares only - PROTOPT_STREAM has been removed - Curl_conn_is_mulitplex() now available to check on capability - Adding query method to connection filters. - ngtcp2+quiche: implementing query for max concurrent transfers. - Adding is_alive and keep_alive cfilter methods. Adding DATA_SETUP event. - setting keepalive timestamp on connect - DATA_SETUP is called after the connection has been completely setup (but may not connected yet) to allow filters to initialize data members they use. - there is no socket to be had with msh3, it is unclear how select shall work - manual test via "curl --http3 https://curl.se" fail with "empty reply from server". - Various socket/conn related cleanups: - Curl_socket is now Curl_socket_open and in cf-socket.c - Curl_closesocket is now Curl_socket_close and in cf-socket.c - Curl_ssl_use has been replaced with Cur_conn_is_ssl - Curl_conn_tcp_accepted_set has been split into Curl_conn_tcp_listen_set and Curl_conn_tcp_accepted_set with a clearer purpose Closes #10141
-rw-r--r--lib/Makefile.inc6
-rw-r--r--lib/cf-socket.c1534
-rw-r--r--lib/cf-socket.h169
-rw-r--r--lib/cfilters.c476
-rw-r--r--lib/cfilters.h224
-rw-r--r--lib/connect.c2285
-rw-r--r--lib/connect.h112
-rw-r--r--lib/easy.c26
-rw-r--r--lib/ftp.c35
-rw-r--r--lib/http.c105
-rw-r--r--lib/http.h93
-rw-r--r--lib/http2.c1546
-rw-r--r--lib/http2.h49
-rw-r--r--lib/http_proxy.c273
-rw-r--r--lib/http_proxy.h6
-rw-r--r--lib/imap.c4
-rw-r--r--lib/multi.c19
-rw-r--r--lib/pop3.c4
-rw-r--r--lib/quic.h68
-rw-r--r--lib/rtsp.c33
-rw-r--r--lib/sendf.c5
-rw-r--r--lib/setopt.c18
-rw-r--r--lib/smtp.c6
-rw-r--r--lib/socks.c28
-rw-r--r--lib/socks.h3
-rw-r--r--lib/transfer.c84
-rw-r--r--lib/url.c166
-rw-r--r--lib/url.h16
-rw-r--r--lib/urldata.h68
-rw-r--r--lib/version.c6
-rw-r--r--lib/vquic/msh3.c575
-rw-r--r--lib/vquic/msh3.h14
-rw-r--r--lib/vquic/ngtcp2.c1279
-rw-r--r--lib/vquic/ngtcp2.h52
-rw-r--r--lib/vquic/quiche.c1302
-rw-r--r--lib/vquic/quiche.h34
-rw-r--r--lib/vquic/vquic.c51
-rw-r--r--lib/vquic/vquic.h24
-rw-r--r--lib/vquic/vquic_int.h33
-rw-r--r--lib/vssh/libssh2.c4
-rw-r--r--lib/vtls/openssl.c30
-rw-r--r--lib/vtls/vtls.c152
-rw-r--r--lib/vtls/vtls.h8
-rw-r--r--tests/data/Makefile.inc2
-rw-r--r--tests/data/test250170
-rw-r--r--tests/data/test2502104
-rw-r--r--tests/libtest/Makefile.inc5
-rw-r--r--tests/libtest/lib2502.c141
48 files changed, 6790 insertions, 4557 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 9eafa93b9..d6b3695c5 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -88,7 +88,8 @@ LIB_VQUIC_HFILES = \
vquic/msh3.h \
vquic/ngtcp2.h \
vquic/quiche.h \
- vquic/vquic.h
+ vquic/vquic.h \
+ vquic/vquic_int.h
LIB_VSSH_CFILES = \
vssh/libssh.c \
@@ -106,6 +107,7 @@ LIB_CFILES = \
base64.c \
bufref.c \
c-hyper.c \
+ cf-socket.c \
cfilters.c \
conncache.c \
connect.c \
@@ -229,6 +231,7 @@ LIB_HFILES = \
asyn.h \
bufref.h \
c-hyper.h \
+ cf-socket.h \
cfilters.h \
conncache.h \
connect.h \
@@ -312,7 +315,6 @@ LIB_HFILES = \
pop3.h \
progress.h \
psl.h \
- quic.h \
rand.h \
rename.h \
rtsp.h \
diff --git a/lib/cf-socket.c b/lib/cf-socket.c
new file mode 100644
index 000000000..b38fd9a87
--- /dev/null
+++ b/lib/cf-socket.c
@@ -0,0 +1,1534 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, 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"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h> /* <netinet/tcp.h> may need it */
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h> /* for sockaddr_un */
+#endif
+#ifdef HAVE_LINUX_TCP_H
+#include <linux/tcp.h>
+#elif defined(HAVE_NETINET_TCP_H)
+#include <netinet/tcp.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "if2ip.h"
+#include "strerror.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "select.h"
+#include "url.h" /* for Curl_safefree() */
+#include "multiif.h"
+#include "sockaddr.h" /* required for Curl_sockaddr_storage */
+#include "inet_ntop.h"
+#include "inet_pton.h"
+#include "progress.h"
+#include "warnless.h"
+#include "conncache.h"
+#include "multihandle.h"
+#include "share.h"
+#include "version_win32.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define DEBUG_CF 0
+
+#if DEBUG_CF
+#define CF_DEBUGF(x) x
+#else
+#define CF_DEBUGF(x) do { } while(0)
+#endif
+
+static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
+{
+#if defined(TCP_NODELAY)
+ curl_socklen_t onoff = (curl_socklen_t) 1;
+ int level = IPPROTO_TCP;
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ char buffer[STRERROR_LEN];
+#else
+ (void) data;
+#endif
+
+ if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff,
+ sizeof(onoff)) < 0)
+ infof(data, "Could not set TCP_NODELAY: %s",
+ Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#else
+ (void)data;
+ (void)sockfd;
+#endif
+}
+
+#ifdef SO_NOSIGPIPE
+/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
+ sending data to a dead peer (instead of relying on the 4th argument to send
+ being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
+ systems? */
+static void nosigpipe(struct Curl_easy *data,
+ curl_socket_t sockfd)
+{
+ int onoff = 1;
+ if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
+ sizeof(onoff)) < 0) {
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ char buffer[STRERROR_LEN];
+ infof(data, "Could not set SO_NOSIGPIPE: %s",
+ Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#endif
+ }
+}
+#else
+#define nosigpipe(x,y) Curl_nop_stmt
+#endif
+
+#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H)
+/* DragonFlyBSD and Windows use millisecond units */
+#define KEEPALIVE_FACTOR(x) (x *= 1000)
+#else
+#define KEEPALIVE_FACTOR(x)
+#endif
+
+#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS)
+#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
+
+struct tcp_keepalive {
+ u_long onoff;
+ u_long keepalivetime;
+ u_long keepaliveinterval;
+};
+#endif
+
+static void
+tcpkeepalive(struct Curl_easy *data,
+ curl_socket_t sockfd)
+{
+ int optval = data->set.tcp_keepalive?1:0;
+
+ /* only set IDLE and INTVL if setting KEEPALIVE is successful */
+ if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd);
+ }
+ else {
+#if defined(SIO_KEEPALIVE_VALS)
+ struct tcp_keepalive vals;
+ DWORD dummy;
+ vals.onoff = 1;
+ optval = curlx_sltosi(data->set.tcp_keepidle);
+ KEEPALIVE_FACTOR(optval);
+ vals.keepalivetime = optval;
+ optval = curlx_sltosi(data->set.tcp_keepintvl);
+ KEEPALIVE_FACTOR(optval);
+ vals.keepaliveinterval = 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 %d: %d",
+ (int)sockfd, WSAGetLastError());
+ }
+#else
+#ifdef TCP_KEEPIDLE
+ optval = curlx_sltosi(data->set.tcp_keepidle);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd);
+ }
+#elif defined(TCP_KEEPALIVE)
+ /* Mac OS X style */
+ optval = curlx_sltosi(data->set.tcp_keepidle);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd);
+ }
+#endif
+#ifdef TCP_KEEPINTVL
+ optval = curlx_sltosi(data->set.tcp_keepintvl);
+ KEEPALIVE_FACTOR(optval);
+ if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
+ (void *)&optval, sizeof(optval)) < 0) {
+ infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd);
+ }
+#endif
+#endif
+ }
+}
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * 'addr' should be a pointer to the correct struct to get data back, or NULL.
+ * 'sockfd' must be a pointer to a socket descriptor.
+ *
+ * If the open socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+ const struct Curl_addrinfo *ai,
+ struct Curl_sockaddr_ex *addr,
+ int transport,
+ curl_socket_t *sockfd)
+{
+ struct connectdata *conn = data->conn;
+ struct Curl_sockaddr_ex dummy;
+
+ if(!addr)
+ /* if the caller doesn't want info back, use a local temp copy */
+ addr = &dummy;
+
+ /*
+ * The Curl_sockaddr_ex structure is basically libcurl's external API
+ * curl_sockaddr structure with enough space available to directly hold
+ * any protocol-specific address structures. The variable declared here
+ * will be used to pass / receive data to/from the fopensocket callback
+ * if this has been set, before that, it is initialized from parameters.
+ */
+
+ addr->family = ai->ai_family;
+ switch(transport) {
+ case TRNSPRT_TCP:
+ addr->socktype = SOCK_STREAM;
+ addr->protocol = IPPROTO_TCP;
+ break;
+ case TRNSPRT_UNIX:
+ addr->socktype = SOCK_STREAM;
+ addr->protocol = IPPROTO_IP;
+ break;
+ default: /* UDP and QUIC */
+ addr->socktype = SOCK_DGRAM;
+ addr->protocol = IPPROTO_UDP;
+ break;
+ }
+ addr->addrlen = ai->ai_addrlen;
+
+ if(addr->addrlen > sizeof(struct Curl_sockaddr_storage))
+ addr->addrlen = sizeof(struct Curl_sockaddr_storage);
+ memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen);
+
+ if(data->set.fopensocket) {
+ /*
+ * If the opensocket callback is set, all the destination address
+ * information is passed to the callback. Depending on this information the
+ * callback may opt to abort the connection, this is indicated returning
+ * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When
+ * the callback returns a valid socket the destination address information
+ * might have been changed and this 'new' address will actually be used
+ * here to connect.
+ */
+ Curl_set_in_callback(data, true);
+ *sockfd = data->set.fopensocket(data->set.opensocket_client,
+ CURLSOCKTYPE_IPCXN,
+ (struct curl_sockaddr *)addr);
+ Curl_set_in_callback(data, false);
+ }
+ else
+ /* opensocket callback not set, so simply create the socket now */
+ *sockfd = socket(addr->family, addr->socktype, addr->protocol);
+
+ if(*sockfd == CURL_SOCKET_BAD)
+ /* no socket, no connection */
+ return CURLE_COULDNT_CONNECT;
+
+ (void)conn;
+#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
+ if(conn->scope_id && (addr->family == AF_INET6)) {
+ struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
+ sa6->sin6_scope_id = conn->scope_id;
+ }
+#endif
+
+ return CURLE_OK;
+}
+
+static int socket_close(struct Curl_easy *data, struct connectdata *conn,
+ int use_callback, curl_socket_t sock)
+{
+ if(use_callback && conn && conn->fclosesocket) {
+ int rc;
+ CF_DEBUGF(infof(data, "socket_close(%d) via callback", (int)sock));
+ Curl_multi_closed(data, sock);
+ Curl_set_in_callback(data, true);
+ rc = conn->fclosesocket(conn->closesocket_client, sock);
+ Curl_set_in_callback(data, false);
+ return rc;
+ }
+
+ CF_DEBUGF(infof(data, "socket_close(%d)", (int)sock));
+ if(conn)
+ /* tell the multi-socket code about this */
+ Curl_multi_closed(data, sock);
+
+ sclose(sock);
+
+ return 0;
+}
+
+/*
+ * Close a socket.
+ *
+ * 'conn' can be NULL, beware!
+ */
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+ curl_socket_t sock)
+{
+ return socket_close(data, conn, FALSE, sock);
+}
+
+bool Curl_socket_is_dead(curl_socket_t sock)
+{
+ int sval;
+ bool ret_val = TRUE;
+
+ sval = SOCKET_READABLE(sock, 0);
+ if(sval == 0)
+ /* timeout */
+ ret_val = FALSE;
+
+ return ret_val;
+}
+
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+ experience slow performance when you copy data to a TCP server.
+
+ https://support.microsoft.com/kb/823764
+
+ Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+ 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
+ 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)
+{
+ int val = CURL_MAX_WRITE_SIZE + 32;
+ int curval = 0;
+ int curlen = sizeof(curval);
+
+ static int detectOsState = DETECT_OS_NONE;
+
+ if(detectOsState == DETECT_OS_NONE) {
+ if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL))
+ detectOsState = DETECT_OS_VISTA_OR_LATER;
+ else
+ detectOsState = DETECT_OS_PREVISTA;
+ }
+
+ if(detectOsState == DETECT_OS_VISTA_OR_LATER)
+ return;
+
+ if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
+ if(curval > val)
+ return;
+
+ setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
+}
+#endif
+
+static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+ curl_socket_t sockfd, int af, unsigned int scope)
+{
+ struct Curl_sockaddr_storage sa;
+ struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */
+ curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
+ struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
+#endif
+
+ struct Curl_dns_entry *h = NULL;
+ unsigned short port = data->set.localport; /* use this port number, 0 for
+ "random" */
+ /* 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];
+ int error;
+#ifdef IP_BIND_ADDRESS_NO_PORT
+ int on = 1;
+#endif
+#ifndef ENABLE_IPV6
+ (void)scope;
+#endif
+
+ /*************************************************************
+ * Select device to bind socket to
+ *************************************************************/
+ if(!dev && !port)
+ /* no local kind of binding was requested */
+ return CURLE_OK;
+
+ memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
+
+ if(dev && (strlen(dev)<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;
+ }
+
+ /* interface */
+ if(!is_host) {
+#ifdef SO_BINDTODEVICE
+ /* I am not sure any other OSs than Linux that provide this feature,
+ * and at the least I cannot test. --Ben
+ *
+ * This feature allows one to tightly bind 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.
+ *
+ * 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 typically "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.
+ */
+ return CURLE_OK;
+ }
+#endif
+
+ switch(Curl_if2ip(af,
+#ifdef ENABLE_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;
+ }
+ }
+ if(!is_interface) {
+ /*
+ * This was not an interface, resolve the name as a host name
+ * or IP number
+ *
+ * Temporarily force name resolution to use only the address type
+ * of the connection. The resolve functions should really be changed
+ * to take a type parameter instead.
+ */
+ unsigned char ipver = conn->ip_version;
+ int rc;
+
+ if(af == AF_INET)
+ conn->ip_version = CURL_IPRESOLVE_V4;
+#ifdef ENABLE_IPV6
+ else if(af == AF_INET6)
+ conn->ip_version = CURL_IPRESOLVE_V6;
+#endif
+
+ rc = Curl_resolv(data, dev, 0, FALSE, &h);
+ if(rc == CURLRESOLV_PENDING)
+ (void)Curl_resolver_wait_resolv(data, &h);
+ conn->ip_version = ipver;
+
+ if(h) {
+ /* 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);
+ Curl_resolv_unlock(data, h);
+ if(af != h->addr->ai_family) {
+ /* bad IP version combo, signal the caller to try another address
+ family if available */
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ }
+ done = 1;
+ }
+ else {
+ /*
+ * provided dev was no interface (or interfaces are not supported
+ * e.g. solaris) no ip address and no domain we fail here
+ */
+ done = -1;
+ }
+ }
+
+ if(done > 0) {
+#ifdef ENABLE_IPV6
+ /* IPv6 address */
+ if(af == AF_INET6) {
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+ char *scope_ptr = strchr(myhost, '%');
+ if(scope_ptr)
+ *(scope_ptr++) = '\0';
+#endif
+ if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
+ si6->sin6_family = AF_INET6;
+ si6->sin6_port = htons(port);
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+ 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
+ present, is known to be numeric */
+ unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
+ if(scope_id > UINT_MAX)
+ return CURLE_UNSUPPORTED_PROTOCOL;
+
+ si6->sin6_scope_id = (unsigned int)scope_id;
+ }
+#endif
+ }
+ sizeof_sa = sizeof(struct sockaddr_in6);
+ }
+ else
+#endif
+ /* IPv4 address */
+ if((af == AF_INET) &&
+ (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
+ si4->sin_family = AF_INET;
+ si4->sin_port = htons(port);
+ sizeof_sa = sizeof(struct sockaddr_in);
+ }
+ }
+
+ if(done < 1) {
+ /* 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. */
+ data->state.errorbuf = FALSE;
+ failf(data, "Couldn't bind to '%s'", dev);
+ return CURLE_INTERFACE_FAILED;
+ }
+ }
+ else {
+ /* no device was given, prepare sa to match af's needs */
+#ifdef ENABLE_IPV6
+ if(af == AF_INET6) {
+ si6->sin6_family = AF_INET6;
+ si6->sin6_port = htons(port);
+ sizeof_sa = sizeof(struct sockaddr_in6);
+ }
+ else
+#endif
+ if(af == AF_INET) {
+ si4->sin_family = AF_INET;
+ si4->sin_port = htons(port);
+ sizeof_sa = sizeof(struct sockaddr_in);
+ }
+ }
+#ifdef IP_BIND_ADDRESS_NO_PORT
+ (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
+#endif
+ for(;;) {
+ if(bind(sockfd, sock, sizeof_sa) >= 0) {
+ /* we succeeded to bind */
+ struct Curl_sockaddr_storage add;
+ curl_socklen_t size = sizeof(add);
+ memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
+ if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
+ char buffer[STRERROR_LEN];
+ data->state.os_errno = error = SOCKERRNO;
+ failf(data, "getsockname() failed with errno %d: %s",
+ error, Curl_strerror(error, buffer, sizeof(buffer)));
+ return CURLE_INTERFACE_FAILED;
+ }
+ infof(data, "Local port: %hu", port);
+ conn->bits.bound = TRUE;
+ return CURLE_OK;
+ }
+
+ if(--portnum > 0) {
+ port++; /* try next port */
+ if(port == 0)
+ break;
+ infof(data, "Bind to local port %hu failed, trying next", port - 1);
+ /* We re-use/clobber the port variable here below */
+ if(sock->sa_family == AF_INET)
+ si4->sin_port = ntohs(port);
+#ifdef ENABLE_IPV6
+ else
+ si6->sin6_port = ntohs(port);
+#endif
+ }
+ else
+ break;
+ }
+ {
+ char buffer[STRERROR_LEN];
+ data->state.os_errno = error = SOCKERRNO;
+ failf(data, "bind failed with errno %d: %s",
+ error, Curl_strerror(error, buffer, sizeof(buffer)));
+ }
+
+ return CURLE_INTERFACE_FAILED;
+}
+
+/*
+ * verifyconnect() returns TRUE if the connect really has happened.
+ */
+static bool verifyconnect(curl_socket_t sockfd, int *error)
+{
+ bool rc = TRUE;
+#ifdef SO_ERROR
+ int err = 0;
+ curl_socklen_t errSize = sizeof(err);
+
+#ifdef WIN32
+ /*
+ * In October 2003 we effectively nullified this function on Windows due to
+ * problems with it using all CPU in multi-threaded cases.
+ *
+ * In May 2004, we bring it back to offer more info back on connect failures.
+ * 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
+ * just Sleep(0) would be enough?) would release whatever
+ * mutex/critical-section the ntdll call is waiting on.
+ *
+ * Someone got to verify this on Win-NT 4.0, 2000."
+ */
+
+#ifdef _WIN32_WCE
+ Sleep(0);
+#else
+ SleepEx(0, FALSE);
+#endif
+
+#endif
+
+ if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
+ err = SOCKERRNO;
+#ifdef _WIN32_WCE
+ /* Old WinCE versions don't 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 */
+ if(EBADIOCTL == err) {
+ SET_SOCKERRNO(0);
+ err = 0;
+ }
+#endif
+ if((0 == err) || (EISCONN == err))
+ /* we are connected, awesome! */
+ rc = TRUE;
+ else
+ /* This wasn't a successful connect */
+ rc = FALSE;
+ if(error)
+ *error = err;
+#else
+ (void)sockfd;
+ if(error)
+ *error = SOCKERRNO;
+#endif
+ return rc;
+}
+
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+ const char *ipaddress, int error)
+{
+ char buffer[STRERROR_LEN];
+
+ switch(error) {
+ case EINPROGRESS:
+ case EWOULDBLOCK:
+#if defined(EAGAIN)
+#if (EAGAIN) != (EWOULDBLOCK)
+ /* On some platforms EAGAIN and EWOULDBLOCK are the
+ * same value, and on others they are different, hence
+ * the odd #if
+ */
+ case EAGAIN:
+#endif
+#endif
+ return CURLE_OK;
+
+ default:
+ /* unknown error, fallthrough and try another address! */
+ infof(data, "Immediate connect fail for %s: %s",
+ ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
+ data->state.os_errno = error;
+ /* connect failed */
+ return CURLE_COULDNT_CONNECT;
+ }
+}
+
+
+struct cf_socket_ctx {
+ int transport;
+ const struct Curl_addrinfo *ai;
+ curl_socket_t sock; /* current attempt socket */
+ struct Curl_sockaddr_ex r_addr; /* remote address sock is trying */
+ char r_ip[MAX_IPADR_LEN]; /* remote IP as string */
+ int r_port; /* remote port number */
+ int error; /* errno of last failure or 0 */
+ BIT(accepted); /* socket was accepted, not connected */
+ BIT(active);
+};
+
+static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+
+ if(ctx && CURL_SOCKET_BAD != ctx->sock) {
+ if(ctx->active) {
+ /* We share our socket at cf->conn->sock[cf->sockindex] when active.
+ * If it is no longer there, someone has stolen (and hopefully
+ * closed it) and we just forget about it.
+ */
+ if(ctx->sock == cf->conn->sock[cf->sockindex]) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "cf_socket_close(%d) active"),
+ (int)ctx->sock));
+ socket_close(data, cf->conn, !ctx->accepted, ctx->sock);
+ cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+ }
+ else {
+ CF_DEBUGF(infof(data, CFMSG(cf, "cf_socket_close(%d) no longer at "
+ "conn->sock[%d], discarding"), (int)ctx->sock));
+ }
+ }
+ else {
+ /* this is our local socket, we did never publish it */
+ CF_DEBUGF(infof(data, CFMSG(cf, "cf_socket_close(%d) local"),
+ (int)ctx->sock));
+ sclose(ctx->sock);
+ }
+ ctx->sock = CURL_SOCKET_BAD;
+ ctx->active = FALSE;
+ }
+
+ cf->connected = FALSE;
+}
+
+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);
+ free(ctx);
+ cf->ctx = NULL;
+}
+
+static CURLcode cf_socket_open(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ int error = 0;
+ bool isconnected = FALSE;
+ CURLcode result = CURLE_COULDNT_CONNECT;
+ bool is_tcp;
+ const char *ipmsg;
+
+ (void)data;
+ ctx->sock = CURL_SOCKET_BAD;
+ result = Curl_socket_open(data, ctx->ai, &ctx->r_addr,
+ ctx->transport, &ctx->sock);
+ if(result)
+ goto out;
+
+ /* store remote address and port used in this connection attempt */
+ if(!Curl_addr2string(&ctx->r_addr.sa_addr, ctx->r_addr.addrlen,
+ ctx->r_ip, &ctx->r_port)) {
+ char buffer[STRERROR_LEN];
+
+ ctx->error = errno;
+ /* malformed address or bug in inet_ntop, try next address */
+ failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
+ errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+ result = CURLE_FAILED_INIT;
+ goto out;
+ }
+#ifdef ENABLE_IPV6
+ if(ctx->r_addr.family == AF_INET6)
+ ipmsg = " Trying [%s]:%d...";
+ else
+#endif
+ ipmsg = " Trying %s:%d...";
+ infof(data, ipmsg, ctx->r_ip, ctx->r_port);
+
+#ifdef ENABLE_IPV6
+ is_tcp = (ctx->r_addr.family == AF_INET
+ || ctx->r_addr.family == AF_INET6) &&
+ ctx->r_addr.socktype == SOCK_STREAM;
+#else
+ is_tcp = (ctx->r_addr.family == AF_INET) &&
+ ctx->r_addr.socktype == SOCK_STREAM;
+#endif
+ if(is_tcp && data->set.tcp_nodelay)
+ tcpnodelay(data, ctx->sock);
+
+ nosigpipe(data, ctx->sock);
+
+ Curl_sndbufset(ctx->sock);
+
+ if(is_tcp && data->set.tcp_keepalive)
+ tcpkeepalive(data, ctx->sock);
+
+ if(data->set.fsockopt) {
+ /* activate callback for setting socket options */
+ Curl_set_in_callback(data, true);
+ error = data->set.fsockopt(data->set.sockopt_client,
+ ctx->sock,
+ CURLSOCKTYPE_IPCXN);
+ Curl_set_in_callback(data, false);
+
+ if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
+ isconnected = TRUE;
+ else if(error) {
+ result = CURLE_ABORTED_BY_CALLBACK;
+ goto out;
+ }
+ }
+
+ /* possibly bind the local end to an IP, interface or port */
+ if(ctx->r_addr.family == AF_INET
+#ifdef ENABLE_IPV6
+ || ctx->r_addr.family == AF_INET6
+#endif
+ ) {
+ result = bindlocal(data, cf->conn, ctx->sock, ctx->r_addr.family,
+ Curl_ipv6_scope(&ctx->r_addr.sa_addr));
+ if(result) {
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ /* The address family is not supported on this interface.
+ We can continue trying addresses */
+ result = CURLE_COULDNT_CONNECT;
+ }
+ goto out;
+ }
+ }
+
+ /* set socket non-blocking */
+ (void)curlx_nonblock(ctx->sock, TRUE);
+
+out:
+ if(result) {
+ if(ctx->sock != CURL_SOCKET_BAD) {
+ socket_close(data, cf->conn, TRUE, ctx->sock);
+ ctx->sock = CURL_SOCKET_BAD;
+ }
+ }
+ else if(isconnected) {
+ cf->connected = TRUE;
+ }
+ return result;
+}
+
+static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef TCP_FASTOPEN_CONNECT
+ int optval = 1;
+#endif
+ int rc = -1;
+
+ (void)data;
+ if(cf->conn->bits.tcp_fastopen) {
+#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
+# if defined(HAVE_BUILTIN_AVAILABLE)
+ /* while connectx function is available since macOS 10.11 / iOS 9,
+ it did not have the interface declared correctly until
+ Xcode 9 / macOS SDK 10.13 */
+ if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
+ sa_endpoints_t endpoints;
+ endpoints.sae_srcif = 0;
+ endpoints.sae_srcaddr = NULL;
+ endpoints.sae_srcaddrlen = 0;
+ endpoints.sae_dstaddr = &ctx->r_addr.sa_addr;
+ endpoints.sae_dstaddrlen = ctx->r_addr.addrlen;
+
+ rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY,
+ CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
+ NULL, 0, NULL, NULL);
+ }
+ else {
+ rc = connect(ctx->sock, &ctx->r_addr.sa_addr, ctx->r_addr.addrlen);
+ }
+# else
+ rc = connect(ctx->sock, &ctx->r_addr.sa_addr, ctx->r_addr.addrlen);
+# endif /* HAVE_BUILTIN_AVAILABLE */
+#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);
+
+ rc = connect(ctx->sock, &ctx->r_addr.sa_addr, ctx->r_addr.addrlen);
+#elif defined(MSG_FASTOPEN) /* old Linux */
+ if(conn->given->flags & PROTOPT_SSL)
+ rc = connect(ctx->sock, &ctx->r_addr.sa_addr, ctx->r_addr.addrlen);
+ else
+ rc = 0; /* Do nothing */
+#endif
+ }
+ else {
+ rc = connect(ctx->sock, &ctx->r_addr.sa_addr, ctx->r_addr.addrlen);
+ }
+ return rc;
+}
+
+static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_COULDNT_CONNECT;
+ int rc = 0;
+
+ CF_DEBUGF(infof(data, CFMSG(cf, "connect")));
+ (void)data;
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ /* TODO: need to support blocking connect? */
+ if(blocking)
+ return CURLE_UNSUPPORTED_PROTOCOL;
+
+ *done = FALSE; /* a very negative world view is best */
+ if(ctx->sock == CURL_SOCKET_BAD) {
+
+ result = cf_socket_open(cf, data);
+ if(result)
+ goto out;
+
+ CF_DEBUGF(infof(data, CFMSG(cf, "connect opened(%d)"), (int)ctx->sock));
+ /* Connect TCP socket */
+ rc = do_connect(cf, data);
+ if(-1 == rc) {
+ result = Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
+ goto out;
+ }
+ }
+
+#ifdef mpeix
+ /* Call this function once now, and ignore the results. We do this to
+ "clear" the error state on the socket so that we can later read it
+ reliably. This is reported necessary on the MPE/iX operating
+ system. */
+ (void)verifyconnect(ctx->sock, NULL);
+#endif
+ /* check socket for connect */
+ rc = SOCKET_WRITABLE(ctx->sock, 0);
+
+ if(rc == 0) { /* no connection yet */
+ return CURLE_OK;
+ }
+ else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) {
+ if(verifyconnect(ctx->sock, &ctx->error)) {
+ /* we are connected with TCP, awesome! */
+ *done = TRUE;
+ cf->connected = TRUE;
+ return CURLE_OK;
+ }
+ }
+ else if(rc & CURL_CSELECT_ERR) {
+ (void)verifyconnect(ctx->sock, &ctx->error);
+ result = CURLE_COULDNT_CONNECT;
+ }
+
+out:
+ if(result) {
+ if(ctx->error) {
+ data->state.os_errno = ctx->error;
+ SET_SOCKERRNO(ctx->error);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ {
+ char buffer[STRERROR_LEN];
+ infof(data, "connect to %s port %u failed: %s",
+ ctx->r_ip, ctx->r_port,
+ Curl_strerror(ctx->error, buffer, sizeof(buffer)));
+ }
+#endif
+ }
+ if(ctx->sock != CURL_SOCKET_BAD) {
+ socket_close(data, cf->conn, TRUE, ctx->sock);
+ ctx->sock = CURL_SOCKET_BAD;
+ }
+ *done = FALSE;
+ }
+ return result;
+}
+
+static void cf_socket_get_host(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const char **phost,
+ const char **pdisplay_host,
+ int *pport)
+{
+ (void)data;
+ *phost = cf->conn->host.name;
+ *pdisplay_host = cf->conn->host.dispname;
+ *pport = cf->conn->port;
+}
+
+static int cf_socket_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ int rc = GETSOCK_BLANK;
+
+ (void)data;
+ if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) {
+ socks[0] = ctx->sock;
+ rc |= GETSOCK_WRITESOCK(0);
+ }
+
+ return rc;
+}
+
+static bool cf_socket_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ int readable;
+
+ (void)data;
+ readable = SOCKET_READABLE(ctx->sock, 0);
+ return (readable > 0 && (readable & CURL_CSELECT_IN));
+}
+
+static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
+{
+ ssize_t nwritten;
+
+ DEBUGASSERT(data->conn == cf->conn);
+ nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err);
+ CF_DEBUGF(infof(data, CFMSG(cf, "send(len=%d) -> %d, err=%d"),
+ len, (int)nwritten, *err));
+ return nwritten;
+}
+
+static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
+{
+ ssize_t nread;
+
+ DEBUGASSERT(data->conn == cf->conn);
+ nread = Curl_recv_plain(data, cf->sockindex, buf, len, err);
+ CF_DEBUGF(infof(data, CFMSG(cf, "recv(len=%d) -> %d, err=%d"),
+ len, (int)nread, *err));
+ return nread;
+}
+
+/* retrieves the start/end point information of a socket of an established
+ connection */
+static void conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
+ char *local_ip, int *local_port)
+{
+#ifdef HAVE_GETSOCKNAME
+ char buffer[STRERROR_LEN];
+ struct Curl_sockaddr_storage ssloc;
+ curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage);
+
+ memset(&ssloc, 0, sizeof(ssloc));
+ if(getsockname(sockfd, (struct sockaddr*) &ssloc, &slen)) {
+ int error = SOCKERRNO;
+ failf(data, "getsockname() failed with errno %d: %s",
+ error, Curl_strerror(error, buffer, sizeof(buffer)));
+ return;
+ }
+ if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
+ local_ip, local_port)) {
+ failf(data, "ssloc inet_ntop() failed with errno %d: %s",
+ errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+ return;
+ }
+#else
+ (void)data;
+ (void)sockfd;
+ (void)local_ip;
+ (void)local_port;
+#endif
+}
+
+/* retrieves the start/end point information of a socket of an established
+ connection */
+static void conninfo_remote(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t sockfd)
+{
+#ifdef HAVE_GETPEERNAME
+ char buffer[STRERROR_LEN];
+ struct Curl_sockaddr_storage ssrem;
+ curl_socklen_t plen;
+ int port;
+ plen = sizeof(struct Curl_sockaddr_storage);
+ memset(&ssrem, 0, sizeof(ssrem));
+ if(getpeername(sockfd, (struct sockaddr*) &ssrem, &plen)) {
+ int error = SOCKERRNO;
+ failf(data, "getpeername() failed with errno %d: %s",
+ error, Curl_strerror(error, buffer, sizeof(buffer)));
+ return;
+ }
+ if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
+ conn->primary_ip, &port)) {
+ failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+ errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+ return;
+ }
+#else
+ (void)data;
+ (void)conn;
+ (void)sockfd;
+#endif
+}
+
+static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ char local_ip[MAX_IPADR_LEN] = "";
+ int local_port = -1;
+
+ /* use this socket from now on */
+ DEBUGASSERT(ctx);
+ DEBUGASSERT(cf->conn);
+ cf->conn->sock[cf->sockindex] = ctx->sock;
+
+ if(cf->sockindex == FIRSTSOCKET) {
+ cf->conn->ip_addr = ctx->ai;
+ #ifdef ENABLE_IPV6
+ cf->conn->bits.ipv6 = (ctx->ai->ai_family == AF_INET6)? TRUE : FALSE;
+ #endif
+ conninfo_remote(data, cf->conn, ctx->sock);
+ conninfo_local(data, ctx->sock, local_ip, &local_port);
+ Curl_persistconninfo(data, cf->conn, local_ip, local_port);
+ }
+ ctx->active = TRUE;
+}
+
+static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
+{
+ (void)arg1;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_CONN_INFO_UPDATE:
+ cf_socket_active(cf, data);
+ break;
+ }
+ return CURLE_OK;
+}
+
+static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ int sval;
+
+ (void)data;
+ if(!ctx || ctx->sock == CURL_SOCKET_BAD)
+ return FALSE;
+
+ sval = SOCKET_READABLE(ctx->sock, 0);
+ if(sval == 0) {
+ /* timeout */
+ return TRUE;
+ }
+ else if(sval & CURL_CSELECT_ERR) {
+ /* socket is in an error state */
+ return FALSE;
+ }
+ else if(sval & CURL_CSELECT_IN) {
+ /* readable with no error. could still be closed */
+/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */
+#ifdef MSG_PEEK
+ /* use the socket */
+ char buf;
+ if(recv((RECV_TYPE_ARG1)ctx->sock, (RECV_TYPE_ARG2)&buf,
+ (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
+ return FALSE; /* FIN received */
+ }
+#endif
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static const struct Curl_cftype cft_tcp = {
+ "TCP",
+ CF_TYPE_IP_CONNECT,
+ cf_socket_destroy,
+ cf_tcp_connect,
+ cf_socket_close,
+ cf_socket_get_host,
+ cf_socket_get_select_socks,
+ cf_socket_data_pending,
+ cf_socket_send,
+ cf_socket_recv,
+ cf_socket_cntrl,
+ cf_socket_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+ struct cf_socket_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL;
+ CURLcode result;
+
+ (void)data;
+ (void)conn;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ ctx->transport = TRNSPRT_TCP;
+ ctx->ai = ai;
+ ctx->sock = CURL_SOCKET_BAD;
+
+ result = Curl_cf_create(&cf, &cft_tcp, ctx);
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
+}
+
+static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct cf_socket_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_COULDNT_CONNECT;
+
+ (void)blocking;
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+ *done = FALSE;
+ if(ctx->sock == CURL_SOCKET_BAD) {
+ result = cf_socket_open(cf, data);
+ if(result) {
+ if(ctx->sock != CURL_SOCKET_BAD) {
+ socket_close(data, cf->conn, TRUE, ctx->sock);
+ ctx->sock = CURL_SOCKET_BAD;
+ }
+ }
+ else {
+ *done = TRUE;
+ cf->connected = TRUE;
+ }
+ }
+ return result;
+}
+
+static const struct Curl_cftype cft_udp = {
+ "UDP",
+ CF_TYPE_IP_CONNECT,
+ cf_socket_destroy,
+ cf_udp_connect,
+ cf_socket_close,
+ cf_socket_get_host,
+ cf_socket_get_select_socks,
+ cf_socket_data_pending,
+ cf_socket_send,
+ cf_socket_recv,
+ cf_socket_cntrl,
+ cf_socket_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+ struct cf_socket_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL;
+ CURLcode result;
+
+ (void)data;
+ (void)conn;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ ctx->transport = TRNSPRT_UDP;
+ ctx->ai = ai;
+ ctx->sock = CURL_SOCKET_BAD;
+
+ result = Curl_cf_create(&cf, &cft_udp, ctx);
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
+}
+
+/* this is the TCP filter which can also handle this case */
+static const struct Curl_cftype cft_unix = {
+ "UNIX",
+ CF_TYPE_IP_CONNECT,
+ cf_socket_destroy,
+ cf_tcp_connect,
+ cf_socket_close,
+ cf_socket_get_host,
+ cf_socket_get_select_socks,
+ cf_socket_data_pending,
+ cf_socket_send,
+ cf_socket_recv,
+ cf_socket_cntrl,
+ cf_socket_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+ struct cf_socket_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL;
+ CURLcode result;
+
+ (void)data;
+ (void)conn;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ ctx->transport = TRNSPRT_UNIX;
+ ctx->ai = ai;
+ ctx->sock = CURL_SOCKET_BAD;
+
+ result = Curl_cf_create(&cf, &cft_unix, ctx);
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
+}
+
+static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ /* we start accepted, if we ever close, we cannot go on */
+ (void)data;
+ (void)blocking;
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+ return CURLE_FAILED_INIT;
+}
+
+static const struct Curl_cftype cft_tcp_accept = {
+ "TCP-ACCEPT",
+ CF_TYPE_IP_CONNECT,
+ cf_socket_destroy,
+ cf_tcp_accept_connect,
+ cf_socket_close,
+ cf_socket_get_host, /* TODO: not accurate */
+ cf_socket_get_select_socks,
+ cf_socket_data_pending,
+ cf_socket_send,
+ cf_socket_recv,
+ cf_socket_cntrl,
+ cf_socket_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
+};
+
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex, curl_socket_t *s)
+{
+ CURLcode result;
+ struct Curl_cfilter *cf = NULL;
+ struct cf_socket_ctx *ctx = NULL;
+
+ /* replace any existing */
+ Curl_conn_cf_discard_all(data, conn, sockindex);
+ DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD);
+
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ ctx->transport = conn->transport;
+ ctx->sock = *s;
+ ctx->accepted = FALSE;
+ result = Curl_cf_create(&cf, &cft_tcp_accept, ctx);
+ if(result)
+ goto out;
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+
+ conn->sock[sockindex] = ctx->sock;
+ ctx->active = TRUE;
+ cf->connected = TRUE;
+ CF_DEBUGF(infof(data, CFMSG(cf, "Curl_conn_tcp_listen_set(%d)"),
+ (int)ctx->sock));
+
+out:
+ if(result) {
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+ return result;
+}
+
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex, curl_socket_t *s)
+{
+ struct Curl_cfilter *cf = NULL;
+ struct cf_socket_ctx *ctx = NULL;
+
+ cf = conn->cfilter[sockindex];
+ if(!cf || cf->cft != &cft_tcp_accept)
+ return CURLE_FAILED_INIT;
+
+ ctx = cf->ctx;
+ /* discard the listen socket */
+ socket_close(data, conn, TRUE, ctx->sock);
+ ctx->sock = *s;
+ conn->sock[sockindex] = ctx->sock;
+ ctx->active = TRUE;
+ ctx->accepted = TRUE;
+ cf->connected = TRUE;
+ CF_DEBUGF(infof(data, CFMSG(cf, "Curl_conn_tcp_accepted_set(%d)"),
+ (int)ctx->sock));
+
+ return CURLE_OK;
+}
+
+bool Curl_cf_is_socket(struct Curl_cfilter *cf)
+{
+ return cf && (cf->cft == &cft_tcp || cf->cft == &cft_udp ||
+ cf->cft == &cft_unix || cf->cft == &cft_tcp_accept);
+}
+
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+ curl_socket_t *psock,
+ const struct Curl_sockaddr_ex **paddr,
+ const char **premote_ip_str,
+ int *premote_port)
+{
+ if(Curl_cf_is_socket(cf) && cf->ctx) {
+ struct cf_socket_ctx *ctx = cf->ctx;
+
+ *psock = ctx->sock;
+ *paddr = &ctx->r_addr;
+ *premote_ip_str = ctx->r_ip;
+ *premote_port = ctx->r_port;
+ return CURLE_OK;
+ }
+ return CURLE_FAILED_INIT;
+}
+
diff --git a/lib/cf-socket.h b/lib/cf-socket.h
new file mode 100644
index 000000000..459d51d17
--- /dev/null
+++ b/lib/cf-socket.h
@@ -0,0 +1,169 @@
+#ifndef HEADER_CURL_CF_SOCKET_H
+#define HEADER_CURL_CF_SOCKET_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, 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 "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */
+#include "sockaddr.h"
+
+struct Curl_addrinfo;
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_sockaddr_ex;
+
+/*
+ * The Curl_sockaddr_ex structure is basically libcurl's external API
+ * curl_sockaddr structure with enough space available to directly hold any
+ * protocol-specific address structures. The variable declared here will be
+ * used to pass / receive data to/from the fopensocket callback if this has
+ * been set, before that, it is initialized from parameters.
+ */
+struct Curl_sockaddr_ex {
+ int family;
+ int socktype;
+ int protocol;
+ unsigned int addrlen;
+ union {
+ struct sockaddr addr;
+ struct Curl_sockaddr_storage buff;
+ } _sa_ex_u;
+};
+#define sa_addr _sa_ex_u.addr
+
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open
+ * socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+ const struct Curl_addrinfo *ai,
+ struct Curl_sockaddr_ex *addr,
+ int transport,
+ curl_socket_t *sockfd);
+
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+ curl_socket_t sock);
+
+/*
+ * This function should return TRUE if the socket is to be assumed to
+ * be dead. Most commonly this happens when the server has closed the
+ * connection due to inactivity.
+ */
+bool Curl_socket_is_dead(curl_socket_t sock);
+
+/**
+ * Determine the curl code for a socket connect() == -1 with errno.
+ */
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+ const char *ipaddress, int error);
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+ experience slow performance when you copy data to a TCP server.
+
+ https://support.microsoft.com/kb/823764
+
+ Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+ Buffer Size
+
+*/
+void Curl_sndbufset(curl_socket_t sockfd);
+#else
+#define Curl_sndbufset(y) Curl_nop_stmt
+#endif
+
+/**
+ * Creates a cfilter that opens a TCP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+/**
+ * Creates a cfilter that opens a UDP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+/**
+ * Creates a cfilter that opens a UNIX socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+/**
+ * Creates a cfilter that keeps a listening socket.
+ */
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ curl_socket_t *s);
+
+/**
+ * Replace the listen socket with the accept()ed one.
+ */
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ curl_socket_t *s);
+
+/**
+ * Return TRUE iff `cf` is a socket filter.
+ */
+bool Curl_cf_is_socket(struct Curl_cfilter *cf);
+
+/**
+ * Peek at the socket and remote ip/port the socket filter is using.
+ * The filter owns all returned values.
+ * Returns error if the filter is of invalid type.
+ */
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+ curl_socket_t *psock,
+ const struct Curl_sockaddr_ex **paddr,
+ const char **premote_ip_str,
+ int *premote_port);
+
+#endif /* HEADER_CURL_CF_SOCKET_H */
diff --git a/lib/cfilters.c b/lib/cfilters.c
index 348c09d7e..6408e0bc5 100644
--- a/lib/cfilters.c
+++ b/lib/cfilters.c
@@ -34,9 +34,6 @@
#include "multiif.h"
#include "progress.h"
#include "warnless.h"
-#include "http_proxy.h"
-#include "socks.h"
-#include "vtls/vtls.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -47,30 +44,15 @@
#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
#endif
+#define DEBUG_CF 0
-void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
- (void)cf;
- (void)data;
-}
-
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const struct Curl_dns_entry *remotehost)
-{
- DEBUGASSERT(cf->next);
- return cf->next->cft->setup(cf->next, data, remotehost);
-}
-
-void Curl_cf_def_attach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
-{
- (void)cf;
- (void)data;
-}
+#if DEBUG_CF
+#define CF_DEBUGF(x) x
+#else
+#define CF_DEBUGF(x) do { } while(0)
+#endif
-void Curl_cf_def_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
+void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data)
{
(void)cf;
(void)data;
@@ -78,65 +60,114 @@ void Curl_cf_def_detach_data(struct Curl_cfilter *cf,
void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- DEBUGASSERT(cf->next);
cf->connected = FALSE;
- cf->next->cft->close(cf->next, data);
+ if(cf->next)
+ cf->next->cft->close(cf->next, data);
}
CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
- DEBUGASSERT(cf->next);
- return cf->next->cft->connect(cf->next, data, blocking, done);
+ CURLcode result;
+
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+ if(cf->next) {
+ result = cf->next->cft->connect(cf->next, data, blocking, done);
+ if(!result && *done) {
+ cf->connected = TRUE;
+ }
+ return result;
+ }
+ *done = FALSE;
+ return CURLE_FAILED_INIT;
}
void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data,
const char **phost, const char **pdisplay_host,
int *pport)
{
- DEBUGASSERT(cf->next);
- cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+ if(cf->next)
+ cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+ else {
+ *phost = cf->conn->host.name;
+ *pdisplay_host = cf->conn->host.dispname;
+ *pport = cf->conn->port;
+ }
}
int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *socks)
{
- DEBUGASSERT(cf->next);
- return cf->next->cft->get_select_socks(cf->next, data, socks);
+ return cf->next?
+ cf->next->cft->get_select_socks(cf->next, data, socks) : 0;
}
bool Curl_cf_def_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
- DEBUGASSERT(cf->next);
- return cf->next->cft->has_data_pending(cf->next, data);
+ return cf->next?
+ cf->next->cft->has_data_pending(cf->next, data) : FALSE;
}
ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
- DEBUGASSERT(cf->next);
- return cf->next->cft->do_send(cf->next, data, buf, len, err);
+ return cf->next?
+ cf->next->cft->do_send(cf->next, data, buf, len, err) :
+ CURLE_RECV_ERROR;
}
ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
- DEBUGASSERT(cf->next);
- return cf->next->cft->do_recv(cf->next, data, buf, len, err);
+ return cf->next?
+ cf->next->cft->do_recv(cf->next, data, buf, len, err) :
+ CURLE_SEND_ERROR;
}
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
- struct connectdata *conn, int index)
+bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- struct Curl_cfilter *cfn, *cf = conn->cfilter[index];
+ return cf->next?
+ cf->next->cft->is_alive(cf->next, data) :
+ FALSE; /* pessimistic in absence of data */
+}
+
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ return cf->next?
+ cf->next->cft->keep_alive(cf->next, data) :
+ CURLE_OK;
+}
+
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2)
+{
+ return cf->next?
+ cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+ CURLE_UNKNOWN_OPTION;
+}
+
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cfn, *cf = *pcf;
if(cf) {
- conn->cfilter[index] = NULL;
+ *pcf = NULL;
while(cf) {
cfn = cf->next;
+ /* prevent destroying filter to mess with its sub-chain, since
+ * we have the reference now and will call destroy on it.
+ */
+ cf->next = NULL;
cf->cft->destroy(cf, data);
free(cf);
cf = cfn;
@@ -144,6 +175,12 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data,
}
}
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+ struct connectdata *conn, int index)
+{
+ Curl_conn_cf_discard_chain(&conn->cfilter[index], data);
+}
+
void Curl_conn_close(struct Curl_easy *data, int index)
{
struct Curl_cfilter *cf;
@@ -170,12 +207,11 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf,
}
if(cf) {
nread = cf->cft->do_recv(cf, data, buf, len, code);
- /* DEBUGF(infof(data, "Curl_conn_recv(handle=%p, index=%d)"
- "-> %ld, err=%d", data, num, nread, *code));*/
+ CF_DEBUGF(infof(data, "Curl_conn_recv(handle=%p, index=%d)"
+ "-> %ld, err=%d", data, num, nread, *code));
return nread;
}
- failf(data, "no filter connected, conn=%ld, sockindex=%d",
- data->conn->connection_id, num);
+ failf(data, CMSGI(data->conn, num, "recv: no filter connected"));
*code = CURLE_FAILED_INIT;
return -1;
}
@@ -194,12 +230,12 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int num,
}
if(cf) {
nwritten = cf->cft->do_send(cf, data, mem, len, code);
- /* DEBUGF(infof(data, "Curl_conn_send(handle=%p, index=%d, len=%ld)"
- " -> %ld, err=%d", data, num, len, nwritten, *code));*/
+ CF_DEBUGF(infof(data, "Curl_conn_send(handle=%p, index=%d, len=%ld)"
+ " -> %ld, err=%d", data, num, len, nwritten, *code));
return nwritten;
}
- failf(data, "no filter connected, conn=%ld, sockindex=%d",
- data->conn->connection_id, num);
+ failf(data, CMSGI(data->conn, num, "send: no filter connected"));
+ DEBUGASSERT(0);
*code = CURLE_FAILED_INIT;
return -1;
}
@@ -234,12 +270,31 @@ void Curl_conn_cf_add(struct Curl_easy *data,
DEBUGASSERT(!cf->conn);
DEBUGASSERT(!cf->next);
- DEBUGF(infof(data, CMSGI(conn, index, "cf_add(filter=%s)"),
- cf->cft->name));
cf->next = conn->cfilter[index];
cf->conn = conn;
cf->sockindex = index;
conn->cfilter[index] = cf;
+ CF_DEBUGF(infof(data, CFMSG(cf, "added")));
+}
+
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_cfilter *cf_new)
+{
+ struct Curl_cfilter *tail, **pnext;
+
+ DEBUGASSERT(cf_at);
+ DEBUGASSERT(cf_new);
+ DEBUGASSERT(!cf_new->conn);
+
+ tail = cf_at->next;
+ cf_at->next = cf_new;
+ do {
+ cf_new->conn = cf_at->conn;
+ cf_new->sockindex = cf_at->sockindex;
+ pnext = &cf_new->next;
+ cf_new = cf_new->next;
+ } while(cf_new);
+ *pnext = tail;
}
void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -259,96 +314,46 @@ void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
free(cf);
}
-ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
- const void *buf, size_t len, CURLcode *err)
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
{
- return cf->cft->do_send(cf, data, buf, len, err);
+ if(cf)
+ return cf->cft->connect(cf, data, blocking, done);
+ return CURLE_FAILED_INIT;
}
-ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
- char *buf, size_t len, CURLcode *err)
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- return cf->cft->do_recv(cf, data, buf, len, err);
+ if(cf)
+ cf->cft->close(cf, data);
}
-CURLcode Curl_conn_setup(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- const struct Curl_dns_entry *remotehost,
- int ssl_mode)
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
{
- struct Curl_cfilter *cf;
- CURLcode result;
-
- DEBUGASSERT(data);
- /* If no filter is set, we have the "default" setup of connection filters.
- * The filter chain from bottom to top will be:
- * - SOCKET socket filter for outgoing connection to remotehost
- * if http_proxy tunneling is engaged:
- * - SSL if proxytype is CURLPROXY_HTTPS
- * - HTTP_PROXY_TUNNEL
- * otherwise, if socks_proxy is engaged:
- * - SOCKS_PROXY_TUNNEL
- * - SSL if conn->handler has PROTOPT_SSL
- */
- if(!conn->cfilter[sockindex]) {
- DEBUGF(infof(data, DMSGI(data, sockindex, "setup, init filter chain")));
- result = Curl_conn_socket_set(data, conn, sockindex);
- if(result)
- goto out;
-
-#ifndef CURL_DISABLE_PROXY
- if(conn->bits.socksproxy) {
- result = Curl_conn_socks_proxy_add(data, conn, sockindex);
- if(result)
- goto out;
- }
-
- if(conn->bits.httpproxy) {
-#ifdef USE_SSL
- if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
- result = Curl_ssl_cfilter_proxy_add(data, conn, sockindex);
- if(result)
- goto out;
- }
-#endif /* USE_SSL */
-
-#if !defined(CURL_DISABLE_HTTP)
- if(conn->bits.tunnel_proxy) {
- result = Curl_conn_http_proxy_add(data, conn, sockindex);
- if(result)
- goto out;
- }
- }
-#endif /* !CURL_DISABLE_HTTP */
-
- /* HAProxy protocol comes *before* SSL, see #10165 */
- if(data->set.haproxyprotocol) {
- result = Curl_conn_haproxy_add(data, conn, sockindex);
- if(result)
- goto out;
- }
-#endif /* !CURL_DISABLE_PROXY */
-
-#ifdef USE_SSL
- if(ssl_mode == CURL_CF_SSL_ENABLE
- || (ssl_mode != CURL_CF_SSL_DISABLE
- && conn->handler->flags & PROTOPT_SSL)) {
- result = Curl_ssl_cfilter_add(data, conn, sockindex);
- if(result)
- goto out;
- }
-#else
- (void)ssl_mode;
-#endif /* USE_SSL */
+ if(cf)
+ return cf->cft->get_select_socks(cf, data, socks);
+ return 0;
+}
- }
- DEBUGASSERT(conn->cfilter[sockindex]);
- cf = data->conn->cfilter[sockindex];
- result = cf->cft->setup(cf, data, remotehost);
+ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
+{
+ if(cf)
+ return cf->cft->do_send(cf, data, buf, len, err);
+ *err = CURLE_SEND_ERROR;
+ return -1;
+}
-out:
- return result;
+ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
+{
+ if(cf)
+ return cf->cft->do_recv(cf, data, buf, len, err);
+ *err = CURLE_RECV_ERROR;
+ return -1;
}
CURLcode Curl_conn_connect(struct Curl_easy *data,
@@ -357,16 +362,33 @@ CURLcode Curl_conn_connect(struct Curl_easy *data,
bool *done)
{
struct Curl_cfilter *cf;
- CURLcode result;
+ CURLcode result = CURLE_OK;
DEBUGASSERT(data);
+ DEBUGASSERT(data->conn);
cf = data->conn->cfilter[sockindex];
DEBUGASSERT(cf);
- result = cf->cft->connect(cf, data, blocking, done);
+ *done = cf->connected;
+ if(!*done) {
+ result = cf->cft->connect (cf, data, blocking, done);
+ if(!result && *done) {
+ data->conn->keepalive = Curl_now();
+ }
+ }
- DEBUGF(infof(data, DMSGI(data, sockindex, "connect(block=%d)-> %d, done=%d"),
- blocking, result, *done));
+#ifdef DEBUGBUILD
+ if(result) {
+ CF_DEBUGF(infof(data, DMSGI(data, sockindex, "connect()-> %d, done=%d"),
+ result, *done));
+ }
+ else if(!*done) {
+ while(cf->next && !cf->next->connected)
+ cf = cf->next;
+ CF_DEBUGF(infof(data, DMSGI(data, sockindex, "connect()-> waiting for %s"),
+ cf->cft->name));
+ }
+#endif
return result;
}
@@ -393,11 +415,10 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex)
return FALSE;
}
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex)
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
{
- struct Curl_cfilter *cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
- (void)data;
for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_SSL)
return TRUE;
@@ -407,6 +428,19 @@ bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex)
return FALSE;
}
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
+{
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+ for(; cf; cf = cf->next) {
+ if(cf->cft->flags & CF_TYPE_MULTIPLEX)
+ return TRUE;
+ if(cf->cft->flags & CF_TYPE_IP_CONNECT
+ || cf->cft->flags & CF_TYPE_SSL)
+ return FALSE;
+ }
+ return FALSE;
+}
bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
{
@@ -446,40 +480,6 @@ int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex,
return GETSOCK_BLANK;
}
-void Curl_conn_attach_data(struct connectdata *conn,
- struct Curl_easy *data)
-{
- size_t i;
- struct Curl_cfilter *cf;
-
- for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
- cf = conn->cfilter[i];
- if(cf) {
- while(cf) {
- cf->cft->attach_data(cf, data);
- cf = cf->next;
- }
- }
- }
-}
-
-void Curl_conn_detach_data(struct connectdata *conn,
- struct Curl_easy *data)
-{
- size_t i;
- struct Curl_cfilter *cf;
-
- for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
- cf = conn->cfilter[i];
- if(cf) {
- while(cf) {
- cf->cft->detach_data(cf, data);
- cf = cf->next;
- }
- }
- }
-}
-
void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
const char **phost, const char **pdisplay_host,
int *pport)
@@ -502,4 +502,130 @@ void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
}
}
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
+{
+ (void)cf;
+ (void)data;
+ (void)event;
+ (void)arg1;
+ (void)arg2;
+ return CURLE_OK;
+}
+
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool ignore_result,
+ int event, int arg1, void *arg2)
+{
+ CURLcode result = CURLE_OK;
+
+ for(; cf; cf = cf->next) {
+ if(Curl_cf_def_cntrl == cf->cft->cntrl)
+ continue;
+ result = cf->cft->cntrl(cf, data, event, arg1, arg2);
+ if(!ignore_result && result)
+ break;
+ }
+ return result;
+}
+
+static CURLcode cf_cntrl_all(struct connectdata *conn,
+ struct Curl_easy *data,
+ bool ignore_result,
+ int event, int arg1, void *arg2)
+{
+ CURLcode result = CURLE_OK;
+ size_t i;
+
+ for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
+ result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result,
+ event, arg1, arg2);
+ if(!ignore_result && result)
+ break;
+ }
+ return result;
+}
+
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+ struct Curl_easy *data)
+{
+ cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL);
+}
+
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+ struct Curl_easy *data)
+{
+ cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data)
+{
+ return cf_cntrl_all(data->conn, data, FALSE,
+ CF_CTRL_DATA_SETUP, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data)
+{
+ return cf_cntrl_all(data->conn, data, FALSE,
+ CF_CTRL_DATA_IDLE, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data)
+{
+ cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature)
+{
+ cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL);
+}
+
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause)
+{
+ return cf_cntrl_all(data->conn, data, FALSE,
+ CF_CTRL_DATA_PAUSE, do_pause, NULL);
+}
+
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+}
+
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn)
+{
+ struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
+ return !cf->conn->bits.close && cf && cf->cft->is_alive(cf, data);
+}
+
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf = conn->cfilter[sockindex];
+ return cf? cf->cft->keep_alive(cf, data) : CURLE_OK;
+}
+
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ CURLcode result;
+ int n = 0;
+
+ struct Curl_cfilter *cf = conn->cfilter[sockindex];
+ result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT,
+ &n, NULL) : CURLE_UNKNOWN_OPTION;
+ return (result || n <= 0)? 1 : (size_t)n;
+}
diff --git a/lib/cfilters.h b/lib/cfilters.h
index 4b81b42e6..263c62f33 100644
--- a/lib/cfilters.h
+++ b/lib/cfilters.h
@@ -36,11 +36,6 @@ struct connectdata;
typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf,
struct Curl_easy *data);
-/* Setup the connection for `data`, using destination `remotehost`.
- */
-typedef CURLcode Curl_cft_setup(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const struct Curl_dns_entry *remotehost);
typedef void Curl_cft_close(struct Curl_cfilter *cf,
struct Curl_easy *data);
@@ -89,29 +84,77 @@ typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf,
size_t len, /* amount to read */
CURLcode *err); /* error to return */
-typedef void Curl_cft_attach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data);
-typedef void Curl_cft_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data);
+typedef bool Curl_cft_conn_is_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+
+typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
/**
- * The easy handle `data` is being detached (no longer served)
- * by connection `conn`. All filters are informed to release any resources
- * related to `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * Events/controls for connection filters, their arguments and
+ * return code handling. Filter callbacks are invoked "top down".
+ * Return code handling:
+ * "first fail" meaning that the first filter returning != CURLE_OK, will
+ * abort further event distribution and determine the result.
+ * "ignored" meaning return values are ignored and the event is distributed
+ * to all filters in the chain. Overall result is always CURLE_OK.
*/
-void Curl_conn_detach(struct connectdata *conn, struct Curl_easy *data);
+/* data event arg1 arg2 return */
+#define CF_CTRL_DATA_ATTACH 1 /* 0 NULL ignored */
+#define CF_CTRL_DATA_DETACH 2 /* 0 NULL ignored */
+#define CF_CTRL_DATA_SETUP 4 /* 0 NULL first fail */
+#define CF_CTRL_DATA_IDLE 5 /* 0 NULL first fail */
+#define CF_CTRL_DATA_PAUSE 6 /* on/off NULL first fail */
+#define CF_CTRL_DATA_DONE 7 /* premature NULL ignored */
+#define CF_CTRL_DATA_DONE_SEND 8 /* 0 NULL ignored */
+/* update conn info at connection and data */
+#define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0 NULL ignored */
+
+/**
+ * Handle event/control for the filter.
+ * Implementations MUST NOT chain calls to cf->next.
+ */
+typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2);
+
+
+/**
+ * Queries to ask via a `Curl_cft_query *query` method on a cfilter chain.
+ * - MAX_CONCURRENT: the maximum number of parallel transfers the filter
+ * chain expects to handle at the same time.
+ * default: 1 if no filter overrides.
+ */
+/* query res1 res2 */
+#define CF_QUERY_MAX_CONCURRENT 1 /* number - */
+
+/**
+ * Query the cfilter for properties. Filters ignorant of a query will
+ * pass it "down" the filter chain.
+ */
+typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2);
+/**
+ * Type flags for connection filters. A filter can have none, one or
+ * many of those. Use to evaluate state/capabilities of a filter chain.
+ *
+ * CF_TYPE_IP_CONNECT: provides an IP connection or sth equivalent, like
+ * a CONNECT tunnel, a UNIX domain socket, a QUIC
+ * connection, etc.
+ * CF_TYPE_SSL: provide SSL/TLS
+ * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles
+ */
#define CF_TYPE_IP_CONNECT (1 << 0)
#define CF_TYPE_SSL (1 << 1)
+#define CF_TYPE_MULTIPLEX (1 << 2)
/* A connection filter type, e.g. specific implementation. */
struct Curl_cftype {
const char *name; /* name of the filter type */
long flags; /* flags of filter type */
Curl_cft_destroy_this *destroy; /* destroy resources of this cf */
- Curl_cft_setup *setup; /* setup for a connection */
Curl_cft_connect *connect; /* establish connection */
Curl_cft_close *close; /* close conn */
Curl_cft_get_host *get_host; /* host filter talks to */
@@ -119,8 +162,10 @@ struct Curl_cftype {
Curl_cft_data_pending *has_data_pending;/* conn has data pending */
Curl_cft_send *do_send; /* send data */
Curl_cft_recv *do_recv; /* receive data */
- Curl_cft_attach_data *attach_data; /* data is being handled here */
- Curl_cft_detach_data *detach_data; /* data is no longer handled here */
+ Curl_cft_cntrl *cntrl; /* events/control */
+ Curl_cft_conn_is_alive *is_alive; /* FALSE if conn is dead, Jim! */
+ Curl_cft_conn_keep_alive *keep_alive; /* try to keep it alive */
+ Curl_cft_query *query; /* query filter chain */
};
/* A connection filter instance, e.g. registered at a connection */
@@ -129,7 +174,7 @@ struct Curl_cfilter {
struct Curl_cfilter *next; /* next filter in chain */
void *ctx; /* filter type specific settings */
struct connectdata *conn; /* the connection this filter belongs to */
- int sockindex; /* TODO: like to get rid off this */
+ int sockindex; /* the index the filter is installed at */
BIT(connected); /* != 0 iff this filter is connected */
};
@@ -139,9 +184,6 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf,
/* Default implementations for the type functions, implementing pass-through
* the filter chain. */
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const struct Curl_dns_entry *remotehost);
void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
@@ -158,10 +200,16 @@ ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err);
ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err);
-void Curl_cf_def_attach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data);
-void Curl_cf_def_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data);
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2);
+bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2);
/**
* Create a new filter instance, unattached to the filter chain.
@@ -176,7 +224,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf,
/**
* Add a filter instance to the `sockindex` filter chain at connection
- * `data->conn`. The filter must not already be attached. It is inserted at
+ * `conn`. The filter must not already be attached. It is inserted at
* the start of the chain (top).
*/
void Curl_conn_cf_add(struct Curl_easy *data,
@@ -185,11 +233,11 @@ void Curl_conn_cf_add(struct Curl_easy *data,
struct Curl_cfilter *cf);
/**
- * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ * Insert a filter (chain) after `cf_at`.
+ * `cf_new` must not already be attached.
*/
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex);
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_cfilter *cf_new);
/**
* Discard, e.g. remove and destroy a specific filter instance.
@@ -198,29 +246,43 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data,
*/
void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data);
+/**
+ * Discard all cfilters starting with `*pcf` and clearing it afterwards.
+ */
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+ struct Curl_easy *data);
+
+/**
+ * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ */
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
+
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done);
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data);
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks);
ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err);
ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err);
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool ignore_result,
+ int event, int arg1, void *arg2);
+
+
#define CURL_CF_SSL_DEFAULT -1
#define CURL_CF_SSL_DISABLE 0
#define CURL_CF_SSL_ENABLE 1
/**
- * Setup the filter chain at `sockindex` in connection `conn`, invoking
- * the instance `setup(remotehost)` methods. If no filter chain is
- * installed yet, inspects the configuration in `data` to install a
- * suitable filter chain.
- */
-CURLcode Curl_conn_setup(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- const struct Curl_dns_entry *remotehost,
- int ssl_mode);
-
-/**
* Bring the filter chain at `sockindex` for connection `data->conn` into
* connected state. Which will set `*done` to TRUE.
* This can be called on an already connected chain with no side effects.
@@ -248,7 +310,12 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex);
* (or will be once connected). This will return FALSE, if SSL
* is only used in proxying and not for the tunnel itself.
*/
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex);
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex);
+
+/**
+ * Connection provides multiplexing of easy handles at `socketindex`.
+ */
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
/**
* Close the filter chain at `sockindex` for connection `data->conn`.
@@ -289,13 +356,12 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex,
const void *buf, size_t len, CURLcode *code);
/**
- * The easy handle `data` is being attached (served) by connection `conn`.
- * All filters are informed to adapt to handling `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * The easy handle `data` is being attached to `conn`. This does
+ * not mean that data will actually do a transfer. Attachment is
+ * also used for temporary actions on the connection.
*/
-void Curl_conn_attach_data(struct connectdata *conn,
- struct Curl_easy *data);
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+ struct Curl_easy *data);
/**
* The easy handle `data` is being detached (no longer served)
@@ -304,12 +370,66 @@ void Curl_conn_attach_data(struct connectdata *conn,
* Note: there may be several `data` attached to a connection at the same
* time.
*/
-void Curl_conn_detach_data(struct connectdata *conn,
- struct Curl_easy *data);
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+ struct Curl_easy *data);
+
+/**
+ * Notify connection filters that they need to setup data for
+ * a transfer.
+ */
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that now would be a good time to
+ * perform any idle, e.g. time related, actions.
+ */
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature);
+
+/**
+ * Notify connection filters that the transfer of data is paused/unpaused.
+ */
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause);
+
+/**
+ * Inform connection filters to update their info in `conn`.
+ */
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+ struct connectdata *conn);
+
+/**
+ * Check if FIRSTSOCKET's cfilter chain deems connection alive.
+ */
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn);
+
+/**
+ * Try to upkeep the connection filters at sockindex.
+ */
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
const char **phost, const char **pdisplay_host,
int *pport);
+/**
+ * Get the maximum number of parallel transfers the connection
+ * expects to be able to handle at `sockindex`.
+ */
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
#endif /* HEADER_CURL_CFILTERS_H */
diff --git a/lib/connect.c b/lib/connect.c
index af0413836..455518926 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -59,107 +59,37 @@
#include "strerror.h"
#include "cfilters.h"
#include "connect.h"
+#include "cf-socket.h"
#include "select.h"
#include "url.h" /* for Curl_safefree() */
#include "multiif.h"
#include "sockaddr.h" /* required for Curl_sockaddr_storage */
#include "inet_ntop.h"
#include "inet_pton.h"
-#include "vtls/vtls.h" /* for Curl_ssl_check_cxn() */
+#include "vtls/vtls.h" /* for vtsl cfilters */
#include "progress.h"
#include "warnless.h"
#include "conncache.h"
#include "multihandle.h"
#include "share.h"
#include "version_win32.h"
-#include "quic.h"
+#include "vquic/vquic.h" /* for quic cfilters */
+#include "http_proxy.h"
+#include "socks.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
-static bool verifyconnect(curl_socket_t sockfd, int *error);
+#define DEBUG_CF 0
-#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H)
-/* DragonFlyBSD and Windows use millisecond units */
-#define KEEPALIVE_FACTOR(x) (x *= 1000)
+#if DEBUG_CF
+#define CF_DEBUGF(x) x
#else
-#define KEEPALIVE_FACTOR(x)
+#define CF_DEBUGF(x) do { } while(0)
#endif
-#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS)
-#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
-
-struct tcp_keepalive {
- u_long onoff;
- u_long keepalivetime;
- u_long keepaliveinterval;
-};
-#endif
-
-static void
-tcpkeepalive(struct Curl_easy *data,
- curl_socket_t sockfd)
-{
- int optval = data->set.tcp_keepalive?1:0;
-
- /* only set IDLE and INTVL if setting KEEPALIVE is successful */
- if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
- (void *)&optval, sizeof(optval)) < 0) {
- infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd);
- }
- else {
-#if defined(SIO_KEEPALIVE_VALS)
- struct tcp_keepalive vals;
- DWORD dummy;
- vals.onoff = 1;
- optval = curlx_sltosi(data->set.tcp_keepidle);
- KEEPALIVE_FACTOR(optval);
- vals.keepalivetime = optval;
- optval = curlx_sltosi(data->set.tcp_keepintvl);
- KEEPALIVE_FACTOR(optval);
- vals.keepaliveinterval = 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 %d: %d",
- (int)sockfd, WSAGetLastError());
- }
-#else
-#ifdef TCP_KEEPIDLE
- optval = curlx_sltosi(data->set.tcp_keepidle);
- KEEPALIVE_FACTOR(optval);
- if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
- (void *)&optval, sizeof(optval)) < 0) {
- infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd);
- }
-#elif defined(TCP_KEEPALIVE)
- /* Mac OS X style */
- optval = curlx_sltosi(data->set.tcp_keepidle);
- KEEPALIVE_FACTOR(optval);
- if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
- (void *)&optval, sizeof(optval)) < 0) {
- infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd);
- }
-#endif
-#ifdef TCP_KEEPINTVL
- optval = curlx_sltosi(data->set.tcp_keepintvl);
- KEEPALIVE_FACTOR(optval);
- if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
- (void *)&optval, sizeof(optval)) < 0) {
- infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd);
- }
-#endif
-#endif
- }
-}
-
-static CURLcode
-singleipconnect(struct Curl_easy *data,
- struct connectdata *conn,
- const struct Curl_addrinfo *ai, /* start connecting to this */
- int tempindex); /* 0 or 1 among the temp ones */
-
/*
* Curl_timeleft() returns the amount of milliseconds left allowed for the
* transfer/connection. If the value is 0, there's no timeout (ie there's
@@ -230,387 +160,6 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
return timeout_ms;
}
-static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sockfd, int af, unsigned int scope)
-{
- struct Curl_sockaddr_storage sa;
- struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */
- curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
- struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
-#ifdef ENABLE_IPV6
- struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
-#endif
-
- struct Curl_dns_entry *h = NULL;
- unsigned short port = data->set.localport; /* use this port number, 0 for
- "random" */
- /* 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];
- int error;
-#ifdef IP_BIND_ADDRESS_NO_PORT
- int on = 1;
-#endif
-#ifndef ENABLE_IPV6
- (void)scope;
-#endif
-
- /*************************************************************
- * Select device to bind socket to
- *************************************************************/
- if(!dev && !port)
- /* no local kind of binding was requested */
- return CURLE_OK;
-
- memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
-
- if(dev && (strlen(dev)<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;
- }
-
- /* interface */
- if(!is_host) {
-#ifdef SO_BINDTODEVICE
- /* I am not sure any other OSs than Linux that provide this feature,
- * and at the least I cannot test. --Ben
- *
- * This feature allows one to tightly bind 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.
- *
- * 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 typically "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.
- */
- return CURLE_OK;
- }
-#endif
-
- switch(Curl_if2ip(af,
-#ifdef ENABLE_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;
- }
- }
- if(!is_interface) {
- /*
- * This was not an interface, resolve the name as a host name
- * or IP number
- *
- * Temporarily force name resolution to use only the address type
- * of the connection. The resolve functions should really be changed
- * to take a type parameter instead.
- */
- unsigned char ipver = conn->ip_version;
- int rc;
-
- if(af == AF_INET)
- conn->ip_version = CURL_IPRESOLVE_V4;
-#ifdef ENABLE_IPV6
- else if(af == AF_INET6)
- conn->ip_version = CURL_IPRESOLVE_V6;
-#endif
-
- rc = Curl_resolv(data, dev, 0, FALSE, &h);
- if(rc == CURLRESOLV_PENDING)
- (void)Curl_resolver_wait_resolv(data, &h);
- conn->ip_version = ipver;
-
- if(h) {
- /* 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);
- Curl_resolv_unlock(data, h);
- if(af != h->addr->ai_family) {
- /* bad IP version combo, signal the caller to try another address
- family if available */
- return CURLE_UNSUPPORTED_PROTOCOL;
- }
- done = 1;
- }
- else {
- /*
- * provided dev was no interface (or interfaces are not supported
- * e.g. solaris) no ip address and no domain we fail here
- */
- done = -1;
- }
- }
-
- if(done > 0) {
-#ifdef ENABLE_IPV6
- /* IPv6 address */
- if(af == AF_INET6) {
-#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
- char *scope_ptr = strchr(myhost, '%');
- if(scope_ptr)
- *(scope_ptr++) = '\0';
-#endif
- if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
- si6->sin6_family = AF_INET6;
- si6->sin6_port = htons(port);
-#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
- 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
- present, is known to be numeric */
- unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
- if(scope_id > UINT_MAX)
- return CURLE_UNSUPPORTED_PROTOCOL;
-
- si6->sin6_scope_id = (unsigned int)scope_id;
- }
-#endif
- }
- sizeof_sa = sizeof(struct sockaddr_in6);
- }
- else
-#endif
- /* IPv4 address */
- if((af == AF_INET) &&
- (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
- si4->sin_family = AF_INET;
- si4->sin_port = htons(port);
- sizeof_sa = sizeof(struct sockaddr_in);
- }
- }
-
- if(done < 1) {
- /* 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. */
- data->state.errorbuf = FALSE;
- failf(data, "Couldn't bind to '%s'", dev);
- return CURLE_INTERFACE_FAILED;
- }
- }
- else {
- /* no device was given, prepare sa to match af's needs */
-#ifdef ENABLE_IPV6
- if(af == AF_INET6) {
- si6->sin6_family = AF_INET6;
- si6->sin6_port = htons(port);
- sizeof_sa = sizeof(struct sockaddr_in6);
- }
- else
-#endif
- if(af == AF_INET) {
- si4->sin_family = AF_INET;
- si4->sin_port = htons(port);
- sizeof_sa = sizeof(struct sockaddr_in);
- }
- }
-#ifdef IP_BIND_ADDRESS_NO_PORT
- (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
-#endif
- for(;;) {
- if(bind(sockfd, sock, sizeof_sa) >= 0) {
- /* we succeeded to bind */
- struct Curl_sockaddr_storage add;
- curl_socklen_t size = sizeof(add);
- memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
- if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
- char buffer[STRERROR_LEN];
- data->state.os_errno = error = SOCKERRNO;
- failf(data, "getsockname() failed with errno %d: %s",
- error, Curl_strerror(error, buffer, sizeof(buffer)));
- return CURLE_INTERFACE_FAILED;
- }
- infof(data, "Local port: %hu", port);
- conn->bits.bound = TRUE;
- return CURLE_OK;
- }
-
- if(--portnum > 0) {
- port++; /* try next port */
- if(port == 0)
- break;
- infof(data, "Bind to local port %hu failed, trying next", port - 1);
- /* We re-use/clobber the port variable here below */
- if(sock->sa_family == AF_INET)
- si4->sin_port = ntohs(port);
-#ifdef ENABLE_IPV6
- else
- si6->sin6_port = ntohs(port);
-#endif
- }
- else
- break;
- }
- {
- char buffer[STRERROR_LEN];
- data->state.os_errno = error = SOCKERRNO;
- failf(data, "bind failed with errno %d: %s",
- error, Curl_strerror(error, buffer, sizeof(buffer)));
- }
-
- return CURLE_INTERFACE_FAILED;
-}
-
-/*
- * verifyconnect() returns TRUE if the connect really has happened.
- */
-static bool verifyconnect(curl_socket_t sockfd, int *error)
-{
- bool rc = TRUE;
-#ifdef SO_ERROR
- int err = 0;
- curl_socklen_t errSize = sizeof(err);
-
-#ifdef WIN32
- /*
- * In October 2003 we effectively nullified this function on Windows due to
- * problems with it using all CPU in multi-threaded cases.
- *
- * In May 2004, we bring it back to offer more info back on connect failures.
- * 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
- * just Sleep(0) would be enough?) would release whatever
- * mutex/critical-section the ntdll call is waiting on.
- *
- * Someone got to verify this on Win-NT 4.0, 2000."
- */
-
-#ifdef _WIN32_WCE
- Sleep(0);
-#else
- SleepEx(0, FALSE);
-#endif
-
-#endif
-
- if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
- err = SOCKERRNO;
-#ifdef _WIN32_WCE
- /* Old WinCE versions don't 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 */
- if(EBADIOCTL == err) {
- SET_SOCKERRNO(0);
- err = 0;
- }
-#endif
- if((0 == err) || (EISCONN == err))
- /* we are connected, awesome! */
- rc = TRUE;
- else
- /* This wasn't a successful connect */
- rc = FALSE;
- if(error)
- *error = err;
-#else
- (void)sockfd;
- if(error)
- *error = SOCKERRNO;
-#endif
- return rc;
-}
-
-/* update tempaddr[tempindex] (to the next entry), makes sure to stick
- to the correct family */
-static struct Curl_addrinfo *ainext(struct connectdata *conn,
- int tempindex,
- bool next) /* use next entry? */
-{
- struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
- if(ai && next)
- ai = ai->ai_next;
- while(ai && (ai->ai_family != conn->tempfamily[tempindex]))
- ai = ai->ai_next;
- conn->tempaddr[tempindex] = ai;
- return ai;
-}
-
-/* Used within the multi interface. Try next IP address, returns error if no
- more address exists or error */
-static CURLcode trynextip(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- int tempindex)
-{
- CURLcode result = CURLE_COULDNT_CONNECT;
-
- /* First clean up after the failed socket.
- Don't close it yet to ensure that the next IP's socket gets a different
- file descriptor, which can prevent bugs when the curl_multi_socket_action
- interface is used with certain select() replacements such as kqueue. */
- curl_socket_t fd_to_close = conn->tempsock[tempindex];
- conn->tempsock[tempindex] = CURL_SOCKET_BAD;
-
- if(sockindex == FIRSTSOCKET) {
- struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
-
- while(ai) {
- result = singleipconnect(data, conn, ai, tempindex);
- if(result == CURLE_COULDNT_CONNECT) {
- ai = ainext(conn, tempindex, TRUE);
- continue;
- }
- break;
- }
- }
-
- if(fd_to_close != CURL_SOCKET_BAD)
- Curl_closesocket(data, conn, fd_to_close);
-
- return result;
-}
-
/* Copies connection info into the transfer handle to make it available when
the transfer handle is no longer associated with the connection. */
void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
@@ -629,6 +178,28 @@ void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
data->info.conn_local_port = local_port;
}
+static const struct Curl_addrinfo *
+addr_first_match(const struct Curl_addrinfo *addr, int family)
+{
+ while(addr) {
+ if(addr->ai_family == family)
+ return addr;
+ addr = addr->ai_next;
+ }
+ return NULL;
+}
+
+static const struct Curl_addrinfo *
+addr_next_match(const struct Curl_addrinfo *addr, int family)
+{
+ while(addr->ai_next) {
+ addr = addr->ai_next;
+ if(addr->ai_family == family)
+ return addr;
+ }
+ return NULL;
+}
+
/* retrieves ip address and port from a sockaddr structure.
note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */
bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
@@ -686,623 +257,479 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
return FALSE;
}
-/* retrieves the start/end point information of a socket of an established
- connection */
-void Curl_conninfo_remote(struct Curl_easy *data,
- struct connectdata *conn, curl_socket_t sockfd)
-{
-#ifdef HAVE_GETPEERNAME
- char buffer[STRERROR_LEN];
- struct Curl_sockaddr_storage ssrem;
- curl_socklen_t plen;
- int port;
- plen = sizeof(struct Curl_sockaddr_storage);
- memset(&ssrem, 0, sizeof(ssrem));
- if(getpeername(sockfd, (struct sockaddr*) &ssrem, &plen)) {
- int error = SOCKERRNO;
- failf(data, "getpeername() failed with errno %d: %s",
- error, Curl_strerror(error, buffer, sizeof(buffer)));
- return;
- }
- if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
- conn->primary_ip, &port)) {
- failf(data, "ssrem inet_ntop() failed with errno %d: %s",
- errno, Curl_strerror(errno, buffer, sizeof(buffer)));
- return;
- }
-#else
- (void)data;
- (void)conn;
- (void)sockfd;
-#endif
-}
+struct connfind {
+ long id_tofind;
+ struct connectdata *found;
+};
-/* retrieves the start/end point information of a socket of an established
- connection */
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
- char *local_ip, int *local_port)
+static int conn_is_conn(struct Curl_easy *data,
+ struct connectdata *conn, void *param)
{
-#ifdef HAVE_GETSOCKNAME
- char buffer[STRERROR_LEN];
- struct Curl_sockaddr_storage ssloc;
- curl_socklen_t slen;
- slen = sizeof(struct Curl_sockaddr_storage);
- memset(&ssloc, 0, sizeof(ssloc));
- if(getsockname(sockfd, (struct sockaddr*) &ssloc, &slen)) {
- int error = SOCKERRNO;
- failf(data, "getsockname() failed with errno %d: %s",
- error, Curl_strerror(error, buffer, sizeof(buffer)));
- return;
- }
- if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
- local_ip, local_port)) {
- failf(data, "ssloc inet_ntop() failed with errno %d: %s",
- errno, Curl_strerror(errno, buffer, sizeof(buffer)));
- return;
- }
-#else
+ struct connfind *f = (struct connfind *)param;
(void)data;
- (void)sockfd;
- (void)local_ip;
- (void)local_port;
-#endif
-}
-
-/* retrieves the start/end point information of a socket of an established
- connection */
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sockfd)
-{
- /* 'local_ip' and 'local_port' get filled with local's numerical
- ip address and port number whenever an outgoing connection is
- **established** from the primary socket to a remote address. */
- char local_ip[MAX_IPADR_LEN] = "";
- int local_port = -1;
-
- if(!conn->bits.reuse &&
- (conn->transport != TRNSPRT_TCP || !conn->bits.tcp_fastopen))
- Curl_conninfo_remote(data, conn, sockfd);
- Curl_conninfo_local(data, sockfd, local_ip, &local_port);
-
- /* persist connection info in session handle */
- Curl_persistconninfo(data, conn, local_ip, local_port);
-}
-
-/*
- * post_connect() is called after a successful connect to the peer
- */
-static void post_connect(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex)
-{
- Curl_updateconninfo(data, conn, conn->sock[sockindex]);
- Curl_verboseconnect(data, conn);
- data->info.numconnects++; /* to track the number of connections made */
+ if(conn->connection_id == f->id_tofind) {
+ f->found = conn;
+ return 1;
+ }
+ return 0;
}
/*
- * is_connected() checks if the socket has connected.
+ * Used to extract socket and connectdata struct for the most recent
+ * transfer on the given Curl_easy.
+ *
+ * The returned socket will be CURL_SOCKET_BAD in case of failure!
*/
-static CURLcode is_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- bool *connected)
+curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
+ struct connectdata **connp)
{
- CURLcode result = CURLE_OK;
- timediff_t allow;
- int error = 0;
- struct curltime now;
- int rc = 0;
- int i;
-
- DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
-
- *connected = FALSE; /* a very negative world view is best */
-
- now = Curl_now();
-
- /* Check if any of the conn->tempsock we use for establishing connections
- * succeeded and, if so, close any ongoing other ones.
- * Transfer the successful conn->tempsock to conn->sock[sockindex]
- * and set conn->tempsock to CURL_SOCKET_BAD.
- * If transport is QUIC, we need to shutdown the ongoing 'other'
- * connect attempts in a QUIC appropriate way. */
- for(i = 0; i<2; i++) {
- const int other = i ^ 1;
- if(conn->tempsock[i] == CURL_SOCKET_BAD)
- continue;
- error = 0;
-#ifdef ENABLE_QUIC
- if(conn->transport == TRNSPRT_QUIC) {
- result = Curl_quic_is_connected(data, conn, i, connected);
- if(!result && *connected) {
- /* use this socket from now on */
- conn->sock[sockindex] = conn->tempsock[i];
- conn->ip_addr = conn->tempaddr[i];
- conn->tempsock[i] = CURL_SOCKET_BAD;
- post_connect(data, conn, sockindex);
- connkeep(conn, "HTTP/3 default");
- if(conn->tempsock[other] != CURL_SOCKET_BAD)
- Curl_quic_disconnect(data, conn, other);
- return CURLE_OK;
- }
- /* When a QUIC connect attempt fails, the better error explanation is in
- 'result' and not in errno */
- if(result) {
- conn->tempsock[i] = CURL_SOCKET_BAD;
- error = SOCKERRNO;
- }
- }
- else
-#endif
- {
-#ifdef mpeix
- /* Call this function once now, and ignore the results. We do this to
- "clear" the error state on the socket so that we can later read it
- reliably. This is reported necessary on the MPE/iX operating
- system. */
- (void)verifyconnect(conn->tempsock[i], NULL);
-#endif
+ DEBUGASSERT(data);
- /* check socket for connect */
- rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
- }
+ /* this works for an easy handle:
+ * - that has been used for curl_easy_perform()
+ * - that is associated with a multi handle, and whose connection
+ * was detached with CURLOPT_CONNECT_ONLY
+ */
+ if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) {
+ struct connectdata *c;
+ struct connfind find;
+ find.id_tofind = data->state.lastconnect_id;
+ find.found = NULL;
- if(rc == 0) { /* no connection yet */
- if(Curl_timediff(now, conn->connecttime) >=
- conn->timeoutms_per_addr[i]) {
- infof(data, "After %" CURL_FORMAT_TIMEDIFF_T
- "ms connect time, move on!", conn->timeoutms_per_addr[i]);
- error = ETIMEDOUT;
- }
+ Curl_conncache_foreach(data,
+ data->share && (data->share->specifier
+ & (1<< CURL_LOCK_DATA_CONNECT))?
+ &data->share->conn_cache:
+ data->multi_easy?
+ &data->multi_easy->conn_cache:
+ &data->multi->conn_cache, &find, conn_is_conn);
- /* should we try another protocol family? */
- if(i == 0 && !conn->bits.parallel_connect &&
- (Curl_timediff(now, conn->connecttime) >=
- data->set.happy_eyeballs_timeout)) {
- conn->bits.parallel_connect = TRUE; /* starting now */
- trynextip(data, conn, sockindex, 1);
- }
+ if(!find.found) {
+ data->state.lastconnect_id = -1;
+ return CURL_SOCKET_BAD;
}
- else if(rc == CURL_CSELECT_OUT || conn->bits.tcp_fastopen) {
- if(verifyconnect(conn->tempsock[i], &error)) {
- /* we are connected with TCP, awesome! */
-
- /* use this socket from now on */
- conn->sock[sockindex] = conn->tempsock[i];
- conn->ip_addr = conn->tempaddr[i];
- conn->tempsock[i] = CURL_SOCKET_BAD;
-#ifdef ENABLE_IPV6
- conn->bits.ipv6 = (conn->ip_addr->ai_family == AF_INET6)?TRUE:FALSE;
-#endif
-
- /* close the other socket, if open */
- if(conn->tempsock[other] != CURL_SOCKET_BAD) {
- Curl_closesocket(data, conn, conn->tempsock[other]);
- conn->tempsock[other] = CURL_SOCKET_BAD;
- }
- *connected = TRUE;
- return CURLE_OK;
- }
- }
- else if(rc & CURL_CSELECT_ERR) {
- (void)verifyconnect(conn->tempsock[i], &error);
- }
+ c = find.found;
+ if(connp)
+ /* only store this if the caller cares for it */
+ *connp = c;
+ return c->sock[FIRSTSOCKET];
+ }
+ return CURL_SOCKET_BAD;
+}
- /*
- * The connection failed here, we should attempt to connect to the "next
- * address" for the given host. But first remember the latest error.
- */
- if(error) {
- data->state.os_errno = error;
- SET_SOCKERRNO(error);
- if(conn->tempaddr[i]) {
- CURLcode status;
-#ifndef CURL_DISABLE_VERBOSE_STRINGS
- char ipaddress[MAX_IPADR_LEN];
- char buffer[STRERROR_LEN];
- Curl_printable_address(conn->tempaddr[i], ipaddress,
- sizeof(ipaddress));
-#ifdef ENABLE_QUIC
- if(conn->transport == TRNSPRT_QUIC) {
- infof(data, "connect to %s port %u failed: %s",
- ipaddress, conn->port, curl_easy_strerror(result));
- }
- else
+/*
+ * Curl_conncontrol() marks streams or connection for closure.
+ */
+void Curl_conncontrol(struct connectdata *conn,
+ int ctrl /* see defines in header */
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ , const char *reason
#endif
- infof(data, "connect to %s port %u failed: %s",
- ipaddress, conn->port,
- Curl_strerror(error, buffer, sizeof(buffer)));
+ )
+{
+ /* close if a connection, or a stream that isn't multiplexed. */
+ /* This function will be called both before and after this connection is
+ associated with a transfer. */
+ bool closeit, is_multiplex;
+ DEBUGASSERT(conn);
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ (void)reason; /* useful for debugging */
#endif
-
- allow = Curl_timeleft(data, &now, TRUE);
- conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ?
- allow : allow / 2;
- ainext(conn, i, TRUE);
- status = trynextip(data, conn, sockindex, i);
- if((status != CURLE_COULDNT_CONNECT) ||
- conn->tempsock[other] == CURL_SOCKET_BAD) {
- /* the last attempt failed and no other sockets remain open */
- if(!result)
- result = status;
- }
- }
- }
- }
-
- /*
- * Now that we've checked whether we are connected, check whether we've
- * already timed out.
- *
- * First figure out how long time we have left to connect */
-
- allow = Curl_timeleft(data, &now, TRUE);
-
- if(allow < 0) {
- /* time-out, bail out, go home */
- failf(data, "Connection timeout after %ld ms",
- Curl_timediff(now, data->progress.t_startsingle));
- return CURLE_OPERATION_TIMEDOUT;
+ is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
+ closeit = (ctrl == CONNCTRL_CONNECTION) ||
+ ((ctrl == CONNCTRL_STREAM) && !is_multiplex);
+ if((ctrl == CONNCTRL_STREAM) && is_multiplex)
+ ; /* stream signal on multiplex conn never affects close state */
+ else if((bit)closeit != conn->bits.close) {
+ conn->bits.close = closeit; /* the only place in the source code that
+ should assign this bit */
}
+}
- if(result &&
- (conn->tempsock[0] == CURL_SOCKET_BAD) &&
- (conn->tempsock[1] == CURL_SOCKET_BAD)) {
- /* no more addresses to try */
- const char *hostname;
- CURLcode failreason = result;
-
- /* if the first address family runs out of addresses to try before the
- happy eyeball timeout, go ahead and try the next family now */
- result = trynextip(data, conn, sockindex, 1);
- if(!result)
- return result;
-
- result = failreason;
-
-#ifndef CURL_DISABLE_PROXY
- if(conn->bits.socksproxy)
- hostname = conn->socks_proxy.host.name;
- else if(conn->bits.httpproxy)
- hostname = conn->http_proxy.host.name;
- else
-#endif
- if(conn->bits.conn_to_host)
- hostname = conn->conn_to_host.name;
- else
- hostname = conn->host.name;
-
- failf(data, "Failed to connect to %s port %u after "
- "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
- hostname, conn->port,
- Curl_timediff(now, data->progress.t_startsingle),
- curl_easy_strerror(result));
+/**
+ * job walking the matching addr infos, createing a sub-cfilter with the
+ * provided method `cf_create` and running setup/connect on it.
+ */
+struct eyeballer {
+ const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */
+ int ai_family; /* matching address family only */
+ cf_ip_connect_create *cf_create; /* for creating cf */
+ struct Curl_cfilter *cf; /* current sub-cfilter connecting */
+ struct eyeballer *primary; /* eyeballer this one is is backup for */
+ timediff_t delay_ms; /* delay until start */
+ timediff_t timeoutms; /* timeout for all tries */
+ expire_id timeout_id; /* ID for Curl_expire() */
+ CURLcode result;
+ int error;
+ BIT(has_started); /* attempts have started */
+ BIT(is_done); /* out of addresses/time */
+ BIT(connected); /* cf has connected */
+};
- Curl_quic_disconnect(data, conn, 0);
- Curl_quic_disconnect(data, conn, 1);
-#ifdef WSAETIMEDOUT
- if(WSAETIMEDOUT == data->state.os_errno)
- result = CURLE_OPERATION_TIMEDOUT;
-#elif defined(ETIMEDOUT)
- if(ETIMEDOUT == data->state.os_errno)
- result = CURLE_OPERATION_TIMEDOUT;
-#endif
- }
- else
- result = CURLE_OK; /* still trying */
+typedef enum {
+ SCFST_INIT,
+ SCFST_WAITING,
+ SCFST_DONE
+} cf_connect_state;
- return result;
-}
+struct cf_he_ctx {
+ int transport;
+ cf_ip_connect_create *cf_create;
+ const struct Curl_dns_entry *remotehost;
+ cf_connect_state state;
+ struct eyeballer *baller[5];
+ struct eyeballer *winner;
+ struct curltime started;
+};
-static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
+static CURLcode eyeballer_new(struct eyeballer **pballer,
+ cf_ip_connect_create *cf_create,
+ const struct Curl_addrinfo *addr,
+ int ai_family,
+ struct eyeballer *primary,
+ timediff_t delay_ms,
+ timediff_t timeout_ms,
+ expire_id timeout_id)
{
-#if defined(TCP_NODELAY)
- curl_socklen_t onoff = (curl_socklen_t) 1;
- int level = IPPROTO_TCP;
-#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
- char buffer[STRERROR_LEN];
-#else
- (void) data;
-#endif
-
- if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff,
- sizeof(onoff)) < 0)
- infof(data, "Could not set TCP_NODELAY: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-#else
- (void)data;
- (void)sockfd;
-#endif
+ struct eyeballer *baller;
+
+ *pballer = NULL;
+ baller = calloc(1, sizeof(*baller) + 1000);
+ if(!baller)
+ return CURLE_OUT_OF_MEMORY;
+
+ baller->cf_create = cf_create;
+ baller->addr = addr;
+ baller->ai_family = ai_family;
+ baller->primary = primary;
+ baller->delay_ms = delay_ms;
+ baller->timeoutms = (addr && addr->ai_next)? timeout_ms / 2 : timeout_ms;
+ baller->timeout_id = timeout_id;
+ baller->result = CURLE_COULDNT_CONNECT;
+
+ *pballer = baller;
+ return CURLE_OK;
}
-#ifdef SO_NOSIGPIPE
-/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
- sending data to a dead peer (instead of relying on the 4th argument to send
- being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
- systems? */
-static void nosigpipe(struct Curl_easy *data,
- curl_socket_t sockfd)
+static void baller_close(struct eyeballer *baller,
+ struct Curl_easy *data)
{
- int onoff = 1;
- if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
- sizeof(onoff)) < 0) {
-#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
- char buffer[STRERROR_LEN];
- infof(data, "Could not set SO_NOSIGPIPE: %s",
- Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-#endif
+ if(baller && baller->cf) {
+ Curl_conn_cf_discard_chain(&baller->cf, data);
}
}
-#else
-#define nosigpipe(x,y) Curl_nop_stmt
-#endif
-
-#ifdef USE_WINSOCK
-/* When you run a program that uses the Windows Sockets API, you may
- experience slow performance when you copy data to a TCP server.
-
- https://support.microsoft.com/kb/823764
-
- Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
- 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
- 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)
+static void baller_free(struct eyeballer *baller,
+ struct Curl_easy *data)
{
- int val = CURL_MAX_WRITE_SIZE + 32;
- int curval = 0;
- int curlen = sizeof(curval);
-
- static int detectOsState = DETECT_OS_NONE;
-
- if(detectOsState == DETECT_OS_NONE) {
- if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
- VERSION_GREATER_THAN_EQUAL))
- detectOsState = DETECT_OS_VISTA_OR_LATER;
- else
- detectOsState = DETECT_OS_PREVISTA;
+ if(baller) {
+ baller_close(baller, data);
+ free(baller);
}
+}
- if(detectOsState == DETECT_OS_VISTA_OR_LATER)
- return;
-
- if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
- if(curval > val)
- return;
-
- setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
+static void baller_next_addr(struct eyeballer *baller)
+{
+ baller->addr = addr_next_match(baller->addr, baller->ai_family);
}
-#endif
/*
- * singleipconnect()
+ * Initiate a connect attempt walk.
*
* Note that even on connect fail it returns CURLE_OK, but with 'sock' set to
* CURL_SOCKET_BAD. Other errors will however return proper errors.
- *
- * singleipconnect() connects to the given IP only, and it may return without
- * having connected.
*/
-static CURLcode singleipconnect(struct Curl_easy *data,
- struct connectdata *conn,
- const struct Curl_addrinfo *ai,
- int tempindex)
+static void baller_initiate(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct eyeballer *baller)
{
- struct Curl_sockaddr_ex addr;
- int rc = -1;
- int error = 0;
- bool isconnected = FALSE;
- curl_socket_t sockfd;
+ struct Curl_cfilter *cf_prev = baller->cf;
+ struct Curl_cfilter *wcf;
CURLcode result;
- char ipaddress[MAX_IPADR_LEN];
- int port;
- bool is_tcp;
-#ifdef TCP_FASTOPEN_CONNECT
- int optval = 1;
-#endif
- const char *ipmsg;
- char buffer[STRERROR_LEN];
- curl_socket_t *sockp = &conn->tempsock[tempindex];
- *sockp = CURL_SOCKET_BAD;
- result = Curl_socket(data, ai, &addr, &sockfd);
+
+ /* Don't close a previous cfilter yet to ensure that the next IP's
+ socket gets a different file descriptor, which can prevent bugs when
+ the curl_multi_socket_action interface is used with certain select()
+ replacements such as kqueue. */
+ result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr);
if(result)
- return result;
+ goto out;
- /* store remote address and port used in this connection attempt */
- if(!Curl_addr2string(&addr.sa_addr, addr.addrlen,
- ipaddress, &port)) {
- /* malformed address or bug in inet_ntop, try next address */
- failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
- errno, Curl_strerror(errno, buffer, sizeof(buffer)));
- Curl_closesocket(data, conn, sockfd);
- return CURLE_OK;
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer created %s"),
+ baller->cf->cft->name));
+ /* the new filter might have sub-filters */
+ for(wcf = baller->cf; wcf; wcf = wcf->next) {
+ wcf->conn = cf->conn;
+ wcf->sockindex = cf->sockindex;
}
-#ifdef ENABLE_IPV6
- if(addr.family == AF_INET6)
- ipmsg = " Trying [%s]:%d...";
- else
-#endif
- ipmsg = " Trying %s:%d...";
- infof(data, ipmsg, ipaddress, port);
-#ifdef ENABLE_IPV6
- is_tcp = (addr.family == AF_INET || addr.family == AF_INET6) &&
- addr.socktype == SOCK_STREAM;
-#else
- is_tcp = (addr.family == AF_INET) && addr.socktype == SOCK_STREAM;
-#endif
- if(is_tcp && data->set.tcp_nodelay)
- tcpnodelay(data, sockfd);
+ if(cf->conn->num_addr > 1) {
+ Curl_expire(data, baller->timeoutms, baller->timeout_id);
+ }
- nosigpipe(data, sockfd);
+out:
+ if(result) {
+ CF_DEBUGF(infof(data, "eyeballer failed"));
+ baller_close(baller, data);
+ }
+ if(cf_prev)
+ Curl_conn_cf_discard_chain(&cf_prev, data);
+ baller->result = result;
+}
- Curl_sndbufset(sockfd);
+/**
+ * Start a connection attempt on the current baller address.
+ * Will return CURLE_OK on the first address where a socket
+ * could be created and the non-blocking connect started.
+ * Returns error when all remaining addresses have been tried.
+ */
+static CURLcode baller_start(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct eyeballer *baller)
+{
+ baller->error = 0;
+ baller->connected = FALSE;
+ baller->has_started = TRUE;
- if(is_tcp && data->set.tcp_keepalive)
- tcpkeepalive(data, sockfd);
+ while(baller->addr) {
+ baller_initiate(cf, data, baller);
+ if(!baller->result)
+ break;
+ baller_next_addr(baller);
+ }
+ if(!baller->addr) {
+ baller->is_done = TRUE;
+ }
+ return baller->result;
+}
- if(data->set.fsockopt) {
- /* activate callback for setting socket options */
- Curl_set_in_callback(data, true);
- error = data->set.fsockopt(data->set.sockopt_client,
- sockfd,
- CURLSOCKTYPE_IPCXN);
- Curl_set_in_callback(data, false);
- if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
- isconnected = TRUE;
- else if(error) {
- Curl_closesocket(data, conn, sockfd); /* close the socket and bail out */
- return CURLE_ABORTED_BY_CALLBACK;
- }
+/* Used within the multi interface. Try next IP address, returns error if no
+ more address exists or error */
+static CURLcode baller_start_next(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct eyeballer *baller)
+{
+ if(cf->sockindex == FIRSTSOCKET) {
+ baller_next_addr(baller);
+ baller_start(cf, data, baller);
+ }
+ else {
+ baller->error = 0;
+ baller->connected = FALSE;
+ baller->has_started = TRUE;
+ baller->is_done = TRUE;
+ baller->result = CURLE_COULDNT_CONNECT;
}
+ return baller->result;
+}
- /* possibly bind the local end to an IP, interface or port */
- if(addr.family == AF_INET
-#ifdef ENABLE_IPV6
- || addr.family == AF_INET6
+static CURLcode baller_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct eyeballer *baller,
+ struct curltime *now,
+ bool *connected)
+{
+ struct cf_he_ctx *ctx = cf->ctx;
+
+ *connected = baller->connected;
+ if(!baller->result && !*connected) {
+ /* evaluate again */
+ baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected);
+
+ if(!baller->result) {
+ if (*connected) {
+ baller->connected = TRUE;
+ baller->is_done = TRUE;
+ }
+ else if(Curl_timediff(*now, ctx->started) >= baller->timeoutms) {
+ infof(data, "After %" CURL_FORMAT_TIMEDIFF_T
+ "ms connect time, move on!", baller->timeoutms);
+#if defined(ETIMEDOUT)
+ baller->error = ETIMEDOUT;
#endif
- ) {
- result = bindlocal(data, conn, sockfd, addr.family,
- Curl_ipv6_scope(&addr.sa_addr));
- if(result) {
- Curl_closesocket(data, conn, sockfd); /* close socket and bail out */
- if(result == CURLE_UNSUPPORTED_PROTOCOL) {
- /* The address family is not supported on this interface.
- We can continue trying addresses */
- return CURLE_COULDNT_CONNECT;
+ baller->result = CURLE_OPERATION_TIMEDOUT;
+ baller->is_done = TRUE;
}
- return result;
}
}
+ return baller->result;
+}
+
+/*
+ * is_connected() checks if the socket has connected.
+ */
+static CURLcode is_connected(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *connected)
+{
+ struct cf_he_ctx *ctx = cf->ctx;
+ struct connectdata *conn = cf->conn;
+ CURLcode result;
+ timediff_t allow;
+ struct curltime now;
+ size_t i;
+ int ongoing, not_started;
+ const char *hostname;
- /* set socket non-blocking */
- (void)curlx_nonblock(sockfd, TRUE);
+ /* Check if any of the conn->tempsock we use for establishing connections
+ * succeeded and, if so, close any ongoing other ones.
+ * Transfer the successful conn->tempsock to conn->sock[sockindex]
+ * and set conn->tempsock to CURL_SOCKET_BAD.
+ * If transport is QUIC, we need to shutdown the ongoing 'other'
+ * cot ballers in a QUIC appropriate way. */
+evaluate:
+ *connected = FALSE; /* a very negative world view is best */
+ now = Curl_now();
+ ongoing = not_started = 0;
+ for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ struct eyeballer *baller = ctx->baller[i];
- conn->connecttime = Curl_now();
- if(conn->num_addr > 1) {
- Curl_expire(data, conn->timeoutms_per_addr[0], EXPIRE_DNS_PER_NAME);
- Curl_expire(data, conn->timeoutms_per_addr[1], EXPIRE_DNS_PER_NAME2);
- }
+ if(!baller || baller->is_done)
+ continue;
- /* Connect TCP and QUIC sockets */
- if(!isconnected && (conn->transport != TRNSPRT_UDP)) {
- if(conn->bits.tcp_fastopen) {
-#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
-# if defined(HAVE_BUILTIN_AVAILABLE)
- /* while connectx function is available since macOS 10.11 / iOS 9,
- it did not have the interface declared correctly until
- Xcode 9 / macOS SDK 10.13 */
- if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
- sa_endpoints_t endpoints;
- endpoints.sae_srcif = 0;
- endpoints.sae_srcaddr = NULL;
- endpoints.sae_srcaddrlen = 0;
- endpoints.sae_dstaddr = &addr.sa_addr;
- endpoints.sae_dstaddrlen = addr.addrlen;
-
- rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY,
- CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
- NULL, 0, NULL, NULL);
+ if(!baller->has_started) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] not started yet"), i));
+ ++not_started;
+ continue;
+ }
+ baller->result = baller_connect(cf, data, baller, &now, connected);
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] connect -> %d, "
+ "connected=%d"), i, baller->result, *connected));
+
+ if(!baller->result) {
+ if(*connected) {
+ /* connected, declare the winner */
+ ctx->winner = baller;
+ ctx->baller[i] = NULL;
+ break;
}
- else {
- rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
+ else { /* still waiting */
+ ++ongoing;
}
-# else
- rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-# endif /* HAVE_BUILTIN_AVAILABLE */
-#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */
- if(setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
- (void *)&optval, sizeof(optval)) < 0)
- infof(data, "Failed to enable TCP Fast Open on fd %d", sockfd);
-
- rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-#elif defined(MSG_FASTOPEN) /* old Linux */
- if(conn->given->flags & PROTOPT_SSL)
- rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
- else
- rc = 0; /* Do nothing */
-#endif
}
- else {
- rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
+ else if(!baller->is_done) {
+ /* The baller failed to connect, start its next attempt */
+ if(baller->error) {
+ data->state.os_errno = baller->error;
+ SET_SOCKERRNO(baller->error);
+ }
+ allow = Curl_timeleft(data, &now, TRUE);
+ baller->timeoutms = baller->addr->ai_next == NULL ? allow : allow / 2;
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] starting"), i));
+ baller_start_next(cf, data, baller);
+ if(!baller->is_done) {
+ /* next attempt was started */
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] started"), i));
+ ++ongoing;
+ }
}
+ }
- if(-1 == rc)
- error = SOCKERRNO;
-#ifdef ENABLE_QUIC
- else if(conn->transport == TRNSPRT_QUIC) {
- /* pass in 'sockfd' separately since it hasn't been put into the
- tempsock array at this point */
- result = Curl_quic_connect(data, conn, sockfd, tempindex,
- &addr.sa_addr, addr.addrlen);
- if(result)
- error = SOCKERRNO;
+ if(ctx->winner) {
+ *connected = TRUE;
+ return CURLE_OK;
+ }
+
+ /* Nothing connected, have we timed out completely yet? */
+ allow = Curl_timeleft(data, &now, TRUE);
+ if(allow < 0) {
+ failf(data, "Connection timeout after %ld ms",
+ Curl_timediff(now, data->progress.t_startsingle));
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* Check if we have any waiting ballers to start now. */
+ if(not_started > 0) {
+ int added = 0;
+
+ for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ struct eyeballer *baller = ctx->baller[i];
+
+ if(!baller || baller->has_started)
+ continue;
+ /* We start its primary baller has failed to connect or if
+ * its start delay_ms have expired */
+ if((baller->primary && baller->primary->is_done) ||
+ Curl_timediff(now, ctx->started) >= baller->delay_ms) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] starting"), i));
+ baller_start(cf, data, baller);
+ if(!baller->is_done) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] has started"), i));
+ ++ongoing;
+ ++added;
+ }
+ }
}
-#endif
+ if(added > 0)
+ goto evaluate;
}
- else {
- *sockp = sockfd;
+
+ if(ongoing > 0) {
+ /* We are still trying, return for more waiting */
+ *connected = FALSE;
return CURLE_OK;
}
- if(-1 == rc) {
- switch(error) {
- case EINPROGRESS:
- case EWOULDBLOCK:
-#if defined(EAGAIN)
-#if (EAGAIN) != (EWOULDBLOCK)
- /* On some platforms EAGAIN and EWOULDBLOCK are the
- * same value, and on others they are different, hence
- * the odd #if
- */
- case EAGAIN:
-#endif
-#endif
- result = CURLE_OK;
+ /* all ballers have failed to connect. */
+ CF_DEBUGF(infof(data, CFMSG(cf, "all eyeballers failed")));
+ result = CURLE_COULDNT_CONNECT;
+ for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ struct eyeballer *baller = ctx->baller[i];
+ CF_DEBUGF(infof(data, CFMSG(cf, "eyeballer[%d] assess started=%d, "
+ "result=%d"), i, baller->has_started, baller->result));
+ if(baller && baller->has_started && baller->result) {
+ result = baller->result;
break;
-
- default:
- /* unknown error, fallthrough and try another address! */
- infof(data, "Immediate connect fail for %s: %s",
- ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
- data->state.os_errno = error;
-
- /* connect failed */
- Curl_closesocket(data, conn, sockfd);
- result = CURLE_COULDNT_CONNECT;
}
}
- if(!result)
- *sockp = sockfd;
+#ifndef CURL_DISABLE_PROXY
+ if(conn->bits.socksproxy)
+ hostname = conn->socks_proxy.host.name;
+ else if(conn->bits.httpproxy)
+ hostname = conn->http_proxy.host.name;
+ else
+#endif
+ if(conn->bits.conn_to_host)
+ hostname = conn->conn_to_host.name;
+ else
+ hostname = conn->host.name;
+
+ failf(data, "Failed to connect to %s port %u after "
+ "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
+ hostname, conn->port,
+ Curl_timediff(now, data->progress.t_startsingle),
+ curl_easy_strerror(result));
+
+#ifdef WSAETIMEDOUT
+ if(WSAETIMEDOUT == data->state.os_errno)
+ result = CURLE_OPERATION_TIMEDOUT;
+#elif defined(ETIMEDOUT)
+ if(ETIMEDOUT == data->state.os_errno)
+ result = CURLE_OPERATION_TIMEDOUT;
+#endif
return result;
}
/*
- * TCP connect to the given host with timeout, proxy or remote doesn't matter.
- * There might be more than one IP address to try out. Fill in the passed
- * pointer with the connected socket.
+ * Connect to the given host with timeout, proxy or remote doesn't matter.
+ * There might be more than one IP address to try out.
*/
-
-CURLcode Curl_connecthost(struct Curl_easy *data,
- struct connectdata *conn, /* context */
- const struct Curl_dns_entry *remotehost)
+static CURLcode start_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const struct Curl_dns_entry *remotehost)
{
+ struct cf_he_ctx *ctx = cf->ctx;
+ struct connectdata *conn = cf->conn;
CURLcode result = CURLE_COULDNT_CONNECT;
- int i;
+ int ai_family0, ai_family1;
timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+ const struct Curl_addrinfo *addr0, *addr1;
if(timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */
@@ -1310,58 +737,73 @@ CURLcode Curl_connecthost(struct Curl_easy *data,
return CURLE_OPERATION_TIMEDOUT;
}
+ ctx->started = Curl_now();
conn->num_addr = Curl_num_addresses(remotehost->addr);
- conn->tempaddr[0] = conn->tempaddr[1] = remotehost->addr;
- conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD;
-
- /* Max time for the next connection attempt */
- conn->timeoutms_per_addr[0] =
- conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
- conn->timeoutms_per_addr[1] =
- conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
+ /* remotehost->addr is the list of addresses from the resolver, each
+ * with an address family. The list has at least one entry, possibly
+ * many more.
+ * We try at most 2 at a time, until we either get a connection or
+ * run out of addresses to try. Since likelihood of success is tied
+ * to the address family (e.g. IPV6 might not work at all ), we want
+ * the 2 connect attempt ballers to try different families, if possible.
+ *
+ */
if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) {
/* any IP version is allowed */
- conn->tempfamily[0] = conn->tempaddr[0]?
- conn->tempaddr[0]->ai_family:0;
+ ai_family0 = remotehost->addr?
+ remotehost->addr->ai_family : 0;
#ifdef ENABLE_IPV6
- conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ?
+ ai_family1 = ai_family0 == AF_INET6 ?
AF_INET : AF_INET6;
#else
- conn->tempfamily[1] = AF_UNSPEC;
+ ai_family1 = AF_UNSPEC;
#endif
}
else {
/* only one IP version is allowed */
- conn->tempfamily[0] = (conn->ip_version == CURL_IPRESOLVE_V4) ?
+ ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ?
AF_INET :
#ifdef ENABLE_IPV6
AF_INET6;
#else
AF_UNSPEC;
#endif
- conn->tempfamily[1] = AF_UNSPEC;
-
- ainext(conn, 0, FALSE); /* find first address of the right type */
+ ai_family1 = AF_UNSPEC;
}
- ainext(conn, 1, FALSE); /* assigns conn->tempaddr[1] accordingly */
-
- DEBUGF(infof(data, "family0 == %s, family1 == %s",
- conn->tempfamily[0] == AF_INET ? "v4" : "v6",
- conn->tempfamily[1] == AF_INET ? "v4" : "v6"));
+ /* Get the first address in the list that matches the family,
+ * this might give NULL, if we do not have any matches. */
+ addr0 = addr_first_match(remotehost->addr, ai_family0);
+ addr1 = addr_first_match(remotehost->addr, ai_family1);
+ if(!addr0 && addr1) {
+ /* switch around, so a single baller always uses addr0 */
+ addr0 = addr1;
+ ai_family0 = ai_family1;
+ addr1 = NULL;
+ }
- /* get through the list in family order in case of quick failures */
- for(i = 0; (i < 2) && result; i++) {
- while(conn->tempaddr[i]) {
- result = singleipconnect(data, conn, conn->tempaddr[i], i);
- if(!result)
- break;
- ainext(conn, i, TRUE);
- }
+ /* We found no address that matches our criteria, we cannot connect */
+ if(!addr0) {
+ return CURLE_COULDNT_CONNECT;
}
+
+ memset(ctx->baller, 0, sizeof(ctx->baller));
+ result = eyeballer_new(&ctx->baller[0], ctx->cf_create, addr0, ai_family0,
+ NULL, 0, /* no primary/delay, start now */
+ timeout_ms, EXPIRE_DNS_PER_NAME);
if(result)
return result;
+ if(addr1) {
+ /* second one gets a delayed start */
+ result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1,
+ ctx->baller[0], /* wait on that to fail */
+ /* or start this delayed */
+ data->set.happy_eyeballs_timeout,
+ timeout_ms, EXPIRE_DNS_PER_NAME2);
+ if(result)
+ return result;
+ }
Curl_expire(data, data->set.happy_eyeballs_timeout,
EXPIRE_HAPPY_EYEBALLS);
@@ -1369,310 +811,57 @@ CURLcode Curl_connecthost(struct Curl_easy *data,
return CURLE_OK;
}
-struct connfind {
- long id_tofind;
- struct connectdata *found;
-};
-
-static int conn_is_conn(struct Curl_easy *data,
- struct connectdata *conn, void *param)
+static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct connfind *f = (struct connfind *)param;
- (void)data;
- if(conn->connection_id == f->id_tofind) {
- f->found = conn;
- return 1;
- }
- return 0;
-}
+ struct cf_he_ctx *ctx = cf->ctx;
+ size_t i;
-/*
- * Used to extract socket and connectdata struct for the most recent
- * transfer on the given Curl_easy.
- *
- * The returned socket will be CURL_SOCKET_BAD in case of failure!
- */
-curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
- struct connectdata **connp)
-{
+ DEBUGASSERT(ctx);
DEBUGASSERT(data);
-
- /* this works for an easy handle:
- * - that has been used for curl_easy_perform()
- * - that is associated with a multi handle, and whose connection
- * was detached with CURLOPT_CONNECT_ONLY
- */
- if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) {
- struct connectdata *c;
- struct connfind find;
- find.id_tofind = data->state.lastconnect_id;
- find.found = NULL;
-
- Curl_conncache_foreach(data,
- data->share && (data->share->specifier
- & (1<< CURL_LOCK_DATA_CONNECT))?
- &data->share->conn_cache:
- data->multi_easy?
- &data->multi_easy->conn_cache:
- &data->multi->conn_cache, &find, conn_is_conn);
-
- if(!find.found) {
- data->state.lastconnect_id = -1;
- return CURL_SOCKET_BAD;
- }
-
- c = find.found;
- if(connp)
- /* only store this if the caller cares for it */
- *connp = c;
- return c->sock[FIRSTSOCKET];
- }
- return CURL_SOCKET_BAD;
-}
-
-/*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn)
-{
- (void)data;
- /* First determine if ssl */
- if(Curl_conn_is_ssl(data, FIRSTSOCKET)) {
- /* use the SSL context */
- if(!Curl_ssl_check_cxn(data, conn))
- return false; /* FIN received */
- }
-/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */
-#ifdef MSG_PEEK
- else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD)
- return false;
- else {
- /* use the socket */
- char buf;
- if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf,
- (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
- return false; /* FIN received */
- }
- }
-#endif
- return true;
-}
-
-/*
- * Close a socket.
- *
- * 'conn' can be NULL, beware!
- */
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sock)
-{
- if(conn && conn->fclosesocket) {
- if((sock == conn->sock[SECONDARYSOCKET]) && conn->bits.sock_accepted)
- /* if this socket matches the second socket, and that was created with
- accept, then we MUST NOT call the callback but clear the accepted
- status */
- conn->bits.sock_accepted = FALSE;
- else {
- int rc;
- Curl_multi_closed(data, sock);
- Curl_set_in_callback(data, true);
- rc = conn->fclosesocket(conn->closesocket_client, sock);
- Curl_set_in_callback(data, false);
- return rc;
- }
- }
-
- if(conn)
- /* tell the multi-socket code about this */
- Curl_multi_closed(data, sock);
-
- sclose(sock);
-
- return 0;
-}
-
-/*
- * Create a socket based on info from 'conn' and 'ai'.
- *
- * 'addr' should be a pointer to the correct struct to get data back, or NULL.
- * 'sockfd' must be a pointer to a socket descriptor.
- *
- * If the open socket callback is set, used that!
- *
- */
-CURLcode Curl_socket(struct Curl_easy *data,
- const struct Curl_addrinfo *ai,
- struct Curl_sockaddr_ex *addr,
- curl_socket_t *sockfd)
-{
- struct connectdata *conn = data->conn;
- struct Curl_sockaddr_ex dummy;
-
- if(!addr)
- /* if the caller doesn't want info back, use a local temp copy */
- addr = &dummy;
-
- /*
- * The Curl_sockaddr_ex structure is basically libcurl's external API
- * curl_sockaddr structure with enough space available to directly hold
- * any protocol-specific address structures. The variable declared here
- * will be used to pass / receive data to/from the fopensocket callback
- * if this has been set, before that, it is initialized from parameters.
- */
-
- addr->family = ai->ai_family;
- switch(conn->transport) {
- case TRNSPRT_TCP:
- addr->socktype = SOCK_STREAM;
- addr->protocol = IPPROTO_TCP;
- break;
- case TRNSPRT_UNIX:
- addr->socktype = SOCK_STREAM;
- addr->protocol = IPPROTO_IP;
- break;
- default: /* UDP and QUIC */
- addr->socktype = SOCK_DGRAM;
- addr->protocol = IPPROTO_UDP;
- break;
- }
- addr->addrlen = ai->ai_addrlen;
-
- if(addr->addrlen > sizeof(struct Curl_sockaddr_storage))
- addr->addrlen = sizeof(struct Curl_sockaddr_storage);
- memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen);
-
- if(data->set.fopensocket) {
- /*
- * If the opensocket callback is set, all the destination address
- * information is passed to the callback. Depending on this information the
- * callback may opt to abort the connection, this is indicated returning
- * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When
- * the callback returns a valid socket the destination address information
- * might have been changed and this 'new' address will actually be used
- * here to connect.
- */
- Curl_set_in_callback(data, true);
- *sockfd = data->set.fopensocket(data->set.opensocket_client,
- CURLSOCKTYPE_IPCXN,
- (struct curl_sockaddr *)addr);
- Curl_set_in_callback(data, false);
- }
- else
- /* opensocket callback not set, so simply create the socket now */
- *sockfd = socket(addr->family, addr->socktype, addr->protocol);
-
- if(*sockfd == CURL_SOCKET_BAD)
- /* no socket, no connection */
- return CURLE_COULDNT_CONNECT;
-
- if(conn->transport == TRNSPRT_QUIC) {
- /* QUIC sockets need to be nonblocking */
- (void)curlx_nonblock(*sockfd, TRUE);
- switch(addr->family) {
-#if defined(__linux__) && defined(IP_MTU_DISCOVER)
- case AF_INET: {
- int val = IP_PMTUDISC_DO;
- (void)setsockopt(*sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
- sizeof(val));
- break;
- }
-#endif
-#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
- case AF_INET6: {
- int val = IPV6_PMTUDISC_DO;
- (void)setsockopt(*sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
- sizeof(val));
- break;
- }
-#endif
- }
- }
-
-#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
- if(conn->scope_id && (addr->family == AF_INET6)) {
- struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
- sa6->sin6_scope_id = conn->scope_id;
+ for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ baller_free(ctx->baller[i], data);
+ ctx->baller[i] = NULL;
}
-#endif
-
- return CURLE_OK;
+ baller_free(ctx->winner, data);
+ ctx->winner = NULL;
}
-/*
- * Curl_conncontrol() marks streams or connection for closure.
- */
-void Curl_conncontrol(struct connectdata *conn,
- int ctrl /* see defines in header */
-#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
- , const char *reason
-#endif
- )
+static int cf_he_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
{
- /* close if a connection, or a stream that isn't multiplexed. */
- /* This function will be called both before and after this connection is
- associated with a transfer. */
- bool closeit;
- DEBUGASSERT(conn);
-#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
- (void)reason; /* useful for debugging */
-#endif
- closeit = (ctrl == CONNCTRL_CONNECTION) ||
- ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM));
- if((ctrl == CONNCTRL_STREAM) &&
- (conn->handler->flags & PROTOPT_STREAM))
- ;
- else if((bit)closeit != conn->bits.close) {
- conn->bits.close = closeit; /* the only place in the source code that
- should assign this bit */
- }
-}
-
-typedef enum {
- SCFST_INIT,
- SCFST_WAITING,
- SCFST_DONE
-} cf_connect_state;
-
-struct socket_cf_ctx {
- const struct Curl_dns_entry *remotehost;
- cf_connect_state state;
-};
+ struct cf_he_ctx *ctx = cf->ctx;
+ size_t i, s;
+ int wrc, rc = GETSOCK_BLANK;
+ curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE];
-static int socket_cf_get_select_socks(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- curl_socket_t *socks)
-{
- struct connectdata *conn = cf->conn;
- int i, s, rc = GETSOCK_BLANK;
+ if(cf->connected)
+ return cf->next->cft->get_select_socks(cf->next, data, socks);
- (void)data;
- if(cf->connected) {
- return rc;
- }
+ for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ struct eyeballer *baller = ctx->baller[i];
+ if(!baller || !baller->cf)
+ continue;
- for(i = s = 0; i<2; i++) {
- if(conn->tempsock[i] != CURL_SOCKET_BAD) {
- socks[s] = conn->tempsock[i];
- rc |= GETSOCK_WRITESOCK(s);
-#ifdef ENABLE_QUIC
- if(conn->transport == TRNSPRT_QUIC)
- /* when connecting QUIC, we want to read the socket too */
+ wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks);
+ if(wrc) {
+ /* TODO: we assume we get at most one socket back */
+ socks[s] = wsocks[0];
+ if(wrc & GETSOCK_WRITESOCK(0))
+ rc |= GETSOCK_WRITESOCK(s);
+ if(wrc & GETSOCK_READSOCK(0))
rc |= GETSOCK_READSOCK(s);
-#endif
s++;
}
}
-
return rc;
}
-static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- bool blocking, bool *done)
+static CURLcode cf_he_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
{
- struct connectdata *conn = cf->conn;
- int sockindex = cf->sockindex;
- struct socket_cf_ctx *ctx = cf->ctx;
+ struct cf_he_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
if(cf->connected) {
@@ -1680,27 +869,40 @@ static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
return CURLE_OK;
}
- (void)blocking;
+ (void)blocking; /* TODO: do we want to support this? */
DEBUGASSERT(ctx);
*done = FALSE;
+
switch(ctx->state) {
case SCFST_INIT:
- DEBUGASSERT(CURL_SOCKET_BAD == conn->sock[sockindex]);
+ DEBUGASSERT(CURL_SOCKET_BAD == cf->conn->sock[cf->sockindex]);
DEBUGASSERT(!cf->connected);
- result = Curl_connecthost(data, conn, ctx->remotehost);
- if(!result)
- ctx->state = SCFST_WAITING;
- break;
+ result = start_connect(cf, data, ctx->remotehost);
+ if(result)
+ return result;
+ ctx->state = SCFST_WAITING;
+ /* FALLTHROUGH */
case SCFST_WAITING:
- result = is_connected(data, conn, sockindex, done);
+ result = is_connected(cf, data, done);
if(!result && *done) {
- Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
- if(Curl_conn_is_ssl(data, FIRSTSOCKET) ||
- (conn->handler->protocol & PROTO_FAMILY_SSH))
- Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
- post_connect(data, conn, sockindex);
+ DEBUGASSERT(ctx->winner);
+ DEBUGASSERT(ctx->winner->cf);
+ /* we have a winner. Install and activate it.
+ * close/free all others. */
ctx->state = SCFST_DONE;
cf->connected = TRUE;
+ cf->next = ctx->winner->cf;
+ ctx->winner->cf = NULL;
+ cf_he_ctx_clear(cf, data);
+ Curl_conn_cf_cntrl(cf->next, data, TRUE,
+ CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+
+ Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
+ if(Curl_conn_is_ssl(cf->conn, FIRSTSOCKET) ||
+ (cf->conn->handler->protocol & PROTO_FAMILY_SSH))
+ Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
+ Curl_verboseconnect(data, cf->conn);
+ data->info.numconnects++; /* to track the # of connections made */
}
break;
case SCFST_DONE:
@@ -1710,223 +912,344 @@ static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
return result;
}
-static CURLcode socket_cf_setup(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const struct Curl_dns_entry *remotehost)
+static void cf_he_close(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- struct socket_cf_ctx *ctx = cf->ctx;
+ struct cf_he_ctx *ctx = cf->ctx;
- (void)data;
- DEBUGASSERT(ctx);
- if(ctx->remotehost != remotehost) {
- if(ctx->remotehost) {
- /* switching dns entry? TODO: reset? */
- }
- ctx->remotehost = remotehost;
- }
- DEBUGF(infof(data, CFMSG(cf, "setup(remotehost=%s)"),
- cf->conn->hostname_resolve));
- return CURLE_OK;
-}
-
-static void socket_cf_close(struct Curl_cfilter *cf,
- struct Curl_easy *data)
-{
- int sockindex = cf->sockindex;
- struct socket_cf_ctx *ctx = cf->ctx;
-
- DEBUGASSERT(ctx);
- /* close possibly still open sockets */
- if(CURL_SOCKET_BAD != cf->conn->sock[sockindex]) {
- Curl_closesocket(data, cf->conn, cf->conn->sock[sockindex]);
- cf->conn->sock[sockindex] = CURL_SOCKET_BAD;
- }
- if(CURL_SOCKET_BAD != cf->conn->tempsock[sockindex]) {
- Curl_closesocket(data, cf->conn, cf->conn->tempsock[sockindex]);
- cf->conn->tempsock[sockindex] = CURL_SOCKET_BAD;
- }
+ CF_DEBUGF(infof(data, CFMSG(cf, "close")));
+ cf_he_ctx_clear(cf, data);
cf->connected = FALSE;
ctx->state = SCFST_INIT;
-}
-static void socket_cf_get_host(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const char **phost,
- const char **pdisplay_host,
- int *pport)
-{
- (void)data;
- *phost = cf->conn->host.name;
- *pdisplay_host = cf->conn->host.dispname;
- *pport = cf->conn->port;
+ if(cf->next) {
+ cf->next->cft->close(cf->next, data);
+ Curl_conn_cf_discard_chain(&cf->next, data);
+ }
}
-static bool socket_cf_data_pending(struct Curl_cfilter *cf,
- const struct Curl_easy *data)
+static bool cf_he_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
{
- int readable;
- (void)data;
- DEBUGASSERT(cf);
+ struct cf_he_ctx *ctx = cf->ctx;
+ size_t i;
- readable = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
- return (readable > 0 && (readable & CURL_CSELECT_IN));
-}
+ if(cf->connected)
+ return cf->next->cft->has_data_pending(cf->next, data);
-static ssize_t socket_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
- const void *buf, size_t len, CURLcode *err)
-{
- ssize_t nwritten;
- nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err);
- return nwritten;
-}
-
-static ssize_t socket_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
- char *buf, size_t len, CURLcode *err)
-{
- ssize_t nread;
- nread = Curl_recv_plain(data, cf->sockindex, buf, len, err);
- return nread;
+ for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+ struct eyeballer *baller = ctx->baller[i];
+ if(!baller || !baller->cf)
+ continue;
+ if(baller->cf->cft->has_data_pending(baller->cf, data))
+ return TRUE;
+ }
+ return FALSE;
}
-static void socket_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct socket_cf_ctx *state = cf->ctx;
+ struct cf_he_ctx *ctx = cf->ctx;
- (void)data;
- if(cf->connected) {
- socket_cf_close(cf, data);
+ CF_DEBUGF(infof(data, CFMSG(cf, "destroy")));
+ if(ctx) {
+ cf_he_close(cf, data);
}
/* release any resources held in state */
- Curl_safefree(state);
+ Curl_safefree(ctx);
}
-static const struct Curl_cftype cft_socket = {
- "SOCKET",
- CF_TYPE_IP_CONNECT,
- socket_cf_destroy,
- socket_cf_setup,
- socket_cf_connect,
- socket_cf_close,
- socket_cf_get_host,
- socket_cf_get_select_socks,
- socket_cf_data_pending,
- socket_cf_send,
- socket_cf_recv,
- Curl_cf_def_attach_data,
- Curl_cf_def_detach_data,
+static const struct Curl_cftype cft_happy_eyeballs = {
+ "HAPPY-EYEBALLS",
+ 0,
+ cf_he_destroy,
+ cf_he_connect,
+ cf_he_close,
+ Curl_cf_def_get_host,
+ cf_he_get_select_socks,
+ cf_he_data_pending,
+ Curl_cf_def_send,
+ Curl_cf_def_recv,
+ Curl_cf_def_cntrl,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex)
+CURLcode Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ cf_ip_connect_create *cf_create,
+ const struct Curl_dns_entry *remotehost)
{
+ struct cf_he_ctx *ctx = NULL;
CURLcode result;
- struct Curl_cfilter *cf = NULL;
- struct socket_cf_ctx *scf_ctx = NULL;
- /* Need to be first */
- DEBUGASSERT(conn);
- DEBUGASSERT(!conn->cfilter[sockindex]);
- scf_ctx = calloc(sizeof(*scf_ctx), 1);
- if(!scf_ctx) {
+ (void)data;
+ (void)conn;
+ *pcf = NULL;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- result = Curl_cf_create(&cf, &cft_socket, scf_ctx);
- if(result)
- goto out;
- Curl_conn_cf_add(data, conn, sockindex, cf);
+ ctx->cf_create = cf_create;
+ ctx->remotehost = remotehost;
+
+ result = Curl_cf_create(pcf, &cft_happy_eyeballs, ctx);
out:
if(result) {
- Curl_safefree(cf);
- Curl_safefree(scf_ctx);
+ Curl_safefree(*pcf);
+ Curl_safefree(ctx);
}
return result;
}
-static CURLcode socket_accept_cf_connect(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- bool blocking, bool *done)
+static cf_ip_connect_create *get_cf_create(int transport)
{
- /* we start accepted, if we ever close, we cannot go on */
- (void)data;
- (void)blocking;
+ switch(transport) {
+ case TRNSPRT_TCP:
+ return Curl_cf_tcp_create;
+ case TRNSPRT_UDP:
+ return Curl_cf_udp_create;
+ case TRNSPRT_UNIX:
+ return Curl_cf_unix_create;
+#ifdef ENABLE_QUIC
+ case TRNSPRT_QUIC:
+ return Curl_cf_quic_create;
+#endif
+ default:
+ return NULL;
+ }
+}
+
+static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data,
+ const struct Curl_dns_entry *remotehost,
+ int transport)
+{
+ cf_ip_connect_create *cf_create;
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ /* Need to be first */
+ DEBUGASSERT(cf_at);
+ cf_create = get_cf_create(transport);
+ if(!cf_create) {
+ CF_DEBUGF(infof(data, DMSG(data, "unsupported transport type %d"),
+ transport));
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ }
+ result = Curl_cf_happy_eyeballs_create(&cf, data, cf_at->conn,
+ cf_create, remotehost);
+ if(result)
+ return result;
+
+ Curl_conn_cf_insert_after(cf_at, cf);
+ return CURLE_OK;
+}
+
+typedef enum {
+ CF_SETUP_INIT,
+ CF_SETUP_CNNCT_EYEBALLS,
+ CF_SETUP_CNNCT_SOCKS,
+ CF_SETUP_CNNCT_HTTP_PROXY,
+ CF_SETUP_CNNCT_HAPROXY,
+ CF_SETUP_CNNCT_SSL,
+ CF_SETUP_DONE
+} cf_setup_state;
+
+struct cf_setup_ctx {
+ cf_setup_state state;
+ const struct Curl_dns_entry *remotehost;
+ int ssl_mode;
+};
+
+static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct cf_setup_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
- return CURLE_FAILED_INIT;
+
+ /* connect current sub-chain */
+connect_sub_chain:
+ if(cf->next && !cf->next->connected) {
+ result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+ if(result || !*done)
+ return result;
+ }
+
+ if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
+ result = cf_he_insert_after(cf, data, ctx->remotehost,
+ cf->conn->transport);
+ if(result)
+ return result;
+ ctx->state = CF_SETUP_CNNCT_EYEBALLS;
+ if(!cf->next || !cf->next->connected)
+ goto connect_sub_chain;
+ }
+
+ /* sub-chain connected, do we need to add more? */
+#ifndef CURL_DISABLE_PROXY
+ if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
+ result = Curl_cf_socks_proxy_insert_after(cf, data);
+ if(result)
+ return result;
+ ctx->state = CF_SETUP_CNNCT_SOCKS;
+ if(!cf->next || !cf->next->connected)
+ goto connect_sub_chain;
+ }
+
+ if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
+#ifdef USE_SSL
+ if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS
+ && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+ result = Curl_cf_ssl_proxy_insert_after(cf, data);
+ if(result)
+ return result;
+ }
+#endif /* USE_SSL */
+
+#if !defined(CURL_DISABLE_HTTP)
+ if(cf->conn->bits.tunnel_proxy) {
+ result = Curl_cf_http_proxy_insert_after(cf, data);
+ if(result)
+ return result;
+ }
+#endif /* !CURL_DISABLE_HTTP */
+ ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
+ if(!cf->next || !cf->next->connected)
+ goto connect_sub_chain;
+ }
+#endif /* !CURL_DISABLE_PROXY */
+
+ if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
+#if !defined(CURL_DISABLE_PROXY)
+ if(data->set.haproxyprotocol) {
+ if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+ failf(data, "haproxy protocol not support with SSL "
+ "encryption in place (QUIC?)");
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ }
+ result = Curl_cf_haproxy_insert_after(cf, data);
+ if(result)
+ return result;
+ }
+#endif /* !CURL_DISABLE_PROXY */
+ ctx->state = CF_SETUP_CNNCT_HAPROXY;
+ if(!cf->next || !cf->next->connected)
+ goto connect_sub_chain;
+ }
+
+ if(ctx->state < CF_SETUP_CNNCT_SSL) {
+#ifdef USE_SSL
+ if((ctx->ssl_mode == CURL_CF_SSL_ENABLE
+ || (ctx->ssl_mode != CURL_CF_SSL_DISABLE
+ && cf->conn->handler->flags & PROTOPT_SSL)) /* we want SSL */
+ && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
+ result = Curl_cf_ssl_insert_after(cf, data);
+ if(result)
+ return result;
+ }
+#endif /* USE_SSL */
+ ctx->state = CF_SETUP_CNNCT_SSL;
+ if(!cf->next || !cf->next->connected)
+ goto connect_sub_chain;
+ }
+
+ ctx->state = CF_SETUP_DONE;
+ cf->connected = TRUE;
+ *done = TRUE;
+ return CURLE_OK;
}
-static CURLcode socket_accept_cf_setup(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const struct Curl_dns_entry *remotehost)
+static void cf_setup_close(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- /* we start accepted, if we ever close, we cannot go on */
- (void)data;
- (void)remotehost;
- if(cf->connected) {
- return CURLE_OK;
+ struct cf_setup_ctx *ctx = cf->ctx;
+
+ CF_DEBUGF(infof(data, CFMSG(cf, "close")));
+ cf->connected = FALSE;
+ ctx->state = CF_SETUP_INIT;
+
+ if(cf->next) {
+ cf->next->cft->close(cf->next, data);
+ Curl_conn_cf_discard_chain(&cf->next, data);
}
- return CURLE_FAILED_INIT;
}
-static const struct Curl_cftype cft_socket_accept = {
- "SOCKET-ACCEPT",
- CF_TYPE_IP_CONNECT,
- socket_cf_destroy,
- socket_accept_cf_setup,
- socket_accept_cf_connect,
- socket_cf_close,
- socket_cf_get_host, /* TODO: not accurate */
+static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_setup_ctx *ctx = cf->ctx;
+
+ CF_DEBUGF(infof(data, CFMSG(cf, "destroy")));
+ if(ctx) {
+ cf_setup_close(cf, data);
+ }
+ /* release any resources held in state */
+ Curl_safefree(ctx);
+}
+
+
+static const struct Curl_cftype cft_setup = {
+ "SETUP",
+ 0,
+ cf_setup_destroy,
+ cf_setup_connect,
+ cf_setup_close,
+ Curl_cf_def_get_host,
Curl_cf_def_get_select_socks,
- socket_cf_data_pending,
- socket_cf_send,
- socket_cf_recv,
- Curl_cf_def_attach_data,
- Curl_cf_def_detach_data,
+ Curl_cf_def_data_pending,
+ Curl_cf_def_send,
+ Curl_cf_def_recv,
+ Curl_cf_def_cntrl,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex, curl_socket_t *s)
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ const struct Curl_dns_entry *remotehost,
+ int ssl_mode)
{
- CURLcode result;
- struct Curl_cfilter *cf = NULL;
- struct socket_cf_ctx *scf_ctx = NULL;
-
- cf = conn->cfilter[sockindex];
- if(cf && cf->cft == &cft_socket_accept) {
- /* already an accept filter installed, just replace the socket */
- scf_ctx = cf->ctx;
- result = CURLE_OK;
- }
- else {
- /* replace any existing */
- Curl_conn_cf_discard_all(data, conn, sockindex);
- scf_ctx = calloc(sizeof(*scf_ctx), 1);
- if(!scf_ctx) {
+ CURLcode result = CURLE_OK;
+ struct cf_setup_ctx *ctx = NULL;
+
+ DEBUGASSERT(data);
+ /* If no filter is set, we add the "default" setup connection filter.
+ */
+ if(!conn->cfilter[sockindex]) {
+ struct Curl_cfilter *cf;
+
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- result = Curl_cf_create(&cf, &cft_socket_accept, scf_ctx);
+ ctx->state = CF_SETUP_INIT;
+ ctx->remotehost = remotehost;
+ ctx->ssl_mode = ssl_mode;
+
+ result = Curl_cf_create(&cf, &cft_setup, ctx);
if(result)
goto out;
+ ctx = NULL;
Curl_conn_cf_add(data, conn, sockindex, cf);
}
- /* close any existing socket and replace */
- Curl_closesocket(data, conn, conn->sock[sockindex]);
- conn->sock[sockindex] = *s;
- conn->bits.sock_accepted = TRUE;
- cf->connected = TRUE;
- scf_ctx->state = SCFST_DONE;
+ DEBUGASSERT(conn->cfilter[sockindex]);
out:
- if(result) {
- Curl_safefree(cf);
- Curl_safefree(scf_ctx);
- }
+ free(ctx);
return result;
}
+
diff --git a/lib/connect.h b/lib/connect.h
index 1e90a8561..b69333a51 100644
--- a/lib/connect.h
+++ b/lib/connect.h
@@ -29,9 +29,7 @@
#include "sockaddr.h"
#include "timeval.h"
-CURLcode Curl_connecthost(struct Curl_easy *data,
- struct connectdata *conn,
- const struct Curl_dns_entry *host);
+struct Curl_dns_entry;
/* generic function that returns how much time there's left to run, according
to the timeouts set */
@@ -53,67 +51,8 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
char *addr, int *port);
-/*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn);
-
-#ifdef USE_WINSOCK
-/* When you run a program that uses the Windows Sockets API, you may
- experience slow performance when you copy data to a TCP server.
-
- https://support.microsoft.com/kb/823764
-
- Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
- Buffer Size
-
-*/
-void Curl_sndbufset(curl_socket_t sockfd);
-#else
-#define Curl_sndbufset(y) Curl_nop_stmt
-#endif
-
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sockfd);
-void Curl_conninfo_remote(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sockfd);
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
- char *local_ip, int *local_port);
void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
char *local_ip, int local_port);
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t sock);
-
-/*
- * The Curl_sockaddr_ex structure is basically libcurl's external API
- * curl_sockaddr structure with enough space available to directly hold any
- * protocol-specific address structures. The variable declared here will be
- * used to pass / receive data to/from the fopensocket callback if this has
- * been set, before that, it is initialized from parameters.
- */
-struct Curl_sockaddr_ex {
- int family;
- int socktype;
- int protocol;
- unsigned int addrlen;
- union {
- struct sockaddr addr;
- struct Curl_sockaddr_storage buff;
- } _sa_ex_u;
-};
-#define sa_addr _sa_ex_u.addr
-
-/*
- * Create a socket based on info from 'conn' and 'ai'.
- *
- * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open
- * socket callback is set, used that!
- *
- */
-CURLcode Curl_socket(struct Curl_easy *data,
- const struct Curl_addrinfo *ai,
- struct Curl_sockaddr_ex *addr,
- curl_socket_t *sockfd);
/*
* Curl_conncontrol() marks the end of a connection/stream. The 'closeit'
@@ -148,13 +87,50 @@ void Curl_conncontrol(struct connectdata *conn,
#define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
#endif
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
+/**
+ * Create a cfilter for making an "ip" connection to the
+ * given address, using paramters from `conn`. The "ip" connection
+ * can be a TCP socket, a UDP socket or even a QUIC connection.
+ *
+ * It MUST use only the supplied `ai` for its connection attempt.
+ *
+ * Such a filter may be used in "happy eyeball" scenarios, and its
+ * `connect` implementation needs to support non-blocking. Once connected,
+ * it MAY be installed in the connection filter chain to serve transfers.
+ */
+typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+/**
+ * Create a happy eyeball connection filter that uses the, once resolved,
+ * address information to connect on ip families based on connection
+ * configuration.
+ * @param pcf output, the created cfilter
+ * @param data easy handle used in creation
+ * @param conn connection the filter is created for
+ * @param cf_create method to create the sub-filters performing the
+ * actual connects.
+ */
+CURLcode
+Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
struct connectdata *conn,
- int sockindex);
+ cf_ip_connect_create *cf_create,
+ const struct Curl_dns_entry *remotehost);
+
+/**
+ * Setup the cfilters at `sockindex` in connection `conn`, invoking
+ * the instance `setup(remotehost)` methods. If no filter chain is
+ * installed yet, inspects the configuration in `data` to install a
+ * suitable filter chain.
+ */
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ const struct Curl_dns_entry *remotehost,
+ int ssl_mode);
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- curl_socket_t *s);
#endif /* HEADER_CURL_CONNECT_H */
diff --git a/lib/easy.c b/lib/easy.c
index 2a94bc52a..4ec072d4e 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -65,6 +65,7 @@
#include "easyif.h"
#include "multiif.h"
#include "select.h"
+#include "cfilters.h"
#include "sendf.h" /* for failf function prototype */
#include "connect.h" /* for Curl_getconnectinfo */
#include "slist.h"
@@ -1099,7 +1100,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action)
k->keepon = newstate;
if(!(newstate & KEEP_RECV_PAUSE)) {
- Curl_http2_stream_pause(data, FALSE);
+ Curl_conn_ev_data_pause(data, FALSE);
if(data->state.tempcount) {
/* there are buffers for sending that can be delivered as the receive
@@ -1292,29 +1293,34 @@ static int conn_upkeep(struct Curl_easy *data,
struct connectdata *conn,
void *param)
{
- /* Param is unused. */
- (void)param;
+ struct curltime *now = param;
- if(conn->handler->connection_check) {
- /* briefly attach the connection to this transfer for the purpose of
- checking it */
- Curl_attach_connection(data, conn);
+ if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms)
+ return 0;
+ /* briefly attach for action */
+ Curl_attach_connection(data, conn);
+ if(conn->handler->connection_check) {
/* Do a protocol-specific keepalive check on the connection. */
conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE);
- /* detach the connection again */
- Curl_detach_connection(data);
}
+ else {
+ /* Do the generic action on the FIRSTSOCKE filter chain */
+ Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
+ }
+ Curl_detach_connection(data);
+ conn->keepalive = *now;
return 0; /* continue iteration */
}
static CURLcode upkeep(struct conncache *conn_cache, void *data)
{
+ struct curltime now = Curl_now();
/* Loop over every connection and make connection alive. */
Curl_conncache_foreach(data,
conn_cache,
- data,
+ &now,
conn_upkeep);
return CURLE_OK;
}
diff --git a/lib/ftp.c b/lib/ftp.c
index b6dc96119..254018851 100644
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -61,6 +61,7 @@
#include "strcase.h"
#include "vtls/vtls.h"
#include "cfilters.h"
+#include "cf-socket.h"
#include "connect.h"
#include "strerror.h"
#include "inet_ntop.h"
@@ -285,8 +286,8 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data)
conn->bits.do_more = FALSE;
(void)curlx_nonblock(s, TRUE); /* enable non-blocking */
- /* Replace any filter on SECONDARY with one listening on this socket */
- result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET, &s);
+ /* Replace any filter on SECONDARY with one listeing on this socket */
+ result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s);
if(result)
return result;
@@ -819,26 +820,11 @@ static int ftp_domore_getsock(struct Curl_easy *data,
if(FTP_STOP == ftpc->state) {
int bits = GETSOCK_READSOCK(0);
- bool any = FALSE;
/* if stopped and still in this state, then we're also waiting for a
connect on the secondary connection */
socks[0] = conn->sock[FIRSTSOCKET];
-
- if(!data->set.ftp_use_port) {
- int s;
- int i;
- /* PORT is used to tell the server to connect to us, and during that we
- don't do happy eyeballs, but we do if we connect to the server */
- for(s = 1, i = 0; i<2; i++) {
- if(conn->tempsock[i] != CURL_SOCKET_BAD) {
- socks[s] = conn->tempsock[i];
- bits |= GETSOCK_WRITESOCK(s++);
- any = TRUE;
- }
- }
- }
- if(!any) {
+ if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) {
socks[1] = conn->sock[SECONDARYSOCKET];
bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1);
}
@@ -1097,7 +1083,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
portsock = CURL_SOCKET_BAD;
error = 0;
for(ai = res; ai; ai = ai->ai_next) {
- if(Curl_socket(data, ai, NULL, &portsock)) {
+ if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) {
error = SOCKERRNO;
continue;
}
@@ -1266,9 +1252,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
/* store which command was sent */
ftpc->count1 = fcmd;
- /* Replace any filter on SECONDARY with one listening on this socket */
- result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET,
- &portsock);
+ /* Replace any filter on SECONDARY with one listeing on this socket */
+ result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock);
if(result)
goto out;
portsock = CURL_SOCKET_BAD; /* now held in filter */
@@ -1279,7 +1264,7 @@ out:
state(data, FTP_STOP);
}
if(portsock != CURL_SOCKET_BAD)
- Curl_closesocket(data, conn, portsock);
+ Curl_socket_close(data, conn, portsock);
free(addr);
return result;
}
@@ -1954,7 +1939,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data,
/* postponed address resolution in case of tcp fastopen */
if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) {
- Curl_conninfo_remote(data, conn, conn->sock[FIRSTSOCKET]);
+ Curl_conn_ev_update_info(data, conn);
Curl_safefree(ftpc->newhost);
ftpc->newhost = strdup(control_address(conn));
if(!ftpc->newhost)
@@ -2742,7 +2727,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data,
if((ftpcode == 234) || (ftpcode == 334)) {
/* this was BLOCKING, keep it so for now */
bool done;
- if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result) {
/* we failed and bail out */
diff --git a/lib/http.c b/lib/http.c
index 275c0cb21..3f2b33c83 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -62,6 +62,7 @@
#include "cookie.h"
#include "vauth/vauth.h"
#include "vtls/vtls.h"
+#include "vquic/vquic.h"
#include "http_digest.h"
#include "http_ntlm.h"
#include "curl_ntlm_wb.h"
@@ -241,10 +242,7 @@ static CURLcode h3_setup_conn(struct Curl_easy *data,
}
#endif
- DEBUGF(infof(data, "HTTP/3 direct conn setup(conn #%ld, index=%d)",
- conn->connection_id, FIRSTSOCKET));
- return Curl_conn_socket_set(data, conn, FIRSTSOCKET);
-
+ return CURLE_OK;
#else /* ENABLE_QUIC */
(void)conn;
(void)data;
@@ -275,12 +273,6 @@ static CURLcode http_setup_conn(struct Curl_easy *data,
if(conn->transport == TRNSPRT_QUIC) {
return h3_setup_conn(data, conn);
}
- else {
- if(!CONN_INUSE(conn))
- /* if not already multi-using, setup connection details */
- Curl_http2_setup_conn(conn);
- Curl_http2_setup_req(data);
- }
return CURLE_OK;
}
@@ -1610,8 +1602,6 @@ CURLcode Curl_http_done(struct Curl_easy *data,
return CURLE_OK;
Curl_dyn_free(&http->send_buffer);
- Curl_http2_done(data, premature);
- Curl_quic_done(data, premature);
Curl_mime_cleanpart(&http->form);
Curl_dyn_reset(&data->state.headerb);
Curl_hyper_done(data);
@@ -1664,17 +1654,10 @@ bool Curl_use_http_1_1plus(const struct Curl_easy *data,
static const char *get_http_string(const struct Curl_easy *data,
const struct connectdata *conn)
{
-#ifdef ENABLE_QUIC
- if((data->state.httpwant == CURL_HTTP_VERSION_3) ||
- (conn->httpversion == 30))
+ if(Curl_conn_is_http3(data, conn, FIRSTSOCKET))
return "3";
-#endif
-
-#ifdef USE_NGHTTP2
- if(conn->proto.httpc.h2)
+ if(Curl_conn_is_http2(data, conn, FIRSTSOCKET))
return "2";
-#endif
-
if(Curl_use_http_1_1plus(data, conn))
return "1.1";
@@ -2561,7 +2544,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
/* In HTTP2, we send request body in DATA frame regardless of
its size. */
- if(conn->httpversion != 20 &&
+ if(conn->httpversion < 20 &&
!data->state.expect100header &&
(http->postsize < MAX_INITIAL_POST_SIZE)) {
/* if we don't use expect: 100 AND
@@ -3021,50 +3004,35 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
the rest of the request in the PERFORM phase. */
*done = TRUE;
- if(conn->transport != TRNSPRT_QUIC) {
- if(conn->httpversion < 20) { /* unless the connection is re-used and
- already http2 */
- switch(conn->alpn) {
- case CURL_HTTP_VERSION_2:
- conn->httpversion = 20; /* we know we're on HTTP/2 now */
+ if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)
+ || Curl_conn_is_http2(data, conn, FIRSTSOCKET)
+ || conn->httpversion == 20 /* like to get rid of this */) {
+ /* all fine, we are set */
+ }
+ else { /* undecided */
+ switch(conn->alpn) {
+ case CURL_HTTP_VERSION_2:
+ result = Curl_http2_switch(data, conn, FIRSTSOCKET, NULL, 0);
+ if(result)
+ return result;
+ break;
- result = Curl_http2_switched(data, NULL, 0);
+ case CURL_HTTP_VERSION_1_1:
+ /* continue with HTTP/1.1 when explicitly requested */
+ break;
+
+ default:
+ /* Check if user wants to use HTTP/2 with clear TCP */
+ if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) {
+ DEBUGF(infof(data, "HTTP/2 over clean TCP"));
+ result = Curl_http2_switch(data, conn, FIRSTSOCKET, NULL, 0);
if(result)
return result;
- break;
- case CURL_HTTP_VERSION_1_1:
- /* continue with HTTP/1.1 when explicitly requested */
- break;
- default:
- /* Check if user wants to use HTTP/2 with clear TCP */
-#ifdef USE_NGHTTP2
- if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
-#ifndef CURL_DISABLE_PROXY
- if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
- /* We don't support HTTP/2 proxies yet. Also it's debatable
- whether or not this setting should apply to HTTP/2 proxies. */
- infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
- break;
- }
-#endif
- DEBUGF(infof(data, "HTTP/2 over clean TCP"));
- conn->httpversion = 20;
-
- result = Curl_http2_switched(data, NULL, 0);
- if(result)
- return result;
- }
-#endif
- break;
}
- }
- else {
- /* prepare for an http2 request */
- result = Curl_http2_setup(data, conn);
- if(result)
- return result;
+ break;
}
}
+
http = data->req.p.http;
DEBUGASSERT(http);
@@ -3224,7 +3192,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
}
if(!(conn->handler->flags&PROTOPT_SSL) &&
- conn->httpversion != 20 &&
+ conn->httpversion < 20 &&
(data->state.httpwant == CURL_HTTP_VERSION_2)) {
/* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done
over SSL */
@@ -3282,7 +3250,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
}
}
- if((conn->httpversion == 20) && data->req.upload_chunky)
+ if((conn->httpversion >= 20) && data->req.upload_chunky)
/* upload_chunky was set above to set up the request in a chunky fashion,
but is disabled here again to avoid that the chunked encoded version is
actually used when sending the request body over h2 */
@@ -3669,7 +3637,8 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
#endif
)) {
/* the ALPN of the current request */
- enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
+ enum alpnid id = (conn->httpversion == 30)? ALPN_h3 :
+ (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
result = Curl_altsvc_parse(data, data->asi,
headp + strlen("Alt-Svc:"),
id, conn->host.name,
@@ -3963,7 +3932,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
/* switch to http2 now. The bytes after response headers
are also processed here, otherwise they are lost. */
- result = Curl_http2_switched(data, k->str, *nread);
+ result = Curl_http2_switch(data, conn, FIRSTSOCKET,
+ k->str, *nread);
if(result)
return result;
*nread = 0;
@@ -4191,11 +4161,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
stream. In order to do this, we keep reading until we
close the stream. */
if(0 == k->maxdownload
-#if defined(USE_NGHTTP2)
- && !((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
- conn->httpversion == 20)
-#endif
- )
+ && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
+ && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
*stop_reading = TRUE;
if(*stop_reading) {
diff --git a/lib/http.h b/lib/http.h
index ecfe4eed0..1d8e590ad 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -24,6 +24,11 @@
*
***************************************************************************/
#include "curl_setup.h"
+
+#if defined(USE_MSH3) && !defined(_WIN32)
+#include <pthread.h>
+#endif
+
#include "ws.h"
typedef enum {
@@ -37,10 +42,6 @@ typedef enum {
#ifndef CURL_DISABLE_HTTP
-#ifdef USE_NGHTTP2
-#include <nghttp2/nghttp2.h>
-#endif
-
#if defined(_WIN32) && defined(ENABLE_QUIC)
#include <stdint.h>
#endif
@@ -179,29 +180,6 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data);
struct h3out; /* see ngtcp2 */
#endif
-#ifdef USE_MSH3
-#ifdef _WIN32
-#define msh3_lock CRITICAL_SECTION
-#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
-#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
-#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
-#define msh3_lock_release(lock) LeaveCriticalSection(lock)
-#else /* !_WIN32 */
-#include <pthread.h>
-#define msh3_lock pthread_mutex_t
-#define msh3_lock_initialize(lock) { \
- pthread_mutexattr_t attr; \
- pthread_mutexattr_init(&attr); \
- pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
- pthread_mutex_init(lock, &attr); \
- pthread_mutexattr_destroy(&attr); \
-}
-#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
-#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
-#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
-#endif /* _WIN32 */
-#endif /* USE_MSH3 */
-
/****************************************************************************
* HTTP unique setup
***************************************************************************/
@@ -278,17 +256,22 @@ struct HTTP {
bool firstheader; /* FALSE until headers arrive */
bool firstbody; /* FALSE until body arrives */
bool h3req; /* FALSE until request is issued */
-#endif
+#endif /* !USE_MSH3 */
bool upload_done;
-#endif
+#endif /* ENABLE_QUIC */
#ifdef USE_NGHTTP3
size_t unacked_window;
struct h3out *h3out; /* per-stream buffers for upload */
struct dynbuf overflow; /* excess data received during a single Curl_read */
-#endif
+#endif /* USE_NGHTTP3 */
#ifdef USE_MSH3
struct MSH3_REQUEST *req;
- msh3_lock recv_lock;
+#ifdef _WIN32
+ CRITICAL_SECTION recv_lock;
+#else /* !_WIN32 */
+ pthread_mutex_t recv_lock;
+#endif /* _WIN32 */
+
/* Receive Buffer (Headers and Data) */
uint8_t* recv_buf;
size_t recv_buf_alloc;
@@ -300,53 +283,7 @@ struct HTTP {
bool recv_data_complete;
/* General Receive Error */
CURLcode recv_error;
-#endif
-};
-
-#ifdef USE_NGHTTP2
-/* h2 settings for this connection */
-struct h2settings {
- uint32_t max_concurrent_streams;
- bool enable_push;
-};
-#endif
-
-struct http_conn {
-#ifdef USE_NGHTTP2
-#define H2_BINSETTINGS_LEN 80
- uint8_t binsettings[H2_BINSETTINGS_LEN];
- size_t binlen; /* length of the binsettings data */
-
- /* We associate the connectdata struct with the connection, but we need to
- make sure we can identify the current "driving" transfer. This is a
- work-around for the lack of nghttp2_session_set_user_data() in older
- nghttp2 versions that we want to support. (Added in 1.31.0) */
- struct Curl_easy *trnsfr;
-
- nghttp2_session *h2;
- Curl_send *send_underlying; /* underlying send Curl_send callback */
- Curl_recv *recv_underlying; /* underlying recv Curl_recv callback */
- char *inbuf; /* buffer to receive data from underlying socket */
- size_t inbuflen; /* number of bytes filled in inbuf */
- size_t nread_inbuf; /* number of bytes read from in inbuf */
- /* We need separate buffer for transmission and reception because we
- may call nghttp2_session_send() after the
- nghttp2_session_mem_recv() but mem buffer is still not full. In
- this case, we wrongly sends the content of mem buffer if we share
- them for both cases. */
- int32_t pause_stream_id; /* stream ID which paused
- nghttp2_session_mem_recv */
- size_t drain_total; /* sum of all stream's UrlState.drain */
-
- /* this is a hash of all individual streams (Curl_easy structs) */
- struct h2settings settings;
-
- /* list of settings that will be sent */
- nghttp2_settings_entry local_settings[3];
- size_t local_settings_num;
-#else
- int unused; /* prevent a compiler warning */
-#endif
+#endif /* USE_MSH3 */
};
CURLcode Curl_http_size(struct Curl_easy *data);
diff --git a/lib/http2.c b/lib/http2.c
index 6d060ae17..3c8674cbf 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -35,6 +35,7 @@
#include "strcase.h"
#include "multiif.h"
#include "url.h"
+#include "cfilters.h"
#include "connect.h"
#include "strtoofft.h"
#include "strdup.h"
@@ -63,124 +64,320 @@
#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
+#define DEBUG_HTTP2
#ifdef DEBUG_HTTP2
#define H2BUGF(x) x
#else
#define H2BUGF(x) do { } while(0)
#endif
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
- char *mem, size_t len, CURLcode *err);
-static bool http2_connisdead(struct Curl_easy *data,
- struct connectdata *conn);
-static int h2_session_send(struct Curl_easy *data,
- nghttp2_session *h2);
-static int h2_process_pending_input(struct Curl_easy *data,
- struct http_conn *httpc,
- CURLcode *err);
-/*
- * Curl_http2_init_state() is called when the easy handle is created and
- * allows for HTTP/2 specific init of state.
- */
-void Curl_http2_init_state(struct UrlState *state)
+#define H2_SETTINGS_IV_LEN 3
+#define H2_BINSETTINGS_LEN 80
+
+static int populate_settings(nghttp2_settings_entry *iv,
+ struct Curl_easy *data)
+{
+ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
+
+ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
+
+ iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[2].value = data->multi->push_cb != NULL;
+
+ return 3;
+}
+
+static size_t populate_binsettings(uint8_t *binsettings,
+ struct Curl_easy *data)
+{
+ nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+ int ivlen;
+
+ ivlen = populate_settings(iv, data);
+ /* this returns number of bytes it wrote */
+ return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
+ iv, ivlen);
+}
+
+struct h2_cf_ctx {
+ nghttp2_session *h2;
+ uint32_t max_concurrent_streams;
+ bool enable_push;
+
+ /* We associate the connectdata struct with the connection, but we need to
+ make sure we can identify the current "driving" transfer. This is a
+ work-around for the lack of nghttp2_session_set_user_data() in older
+ nghttp2 versions that we want to support. (Added in 1.31.0) */
+ struct Curl_easy *trnsfr;
+
+ char *inbuf; /* buffer to receive data from underlying socket */
+ size_t inbuflen; /* number of bytes filled in inbuf */
+ size_t nread_inbuf; /* number of bytes read from in inbuf */
+
+ /* We need separate buffer for transmission and reception because we
+ may call nghttp2_session_send() after the
+ nghttp2_session_mem_recv() but mem buffer is still not full. In
+ this case, we wrongly sends the content of mem buffer if we share
+ them for both cases. */
+ int32_t pause_stream_id; /* stream ID which paused
+ nghttp2_session_mem_recv */
+ size_t drain_total; /* sum of all stream's UrlState.drain */
+};
+
+static void h2_cf_ctx_clear(struct h2_cf_ctx *ctx)
+{
+ if(ctx->h2) {
+ nghttp2_session_del(ctx->h2);
+ }
+ free(ctx->inbuf);
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+static void h2_cf_ctx_free(struct h2_cf_ctx *ctx)
{
- state->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
+ if(ctx) {
+ h2_cf_ctx_clear(ctx);
+ free(ctx);
+ }
+}
+
+static int h2_client_new(struct Curl_cfilter *cf,
+ nghttp2_session_callbacks *cbs)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
+
+#if NGHTTP2_VERSION_NUM < 0x013200
+ /* before 1.50.0 */
+ return nghttp2_session_client_new(&ctx->h2, cbs, cf);
+#else
+ nghttp2_option *o;
+ int rc = nghttp2_option_new(&o);
+ if(rc)
+ return rc;
+ /* turn off RFC 9113 leading and trailing white spaces validation against
+ HTTP field value. */
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
+ rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
+ nghttp2_option_del(o);
+ return rc;
+#endif
}
+static ssize_t send_callback(nghttp2_session *h2,
+ const uint8_t *mem, size_t length, int flags,
+ void *userp);
+static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
+ void *userp);
+static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *mem, size_t len, void *userp);
+static int on_stream_close(nghttp2_session *session, int32_t stream_id,
+ uint32_t error_code, void *userp);
+static int on_begin_headers(nghttp2_session *session,
+ const nghttp2_frame *frame, void *userp);
+static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags,
+ void *userp);
+static int error_callback(nghttp2_session *session, const char *msg,
+ size_t len, void *userp);
+
/*
- * Curl_http2_init_userset() is called when the easy handle is created and
- * allows for HTTP/2 specific user-set fields.
+ * multi_connchanged() is called to tell that there is a connection in
+ * this multi handle that has changed state (multiplexing become possible, the
+ * number of allowed streams changed or similar), and a subsequent use of this
+ * multi handle should move CONNECT_PEND handles back to CONNECT to have them
+ * retry.
*/
-void Curl_http2_init_userset(struct UserDefined *set)
+static void multi_connchanged(struct Curl_multi *multi)
{
- set->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
+ multi->recheckstate = TRUE;
}
-static int http2_getsock(struct Curl_easy *data,
- struct connectdata *conn,
- curl_socket_t *sock)
+static CURLcode http2_data_setup(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- const struct http_conn *c = &conn->proto.httpc;
- struct SingleRequest *k = &data->req;
- int bitmap = GETSOCK_BLANK;
struct HTTP *stream = data->req.p.http;
- sock[0] = conn->sock[FIRSTSOCKET];
+ (void)cf;
+ DEBUGASSERT(stream);
+ DEBUGASSERT(data->state.buffer);
- if(!(k->keepon & KEEP_RECV_PAUSE))
- /* Unless paused - in an HTTP/2 connection we can basically always get a
- frame so we should always be ready for one */
- bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ stream->stream_id = -1;
- /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
- there's a window to send data in */
- if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) ||
- nghttp2_session_want_write(c->h2)) &&
- (nghttp2_session_get_remote_window_size(c->h2) &&
- nghttp2_session_get_stream_remote_window_size(c->h2,
- stream->stream_id)))
- bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+ Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
+ Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
- return bitmap;
+ stream->bodystarted = FALSE;
+ stream->status_code = -1;
+ stream->pausedata = NULL;
+ stream->pauselen = 0;
+ stream->closed = FALSE;
+ stream->close_handled = FALSE;
+ stream->memlen = 0;
+ stream->error = NGHTTP2_NO_ERROR;
+ stream->upload_left = 0;
+ stream->upload_mem = NULL;
+ stream->upload_len = 0;
+ stream->mem = data->state.buffer;
+ stream->len = data->set.buffer_size;
+
+ return CURLE_OK;
}
/*
- * http2_stream_free() free HTTP2 stream related data
+ * Initialize the cfilter context
*/
-static void http2_stream_free(struct HTTP *http)
+static CURLcode h2_cf_ctx_init(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool via_h1_upgrade)
{
- if(http) {
- Curl_dyn_free(&http->header_recvbuf);
- for(; http->push_headers_used > 0; --http->push_headers_used) {
- free(http->push_headers[http->push_headers_used - 1]);
+ struct h2_cf_ctx *ctx = cf->ctx;
+ struct HTTP *stream = data->req.p.http;
+ CURLcode result = CURLE_OUT_OF_MEMORY;
+ int rc;
+ nghttp2_session_callbacks *cbs = NULL;
+
+ DEBUGASSERT(!ctx->h2);
+ ctx->inbuf = malloc(H2_BUFSIZE);
+ if(!ctx->inbuf)
+ goto out;
+
+ rc = nghttp2_session_callbacks_new(&cbs);
+ if(rc) {
+ failf(data, "Couldn't initialize nghttp2 callbacks");
+ goto out;
+ }
+
+ nghttp2_session_callbacks_set_send_callback(cbs, send_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ cbs, on_data_chunk_recv);
+ nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ cbs, on_begin_headers);
+ nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
+ nghttp2_session_callbacks_set_error_callback(cbs, error_callback);
+
+ /* The nghttp2 session is not yet setup, do it */
+ rc = h2_client_new(cf, cbs);
+ if(rc) {
+ failf(data, "Couldn't initialize nghttp2");
+ goto out;
+ }
+ ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS;
+
+ result = http2_data_setup(cf, data);
+ if(result)
+ goto out;
+
+ if(via_h1_upgrade) {
+ /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted
+ * in the H1 request and we upgrade from there. This stream
+ * is opened implicitly as #1. */
+ uint8_t binsettings[H2_BINSETTINGS_LEN];
+ size_t binlen; /* length of the binsettings data */
+
+ binlen = populate_binsettings(binsettings, data);
+
+ stream->stream_id = 1;
+ /* queue SETTINGS frame (again) */
+ rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen,
+ data->state.httpreq == HTTPREQ_HEAD,
+ NULL);
+ if(rc) {
+ failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
+ nghttp2_strerror(rc), rc);
+ result = CURLE_HTTP2;
+ goto out;
+ }
+
+ rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id,
+ data);
+ if(rc) {
+ infof(data, "http/2: failed to set user_data for stream %u",
+ stream->stream_id);
+ DEBUGASSERT(0);
}
- free(http->push_headers);
- http->push_headers = NULL;
}
-}
+ else {
+ nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+ int ivlen;
-/*
- * Disconnects *a* connection used for HTTP/2. It might be an old one from the
- * connection cache and not the "main" one. Don't touch the easy handle!
- */
+ /* H2 Settings need to be submitted. Stream is not open yet. */
+ DEBUGASSERT(stream->stream_id == -1);
-static CURLcode http2_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool dead_connection)
-{
- struct http_conn *c = &conn->proto.httpc;
- (void)dead_connection;
-#ifndef DEBUG_HTTP2
- (void)data;
-#endif
+ ivlen = populate_settings(iv, data);
+ rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
+ iv, ivlen);
+ if(rc) {
+ failf(data, "nghttp2_submit_settings() failed: %s(%d)",
+ nghttp2_strerror(rc), rc);
+ result = CURLE_HTTP2;
+ goto out;
+ }
+ }
- H2BUGF(infof(data, "HTTP/2 DISCONNECT starts now"));
+ rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
+ HTTP2_HUGE_WINDOW_SIZE);
+ if(rc) {
+ failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
+ nghttp2_strerror(rc), rc);
+ result = CURLE_HTTP2;
+ goto out;
+ }
- nghttp2_session_del(c->h2);
- Curl_safefree(c->inbuf);
+ /* all set, traffic will be send on connect */
+ result = CURLE_OK;
- H2BUGF(infof(data, "HTTP/2 DISCONNECT done"));
+out:
+ if(cbs)
+ nghttp2_session_callbacks_del(cbs);
+ return result;
+}
- return CURLE_OK;
+static int h2_session_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ CURLcode *err);
+
+/*
+ * http2_stream_free() free HTTP2 stream related data
+ */
+static void http2_stream_free(struct HTTP *stream)
+{
+ if(stream) {
+ Curl_dyn_free(&stream->header_recvbuf);
+ for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+ free(stream->push_headers[stream->push_headers_used - 1]);
+ }
+ free(stream->push_headers);
+ stream->push_headers = NULL;
+ }
}
/*
* The server may send us data at any point (e.g. PING frames). Therefore,
* we cannot assume that an HTTP/2 socket is dead just because it is readable.
*
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
+ * Check the lower filters first and, if successful, peek at the socket
* and distinguish between closed and data.
*/
-static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
+static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
int sval;
bool dead = TRUE;
- if(conn->bits.close)
+ if(!cf->next || !cf->next->cft->is_alive(cf->next, data))
return TRUE;
- sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0);
+ sval = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
if(sval == 0) {
/* timeout */
dead = FALSE;
@@ -190,33 +387,27 @@ static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
dead = TRUE;
}
else if(sval & CURL_CSELECT_IN) {
- /* readable with no error. could still be closed */
- dead = !Curl_connalive(data, conn);
- if(!dead) {
- /* This happens before we've sent off a request and the connection is
- not in use by any other transfer, there shouldn't be any data here,
- only "protocol frames" */
- CURLcode result;
- struct http_conn *httpc = &conn->proto.httpc;
- ssize_t nread = -1;
- if(httpc->recv_underlying)
- /* if called "too early", this pointer isn't setup yet! */
- nread = ((Curl_recv *)httpc->recv_underlying)(
- data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result);
- if(nread != -1) {
- H2BUGF(infof(data,
- "%d bytes stray data read before trying h2 connection",
- (int)nread));
- httpc->nread_inbuf = 0;
- httpc->inbuflen = nread;
- if(h2_process_pending_input(data, httpc, &result) < 0)
- /* immediate error, considered dead */
- dead = TRUE;
- }
- else
- /* the read failed so let's say this is dead anyway */
+ /* This happens before we've sent off a request and the connection is
+ not in use by any other transfer, there shouldn't be any data here,
+ only "protocol frames" */
+ CURLcode result;
+ ssize_t nread = -1;
+
+ nread = Curl_conn_cf_recv(cf->next, data,
+ ctx->inbuf, H2_BUFSIZE, &result);
+ if(nread != -1) {
+ H2BUGF(infof(data,
+ "%d bytes stray data read before trying h2 connection",
+ (int)nread));
+ ctx->nread_inbuf = 0;
+ ctx->inbuflen = nread;
+ if(h2_process_pending_input(cf, data, &result) < 0)
+ /* immediate error, considered dead */
dead = TRUE;
}
+ else
+ /* the read failed so let's say this is dead anyway */
+ dead = TRUE;
}
return dead;
@@ -225,142 +416,45 @@ static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
/*
* Set the transfer that is currently using this HTTP/2 connection.
*/
-static void set_transfer(struct http_conn *c,
+static void set_transfer(struct h2_cf_ctx *ctx,
struct Curl_easy *data)
{
- c->trnsfr = data;
+ ctx->trnsfr = data;
}
/*
* Get the transfer that is currently using this HTTP/2 connection.
*/
-static struct Curl_easy *get_transfer(struct http_conn *c)
+static struct Curl_easy *get_transfer(struct h2_cf_ctx *ctx)
{
- DEBUGASSERT(c && c->trnsfr);
- return c->trnsfr;
+ DEBUGASSERT(ctx && ctx->trnsfr);
+ return ctx->trnsfr;
}
-static unsigned int http2_conncheck(struct Curl_easy *data,
- struct connectdata *conn,
- unsigned int checks_to_perform)
+static CURLcode http2_send_ping(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- unsigned int ret_val = CONNRESULT_NONE;
- struct http_conn *c = &conn->proto.httpc;
+ struct h2_cf_ctx *ctx = cf->ctx;
int rc;
- bool send_frames = false;
-
- if(checks_to_perform & CONNCHECK_ISDEAD) {
- if(http2_connisdead(data, conn))
- ret_val |= CONNRESULT_DEAD;
- }
-
- if(checks_to_perform & CONNCHECK_KEEPALIVE) {
- struct curltime now = Curl_now();
- timediff_t elapsed = Curl_timediff(now, conn->keepalive);
-
- if(elapsed > data->set.upkeep_interval_ms) {
- /* Perform an HTTP/2 PING */
- rc = nghttp2_submit_ping(c->h2, 0, ZERO_NULL);
- if(!rc) {
- /* Successfully added a PING frame to the session. Need to flag this
- so the frame is sent. */
- send_frames = true;
- }
- else {
- failf(data, "nghttp2_submit_ping() failed: %s(%d)",
- nghttp2_strerror(rc), rc);
- }
- conn->keepalive = now;
- }
+ rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL);
+ if(rc) {
+ failf(data, "nghttp2_submit_ping() failed: %s(%d)",
+ nghttp2_strerror(rc), rc);
+ return CURLE_HTTP2;
}
- if(send_frames) {
- set_transfer(c, data); /* set the transfer */
- rc = nghttp2_session_send(c->h2);
- if(rc)
- failf(data, "nghttp2_session_send() failed: %s(%d)",
- nghttp2_strerror(rc), rc);
+ set_transfer(ctx, data); /* set the transfer */
+ rc = nghttp2_session_send(ctx->h2);
+ if(rc) {
+ failf(data, "nghttp2_session_send() failed: %s(%d)",
+ nghttp2_strerror(rc), rc);
+ return CURLE_SEND_ERROR;
}
-
- return ret_val;
-}
-
-/* called from http_setup_conn */
-void Curl_http2_setup_req(struct Curl_easy *data)
-{
- struct HTTP *http = data->req.p.http;
- http->bodystarted = FALSE;
- http->status_code = -1;
- http->pausedata = NULL;
- http->pauselen = 0;
- http->closed = FALSE;
- http->close_handled = FALSE;
- http->mem = NULL;
- http->len = 0;
- http->memlen = 0;
- http->error = NGHTTP2_NO_ERROR;
-}
-
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn)
-{
- conn->proto.httpc.settings.max_concurrent_streams =
- DEFAULT_MAX_CONCURRENT_STREAMS;
+ return CURLE_OK;
}
/*
- * HTTP2 handler interface. This isn't added to the general list of protocols
- * but will be used at run-time when the protocol is dynamically switched from
- * HTTP to HTTP2.
- */
-static const struct Curl_handler Curl_handler_http2 = {
- "HTTP", /* scheme */
- ZERO_NULL, /* setup_connection */
- Curl_http, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- http2_getsock, /* proto_getsock */
- http2_getsock, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- http2_getsock, /* perform_getsock */
- http2_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- http2_conncheck, /* connection_check */
- ZERO_NULL, /* attach connection */
- PORT_HTTP, /* defport */
- CURLPROTO_HTTP, /* protocol */
- CURLPROTO_HTTP, /* family */
- PROTOPT_STREAM /* flags */
-};
-
-static const struct Curl_handler Curl_handler_http2_ssl = {
- "HTTPS", /* scheme */
- ZERO_NULL, /* setup_connection */
- Curl_http, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- http2_getsock, /* proto_getsock */
- http2_getsock, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- http2_getsock, /* perform_getsock */
- http2_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- http2_conncheck, /* connection_check */
- ZERO_NULL, /* attach connection */
- PORT_HTTP, /* defport */
- CURLPROTO_HTTPS, /* protocol */
- CURLPROTO_HTTP, /* family */
- PROTOPT_SSL | PROTOPT_STREAM /* flags */
-};
-
-/*
* Store nghttp2 version info in this buffer.
*/
void Curl_http2_ver(char *p, size_t len)
@@ -375,25 +469,19 @@ void Curl_http2_ver(char *p, size_t len)
* written. See the documentation of nghttp2_send_callback for the details.
*/
static ssize_t send_callback(nghttp2_session *h2,
- const uint8_t *mem, size_t length, int flags,
+ const uint8_t *buf, size_t blen, int flags,
void *userp)
{
- struct connectdata *conn = (struct connectdata *)userp;
- struct http_conn *c = &conn->proto.httpc;
- struct Curl_easy *data = get_transfer(c);
+ struct Curl_cfilter *cf = userp;
+ struct h2_cf_ctx *ctx = cf->ctx;
+ struct Curl_easy *data = get_transfer(ctx);
ssize_t written;
CURLcode result = CURLE_OK;
(void)h2;
(void)flags;
- if(!c->send_underlying)
- /* called before setup properly! */
- return NGHTTP2_ERR_CALLBACK_FAILURE;
-
- written = ((Curl_send*)c->send_underlying)(data, FIRSTSOCKET,
- mem, length, &result);
-
+ written = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
if(result == CURLE_AGAIN) {
return NGHTTP2_ERR_WOULDBLOCK;
}
@@ -467,26 +555,31 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
/*
* This specific transfer on this connection has been "drained".
*/
-static void drained_transfer(struct Curl_easy *data,
- struct http_conn *httpc)
+static void drained_transfer(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- DEBUGASSERT(httpc->drain_total >= data->state.drain);
- httpc->drain_total -= data->state.drain;
+ struct h2_cf_ctx *ctx = cf->ctx;
+
+ DEBUGASSERT(ctx->drain_total >= data->state.drain);
+ ctx->drain_total -= data->state.drain;
data->state.drain = 0;
}
/*
* Mark this transfer to get "drained".
*/
-static void drain_this(struct Curl_easy *data,
- struct http_conn *httpc)
+static void drain_this(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
+
data->state.drain++;
- httpc->drain_total++;
- DEBUGASSERT(httpc->drain_total >= data->state.drain);
+ ctx->drain_total++;
+ DEBUGASSERT(ctx->drain_total >= data->state.drain);
}
-static struct Curl_easy *duphandle(struct Curl_easy *data)
+static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
struct Curl_easy *second = curl_easy_duphandle(data);
if(second) {
@@ -497,9 +590,8 @@ static struct Curl_easy *duphandle(struct Curl_easy *data)
}
else {
second->req.p.http = http;
- Curl_dyn_init(&http->header_recvbuf, DYN_H2_HEADERS);
- Curl_http2_setup_req(second);
- second->state.stream_weight = data->state.stream_weight;
+ http2_data_setup(cf, second);
+ second->state.priority.weight = data->state.priority.weight;
}
}
return second;
@@ -559,11 +651,13 @@ static int set_transfer_url(struct Curl_easy *data,
return 0;
}
-static int push_promise(struct Curl_easy *data,
- struct connectdata *conn,
+static int push_promise(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
const nghttp2_push_promise *frame)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
int rv; /* one of the CURL_PUSH_* defines */
+
H2BUGF(infof(data, "PUSH_PROMISE received, stream %u",
frame->promised_stream_id));
if(data->multi->push_cb) {
@@ -571,10 +665,9 @@ static int push_promise(struct Curl_easy *data,
struct HTTP *newstream;
struct curl_pushheaders heads;
CURLMcode rc;
- struct http_conn *httpc;
size_t i;
/* clone the parent */
- struct Curl_easy *newhandle = duphandle(data);
+ struct Curl_easy *newhandle = h2_duphandle(cf, data);
if(!newhandle) {
infof(data, "failed to duplicate handle");
rv = CURL_PUSH_DENY; /* FAIL HARD */
@@ -630,7 +723,7 @@ static int push_promise(struct Curl_easy *data,
/* approved, add to the multi handle and immediately switch to PERFORM
state with the given connection !*/
- rc = Curl_multi_add_perform(data->multi, newhandle, conn);
+ rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn);
if(rc) {
infof(data, "failed to add handle to multi");
http2_stream_free(newhandle->req.p.http);
@@ -640,8 +733,7 @@ static int push_promise(struct Curl_easy *data,
goto fail;
}
- httpc = &conn->proto.httpc;
- rv = nghttp2_session_set_stream_user_data(httpc->h2,
+ rv = nghttp2_session_set_stream_user_data(ctx->h2,
frame->promised_stream_id,
newhandle);
if(rv) {
@@ -662,26 +754,14 @@ static int push_promise(struct Curl_easy *data,
return rv;
}
-/*
- * multi_connchanged() is called to tell that there is a connection in
- * this multi handle that has changed state (multiplexing become possible, the
- * number of allowed streams changed or similar), and a subsequent use of this
- * multi handle should move CONNECT_PEND handles back to CONNECT to have them
- * retry.
- */
-static void multi_connchanged(struct Curl_multi *multi)
-{
- multi->recheckstate = TRUE;
-}
-
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
- struct connectdata *conn = (struct connectdata *)userp;
- struct http_conn *httpc = &conn->proto.httpc;
+ struct Curl_cfilter *cf = userp;
+ struct h2_cf_ctx *ctx = cf->ctx;
struct Curl_easy *data_s = NULL;
struct HTTP *stream = NULL;
- struct Curl_easy *data = get_transfer(httpc);
+ struct Curl_easy *data = get_transfer(ctx);
int rv;
size_t left, ncopy;
int32_t stream_id = frame->hd.stream_id;
@@ -690,23 +770,21 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
if(!stream_id) {
/* stream ID zero is for connection-oriented stuff */
if(frame->hd.type == NGHTTP2_SETTINGS) {
- uint32_t max_conn = httpc->settings.max_concurrent_streams;
+ uint32_t max_conn = ctx->max_concurrent_streams;
H2BUGF(infof(data, "Got SETTINGS"));
- httpc->settings.max_concurrent_streams =
- nghttp2_session_get_remote_settings(
+ ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
- httpc->settings.enable_push =
- nghttp2_session_get_remote_settings(
+ ctx->enable_push = nghttp2_session_get_remote_settings(
session, NGHTTP2_SETTINGS_ENABLE_PUSH);
H2BUGF(infof(data, "MAX_CONCURRENT_STREAMS == %d",
- httpc->settings.max_concurrent_streams));
+ ctx->max_concurrent_streams));
H2BUGF(infof(data, "ENABLE_PUSH == %s",
- httpc->settings.enable_push?"TRUE":"false"));
- if(max_conn != httpc->settings.max_concurrent_streams) {
+ ctx->enable_push?"TRUE":"false"));
+ if(max_conn != ctx->max_concurrent_streams) {
/* only signal change if the value actually changed */
infof(data,
"Connection state changed (MAX_CONCURRENT_STREAMS == %u)!",
- httpc->settings.max_concurrent_streams);
+ ctx->max_concurrent_streams);
multi_connchanged(data->multi);
}
}
@@ -782,13 +860,13 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
stream->len -= ncopy;
stream->memlen += ncopy;
- drain_this(data_s, httpc);
+ drain_this(cf, data_s);
/* if we receive data for another handle, wake that up */
- if(get_transfer(httpc) != data_s)
+ if(get_transfer(ctx) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
break;
case NGHTTP2_PUSH_PROMISE:
- rv = push_promise(data_s, conn, &frame->push_promise);
+ rv = push_promise(cf, data_s, &frame->push_promise);
if(rv) { /* deny! */
int h2;
DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
@@ -815,12 +893,11 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
int32_t stream_id,
const uint8_t *mem, size_t len, void *userp)
{
+ struct Curl_cfilter *cf = userp;
+ struct h2_cf_ctx *ctx = cf->ctx;
struct HTTP *stream;
struct Curl_easy *data_s;
size_t nread;
- struct connectdata *conn = (struct connectdata *)userp;
- struct http_conn *httpc = &conn->proto.httpc;
- (void)session;
(void)flags;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
@@ -846,10 +923,10 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
stream->len -= nread;
stream->memlen += nread;
- drain_this(data_s, &conn->proto.httpc);
+ drain_this(cf, data_s);
/* if we receive data for another handle, wake that up */
- if(get_transfer(httpc) != data_s)
+ if(get_transfer(ctx) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
H2BUGF(infof(data_s, "%zu data received for stream %u "
@@ -864,15 +941,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer"
", stream %u",
len - nread, stream_id));
- data_s->conn->proto.httpc.pause_stream_id = stream_id;
+ ctx->pause_stream_id = stream_id;
return NGHTTP2_ERR_PAUSE;
}
/* pause execution of nghttp2 if we received data for another handle
in order to process them first. */
- if(get_transfer(httpc) != data_s) {
- data_s->conn->proto.httpc.pause_stream_id = stream_id;
+ if(get_transfer(ctx) != data_s) {
+ ctx->pause_stream_id = stream_id;
return NGHTTP2_ERR_PAUSE;
}
@@ -883,15 +960,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *userp)
{
+ struct Curl_cfilter *cf = userp;
+ struct h2_cf_ctx *ctx = cf->ctx;
struct Curl_easy *data_s;
struct HTTP *stream;
- struct connectdata *conn = (struct connectdata *)userp;
int rv;
(void)session;
(void)stream_id;
if(stream_id) {
- struct http_conn *httpc;
/* get the stream from the hash based on Stream ID, stream ID zero is for
connection-oriented stuff */
data_s = nghttp2_session_get_stream_user_data(session, stream_id);
@@ -907,8 +984,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
return NGHTTP2_ERR_CALLBACK_FAILURE;
stream->closed = TRUE;
- httpc = &conn->proto.httpc;
- drain_this(data_s, httpc);
+ drain_this(cf, data_s);
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
stream->error = error_code;
@@ -919,9 +995,9 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
stream_id);
DEBUGASSERT(0);
}
- if(stream_id == httpc->pause_stream_id) {
+ if(stream_id == ctx->pause_stream_id) {
H2BUGF(infof(data_s, "Stopped the pause stream"));
- httpc->pause_stream_id = 0;
+ ctx->pause_stream_id = 0;
}
H2BUGF(infof(data_s, "Removed stream %u hash", stream_id));
stream->stream_id = 0; /* cleared */
@@ -989,11 +1065,11 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
uint8_t flags,
void *userp)
{
+ struct Curl_cfilter *cf = userp;
+ struct h2_cf_ctx *ctx = cf->ctx;
struct HTTP *stream;
struct Curl_easy *data_s;
int32_t stream_id = frame->hd.stream_id;
- struct connectdata *conn = (struct connectdata *)userp;
- struct http_conn *httpc = &conn->proto.httpc;
CURLcode result;
(void)flags;
@@ -1020,13 +1096,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) {
/* pseudo headers are lower case */
int rc = 0;
- char *check = aprintf("%s:%d", conn->host.name, conn->remote_port);
+ char *check = aprintf("%s:%d", cf->conn->host.name,
+ cf->conn->remote_port);
if(!check)
/* no memory */
return NGHTTP2_ERR_CALLBACK_FAILURE;
if(!strcasecompare(check, (const char *)value) &&
- ((conn->remote_port != conn->given->defport) ||
- !strcasecompare(conn->host.name, (const char *)value))) {
+ ((cf->conn->remote_port != cf->conn->given->defport) ||
+ !strcasecompare(cf->conn->host.name, (const char *)value))) {
/* This is push is not for the same authority that was asked for in
* the URL. RFC 7540 section 8.2 says: "A client MUST treat a
* PUSH_PROMISE for which the server is not authoritative as a stream
@@ -1110,7 +1187,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* if we receive data for another handle, wake that up */
- if(get_transfer(httpc) != data_s)
+ if(get_transfer(ctx) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)",
@@ -1134,7 +1211,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* if we receive data for another handle, wake that up */
- if(get_transfer(httpc) != data_s)
+ if(get_transfer(ctx) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
H2BUGF(infof(data_s, "h2 header: %.*s: %.*s", namelen, name, valuelen,
@@ -1207,147 +1284,58 @@ static int error_callback(nghttp2_session *session,
}
#endif
-static void populate_settings(struct Curl_easy *data,
- struct http_conn *httpc)
-{
- nghttp2_settings_entry *iv = httpc->local_settings;
-
- iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
- iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
-
- iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
- iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
-
- iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
- iv[2].value = data->multi->push_cb != NULL;
-
- httpc->local_settings_num = 3;
-}
-
-void Curl_http2_done(struct Curl_easy *data, bool premature)
+static void http2_data_done(struct Curl_cfilter *cf,
+ struct Curl_easy *data, bool premature)
{
- struct HTTP *http = data->req.p.http;
- struct http_conn *httpc = &data->conn->proto.httpc;
+ struct h2_cf_ctx *ctx = cf->ctx;
+ struct HTTP *stream = data->req.p.http;
/* there might be allocated resources done before this got the 'h2' pointer
setup */
- Curl_dyn_free(&http->header_recvbuf);
- Curl_dyn_free(&http->trailer_recvbuf);
- if(http->push_headers) {
+ Curl_dyn_free(&stream->header_recvbuf);
+ Curl_dyn_free(&stream->trailer_recvbuf);
+ if(stream->push_headers) {
/* if they weren't used and then freed before */
- for(; http->push_headers_used > 0; --http->push_headers_used) {
- free(http->push_headers[http->push_headers_used - 1]);
+ for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+ free(stream->push_headers[stream->push_headers_used - 1]);
}
- free(http->push_headers);
- http->push_headers = NULL;
+ free(stream->push_headers);
+ stream->push_headers = NULL;
}
- if(!(data->conn->handler->protocol&PROTO_FAMILY_HTTP) ||
- !httpc->h2) /* not HTTP/2 ? */
+ if(!ctx || !ctx->h2)
return;
/* do this before the reset handling, as that might clear ->stream_id */
- if(http->stream_id == httpc->pause_stream_id) {
- H2BUGF(infof(data, "DONE the pause stream (%u)", http->stream_id));
- httpc->pause_stream_id = 0;
+ if(stream->stream_id == ctx->pause_stream_id) {
+ H2BUGF(infof(data, "DONE the pause stream (%u)", stream->stream_id));
+ ctx->pause_stream_id = 0;
}
- if(premature || (!http->closed && http->stream_id)) {
+
+ if(premature || (!stream->closed && stream->stream_id)) {
/* RST_STREAM */
- set_transfer(httpc, data); /* set the transfer */
- H2BUGF(infof(data, "RST stream %u", http->stream_id));
- if(!nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE,
- http->stream_id, NGHTTP2_STREAM_CLOSED))
- (void)nghttp2_session_send(httpc->h2);
+ set_transfer(ctx, data); /* set the transfer */
+ H2BUGF(infof(data, "RST stream %u", stream->stream_id));
+ if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
+ stream->stream_id, NGHTTP2_STREAM_CLOSED))
+ (void)nghttp2_session_send(ctx->h2);
}
if(data->state.drain)
- drained_transfer(data, httpc);
+ drained_transfer(cf, data);
/* -1 means unassigned and 0 means cleared */
- if(http->stream_id > 0) {
- int rv = nghttp2_session_set_stream_user_data(httpc->h2,
- http->stream_id, 0);
+ if(stream->stream_id > 0) {
+ int rv = nghttp2_session_set_stream_user_data(ctx->h2,
+ stream->stream_id, 0);
if(rv) {
infof(data, "http/2: failed to clear user_data for stream %u",
- http->stream_id);
+ stream->stream_id);
DEBUGASSERT(0);
}
- set_transfer(httpc, NULL);
- http->stream_id = 0;
- }
-}
-
-static int client_new(struct connectdata *conn,
- nghttp2_session_callbacks *callbacks)
-{
-#if NGHTTP2_VERSION_NUM < 0x013200
- /* before 1.50.0 */
- return nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn);
-#else
- nghttp2_option *o;
- int rc = nghttp2_option_new(&o);
- if(rc)
- return rc;
- /* turn off RFC 9113 leading and trailing white spaces validation against
- HTTP field value. */
- nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
- rc = nghttp2_session_client_new2(&conn->proto.httpc.h2, callbacks, conn,
- o);
- nghttp2_option_del(o);
- return rc;
-#endif
-}
-
-/*
- * Initialize nghttp2 for a Curl connection
- */
-static CURLcode http2_init(struct Curl_easy *data, struct connectdata *conn)
-{
- if(!conn->proto.httpc.h2) {
- int rc;
- nghttp2_session_callbacks *callbacks;
-
- conn->proto.httpc.inbuf = malloc(H2_BUFSIZE);
- if(!conn->proto.httpc.inbuf)
- return CURLE_OUT_OF_MEMORY;
-
- rc = nghttp2_session_callbacks_new(&callbacks);
-
- if(rc) {
- failf(data, "Couldn't initialize nghttp2 callbacks");
- return CURLE_OUT_OF_MEMORY; /* most likely at least */
- }
-
- /* nghttp2_send_callback */
- nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
- /* nghttp2_on_frame_recv_callback */
- nghttp2_session_callbacks_set_on_frame_recv_callback
- (callbacks, on_frame_recv);
- /* nghttp2_on_data_chunk_recv_callback */
- nghttp2_session_callbacks_set_on_data_chunk_recv_callback
- (callbacks, on_data_chunk_recv);
- /* nghttp2_on_stream_close_callback */
- nghttp2_session_callbacks_set_on_stream_close_callback
- (callbacks, on_stream_close);
- /* nghttp2_on_begin_headers_callback */
- nghttp2_session_callbacks_set_on_begin_headers_callback
- (callbacks, on_begin_headers);
- /* nghttp2_on_header_callback */
- nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header);
-
- nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
-
- /* The nghttp2 session is not yet setup, do it */
- rc = client_new(conn, callbacks);
-
- nghttp2_session_callbacks_del(callbacks);
-
- if(rc) {
- failf(data, "Couldn't initialize nghttp2");
- return CURLE_OUT_OF_MEMORY; /* most likely at least */
- }
+ set_transfer(ctx, NULL);
+ stream->stream_id = 0;
}
- return CURLE_OK;
}
/*
@@ -1357,26 +1345,18 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
struct Curl_easy *data)
{
CURLcode result;
- ssize_t binlen;
char *base64;
size_t blen;
- struct connectdata *conn = data->conn;
struct SingleRequest *k = &data->req;
- uint8_t *binsettings = conn->proto.httpc.binsettings;
- struct http_conn *httpc = &conn->proto.httpc;
-
- populate_settings(data, httpc);
+ uint8_t binsettings[H2_BINSETTINGS_LEN];
+ size_t binlen; /* length of the binsettings data */
- /* this returns number of bytes it wrote */
- binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
- httpc->local_settings,
- httpc->local_settings_num);
+ binlen = populate_binsettings(binsettings, data);
if(binlen <= 0) {
failf(data, "nghttp2 unexpectedly failed on pack_settings_payload");
Curl_dyn_free(req);
return CURLE_FAILED_INIT;
}
- conn->proto.httpc.binlen = binlen;
result = Curl_base64url_encode((const char *)binsettings, binlen,
&base64, &blen);
@@ -1400,10 +1380,10 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
/*
* Returns nonzero if current HTTP/2 session should be closed.
*/
-static int should_close_session(struct http_conn *httpc)
+static int should_close_session(struct h2_cf_ctx *ctx)
{
- return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) &&
- !nghttp2_session_want_write(httpc->h2);
+ return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
+ !nghttp2_session_want_write(ctx->h2);
}
/*
@@ -1412,19 +1392,20 @@ static int should_close_session(struct http_conn *httpc)
* This function returns 0 if it succeeds, or -1 and error code will
* be assigned to *err.
*/
-static int h2_process_pending_input(struct Curl_easy *data,
- struct http_conn *httpc,
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
CURLcode *err)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
ssize_t nread;
char *inbuf;
ssize_t rv;
- nread = httpc->inbuflen - httpc->nread_inbuf;
- inbuf = httpc->inbuf + httpc->nread_inbuf;
+ nread = ctx->inbuflen - ctx->nread_inbuf;
+ inbuf = ctx->inbuf + ctx->nread_inbuf;
- set_transfer(httpc, data); /* set the transfer */
- rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread);
+ set_transfer(ctx, data); /* set the transfer */
+ rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)inbuf, nread);
if(rv < 0) {
failf(data,
"h2_process_pending_input: nghttp2_session_mem_recv() returned "
@@ -1437,32 +1418,32 @@ static int h2_process_pending_input(struct Curl_easy *data,
H2BUGF(infof(data,
"h2_process_pending_input: All data in connection buffer "
"processed"));
- httpc->inbuflen = 0;
- httpc->nread_inbuf = 0;
+ ctx->inbuflen = 0;
+ ctx->nread_inbuf = 0;
}
else {
- httpc->nread_inbuf += rv;
+ ctx->nread_inbuf += rv;
H2BUGF(infof(data,
"h2_process_pending_input: %zu bytes left in connection "
"buffer",
- httpc->inbuflen - httpc->nread_inbuf));
+ ctx->inbuflen - ctx->nread_inbuf));
}
- rv = h2_session_send(data, httpc->h2);
+ rv = h2_session_send(cf, data);
if(rv) {
*err = CURLE_SEND_ERROR;
return -1;
}
- if(nghttp2_session_check_request_allowed(httpc->h2) == 0) {
+ if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
/* No more requests are allowed in the current session, so
the connection may not be reused. This is set when a
GOAWAY frame has been received or when the limit of stream
identifiers has been reached. */
- connclose(data->conn, "http/2: No new requests allowed");
+ connclose(cf->conn, "http/2: No new requests allowed");
}
- if(should_close_session(httpc)) {
+ if(should_close_session(ctx)) {
struct HTTP *stream = data->req.p.http;
H2BUGF(infof(data,
"h2_process_pending_input: nothing to do in this session"));
@@ -1470,7 +1451,7 @@ static int h2_process_pending_input(struct Curl_easy *data,
*err = CURLE_HTTP2;
else {
/* not an error per se, but should still close the connection */
- connclose(data->conn, "GOAWAY received");
+ connclose(cf->conn, "GOAWAY received");
*err = CURLE_OK;
}
return -1;
@@ -1478,67 +1459,62 @@ static int h2_process_pending_input(struct Curl_easy *data,
return 0;
}
-/*
- * Called from transfer.c:done_sending when we stop uploading.
- */
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
- struct connectdata *conn)
+static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
+ struct HTTP *stream = data->req.p.http;
- if((conn->handler == &Curl_handler_http2_ssl) ||
- (conn->handler == &Curl_handler_http2)) {
- /* make sure this is only attempted for HTTP/2 transfers */
- struct HTTP *stream = data->req.p.http;
- struct http_conn *httpc = &conn->proto.httpc;
- nghttp2_session *h2 = httpc->h2;
-
- if(stream->upload_left) {
- /* If the stream still thinks there's data left to upload. */
+ if(!ctx || !ctx->h2)
+ goto out;
- stream->upload_left = 0; /* DONE! */
+ if(stream->upload_left) {
+ /* If the stream still thinks there's data left to upload. */
+ stream->upload_left = 0; /* DONE! */
- /* resume sending here to trigger the callback to get called again so
- that it can signal EOF to nghttp2 */
- (void)nghttp2_session_resume_data(h2, stream->stream_id);
- (void)h2_process_pending_input(data, httpc, &result);
- }
+ /* resume sending here to trigger the callback to get called again so
+ that it can signal EOF to nghttp2 */
+ (void)nghttp2_session_resume_data(ctx->h2, stream->stream_id);
+ (void)h2_process_pending_input(cf, data, &result);
+ }
- /* If nghttp2 still has pending frames unsent */
- if(nghttp2_session_want_write(h2)) {
- struct SingleRequest *k = &data->req;
- int rv;
+ /* If nghttp2 still has pending frames unsent */
+ if(nghttp2_session_want_write(ctx->h2)) {
+ struct SingleRequest *k = &data->req;
+ int rv;
- H2BUGF(infof(data, "HTTP/2 still wants to send data (easy %p)", data));
+ H2BUGF(infof(data, "HTTP/2 still wants to send data (easy %p)", data));
- /* and attempt to send the pending frames */
- rv = h2_session_send(data, h2);
- if(rv)
- result = CURLE_SEND_ERROR;
+ /* and attempt to send the pending frames */
+ rv = h2_session_send(cf, data);
+ if(rv)
+ result = CURLE_SEND_ERROR;
- if(nghttp2_session_want_write(h2)) {
- /* re-set KEEP_SEND to make sure we are called again */
- k->keepon |= KEEP_SEND;
- }
+ if(nghttp2_session_want_write(ctx->h2)) {
+ /* re-set KEEP_SEND to make sure we are called again */
+ k->keepon |= KEEP_SEND;
}
}
+
+out:
return result;
}
-static ssize_t http2_handle_stream_close(struct connectdata *conn,
+static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct HTTP *stream, CURLcode *err)
{
- struct http_conn *httpc = &conn->proto.httpc;
+ struct h2_cf_ctx *ctx = cf->ctx;
- if(httpc->pause_stream_id == stream->stream_id) {
- httpc->pause_stream_id = 0;
+ if(ctx->pause_stream_id == stream->stream_id) {
+ ctx->pause_stream_id = 0;
}
- drained_transfer(data, httpc);
+ drained_transfer(cf, data);
- if(httpc->pause_stream_id == 0) {
- if(h2_process_pending_input(data, httpc, err) != 0) {
+ if(ctx->pause_stream_id == 0) {
+ if(h2_process_pending_input(cf, data, err) != 0) {
return -1;
}
}
@@ -1550,7 +1526,7 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
if(stream->error == NGHTTP2_REFUSED_STREAM) {
H2BUGF(infof(data, "REFUSED_STREAM (%u), try again on a new connection",
stream->stream_id));
- connclose(conn, "REFUSED_STREAM"); /* don't use this anymore */
+ connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
data->state.refused_stream = TRUE;
*err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
return -1;
@@ -1601,6 +1577,20 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
return 0;
}
+static int sweight_wanted(const struct Curl_easy *data)
+{
+ /* 0 weight is not set by user and we take the nghttp2 default one */
+ return data->set.priority.weight?
+ data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
+static int sweight_in_effect(const struct Curl_easy *data)
+{
+ /* 0 weight is not set by user and we take the nghttp2 default one */
+ return data->state.priority.weight?
+ data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
/*
* h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight
* and dependency to the peer. It also stores the updated values in the state
@@ -1610,14 +1600,14 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
static void h2_pri_spec(struct Curl_easy *data,
nghttp2_priority_spec *pri_spec)
{
- struct HTTP *depstream = (data->set.stream_depends_on?
- data->set.stream_depends_on->req.p.http:NULL);
+ struct Curl_data_priority *prio = &data->set.priority;
+ struct HTTP *depstream = (prio->parent?
+ prio->parent->req.p.http:NULL);
int32_t depstream_id = depstream? depstream->stream_id:0;
- nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight,
- data->set.stream_depends_e);
- data->state.stream_weight = data->set.stream_weight;
- data->state.stream_depends_e = data->set.stream_depends_e;
- data->state.stream_depends_on = data->set.stream_depends_on;
+ nghttp2_priority_spec_init(pri_spec, depstream_id,
+ sweight_wanted(data),
+ data->set.priority.exclusive);
+ data->state.priority = *prio;
}
/*
@@ -1625,47 +1615,42 @@ static void h2_pri_spec(struct Curl_easy *data,
* dependency settings and if so it submits a PRIORITY frame with the updated
* info.
*/
-static int h2_session_send(struct Curl_easy *data,
- nghttp2_session *h2)
+static int h2_session_send(struct Curl_cfilter *cf, struct Curl_easy *data)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
- struct http_conn *httpc = &data->conn->proto.httpc;
- set_transfer(httpc, data);
- if((data->set.stream_weight != data->state.stream_weight) ||
- (data->set.stream_depends_e != data->state.stream_depends_e) ||
- (data->set.stream_depends_on != data->state.stream_depends_on) ) {
+
+ set_transfer(ctx, data);
+ if((sweight_wanted(data) != sweight_in_effect(data)) ||
+ (data->set.priority.exclusive != data->state.priority.exclusive) ||
+ (data->set.priority.parent != data->state.priority.parent) ) {
/* send new weight and/or dependency */
nghttp2_priority_spec pri_spec;
int rv;
h2_pri_spec(data, &pri_spec);
-
H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)",
stream->stream_id, data));
DEBUGASSERT(stream->stream_id != -1);
- rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id,
- &pri_spec);
+ rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
+ stream->stream_id, &pri_spec);
if(rv)
return rv;
}
- return nghttp2_session_send(h2);
+ return nghttp2_session_send(ctx->h2);
}
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
- char *mem, size_t len, CURLcode *err)
+static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
{
- ssize_t nread;
- struct connectdata *conn = data->conn;
- struct http_conn *httpc = &conn->proto.httpc;
+ struct h2_cf_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
+ ssize_t nread;
- (void)sockindex; /* we always do HTTP2 on sockindex 0 */
-
- if(should_close_session(httpc)) {
- H2BUGF(infof(data,
- "http2_recv: nothing to do in this session"));
- if(conn->bits.close) {
+ if(should_close_session(ctx)) {
+ H2BUGF(infof(data, "http2_recv: nothing to do in this session"));
+ if(cf->conn->bits.close) {
/* already marked for closure, return OK and we're done */
*err = CURLE_OK;
return 0;
@@ -1690,7 +1675,7 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
size_t left =
Curl_dyn_len(&stream->header_recvbuf) - stream->nread_header_recvbuf;
size_t ncopy = CURLMIN(len, left);
- memcpy(mem, Curl_dyn_ptr(&stream->header_recvbuf) +
+ memcpy(buf, Curl_dyn_ptr(&stream->header_recvbuf) +
stream->nread_header_recvbuf, ncopy);
stream->nread_header_recvbuf += ncopy;
@@ -1701,43 +1686,43 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
H2BUGF(infof(data, "http2_recv: easy %p (stream %u) win %u/%u",
data, stream->stream_id,
- nghttp2_session_get_local_window_size(httpc->h2),
- nghttp2_session_get_stream_local_window_size(httpc->h2,
+ nghttp2_session_get_local_window_size(ctx->h2),
+ nghttp2_session_get_stream_local_window_size(ctx->h2,
stream->stream_id)
));
if((data->state.drain) && stream->memlen) {
H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u (%p => %p)",
stream->memlen, stream->stream_id,
- stream->mem, mem));
- if(mem != stream->mem) {
+ stream->mem, buf));
+ if(buf != stream->mem) {
/* if we didn't get the same buffer this time, we must move the data to
the beginning */
- memmove(mem, stream->mem, stream->memlen);
+ memmove(buf, stream->mem, stream->memlen);
stream->len = len - stream->memlen;
- stream->mem = mem;
+ stream->mem = buf;
}
- if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) {
+ if(ctx->pause_stream_id == stream->stream_id && !stream->pausedata) {
/* We have paused nghttp2, but we have no pause data (see
on_data_chunk_recv). */
- httpc->pause_stream_id = 0;
- if(h2_process_pending_input(data, httpc, err) != 0) {
+ ctx->pause_stream_id = 0;
+ if(h2_process_pending_input(cf, data, err) != 0) {
return -1;
}
}
}
else if(stream->pausedata) {
- DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
+ DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
nread = CURLMIN(len, stream->pauselen);
- memcpy(mem, stream->pausedata, nread);
+ memcpy(buf, stream->pausedata, nread);
stream->pausedata += nread;
stream->pauselen -= nread;
if(stream->pauselen == 0) {
H2BUGF(infof(data, "Unpaused by stream %u", stream->stream_id));
- DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
- httpc->pause_stream_id = 0;
+ DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
+ ctx->pause_stream_id = 0;
stream->pausedata = NULL;
stream->pauselen = 0;
@@ -1749,7 +1734,7 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
frames, then we have to call it again with 0-length data.
Without this, on_stream_close callback will not be called,
and stream could be hanged. */
- if(h2_process_pending_input(data, httpc, err) != 0) {
+ if(h2_process_pending_input(cf, data, err) != 0) {
return -1;
}
}
@@ -1757,7 +1742,7 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
nread, stream->stream_id));
return nread;
}
- else if(httpc->pause_stream_id) {
+ else if(ctx->pause_stream_id) {
/* If a stream paused nghttp2_session_mem_recv previously, and has
not processed all data, it still refers to the buffer in
nghttp2_session. If we call nghttp2_session_mem_recv(), we may
@@ -1770,27 +1755,27 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
/* closed overrides paused */
return 0;
H2BUGF(infof(data, "stream %u is paused, pause id: %u",
- stream->stream_id, httpc->pause_stream_id));
+ stream->stream_id, ctx->pause_stream_id));
*err = CURLE_AGAIN;
return -1;
}
else {
/* remember where to store incoming data for this stream and how big the
buffer is */
- stream->mem = mem;
+ stream->mem = buf;
stream->len = len;
stream->memlen = 0;
- if(httpc->inbuflen == 0) {
- nread = ((Curl_recv *)httpc->recv_underlying)(
- data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, err);
+ if(ctx->inbuflen == 0) {
+ /* Receive data from the "lower" filters */
+ nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err);
if(nread == -1) {
if(*err != CURLE_AGAIN)
failf(data, "Failed receiving HTTP2 data");
else if(stream->closed)
/* received when the stream was already closed! */
- return http2_handle_stream_close(conn, data, stream, err);
+ return http2_handle_stream_close(cf, data, stream, err);
return -1;
}
@@ -1814,18 +1799,18 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
H2BUGF(infof(data, "nread=%zd", nread));
- httpc->inbuflen = nread;
+ ctx->inbuflen = nread;
- DEBUGASSERT(httpc->nread_inbuf == 0);
+ DEBUGASSERT(ctx->nread_inbuf == 0);
}
else {
- nread = httpc->inbuflen - httpc->nread_inbuf;
+ nread = ctx->inbuflen - ctx->nread_inbuf;
(void)nread; /* silence warning, used in debug */
H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd",
nread));
}
- if(h2_process_pending_input(data, httpc, err))
+ if(h2_process_pending_input(cf, data, err))
return -1;
}
if(stream->memlen) {
@@ -1834,14 +1819,14 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
retlen, stream->stream_id));
stream->memlen = 0;
- if(httpc->pause_stream_id == stream->stream_id) {
+ if(ctx->pause_stream_id == stream->stream_id) {
/* data for this stream is returned now, but this stream caused a pause
already so we need it called again asap */
H2BUGF(infof(data, "Data returned for PAUSED stream %u",
stream->stream_id));
}
else if(!stream->closed) {
- drained_transfer(data, httpc);
+ drained_transfer(cf, data);
}
else
/* this stream is closed, trigger a another read ASAP to detect that */
@@ -1850,36 +1835,32 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
return retlen;
}
if(stream->closed)
- return http2_handle_stream_close(conn, data, stream, err);
+ return http2_handle_stream_close(cf, data, stream, err);
*err = CURLE_AGAIN;
H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u",
stream->stream_id));
return -1;
}
-static ssize_t http2_send(struct Curl_easy *data, int sockindex,
- const void *mem, size_t len, CURLcode *err)
+static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
{
/*
* Currently, we send request in this function, but this function is also
* used to send request body. It would be nice to add dedicated function for
* request.
*/
+ struct h2_cf_ctx *ctx = cf->ctx;
int rv;
- struct connectdata *conn = data->conn;
- struct http_conn *httpc = &conn->proto.httpc;
struct HTTP *stream = data->req.p.http;
nghttp2_nv *nva = NULL;
size_t nheader;
nghttp2_data_provider data_prd;
int32_t stream_id;
- nghttp2_session *h2 = httpc->h2;
nghttp2_priority_spec pri_spec;
CURLcode result;
struct h2h3req *hreq;
- (void)sockindex;
-
H2BUGF(infof(data, "http2_send len=%zu", len));
if(stream->stream_id != -1) {
@@ -1889,18 +1870,18 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
return -1;
}
else if(stream->closed) {
- return http2_handle_stream_close(conn, data, stream, err);
+ return http2_handle_stream_close(cf, data, stream, err);
}
/* If stream_id != -1, we have dispatched request HEADERS, and now
are going to send or sending request body in DATA frame */
- stream->upload_mem = mem;
+ stream->upload_mem = buf;
stream->upload_len = len;
- rv = nghttp2_session_resume_data(h2, stream->stream_id);
+ rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
return -1;
}
- rv = h2_session_send(data, h2);
+ rv = h2_session_send(cf, data);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
return -1;
@@ -1912,7 +1893,7 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
stream->upload_mem = NULL;
stream->upload_len = 0;
- if(should_close_session(httpc)) {
+ if(should_close_session(ctx)) {
H2BUGF(infof(data, "http2_send: nothing to do in this session"));
*err = CURLE_HTTP2;
return -1;
@@ -1923,15 +1904,15 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
following API will make nghttp2_session_want_write() return
nonzero if remote window allows it, which then libcurl checks
socket is writable or not. See http2_perform_getsock(). */
- nghttp2_session_resume_data(h2, stream->stream_id);
+ nghttp2_session_resume_data(ctx->h2, stream->stream_id);
}
#ifdef DEBUG_HTTP2
if(!len) {
infof(data, "http2_send: easy %p (stream %u) win %u/%u",
data, stream->stream_id,
- nghttp2_session_get_remote_window_size(httpc->h2),
- nghttp2_session_get_stream_remote_window_size(httpc->h2,
+ nghttp2_session_get_remote_window_size(ctx->h2),
+ nghttp2_session_get_stream_remote_window_size(ctx->h2,
stream->stream_id)
);
@@ -1942,7 +1923,7 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
return len;
}
- result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
+ result = Curl_pseudo_headers(data, buf, len, NULL, &hreq);
if(result) {
*err = result;
return -1;
@@ -1970,7 +1951,7 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
h2_pri_spec(data, &pri_spec);
H2BUGF(infof(data, "http2_send request allowed %d (easy handle %p)",
- nghttp2_session_check_request_allowed(h2), (void *)data));
+ nghttp2_session_check_request_allowed(ctx->h2), (void *)data));
switch(data->state.httpreq) {
case HTTPREQ_POST:
@@ -1985,11 +1966,11 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
data_prd.read_callback = data_source_read_callback;
data_prd.source.ptr = NULL;
- stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+ stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
&data_prd, data);
break;
default:
- stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+ stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
NULL, data);
}
@@ -2007,7 +1988,7 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
stream_id, (void *)data);
stream->stream_id = stream_id;
- rv = h2_session_send(data, h2);
+ rv = h2_session_send(cf, data);
if(rv) {
H2BUGF(infof(data,
"http2_send() nghttp2_session_send error (%s)%d",
@@ -2017,7 +1998,7 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
return -1;
}
- if(should_close_session(httpc)) {
+ if(should_close_session(ctx)) {
H2BUGF(infof(data, "http2_send: nothing to do in this session"));
*err = CURLE_HTTP2;
return -1;
@@ -2030,169 +2011,113 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
results that no writable socket check is performed. To workaround this,
we issue nghttp2_session_resume_data() here to bring back DATA
transmission from deferred state. */
- nghttp2_session_resume_data(h2, stream->stream_id);
+ nghttp2_session_resume_data(ctx->h2, stream->stream_id);
return len;
}
-CURLcode Curl_http2_setup(struct Curl_easy *data,
- struct connectdata *conn)
+static int h2_cf_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *sock)
{
- CURLcode result;
- struct http_conn *httpc = &conn->proto.httpc;
+ struct h2_cf_ctx *ctx = cf->ctx;
+ struct SingleRequest *k = &data->req;
struct HTTP *stream = data->req.p.http;
+ int bitmap = GETSOCK_BLANK;
- DEBUGASSERT(data->state.buffer);
-
- stream->stream_id = -1;
-
- Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
- Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
-
- stream->upload_left = 0;
- stream->upload_mem = NULL;
- stream->upload_len = 0;
- stream->mem = data->state.buffer;
- stream->len = data->set.buffer_size;
-
- multi_connchanged(data->multi);
- /* below this point only connection related inits are done, which only needs
- to be done once per connection */
-
- if((conn->handler == &Curl_handler_http2_ssl) ||
- (conn->handler == &Curl_handler_http2))
- return CURLE_OK; /* already done */
-
- if(conn->handler->flags & PROTOPT_SSL)
- conn->handler = &Curl_handler_http2_ssl;
- else
- conn->handler = &Curl_handler_http2;
-
- result = http2_init(data, conn);
- if(result) {
- Curl_dyn_free(&stream->header_recvbuf);
- return result;
- }
-
- infof(data, "Using HTTP2, server supports multiplexing");
-
- conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- conn->httpversion = 20;
- conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ sock[0] = cf->conn->sock[cf->sockindex];
- httpc->inbuflen = 0;
- httpc->nread_inbuf = 0;
+ if(!(k->keepon & KEEP_RECV_PAUSE))
+ /* Unless paused - in an HTTP/2 connection we can basically always get a
+ frame so we should always be ready for one */
+ bitmap |= GETSOCK_READSOCK(0);
- httpc->pause_stream_id = 0;
- httpc->drain_total = 0;
+ /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
+ there's a window to send data in */
+ if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) ||
+ nghttp2_session_want_write(ctx->h2)) &&
+ (nghttp2_session_get_remote_window_size(ctx->h2) &&
+ nghttp2_session_get_stream_remote_window_size(ctx->h2,
+ stream->stream_id)))
+ bitmap |= GETSOCK_WRITESOCK(0);
- return CURLE_OK;
+ return bitmap;
}
-CURLcode Curl_http2_switched(struct Curl_easy *data,
- const char *mem, size_t nread)
-{
- CURLcode result;
- struct connectdata *conn = data->conn;
- struct http_conn *httpc = &conn->proto.httpc;
- int rv;
- struct HTTP *stream = data->req.p.http;
-
- result = Curl_http2_setup(data, conn);
- if(result)
- return result;
-
- httpc->recv_underlying = conn->recv[FIRSTSOCKET];
- httpc->send_underlying = conn->send[FIRSTSOCKET];
- conn->recv[FIRSTSOCKET] = http2_recv;
- conn->send[FIRSTSOCKET] = http2_send;
- if(data->req.upgr101 == UPGR101_RECEIVED) {
- /* stream 1 is opened implicitly on upgrade */
- stream->stream_id = 1;
- /* queue SETTINGS frame (again) */
- rv = nghttp2_session_upgrade2(httpc->h2, httpc->binsettings, httpc->binlen,
- data->state.httpreq == HTTPREQ_HEAD, NULL);
- if(rv) {
- failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
- nghttp2_strerror(rv), rv);
- return CURLE_HTTP2;
- }
+static CURLcode h2_cf_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
- rv = nghttp2_session_set_stream_user_data(httpc->h2,
- stream->stream_id,
- data);
- if(rv) {
- infof(data, "http/2: failed to set user_data for stream %u",
- stream->stream_id);
- DEBUGASSERT(0);
- }
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
}
- else {
- populate_settings(data, httpc);
- /* stream ID is unknown at this point */
- stream->stream_id = -1;
- rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE,
- httpc->local_settings,
- httpc->local_settings_num);
- if(rv) {
- failf(data, "nghttp2_submit_settings() failed: %s(%d)",
- nghttp2_strerror(rv), rv);
- return CURLE_HTTP2;
- }
+ /* Connect the lower filters first */
+ if(!cf->next->connected) {
+ result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+ if(result || !*done)
+ return result;
}
- rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0,
- HTTP2_HUGE_WINDOW_SIZE);
- if(rv) {
- failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
- nghttp2_strerror(rv), rv);
- return CURLE_HTTP2;
+ *done = FALSE;
+ if(!ctx->h2) {
+ result = h2_cf_ctx_init(cf, data, FALSE);
+ if(result)
+ goto out;
}
- /* we are going to copy mem to httpc->inbuf. This is required since
- mem is part of buffer pointed by stream->mem, and callbacks
- called by nghttp2_session_mem_recv() will write stream specific
- data into stream->mem, overwriting data already there. */
- if(H2_BUFSIZE < nread) {
- failf(data, "connection buffer size is too small to store data following "
- "HTTP Upgrade response header: buflen=%d, datalen=%zu",
- H2_BUFSIZE, nread);
- return CURLE_HTTP2;
+ if(-1 == h2_process_pending_input(cf, data, &result)) {
+ result = CURLE_HTTP2;
+ goto out;
}
- infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
- " after upgrade: len=%zu",
- nread);
+ *done = TRUE;
+ cf->connected = TRUE;
+ result = CURLE_OK;
- if(nread)
- memcpy(httpc->inbuf, mem, nread);
+out:
+ return result;
+}
- httpc->inbuflen = nread;
+static void h2_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
- DEBUGASSERT(httpc->nread_inbuf == 0);
+ (void)data;
+ if(ctx) {
+ /* GOAWAY? */
+ h2_cf_ctx_clear(ctx);
+ }
+}
- if(-1 == h2_process_pending_input(data, httpc, &result))
- return CURLE_HTTP2;
+static void h2_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
- return CURLE_OK;
+ (void)data;
+ if(ctx) {
+ h2_cf_ctx_free(ctx);
+ cf->ctx = NULL;
+ }
}
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
+static CURLcode http2_data_pause(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool pause)
{
+ struct h2_cf_ctx *ctx = cf->ctx;
+
DEBUGASSERT(data);
- DEBUGASSERT(data->conn);
- /* if it isn't HTTP/2, we're done */
- if(!(data->conn->handler->protocol & PROTO_FAMILY_HTTP) ||
- !data->conn->proto.httpc.h2)
- return CURLE_OK;
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
- else {
+ if(ctx && ctx->h2) {
struct HTTP *stream = data->req.p.http;
- struct http_conn *httpc = &data->conn->proto.httpc;
uint32_t window = !pause * HTTP2_HUGE_WINDOW_SIZE;
- int rv = nghttp2_session_set_local_window_size(httpc->h2,
+ int rv = nghttp2_session_set_local_window_size(ctx->h2,
NGHTTP2_FLAG_NONE,
stream->stream_id,
window);
@@ -2203,7 +2128,7 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
}
/* make sure the window update gets sent */
- rv = h2_session_send(data, httpc->h2);
+ rv = h2_session_send(cf, data);
if(rv)
return CURLE_SEND_ERROR;
@@ -2214,7 +2139,7 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
{
/* read out the stream local window again */
uint32_t window2 =
- nghttp2_session_get_stream_local_window_size(httpc->h2,
+ nghttp2_session_get_stream_local_window_size(ctx->h2,
stream->stream_id);
DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u",
window2, stream->stream_id));
@@ -2225,86 +2150,209 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
return CURLE_OK;
}
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
- struct Curl_easy *child,
- bool exclusive)
+static CURLcode h2_cf_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
{
- if(parent) {
- struct Curl_http2_dep **tail;
- struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
- if(!dep)
- return CURLE_OUT_OF_MEMORY;
- dep->data = child;
-
- if(parent->set.stream_dependents && exclusive) {
- struct Curl_http2_dep *node = parent->set.stream_dependents;
- while(node) {
- node->data->set.stream_depends_on = child;
- node = node->next;
- }
+ CURLcode result = CURLE_OK;
- tail = &child->set.stream_dependents;
- while(*tail)
- tail = &(*tail)->next;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_DATA_SETUP: {
+ result = http2_data_setup(cf, data);
+ break;
+ }
+ case CF_CTRL_DATA_PAUSE: {
+ result = http2_data_pause(cf, data, (arg1 != 0));
+ break;
+ }
+ case CF_CTRL_DATA_DONE_SEND: {
+ result = http2_data_done_send(cf, data);
+ break;
+ }
+ case CF_CTRL_DATA_DONE: {
+ http2_data_done(cf, data, arg1 != 0);
+ break;
+ }
+ default:
+ break;
+ }
+ return result;
+}
- DEBUGASSERT(!*tail);
- *tail = parent->set.stream_dependents;
- parent->set.stream_dependents = 0;
- }
+static bool h2_cf_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
+ if(ctx && ctx->inbuflen > 0 && ctx->nread_inbuf > ctx->inbuflen)
+ return TRUE;
+ return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
+}
- tail = &parent->set.stream_dependents;
- while(*tail) {
- (*tail)->data->set.stream_depends_e = FALSE;
- tail = &(*tail)->next;
- }
+static bool h2_cf_is_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
+ return (ctx && ctx->h2 && !http2_connisdead(cf, data));
+}
+
+static CURLcode h2_cf_keep_alive(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ return http2_send_ping(cf, data);
+}
- DEBUGASSERT(!*tail);
- *tail = dep;
+static CURLcode h2_cf_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2)
+{
+ struct h2_cf_ctx *ctx = cf->ctx;
+
+ switch(query) {
+ case CF_QUERY_MAX_CONCURRENT:
+ DEBUGASSERT(pres1);
+ *pres1 = (ctx->max_concurrent_streams > INT_MAX)?
+ INT_MAX : (int)ctx->max_concurrent_streams;
+ return CURLE_OK;
+ default:
+ break;
}
+ return cf->next?
+ cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+ CURLE_UNKNOWN_OPTION;
+}
- child->set.stream_depends_on = parent;
- child->set.stream_depends_e = exclusive;
- return CURLE_OK;
+static const struct Curl_cftype cft_nghttp2 = {
+ "NGHTTP2",
+ CF_TYPE_MULTIPLEX,
+ h2_cf_destroy,
+ h2_cf_connect,
+ h2_cf_close,
+ Curl_cf_def_get_host,
+ h2_cf_get_select_socks,
+ h2_cf_data_pending,
+ h2_cf_send,
+ h2_cf_recv,
+ h2_cf_cntrl,
+ h2_cf_is_alive,
+ h2_cf_keep_alive,
+ h2_cf_query,
+};
+
+static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf;
+ struct h2_cf_ctx *ctx;
+ CURLcode result = CURLE_OUT_OF_MEMORY;
+
+ DEBUGASSERT(data->conn);
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx)
+ goto out;
+
+ result = Curl_cf_create(&cf, &cft_nghttp2, ctx);
+ if(result)
+ goto out;
+
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+ result = CURLE_OK;
+
+out:
+ if(result)
+ h2_cf_ctx_free(ctx);
+ *pcf = result? NULL : cf;
+ return result;
}
-void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child)
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex)
{
- struct Curl_http2_dep *last = 0;
- struct Curl_http2_dep *data = parent->set.stream_dependents;
- DEBUGASSERT(child->set.stream_depends_on == parent);
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
- while(data && data->data != child) {
- last = data;
- data = data->next;
+ (void)data;
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_nghttp2)
+ return TRUE;
+ if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+ return FALSE;
}
+ return FALSE;
+}
- DEBUGASSERT(data);
-
- if(data) {
- if(last) {
- last->next = data->next;
- }
- else {
- parent->set.stream_dependents = data->next;
+bool Curl_http2_may_switch(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ (void)sockindex;
+ if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
+#ifndef CURL_DISABLE_PROXY
+ if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
+ /* We don't support HTTP/2 proxies yet. Also it's debatable
+ whether or not this setting should apply to HTTP/2 proxies. */
+ infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
+ return FALSE;
}
- free(data);
+#endif
+ return TRUE;
}
-
- child->set.stream_depends_on = 0;
- child->set.stream_depends_e = FALSE;
+ return FALSE;
}
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+ struct connectdata *conn, int sockindex,
+ const char *mem, size_t nread)
{
- while(data->set.stream_dependents) {
- struct Curl_easy *tmp = data->set.stream_dependents->data;
- Curl_http2_remove_child(data, tmp);
- if(data->set.stream_depends_on)
- Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE);
+ struct Curl_cfilter *cf;
+ struct h2_cf_ctx *ctx;
+ CURLcode result;
+
+ DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+ DEBUGF(infof(data, DMSGI(data, sockindex, "switching to HTTP/2")));
+
+ result = http2_cfilter_add(&cf, data, conn, sockindex);
+ if(result)
+ return result;
+
+ DEBUGASSERT(cf->cft == &cft_nghttp2);
+ ctx = cf->ctx;
+
+ result = h2_cf_ctx_init(cf, data, (data->req.upgr101 == UPGR101_RECEIVED));
+ if(result)
+ return result;
+
+ if(nread) {
+ /* we are going to copy mem to httpc->inbuf. This is required since
+ mem is part of buffer pointed by stream->mem, and callbacks
+ called by nghttp2_session_mem_recv() will write stream specific
+ data into stream->mem, overwriting data already there. */
+ if(H2_BUFSIZE < nread) {
+ failf(data, "connection buffer size is too small to store data "
+ "following HTTP Upgrade response header: buflen=%d, datalen=%zu",
+ H2_BUFSIZE, nread);
+ return CURLE_HTTP2;
+ }
+
+ infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
+ " after upgrade: len=%zu", nread);
+ DEBUGASSERT(ctx->nread_inbuf == 0);
+ memcpy(ctx->inbuf, mem, nread);
+ ctx->inbuflen = nread;
}
- if(data->set.stream_depends_on)
- Curl_http2_remove_child(data->set.stream_depends_on, data);
+ conn->httpversion = 20; /* we know we're on HTTP/2 now */
+ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ multi_connchanged(data->multi);
+
+ if(cf->next) {
+ bool done;
+ return Curl_conn_cf_connect(cf, data, FALSE, &done);
+ }
+ return CURLE_OK;
}
/* Only call this function for a transfer that already got an HTTP/2
@@ -2312,7 +2360,7 @@ void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
{
struct HTTP *stream = data->req.p.http;
- return (stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
+ return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
}
#else /* !USE_NGHTTP2 */
diff --git a/lib/http2.h b/lib/http2.h
index 966bf75da..0d3efe967 100644
--- a/lib/http2.h
+++ b/lib/http2.h
@@ -40,44 +40,31 @@ void Curl_http2_ver(char *p, size_t len);
const char *Curl_http2_strerror(uint32_t err);
-CURLcode Curl_http2_init(struct connectdata *conn);
-void Curl_http2_init_state(struct UrlState *state);
-void Curl_http2_init_userset(struct UserDefined *set);
CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
struct Curl_easy *data);
-CURLcode Curl_http2_setup(struct Curl_easy *data, struct connectdata *conn);
-CURLcode Curl_http2_switched(struct Curl_easy *data,
- const char *ptr, size_t nread);
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn);
-void Curl_http2_setup_req(struct Curl_easy *data);
-void Curl_http2_done(struct Curl_easy *data, bool premature);
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
- struct connectdata *conn);
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
- struct Curl_easy *child,
- bool exclusive);
-void Curl_http2_remove_child(struct Curl_easy *parent,
- struct Curl_easy *child);
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data);
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause);
/* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */
bool Curl_h2_http_1_1_error(struct Curl_easy *data);
+
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex);
+
+bool Curl_http2_may_switch(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
+
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+ struct connectdata *conn, int sockindex,
+ const char *ptr, size_t nread);
+
#else /* USE_NGHTTP2 */
+
+#define Curl_conn_is_http2(a,b,c) FALSE
+#define Curl_http2_may_switch(a,b,c) FALSE
+
#define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup_conn(x) Curl_nop_stmt
-#define Curl_http2_setup_req(x)
-#define Curl_http2_init_state(x)
-#define Curl_http2_init_userset(x)
-#define Curl_http2_done(x,y)
-#define Curl_http2_done_sending(x,y) (void)y
-#define Curl_http2_add_child(x, y, z)
-#define Curl_http2_remove_child(x, y)
-#define Curl_http2_cleanup_dependencies(x)
-#define Curl_http2_stream_pause(x, y)
+#define Curl_http2_switch(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_h2_http_1_1_error(x) 0
#endif
diff --git a/lib/http_proxy.c b/lib/http_proxy.c
index 56a7bd4aa..66ba6c289 100644
--- a/lib/http_proxy.c
+++ b/lib/http_proxy.c
@@ -26,7 +26,7 @@
#include "http_proxy.h"
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+#if !defined(CURL_DISABLE_PROXY)
#include <curl/curl.h>
#ifdef USE_HYPER
@@ -49,6 +49,17 @@
#include "curl_memory.h"
#include "memdebug.h"
+
+#define DEBUG_CF 0
+
+#if DEBUG_CF
+#define CF_DEBUGF(x) x
+#else
+#define CF_DEBUGF(x) do { } while(0)
+#endif
+
+#if !defined(CURL_DISABLE_HTTP)
+
typedef enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT request is being send */
@@ -183,29 +194,35 @@ static void tunnel_go_state(struct Curl_cfilter *cf,
/* entering this one */
switch(new_state) {
case TUNNEL_INIT:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'init'")));
tunnel_reinit(ts, cf->conn, data);
break;
case TUNNEL_CONNECT:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'connect'")));
ts->tunnel_state = TUNNEL_CONNECT;
ts->keepon = KEEPON_CONNECT;
Curl_dyn_reset(&ts->rcvbuf);
break;
case TUNNEL_RECEIVE:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'receive'")));
ts->tunnel_state = TUNNEL_RECEIVE;
break;
case TUNNEL_RESPONSE:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'response'")));
ts->tunnel_state = TUNNEL_RESPONSE;
break;
case TUNNEL_ESTABLISHED:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'established'")));
infof(data, "CONNECT phase completed");
data->state.authproxy.done = TRUE;
data->state.authproxy.multipass = FALSE;
/* FALLTHROUGH */
case TUNNEL_FAILED:
+ CF_DEBUGF(infof(data, CFMSG(cf, "new tunnel state 'failed'")));
ts->tunnel_state = new_state;
Curl_dyn_reset(&ts->rcvbuf);
Curl_dyn_reset(&ts->req);
@@ -416,7 +433,7 @@ static CURLcode on_resp_header(struct Curl_easy *data,
if(!auth)
return CURLE_OUT_OF_MEMORY;
- DEBUGF(infof(data, "CONNECT: fwd auth header '%s'",
+ CF_DEBUGF(infof(data, "CONNECT: fwd auth header '%s'",
header));
result = Curl_http_input_auth(data, proxy, auth);
@@ -634,7 +651,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
/* without content-length or chunked encoding, we
can't keep the connection alive since the close is
the end signal so we bail out at once instead */
- DEBUGF(infof(data, "CONNECT: no content-length or chunked"));
+ CF_DEBUGF(infof(data, "CONNECT: no content-length or chunked"));
ts->keepon = KEEPON_DONE;
}
}
@@ -972,6 +989,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
switch(ts->tunnel_state) {
case TUNNEL_INIT:
/* Prepare the CONNECT request and make a first attempt to send. */
+ CF_DEBUGF(infof(data, CFMSG(cf, "CONNECT start")));
result = start_CONNECT(data, cf->conn, ts);
if(result)
goto out;
@@ -980,6 +998,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
case TUNNEL_CONNECT:
/* see that the request is completely sent */
+ CF_DEBUGF(infof(data, CFMSG(cf, "CONNECT send")));
result = send_CONNECT(data, cf->conn, ts, &done);
if(result || !done)
goto out;
@@ -988,6 +1007,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
case TUNNEL_RECEIVE:
/* read what is there */
+ CF_DEBUGF(infof(data, CFMSG(cf, "CONNECT receive")));
result = recv_CONNECT_resp(data, cf->conn, ts, &done);
if(Curl_pgrsUpdate(data)) {
result = CURLE_ABORTED_BY_CALLBACK;
@@ -1001,24 +1021,29 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
/* FALLTHROUGH */
case TUNNEL_RESPONSE:
+ CF_DEBUGF(infof(data, CFMSG(cf, "CONNECT response")));
if(data->req.newurl) {
/* not the "final" response, we need to do a follow up request.
* If the other side indicated a connection close, or if someone
- * else told us to close this connection, do so now. */
+ * else told us to close this connection, do so now.
+ */
if(ts->close_connection || conn->bits.close) {
- /* Close the filter chain and trigger connect, non-blocking
- * again, so the process is ongoing. This will
- * a) the close resets our tunnel state
- * b) the connect makes sure that there will be a socket
- * to select on again.
- * We return and expect to be called again. */
+ /* Close this filter and the sub-chain, re-connect the
+ * sub-chain and continue. Closing this filter will
+ * reset our tunnel state. To avoid recursion, we return
+ * and expect to be called again.
+ */
+ CF_DEBUGF(infof(data, CFMSG(cf, "CONNECT need to close+open")));
infof(data, "Connect me again please");
- Curl_conn_close(data, cf->sockindex);
- result = cf->next->cft->connect(cf->next, data, FALSE, &done);
+ Curl_conn_cf_close(cf, data);
+ connkeep(conn, "HTTP proxy CONNECT");
+ result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
goto out;
}
- /* staying on this connection, reset state */
- tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+ else {
+ /* staying on this connection, reset state */
+ tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+ }
}
break;
@@ -1063,10 +1088,12 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
return CURLE_OK;
}
+ CF_DEBUGF(infof(data, CFMSG(cf, "connect")));
result = cf->next->cft->connect(cf->next, data, blocking, done);
if(result || !*done)
return result;
+ CF_DEBUGF(infof(data, CFMSG(cf, "subchain is connected")));
/* TODO: can we do blocking? */
/* We want "seamless" operations through HTTP proxy tunnel */
@@ -1140,24 +1167,18 @@ static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf,
return fds;
}
-static void http_proxy_cf_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
-{
- if(cf->ctx) {
- tunnel_free(cf, data);
- }
-}
-
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- http_proxy_cf_detach_data(cf, data);
+ CF_DEBUGF(infof(data, CFMSG(cf, "destroy")));
+ tunnel_free(cf, data);
}
static void http_proxy_cf_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
DEBUGASSERT(cf->next);
+ CF_DEBUGF(infof(data, CFMSG(cf, "close")));
cf->connected = FALSE;
cf->next->cft->close(cf->next, data);
if(cf->ctx) {
@@ -1170,7 +1191,6 @@ static const struct Curl_cftype cft_http_proxy = {
"HTTP-PROXY",
CF_TYPE_IP_CONNECT,
http_proxy_cf_destroy,
- Curl_cf_def_setup,
http_proxy_cf_connect,
http_proxy_cf_close,
http_proxy_cf_get_host,
@@ -1178,8 +1198,10 @@ static const struct Curl_cftype cft_http_proxy = {
Curl_cf_def_data_pending,
Curl_cf_def_send,
Curl_cf_def_recv,
- Curl_cf_def_attach_data,
- http_proxy_cf_detach_data,
+ Curl_cf_def_cntrl,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
@@ -1195,28 +1217,67 @@ CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
return result;
}
-#endif /* !CURL_DISABLE_PROXY &6 ! CURL_DISABLE_HTTP */
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
-#if !defined(CURL_DISABLE_PROXY)
+ (void)data;
+ result = Curl_cf_create(&cf, &cft_http_proxy, NULL);
+ if(!result)
+ Curl_conn_cf_insert_after(cf_at, cf);
+ return result;
+}
+
+#endif /* ! CURL_DISABLE_HTTP */
-static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
- struct Curl_easy *data)
+
+typedef enum {
+ HAPROXY_INIT, /* init/default/no tunnel state */
+ HAPROXY_SEND, /* data_out being sent */
+ HAPROXY_DONE /* all work done */
+} haproxy_state;
+
+struct cf_haproxy_ctx {
+ int state;
+ struct dynbuf data_out;
+};
+
+static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx)
{
- struct dynbuf req;
+ DEBUGASSERT(ctx);
+ ctx->state = HAPROXY_INIT;
+ Curl_dyn_reset(&ctx->data_out);
+}
+
+static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
+{
+ if(ctx) {
+ Curl_dyn_free(&ctx->data_out);
+ free(ctx);
+ }
+}
+
+static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf,
+ struct Curl_easy *data)
+{
+ struct cf_haproxy_ctx *ctx = cf->ctx;
CURLcode result;
const char *tcp_version;
- Curl_dyn_init(&req, DYN_HAXPROXY);
+ DEBUGASSERT(ctx);
+ DEBUGASSERT(ctx->state == HAPROXY_INIT);
#ifdef USE_UNIX_SOCKETS
if(cf->conn->unix_domain_socket)
/* the buffer is large enough to hold this! */
- result = Curl_dyn_addn(&req, STRCONST("PROXY UNKNOWN\r\n"));
+ result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
else {
#endif /* USE_UNIX_SOCKETS */
/* Emit the correct prefix for IPv6 */
tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
- result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n",
+ result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
tcp_version,
data->info.conn_local_ip,
data->info.conn_primary_ip,
@@ -1226,19 +1287,18 @@ static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
#ifdef USE_UNIX_SOCKETS
}
#endif /* USE_UNIX_SOCKETS */
-
- if(!result)
- result = Curl_buffer_send(&req, data, &data->info.request_size,
- 0, FIRSTSOCKET);
return result;
}
-static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
+static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
+ struct cf_haproxy_ctx *ctx = cf->ctx;
CURLcode result;
+ size_t len;
+ DEBUGASSERT(ctx);
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
@@ -1248,28 +1308,121 @@ static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
if(result || !*done)
return result;
- result = send_haproxy_header(cf, data);
- *done = (!result);
+ switch(ctx->state) {
+ case HAPROXY_INIT:
+ result = cf_haproxy_date_out_set(cf, data);
+ if(result)
+ goto out;
+ ctx->state = HAPROXY_SEND;
+ /* FALLTHROUGH */
+ case HAPROXY_SEND:
+ len = Curl_dyn_len(&ctx->data_out);
+ if(len > 0) {
+ ssize_t written = Curl_conn_send(data, cf->sockindex,
+ Curl_dyn_ptr(&ctx->data_out),
+ len, &result);
+ if(written < 0)
+ goto out;
+ Curl_dyn_tail(&ctx->data_out, len - (size_t)written);
+ if(Curl_dyn_len(&ctx->data_out) > 0) {
+ result = CURLE_OK;
+ goto out;
+ }
+ }
+ ctx->state = HAPROXY_DONE;
+ /* FALLTHROUGH */
+ default:
+ Curl_dyn_free(&ctx->data_out);
+ break;
+ }
+
+out:
+ *done = (!result) && (ctx->state == HAPROXY_DONE);
cf->connected = *done;
return result;
}
+static void cf_haproxy_destroy(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ (void)data;
+ CF_DEBUGF(infof(data, CFMSG(cf, "destroy")));
+ cf_haproxy_ctx_free(cf->ctx);
+}
+
+static void cf_haproxy_close(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CF_DEBUGF(infof(data, CFMSG(cf, "close")));
+ cf->connected = FALSE;
+ cf_haproxy_ctx_reset(cf->ctx);
+ if(cf->next)
+ cf->next->cft->close(cf->next, data);
+}
+
+static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ struct connectdata *conn = cf->conn;
+ int fds;
+
+ DEBUGASSERT(conn);
+ fds = cf->next->cft->get_select_socks(cf->next, data, socks);
+ if(!fds && cf->next->connected && !cf->connected) {
+ /* If we are not connected, but the filter "below" is
+ * and not waiting on something, we are sending. */
+ socks[0] = conn->sock[cf->sockindex];
+ return GETSOCK_WRITESOCK(0);
+ }
+ return fds;
+}
+
+
static const struct Curl_cftype cft_haproxy = {
"HAPROXY",
0,
- Curl_cf_def_destroy_this,
- Curl_cf_def_setup,
- haproxy_cf_connect,
- Curl_cf_def_close,
+ cf_haproxy_destroy,
+ cf_haproxy_connect,
+ cf_haproxy_close,
Curl_cf_def_get_host,
- Curl_cf_def_get_select_socks,
+ cf_haproxy_get_select_socks,
Curl_cf_def_data_pending,
Curl_cf_def_send,
Curl_cf_def_recv,
- Curl_cf_def_attach_data,
- Curl_cf_def_detach_data,
+ Curl_cf_def_cntrl,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
+static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf = NULL;
+ struct cf_haproxy_ctx *ctx;
+ CURLcode result;
+
+ (void)data;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ ctx->state = HAPROXY_INIT;
+ Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY);
+
+ result = Curl_cf_create(&cf, &cft_haproxy, ctx);
+ if(result)
+ goto out;
+ ctx = NULL;
+
+out:
+ cf_haproxy_ctx_free(ctx);
+ *pcf = result? NULL : cf;
+ return result;
+}
+
CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
@@ -1277,9 +1430,27 @@ CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
struct Curl_cfilter *cf;
CURLcode result;
- result = Curl_cf_create(&cf, &cft_haproxy, NULL);
- if(!result)
- Curl_conn_cf_add(data, conn, sockindex, cf);
+ result = cf_haproxy_create(&cf, data);
+ if(result)
+ goto out;
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+
+out:
+ return result;
+}
+
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ result = cf_haproxy_create(&cf, data);
+ if(result)
+ goto out;
+ Curl_conn_cf_insert_after(cf_at, cf);
+
+out:
return result;
}
diff --git a/lib/http_proxy.h b/lib/http_proxy.h
index 935ae0152..320170362 100644
--- a/lib/http_proxy.h
+++ b/lib/http_proxy.h
@@ -36,12 +36,18 @@
CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
+
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data);
+
#endif /* !CURL_DISABLE_HTTP */
CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data);
#endif /* !CURL_DISABLE_PROXY */
#endif /* HEADER_CURL_HTTP_PROXY_H */
diff --git a/lib/imap.c b/lib/imap.c
index bd4c6f220..f459b3b36 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -476,7 +476,7 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data,
struct imap_conn *imapc = &conn->proto.imapc;
CURLcode result;
- if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result)
goto out;
@@ -952,7 +952,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data,
line += wordlen;
}
}
- else if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
/* PREAUTH is not compatible with STARTTLS. */
if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
/* Switch to TLS connection now */
diff --git a/lib/multi.c b/lib/multi.c
index f08add3b0..0d7666125 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -655,6 +655,9 @@ static CURLcode multi_done(struct Curl_easy *data,
result = CURLE_ABORTED_BY_CALLBACK;
}
+ /* Inform connection filters that this transfer is done */
+ Curl_conn_ev_data_done(data, premature);
+
process_pending_handles(data->multi); /* connection / multiplex */
CONNCACHE_LOCK(data);
@@ -709,12 +712,12 @@ static CURLcode multi_done(struct Curl_easy *data,
conn->proxy_negotiate_state == GSS_AUTHRECV)
#endif
) || conn->bits.close
- || (premature && !(conn->handler->flags & PROTOPT_STREAM))) {
+ || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) {
DEBUGF(infof(data, "multi_done, not re-using connection=%ld, forbid=%d"
- ", close=%d, premature=%d, stream=%d",
+ ", close=%d, premature=%d, conn_multiplex=%d",
conn->connection_id,
data->set.reuse_forbid, conn->bits.close, premature,
- (conn->handler->flags & PROTOPT_STREAM)));
+ Curl_conn_is_multiplex(conn, FIRSTSOCKET)));
connclose(conn, "disconnecting");
Curl_conncache_remove_conn(data, conn, FALSE);
CONNCACHE_UNLOCK(data);
@@ -954,7 +957,7 @@ void Curl_detach_connection(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
if(conn) {
- Curl_conn_detach_data(conn, data);
+ Curl_conn_ev_data_detach(conn, data);
Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL);
}
data->conn = NULL;
@@ -973,9 +976,9 @@ void Curl_attach_connection(struct Curl_easy *data,
data->conn = conn;
Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data,
&data->conn_queue);
- Curl_conn_attach_data(conn, data);
if(conn->handler->attach)
conn->handler->attach(data, conn);
+ Curl_conn_ev_data_attach(conn, data);
}
static int domore_getsock(struct Curl_easy *data,
@@ -1002,11 +1005,7 @@ static int protocol_getsock(struct Curl_easy *data,
{
if(conn->handler->proto_getsock)
return conn->handler->proto_getsock(data, conn, socks);
- /* Backup getsock logic. Since there is a live socket in use, we must wait
- for it or it will be removed from watching when the multi_socket API is
- used. */
- socks[0] = conn->sock[FIRSTSOCKET];
- return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0);
+ return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks);
}
/* returns bitmapped flags for this handle and its sockets. The 'socks[]'
diff --git a/lib/pop3.c b/lib/pop3.c
index ce17f2ac7..fe598d525 100644
--- a/lib/pop3.c
+++ b/lib/pop3.c
@@ -371,7 +371,7 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data,
struct pop3_conn *pop3c = &conn->proto.pop3c;
CURLcode result;
- if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result)
goto out;
@@ -769,7 +769,7 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code,
if(pop3code != '+')
pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
- if(!data->set.use_ssl || Curl_conn_is_ssl(data, FIRSTSOCKET))
+ if(!data->set.use_ssl || Curl_conn_is_ssl(conn, FIRSTSOCKET))
result = pop3_perform_authentication(data, conn);
else if(pop3code == '+' && pop3c->tls_supported)
/* Switch to TLS connection now */
diff --git a/lib/quic.h b/lib/quic.h
deleted file mode 100644
index b35774735..000000000
--- a/lib/quic.h
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef HEADER_CURL_QUIC_H
-#define HEADER_CURL_QUIC_H
-/***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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"
-
-#ifdef ENABLE_QUIC
-#ifdef USE_NGTCP2
-#include "vquic/ngtcp2.h"
-#endif
-#ifdef USE_QUICHE
-#include "vquic/quiche.h"
-#endif
-#ifdef USE_MSH3
-#include "vquic/msh3.h"
-#endif
-
-#include "urldata.h"
-
-/* functions provided by the specific backends */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
- struct connectdata *conn,
- curl_socket_t sockfd,
- int sockindex,
- const struct sockaddr *addr,
- socklen_t addrlen);
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- bool *connected);
-void Curl_quic_ver(char *p, size_t len);
-CURLcode Curl_quic_done_sending(struct Curl_easy *data);
-void Curl_quic_done(struct Curl_easy *data, bool premature);
-bool Curl_quic_data_pending(const struct Curl_easy *data);
-void Curl_quic_disconnect(struct Curl_easy *data,
- struct connectdata *conn, int tempindex);
-CURLcode Curl_quic_idle(struct Curl_easy *data);
-
-#else /* ENABLE_QUIC */
-#define Curl_quic_done_sending(x)
-#define Curl_quic_done(x,y)
-#define Curl_quic_data_pending(x)
-#define Curl_quic_disconnect(x,y,z)
-#endif /* !ENABLE_QUIC */
-
-#endif /* HEADER_CURL_QUIC_H */
diff --git a/lib/rtsp.c b/lib/rtsp.c
index 75e620d11..4d55fd3fe 100644
--- a/lib/rtsp.c
+++ b/lib/rtsp.c
@@ -38,6 +38,7 @@
#include "strcase.h"
#include "select.h"
#include "connect.h"
+#include "cfilters.h"
#include "strdup.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -134,36 +135,6 @@ static CURLcode rtsp_setup_connection(struct Curl_easy *data,
/*
- * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not
- * want to block the application forever while receiving a stream. Therefore,
- * we cannot assume that an RTSP socket is dead just because it is readable.
- *
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
- * and distinguish between closed and data.
- */
-static bool rtsp_connisdead(struct Curl_easy *data, struct connectdata *check)
-{
- int sval;
- bool ret_val = TRUE;
-
- sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0);
- if(sval == 0) {
- /* timeout */
- ret_val = FALSE;
- }
- else if(sval & CURL_CSELECT_ERR) {
- /* socket is in an error state */
- ret_val = TRUE;
- }
- else if(sval & CURL_CSELECT_IN) {
- /* readable with no error. could still be closed */
- ret_val = !Curl_connalive(data, check);
- }
-
- return ret_val;
-}
-
-/*
* Function to check on various aspects of a connection.
*/
static unsigned int rtsp_conncheck(struct Curl_easy *data,
@@ -174,7 +145,7 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data,
(void)data;
if(checks_to_perform & CONNCHECK_ISDEAD) {
- if(rtsp_connisdead(data, conn))
+ if(!Curl_conn_is_alive(data, conn))
ret_val |= CONNRESULT_DEAD;
}
diff --git a/lib/sendf.c b/lib/sendf.c
index 63262409b..c0fb66e13 100644
--- a/lib/sendf.c
+++ b/lib/sendf.c
@@ -301,7 +301,7 @@ CURLcode Curl_write(struct Curl_easy *data,
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
conn = data->conn;
- num = (sockfd == conn->sock[SECONDARYSOCKET]);
+ num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
#ifdef CURLDEBUG
{
@@ -498,8 +498,7 @@ static CURLcode pausewrite(struct Curl_easy *data,
unsigned int i;
bool newtype = TRUE;
- /* If this transfers over HTTP/2, pause the stream! */
- Curl_http2_stream_pause(data, TRUE);
+ Curl_conn_ev_data_pause(data, TRUE);
if(s->tempcount) {
for(i = 0; i< s->tempcount; i++) {
diff --git a/lib/setopt.c b/lib/setopt.c
index ebe24f95c..59114927b 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2974,29 +2974,23 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE;
break;
case CURLOPT_STREAM_WEIGHT:
-#ifndef USE_NGHTTP2
- return CURLE_NOT_BUILT_IN;
-#else
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
arg = va_arg(param, long);
if((arg >= 1) && (arg <= 256))
- data->set.stream_weight = (int)arg;
+ data->set.priority.weight = (int)arg;
break;
+#else
+ return CURLE_NOT_BUILT_IN;
#endif
case CURLOPT_STREAM_DEPENDS:
case CURLOPT_STREAM_DEPENDS_E:
{
-#ifndef USE_NGHTTP2
- return CURLE_NOT_BUILT_IN;
-#else
struct Curl_easy *dep = va_arg(param, struct Curl_easy *);
if(!dep || GOOD_EASY_HANDLE(dep)) {
- if(data->set.stream_depends_on) {
- Curl_http2_remove_child(data->set.stream_depends_on, data);
- }
- Curl_http2_add_child(dep, data, (option == CURLOPT_STREAM_DEPENDS_E));
+ return Curl_data_priority_add_child(dep, data,
+ option == CURLOPT_STREAM_DEPENDS_E);
}
break;
-#endif
}
case CURLOPT_CONNECT_TO:
data->set.connect_to = va_arg(param, struct curl_slist *);
diff --git a/lib/smtp.c b/lib/smtp.c
index 6d0783f45..192feae34 100644
--- a/lib/smtp.c
+++ b/lib/smtp.c
@@ -399,7 +399,7 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data)
struct smtp_conn *smtpc = &conn->proto.smtpc;
CURLcode result;
- if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
if(result)
goto out;
@@ -891,7 +891,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
if(smtpcode/100 != 2 && smtpcode != 1) {
if(data->set.use_ssl <= CURLUSESSL_TRY
- || Curl_conn_is_ssl(data, FIRSTSOCKET))
+ || Curl_conn_is_ssl(conn, FIRSTSOCKET))
result = smtp_perform_helo(data, conn);
else {
failf(data, "Remote access denied: %d", smtpcode);
@@ -956,7 +956,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
}
if(smtpcode != 1) {
- if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+ if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
/* We don't have a SSL/TLS connection yet, but SSL is requested */
if(smtpc->tls_supported)
/* Switch to TLS connection now */
diff --git a/lib/socks.c b/lib/socks.c
index d491e089b..acdb1ed49 100644
--- a/lib/socks.c
+++ b/lib/socks.c
@@ -1151,7 +1151,6 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
result = connect_SOCKS(cf, sx, data);
if(!result && sx->state == CONNECT_DONE) {
cf->connected = TRUE;
- Curl_updateconninfo(data, conn, conn->sock[cf->sockindex]);
Curl_verboseconnect(data, conn);
socks_proxy_cf_free(cf);
}
@@ -1205,13 +1204,6 @@ static void socks_proxy_cf_destroy(struct Curl_cfilter *cf,
socks_proxy_cf_free(cf);
}
-static void socks_proxy_cf_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
-{
- (void)data;
- socks_proxy_cf_free(cf);
-}
-
static void socks_cf_get_host(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char **phost,
@@ -1233,7 +1225,6 @@ static const struct Curl_cftype cft_socks_proxy = {
"SOCKS-PROXYY",
CF_TYPE_IP_CONNECT,
socks_proxy_cf_destroy,
- Curl_cf_def_setup,
socks_proxy_cf_connect,
socks_proxy_cf_close,
socks_cf_get_host,
@@ -1241,8 +1232,10 @@ static const struct Curl_cftype cft_socks_proxy = {
Curl_cf_def_data_pending,
Curl_cf_def_send,
Curl_cf_def_recv,
- Curl_cf_def_attach_data,
- socks_proxy_cf_detach_data,
+ Curl_cf_def_cntrl,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
@@ -1258,4 +1251,17 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
return result;
}
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ (void)data;
+ result = Curl_cf_create(&cf, &cft_socks_proxy, NULL);
+ if(!result)
+ Curl_conn_cf_insert_after(cf_at, cf);
+ return result;
+}
+
#endif /* CURL_DISABLE_PROXY */
diff --git a/lib/socks.h b/lib/socks.h
index 2e2fa18f8..285ff0a70 100644
--- a/lib/socks.h
+++ b/lib/socks.h
@@ -55,6 +55,9 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data);
+
#endif /* CURL_DISABLE_PROXY */
#endif /* HEADER_CURL_SOCKS_H */
diff --git a/lib/transfer.c b/lib/transfer.c
index 58bb8be4c..c94f49bd1 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -73,6 +73,7 @@
#include "url.h"
#include "getinfo.h"
#include "vtls/vtls.h"
+#include "vquic/vquic.h"
#include "select.h"
#include "multiif.h"
#include "connect.h"
@@ -367,27 +368,12 @@ static int data_pending(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
-#ifdef ENABLE_QUIC
- if(conn->transport == TRNSPRT_QUIC)
- return Curl_quic_data_pending(data);
-#endif
-
if(conn->handler->protocol&PROTO_FAMILY_FTP)
return Curl_conn_data_pending(data, SECONDARYSOCKET);
/* in the case of libssh2, we can never be really sure that we have emptied
its internal buffers so we MUST always try until we get EAGAIN back */
return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) ||
-#ifdef USE_NGHTTP2
- /* For HTTP/2, we may read up everything including response body
- with header fields in Curl_http_readwrite_headers. If no
- content-length is provided, curl waits for the connection
- close, which we emulate it using conn->proto.httpc.closed =
- TRUE. The thing is if we read everything, then http2_recv won't
- be called and we cannot signal the HTTP/2 stream has closed. As
- a workaround, we return nonzero here to call http2_recv. */
- ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20) ||
-#endif
Curl_conn_data_pending(data, FIRSTSOCKET);
}
@@ -454,29 +440,16 @@ static CURLcode readwrite_data(struct Curl_easy *data,
bool is_empty_data = FALSE;
size_t buffersize = data->set.buffer_size;
size_t bytestoread = buffersize;
-#ifdef USE_NGHTTP2
- bool is_http2 = ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
- (conn->httpversion == 20));
-#endif
- bool is_http3 =
-#ifdef ENABLE_QUIC
- ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
- (conn->httpversion == 30));
-#else
- FALSE;
-#endif
-
- if(
-#ifdef USE_NGHTTP2
- /* For HTTP/2, read data without caring about the content length. This
- is safe because body in HTTP/2 is always segmented thanks to its
- framing layer. Meanwhile, we have to call Curl_read to ensure that
- http2_handle_stream_close is called when we read all incoming bytes
- for a particular stream. */
- !is_http2 &&
-#endif
- !is_http3 && /* Same reason mentioned above. */
- k->size != -1 && !k->header) {
+ /* For HTTP/2 and HTTP/3, read data without caring about the content
+ length. This is safe because body in HTTP/2 is always segmented
+ thanks to its framing layer. Meanwhile, we have to call Curl_read
+ to ensure that http2_handle_stream_close is called when we read all
+ incoming bytes for a particular stream. */
+ bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET);
+ bool data_eof_handled = is_http3
+ || Curl_conn_is_http2(data, conn, FIRSTSOCKET);
+
+ if(!data_eof_handled && k->size != -1 && !k->header) {
/* make sure we don't read too much */
curl_off_t totalleft = k->size - k->bytecount;
if(totalleft < (curl_off_t)bytestoread)
@@ -499,7 +472,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
else {
/* read nothing but since we wanted nothing we consider this an OK
situation to proceed from */
- DEBUGF(infof(data, "readwrite_data: we're done"));
+ DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done")));
nread = 0;
}
@@ -518,14 +491,9 @@ static CURLcode readwrite_data(struct Curl_easy *data,
buf[nread] = 0;
}
else {
- /* if we receive 0 or less here, either the http2 stream is closed or the
+ /* if we receive 0 or less here, either the data transfer is done or the
server closed the connection and we bail out from this! */
-#ifdef USE_NGHTTP2
- if(is_http2 && !nread)
- DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
- else
-#endif
- if(is_http3 && !nread)
+ if(data_eof_handled)
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
else
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
@@ -799,19 +767,18 @@ static CURLcode readwrite_data(struct Curl_easy *data,
}
out:
- DEBUGF(infof(data, "readwrite_data(handle=%p) -> %d", data, result));
+ if(result)
+ DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result));
return result;
}
CURLcode Curl_done_sending(struct Curl_easy *data,
struct SingleRequest *k)
{
- struct connectdata *conn = data->conn;
k->keepon &= ~KEEP_SEND; /* we're done writing */
/* These functions should be moved into the handler struct! */
- Curl_http2_done_sending(data, conn);
- Curl_quic_done_sending(data);
+ Curl_conn_ev_data_done_send(data);
return CURLE_OK;
}
@@ -1182,13 +1149,9 @@ CURLcode Curl_readwrite(struct connectdata *conn,
}
}
-#ifdef ENABLE_QUIC
- if(conn->transport == TRNSPRT_QUIC) {
- result = Curl_quic_idle(data);
- if(result)
- goto out;
- }
-#endif
+ result = Curl_conn_ev_data_idle(data);
+ if(result)
+ goto out;
}
if(Curl_pgrsUpdate(data))
@@ -1264,7 +1227,8 @@ CURLcode Curl_readwrite(struct connectdata *conn,
KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE;
result = CURLE_OK;
out:
- DEBUGF(infof(data, "Curl_readwrite(handle=%p) -> %d", data, result));
+ if(result)
+ DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result));
return result;
}
@@ -1377,6 +1341,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
data->state.authhost.want = data->set.httpauth;
data->state.authproxy.want = data->set.proxyauth;
Curl_safefree(data->info.wouldredirect);
+ Curl_data_priority_clear_state(data);
if(data->state.httpreq == HTTPREQ_PUT)
data->state.infilesize = data->set.filesize;
@@ -1434,7 +1399,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
}
}
#endif
- Curl_http2_init_state(&data->state);
result = Curl_hsts_loadcb(data, data->hsts);
}
@@ -1872,7 +1836,7 @@ Curl_setup_transfer(
httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) &&
(http->sending == HTTPSEND_REQUEST));
- if(conn->bits.multiplex || conn->httpversion == 20 || httpsending) {
+ if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) {
/* when multiplexing, the read/write sockets need to be the same! */
conn->sockfd = sockindex == -1 ?
((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :
diff --git a/lib/url.c b/lib/url.c
index 815c33fec..9a858eff0 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -452,7 +452,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_resolver_cancel(data);
Curl_resolver_cleanup(data->state.async.resolver);
- Curl_http2_cleanup_dependencies(data);
+ Curl_data_priority_cleanup(data);
/* No longer a dirty share, if it exists */
if(data->share) {
@@ -640,14 +640,15 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
set->maxage_conn = 118;
set->maxlifetime_conn = 0;
set->http09_allowed = FALSE;
- set->httpwant =
#ifdef USE_HTTP2
- CURL_HTTP_VERSION_2TLS
+ set->httpwant = CURL_HTTP_VERSION_2TLS
#else
- CURL_HTTP_VERSION_1_1
+ set->httpwant = CURL_HTTP_VERSION_1_1
#endif
;
- Curl_http2_init_userset(set);
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+ memset(&set->priority, 0, sizeof(set->priority));
+#endif
set->quick_exit = 0L;
return result;
}
@@ -878,24 +879,6 @@ void Curl_disconnect(struct Curl_easy *data,
}
/*
- * This function should return TRUE if the socket is to be assumed to
- * be dead. Most commonly this happens when the server has closed the
- * connection due to inactivity.
- */
-static bool SocketIsDead(curl_socket_t sock)
-{
- int sval;
- bool ret_val = TRUE;
-
- sval = SOCKET_READABLE(sock, 0);
- if(sval == 0)
- /* timeout */
- ret_val = FALSE;
-
- return ret_val;
-}
-
-/*
* IsMultiplexingPossible()
*
* Return a bitmask with the available multiplexing options for the given
@@ -1025,8 +1008,7 @@ static bool extract_if_dead(struct connectdata *conn,
}
else {
- /* Use the general method for determining the death of a connection */
- dead = SocketIsDead(conn->sock[FIRSTSOCKET]);
+ dead = !Curl_conn_is_alive(data, conn);
}
if(dead) {
@@ -1355,8 +1337,10 @@ ConnectionExists(struct Curl_easy *data,
/* If multiplexing isn't enabled on the h2 connection and h1 is
explicitly requested, handle it: */
if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
- (check->httpversion >= 20) &&
- (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+ (((check->httpversion >= 20) &&
+ (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+ || ((check->httpversion >= 30) &&
+ (data->state.httpwant < CURL_HTTP_VERSION_3))))
continue;
if(get_protocol_family(needle->handler) == PROTO_FAMILY_SSH) {
@@ -1478,9 +1462,8 @@ ConnectionExists(struct Curl_easy *data,
#ifdef USE_NGHTTP2
/* If multiplexed, make sure we don't go over concurrency limit */
if(check->bits.multiplex) {
- /* Multiplexed connections can only be HTTP/2 for now */
- struct http_conn *httpc = &check->proto.httpc;
- if(multiplexed >= httpc->settings.max_concurrent_streams) {
+ if(multiplexed >= Curl_conn_get_max_concurrent(data, check,
+ FIRSTSOCKET)) {
infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)",
multiplexed);
continue;
@@ -1562,8 +1545,6 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
- conn->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */
- conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */
conn->connection_id = -1; /* no ID */
conn->port = -1; /* unknown at this point */
conn->remote_port = -1; /* unknown at this point */
@@ -3356,12 +3337,6 @@ static void reuse_conn(struct Curl_easy *data,
struct connectdata *temp,
struct connectdata *existing)
{
- /* 'local_ip' and 'local_port' get filled with local's numerical
- ip address and port number whenever an outgoing connection is
- **established** from the primary socket to a remote address. */
- char local_ip[MAX_IPADR_LEN] = "";
- int local_port = -1;
-
/* get the user+password information from the temp struct since it may
* be new for this request even when we re-use an existing connection */
if(temp->user) {
@@ -3409,12 +3384,8 @@ static void reuse_conn(struct Curl_easy *data,
existing->hostname_resolve = temp->hostname_resolve;
temp->hostname_resolve = NULL;
- /* persist connection info in session handle */
- if(existing->transport == TRNSPRT_TCP) {
- Curl_conninfo_local(data, existing->sock[FIRSTSOCKET],
- local_ip, &local_port);
- }
- Curl_persistconninfo(data, existing, local_ip, local_port);
+ /* persist existing connection info in data */
+ Curl_conn_ev_update_info(data, existing);
conn_reset_all_postponed_data(temp); /* free buffers */
@@ -3891,6 +3862,13 @@ static CURLcode create_conn(struct Curl_easy *data,
* Resolve the address of the server or proxy
*************************************************************/
result = resolve_server(data, conn, async);
+ if(result)
+ goto out;
+
+ /* Everything general done, inform filters that they need
+ * to prepare for a data transfer.
+ */
+ result = Curl_conn_ev_data_setup(data);
out:
return result;
@@ -4029,3 +4007,103 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn)
return CURLE_OK;
}
+
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+
+#ifdef USE_NGHTTP2
+
+static void priority_remove_child(struct Curl_easy *parent,
+ struct Curl_easy *child)
+{
+ struct Curl_data_prio_node **pnext = &parent->set.priority.children;
+ struct Curl_data_prio_node *pnode = parent->set.priority.children;
+
+ DEBUGASSERT(child->set.priority.parent == parent);
+ while(pnode && pnode->data != child) {
+ pnext = &pnode->next;
+ pnode = pnode->next;
+ }
+
+ DEBUGASSERT(pnode);
+ if(pnode) {
+ *pnext = pnode->next;
+ free(pnode);
+ }
+
+ child->set.priority.parent = 0;
+ child->set.priority.exclusive = FALSE;
+}
+
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+ struct Curl_easy *child,
+ bool exclusive)
+{
+ if(child->set.priority.parent) {
+ priority_remove_child(child->set.priority.parent, child);
+ }
+
+ if(parent) {
+ struct Curl_data_prio_node **tail;
+ struct Curl_data_prio_node *pnode;
+
+ pnode = calloc(1, sizeof(*pnode));
+ if(!pnode)
+ return CURLE_OUT_OF_MEMORY;
+ pnode->data = child;
+
+ if(parent->set.priority.children && exclusive) {
+ /* exclusive: move all existing children underneath the new child */
+ struct Curl_data_prio_node *node = parent->set.priority.children;
+ while(node) {
+ node->data->set.priority.parent = child;
+ node = node->next;
+ }
+
+ tail = &child->set.priority.children;
+ while(*tail)
+ tail = &(*tail)->next;
+
+ DEBUGASSERT(!*tail);
+ *tail = parent->set.priority.children;
+ parent->set.priority.children = 0;
+ }
+
+ tail = &parent->set.priority.children;
+ while(*tail) {
+ (*tail)->data->set.priority.exclusive = FALSE;
+ tail = &(*tail)->next;
+ }
+
+ DEBUGASSERT(!*tail);
+ *tail = pnode;
+ }
+
+ child->set.priority.parent = parent;
+ child->set.priority.exclusive = exclusive;
+ return CURLE_OK;
+}
+
+#endif /* USE_NGHTTP2 */
+
+void Curl_data_priority_cleanup(struct Curl_easy *data)
+{
+#ifdef USE_NGHTTP2
+ while(data->set.priority.children) {
+ struct Curl_easy *tmp = data->set.priority.children->data;
+ priority_remove_child(data, tmp);
+ if(data->set.priority.parent)
+ Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE);
+ }
+
+ if(data->set.priority.parent)
+ priority_remove_child(data->set.priority.parent, data);
+#endif
+ (void)data;
+}
+
+void Curl_data_priority_clear_state(struct Curl_easy *data)
+{
+ memset(&data->state.priority, 0, sizeof(data->state.priority));
+}
+
+#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */
diff --git a/lib/url.h b/lib/url.h
index 1a03c564c..cf69e45bc 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -59,4 +59,20 @@ const struct Curl_handler *Curl_builtin_scheme(const char *scheme,
void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn);
#endif
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+void Curl_data_priority_cleanup(struct Curl_easy *data);
+void Curl_data_priority_clear_state(struct Curl_easy *data);
+#else
+#define Curl_data_priority_cleanup(x)
+#define Curl_data_priority_clear_state(x)
+#endif /* !(defined(USE_HTTP2) || defined(USE_HTTP3)) */
+
+#ifdef USE_NGHTTP2
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+ struct Curl_easy *child,
+ bool exclusive);
+#else
+#define Curl_data_priority_add_child(x, y, z) CURLE_NOT_BUILT_IN
+#endif
+
#endif /* HEADER_CURL_URL_H */
diff --git a/lib/urldata.h b/lib/urldata.h
index 82cc340d1..a70729c7e 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -123,9 +123,9 @@ typedef unsigned int curl_prot_t;
#define DMSGI(d,i,msg) \
"[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i)
#define CMSG(c,msg) \
- "[CONN-%ld] "msg, (conn)->connection_id
+ "[CONN-%ld] "msg, (c)->connection_id
#define CMSGI(c,i,msg) \
- "[CONN-%ld-%d] "msg, (conn)->connection_id, (i)
+ "[CONN-%ld-%d] "msg, (c)->connection_id, (i)
#define CFMSG(cf,msg) \
"[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \
(cf)->sockindex, (cf)->cft->name
@@ -187,7 +187,6 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data,
#include "mqtt.h"
#include "wildcard.h"
#include "multihandle.h"
-#include "quic.h"
#include "c-hyper.h"
#ifdef HAVE_GSSAPI
@@ -830,7 +829,7 @@ struct Curl_handler {
#define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per
request instead of per connection */
#define PROTOPT_ALPN (1<<8) /* set ALPN for this */
-#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */
+/* (1<<9) was PROTOPT_STREAM, now free */
#define PROTOPT_URLOPTIONS (1<<10) /* allow options part in the userinfo field
of the URL */
#define PROTOPT_PROXY_AS_HTTP (1<<11) /* allow this non-HTTP scheme over a
@@ -912,13 +911,7 @@ struct connectdata {
/* 'ip_addr' is the particular IP we connected to. It points to a struct
within the DNS cache, so this pointer is only valid as long as the DNS
cache entry remains locked. It gets unlocked in multi_done() */
- struct Curl_addrinfo *ip_addr;
- struct Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */
-
-#ifdef ENABLE_QUIC
- struct quicsocket hequic[2]; /* two, for happy eyeballs! */
- struct quicsocket *quic;
-#endif
+ const struct Curl_addrinfo *ip_addr;
struct hostname host;
char *hostname_resolve; /* host name to resolve to address, allocated */
@@ -947,8 +940,6 @@ struct connectdata {
struct curltime lastused; /* when returned to the connection cache */
curl_socket_t sock[2]; /* two sockets, the second is used for the data
transfer when doing FTP */
- curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */
- int tempfamily[2]; /* family used for the temp sockets */
Curl_recv *recv[2];
Curl_send *send[2];
struct Curl_cfilter *cfilter[2]; /* connection filters */
@@ -962,16 +953,6 @@ struct connectdata {
#endif
struct ConnectBits bits; /* various state-flags for this connection */
- /* connecttime: when connect() is called on the current IP address. Used to
- be able to track when to move on to try next IP - but only when the multi
- interface is used. */
- struct curltime connecttime;
-
- /* The field below gets set in Curl_connecthost */
- /* how long time in milliseconds to spend on trying to connect to each IP
- address, per family */
- timediff_t timeoutms_per_addr[2];
-
const struct Curl_handler *handler; /* Connection's protocol handler */
const struct Curl_handler *given; /* The protocol first given */
@@ -1043,9 +1024,6 @@ struct connectdata {
#ifndef CURL_DISABLE_FTP
struct ftp_conn ftpc;
#endif
-#ifndef CURL_DISABLE_HTTP
- struct http_conn httpc;
-#endif
#ifdef USE_SSH
struct ssh_conn sshc;
#endif
@@ -1094,7 +1072,7 @@ struct connectdata {
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
int socks5_gssapi_enctype;
#endif
- /* The field below gets set in Curl_connecthost */
+ /* The field below gets set in connect.c:connecthost() */
int num_addr; /* number of addresses to try to connect to */
int port; /* which port to use locally - to connect to */
int remote_port; /* the remote port, not the proxy port! */
@@ -1240,10 +1218,29 @@ struct auth {
should be RFC compliant */
};
-struct Curl_http2_dep {
- struct Curl_http2_dep *next;
+#ifdef USE_NGHTTP2
+struct Curl_data_prio_node {
+ struct Curl_data_prio_node *next;
struct Curl_easy *data;
};
+#endif
+
+/**
+ * Priority information for an easy handle in relation to others
+ * on the same connection.
+ * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218
+ */
+struct Curl_data_priority {
+#ifdef USE_NGHTTP2
+ /* tree like dependencies only implemented in nghttp2 */
+ struct Curl_easy *parent;
+ struct Curl_data_prio_node *children;
+#endif
+ int weight;
+#ifdef USE_NGHTTP2
+ BIT(exclusive);
+#endif
+};
/*
* This struct is for holding data that was attempted to get sent to the user's
@@ -1391,14 +1388,11 @@ struct UrlState {
size_t drain; /* Increased when this stream has data to read, even if its
socket is not necessarily is readable. Decreased when
checked. */
+ struct Curl_data_priority priority; /* shallow coyp of data->set */
#endif
curl_read_callback fread_func; /* read callback/function */
void *in; /* CURLOPT_READDATA */
-#ifdef USE_HTTP2
- struct Curl_easy *stream_depends_on;
- int stream_weight;
-#endif
CURLU *uh; /* URL handle for the current parsed URL */
struct urlpieces up;
unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any)
@@ -1469,7 +1463,6 @@ struct UrlState {
BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE
when multi_done() is called, to prevent multi_done() to get
invoked twice when the multi interface is used. */
- BIT(stream_depends_e); /* set or don't set the Exclusive bit */
BIT(previouslypending); /* this transfer WAS in the multi->pending queue */
BIT(cookie_engine);
BIT(prefer_ascii); /* ASCII rather than binary */
@@ -1789,10 +1782,8 @@ struct UserDefined {
size_t maxconnects; /* Max idle connections in the connection cache */
long expect_100_timeout; /* in milliseconds */
-#ifdef USE_HTTP2
- struct Curl_easy *stream_depends_on;
- int stream_weight;
- struct Curl_http2_dep *stream_dependents;
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+ struct Curl_data_priority priority;
#endif
curl_resolver_start_callback resolver_start; /* optional callback called
before resolver start */
@@ -1884,7 +1875,6 @@ struct UserDefined {
BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers
from user callbacks */
BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */
- BIT(stream_depends_e); /* set or don't set the Exclusive bit */
BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1
header */
BIT(abstract_unix_socket);
diff --git a/lib/version.c b/lib/version.c
index b43a8bc71..ae2d46338 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -24,12 +24,16 @@
#include "curl_setup.h"
+#ifdef USE_NGHTTP2
+#include <nghttp2/nghttp2.h>
+#endif
+
#include <curl/curl.h>
#include "urldata.h"
#include "vtls/vtls.h"
#include "http2.h"
#include "vssh/ssh.h"
-#include "quic.h"
+#include "vquic/vquic.h"
#include "curl_printf.h"
#include "easy_lock.h"
diff --git a/lib/vquic/msh3.c b/lib/vquic/msh3.c
index caa16e5a7..0abaaff36 100644
--- a/lib/vquic/msh3.c
+++ b/lib/vquic/msh3.c
@@ -30,6 +30,7 @@
#include "timeval.h"
#include "multiif.h"
#include "sendf.h"
+#include "cfilters.h"
#include "connect.h"
#include "h2h3.h"
#include "msh3.h"
@@ -48,17 +49,28 @@
#define MSH3_REQ_INIT_BUF_LEN 8192
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done);
-static int msh3_getsock(struct Curl_easy *data,
- struct connectdata *conn, curl_socket_t *socks);
-static CURLcode msh3_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool dead_connection);
-static unsigned int msh3_conncheck(struct Curl_easy *data,
- struct connectdata *conn,
- unsigned int checks_to_perform);
-static Curl_recv msh3_stream_recv;
-static Curl_send msh3_stream_send;
+#ifdef _WIN32
+#define msh3_lock CRITICAL_SECTION
+#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
+#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
+#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
+#define msh3_lock_release(lock) LeaveCriticalSection(lock)
+#else /* !_WIN32 */
+#include <pthread.h>
+#define msh3_lock pthread_mutex_t
+#define msh3_lock_initialize(lock) do { \
+ pthread_mutexattr_t attr; \
+ pthread_mutexattr_init(&attr); \
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
+ pthread_mutex_init(lock, &attr); \
+ pthread_mutexattr_destroy(&attr); \
+}while(0)
+#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
+#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
+#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
+#endif /* _WIN32 */
+
+
static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
void *IfContext,
const MSH3_HEADER *Header);
@@ -71,27 +83,17 @@ static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext);
static void MSH3_CALL msh3_send_complete(MSH3_REQUEST *Request,
void *IfContext, void *SendContext);
-static const struct Curl_handler msh3_curl_handler_http3 = {
- "HTTPS", /* scheme */
- ZERO_NULL, /* setup_connection */
- msh3_do_it, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- msh3_getsock, /* proto_getsock */
- msh3_getsock, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- msh3_getsock, /* perform_getsock */
- msh3_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- msh3_conncheck, /* connection_check */
- ZERO_NULL, /* attach connection */
- PORT_HTTP, /* defport */
- CURLPROTO_HTTPS, /* protocol */
- CURLPROTO_HTTP, /* family */
- PROTOPT_SSL | PROTOPT_STREAM /* flags */
+
+void Curl_msh3_ver(char *p, size_t len)
+{
+ uint32_t v[4];
+ MsH3Version(v);
+ (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
+}
+
+struct cf_msh3_ctx {
+ MSH3_API *api;
+ MSH3_CONNECTION *qconn;
};
static const MSH3_REQUEST_IF msh3_request_if = {
@@ -102,107 +104,13 @@ static const MSH3_REQUEST_IF msh3_request_if = {
msh3_send_complete
};
-void Curl_quic_ver(char *p, size_t len)
-{
- uint32_t v[4];
- MsH3Version(v);
- (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
-}
-
-CURLcode Curl_quic_connect(struct Curl_easy *data,
- struct connectdata *conn,
- curl_socket_t sockfd,
- int sockindex,
- const struct sockaddr *addr,
- socklen_t addrlen)
-{
- struct quicsocket *qs = &conn->hequic[sockindex];
- bool insecure = !conn->ssl_config.verifypeer;
- memset(qs, 0, sizeof(*qs));
-
- (void)sockfd;
- (void)addr; /* TODO - Pass address along */
- (void)addrlen;
-
- H3BUGF(infof(data, "creating new api/connection"));
-
- qs->api = MsH3ApiOpen();
- if(!qs->api) {
- failf(data, "can't create msh3 api");
- return CURLE_FAILED_INIT;
- }
-
- qs->conn = MsH3ConnectionOpen(qs->api,
- conn->host.name,
- (uint16_t)conn->remote_port,
- insecure);
- if(!qs->conn) {
- failf(data, "can't create msh3 connection");
- if(qs->api) {
- MsH3ApiClose(qs->api);
- }
- return CURLE_FAILED_INIT;
- }
-
- return CURLE_OK;
-}
-
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- bool *connected)
-{
- struct quicsocket *qs = &conn->hequic[sockindex];
- MSH3_CONNECTION_STATE state;
-
- state = MsH3ConnectionGetState(qs->conn, false);
- if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
- failf(data, "failed to connect, state=%u", (uint32_t)state);
- return CURLE_COULDNT_CONNECT;
- }
-
- if(state == MSH3_CONN_CONNECTED) {
- H3BUGF(infof(data, "connection connected"));
- *connected = true;
- conn->quic = qs;
- conn->recv[sockindex] = msh3_stream_recv;
- conn->send[sockindex] = msh3_stream_send;
- conn->handler = &msh3_curl_handler_http3;
- conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- conn->httpversion = 30;
- conn->bundle->multiuse = BUNDLE_MULTIPLEX;
- /* TODO - Clean up other happy-eyeballs connection(s)? */
- }
-
- return CURLE_OK;
-}
-
-static int msh3_getsock(struct Curl_easy *data,
- struct connectdata *conn, curl_socket_t *socks)
+static CURLcode msh3_data_setup(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
struct HTTP *stream = data->req.p.http;
- int bitmap = GETSOCK_BLANK;
-
- socks[0] = conn->sock[FIRSTSOCKET];
-
- if(stream->recv_error) {
- bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
- data->state.drain++;
- }
- else if(stream->recv_header_len || stream->recv_data_len) {
- bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
- data->state.drain++;
- }
-
- H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
-
- return bitmap;
-}
+ (void)cf;
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
-{
- struct HTTP *stream = data->req.p.http;
- H3BUGF(infof(data, "msh3_do_it"));
+ H3BUGF(infof(data, "msh3_data_setup"));
stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
if(!stream->recv_buf) {
return CURLE_OUT_OF_MEMORY;
@@ -215,52 +123,9 @@ static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
stream->recv_data_len = 0;
stream->recv_data_complete = false;
stream->recv_error = CURLE_OK;
- return Curl_http(data, done);
-}
-
-static unsigned int msh3_conncheck(struct Curl_easy *data,
- struct connectdata *conn,
- unsigned int checks_to_perform)
-{
- (void)data;
- (void)conn;
- (void)checks_to_perform;
- H3BUGF(infof(data, "msh3_conncheck"));
- return CONNRESULT_NONE;
-}
-
-static void disconnect(struct quicsocket *qs)
-{
- if(qs->conn) {
- MsH3ConnectionClose(qs->conn);
- qs->conn = ZERO_NULL;
- }
- if(qs->api) {
- MsH3ApiClose(qs->api);
- qs->api = ZERO_NULL;
- }
-}
-
-static CURLcode msh3_disconnect(struct Curl_easy *data,
- struct connectdata *conn, bool dead_connection)
-{
- (void)data;
- (void)dead_connection;
- H3BUGF(infof(data, "disconnecting (msh3)"));
- disconnect(conn->quic);
return CURLE_OK;
}
-void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn,
- int tempindex)
-{
- (void)data;
- if(conn->transport == TRNSPRT_QUIC) {
- H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex));
- disconnect(&conn->hequic[tempindex]);
- }
-}
-
/* Requires stream->recv_lock to be held */
static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
{
@@ -309,7 +174,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
}
msnprintf((char *)stream->recv_buf + stream->recv_header_len,
stream->recv_buf_alloc - stream->recv_header_len,
- "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value);
+ "HTTP/3 %.*s \r\n", (int)Header->ValueLength, Header->Value);
}
else {
total_len = Header->NameLength + 4 + Header->ValueLength;
@@ -319,7 +184,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
}
msnprintf((char *)stream->recv_buf + stream->recv_header_len,
stream->recv_buf_alloc - stream->recv_header_len,
- "%.*s: %.*s\n",
+ "%.*s: %.*s\r\n",
(int)Header->NameLength, Header->Name,
(int)Header->ValueLength, Header->Value);
}
@@ -393,20 +258,67 @@ static void MSH3_CALL msh3_send_complete(MSH3_REQUEST *Request,
(void)SendContext;
}
-static ssize_t msh3_stream_send(struct Curl_easy *data,
- int sockindex,
- const void *mem,
- size_t len,
- CURLcode *curlcode)
+static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
+{
+ struct HTTP *stream = data->req.p.http;
+ size_t outsize = 0;
+
+ (void)cf;
+ H3BUGF(infof(data, "msh3_stream_recv %zu", len));
+
+ if(stream->recv_error) {
+ failf(data, "request aborted");
+ *err = stream->recv_error;
+ return -1;
+ }
+
+ msh3_lock_acquire(&stream->recv_lock);
+
+ if(stream->recv_header_len) {
+ outsize = len;
+ if(stream->recv_header_len < outsize) {
+ outsize = stream->recv_header_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_header_len + stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_header_len + stream->recv_data_len - outsize);
+ }
+ stream->recv_header_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
+ }
+ else if(stream->recv_data_len) {
+ outsize = len;
+ if(stream->recv_data_len < outsize) {
+ outsize = stream->recv_data_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_data_len - outsize);
+ }
+ stream->recv_data_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of data", outsize));
+ }
+ else if(stream->recv_data_complete) {
+ H3BUGF(infof(data, "receive complete"));
+ }
+
+ msh3_lock_release(&stream->recv_lock);
+
+ return (ssize_t)outsize;
+}
+
+static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
{
- struct connectdata *conn = data->conn;
+ struct cf_msh3_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
- struct quicsocket *qs = conn->quic;
struct h2h3req *hreq;
size_t hdrlen = 0;
size_t sentlen = 0;
- (void)sockindex;
/* Sizes must match for cast below to work" */
DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
@@ -416,149 +328,282 @@ static ssize_t msh3_stream_send(struct Curl_easy *data,
/* The first send on the request contains the headers and possibly some
data. Parse out the headers and create the request, then if there is
any data left over go ahead and send it too. */
- *curlcode = Curl_pseudo_headers(data, mem, len, &hdrlen, &hreq);
- if(*curlcode) {
+ *err = Curl_pseudo_headers(data, buf, len, &hdrlen, &hreq);
+ if(*err) {
failf(data, "Curl_pseudo_headers failed");
return -1;
}
H3BUGF(infof(data, "starting request with %zu headers", hreq->entries));
- stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream,
+ stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, stream,
(MSH3_HEADER*)hreq->header, hreq->entries,
hdrlen == len ? MSH3_REQUEST_FLAG_FIN :
- MSH3_REQUEST_FLAG_NONE);
-
+ MSH3_REQUEST_FLAG_NONE);
Curl_pseudo_free(hreq);
if(!stream->req) {
failf(data, "request open failed");
- *curlcode = CURLE_SEND_ERROR;
+ *err = CURLE_SEND_ERROR;
return -1;
}
- *curlcode = CURLE_OK;
+ *err = CURLE_OK;
return len;
}
H3BUGF(infof(data, "send %zd body bytes on request %p", len,
(void *)stream->req));
if(len > 0xFFFFFFFF) {
/* msh3 doesn't support size_t sends currently. */
- *curlcode = CURLE_SEND_ERROR;
+ *err = CURLE_SEND_ERROR;
return -1;
}
/* TODO - Need an explicit signal to know when to FIN. */
- if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, mem, (uint32_t)len,
+ if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, buf, (uint32_t)len,
stream)) {
- *curlcode = CURLE_SEND_ERROR;
+ *err = CURLE_SEND_ERROR;
return -1;
}
/* TODO - msh3/msquic will hold onto this memory until the send complete
event. How do we make sure curl doesn't free it until then? */
sentlen += len;
- *curlcode = CURLE_OK;
+ *err = CURLE_OK;
return sentlen;
}
-static ssize_t msh3_stream_recv(struct Curl_easy *data,
- int sockindex,
- char *buf,
- size_t buffersize,
- CURLcode *curlcode)
+static int cf_msh3_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
{
struct HTTP *stream = data->req.p.http;
- size_t outsize = 0;
- (void)sockindex;
- H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize));
+ int bitmap = GETSOCK_BLANK;
+
+ socks[0] = cf->conn->sock[FIRSTSOCKET];
if(stream->recv_error) {
- failf(data, "request aborted");
- *curlcode = stream->recv_error;
- return -1;
+ bitmap |= GETSOCK_READSOCK(0);
+ data->state.drain++;
+ }
+ else if(stream->recv_header_len || stream->recv_data_len) {
+ bitmap |= GETSOCK_READSOCK(0);
+ data->state.drain++;
}
+ H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
- msh3_lock_acquire(&stream->recv_lock);
+ return bitmap;
+}
- if(stream->recv_header_len) {
- outsize = buffersize;
- if(stream->recv_header_len < outsize) {
- outsize = stream->recv_header_len;
- }
- memcpy(buf, stream->recv_buf, outsize);
- if(outsize < stream->recv_header_len + stream->recv_data_len) {
- memmove(stream->recv_buf, stream->recv_buf + outsize,
- stream->recv_header_len + stream->recv_data_len - outsize);
+static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct HTTP *stream = data->req.p.http;
+
+ (void)data;
+ (void)cf;
+ H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
+ return stream->recv_header_len || stream->recv_data_len;
+}
+
+static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
+{
+ struct HTTP *stream = data->req.p.http;
+ CURLcode result = CURLE_OK;
+
+ (void)arg1;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_DATA_SETUP:
+ result = msh3_data_setup(cf, data);
+ break;
+
+ case CF_CTRL_DATA_DONE:
+ H3BUGF(infof(data, "Curl_quic_done"));
+ if(stream) {
+ if(stream->recv_buf) {
+ Curl_safefree(stream->recv_buf);
+ msh3_lock_uninitialize(&stream->recv_lock);
+ }
+ if(stream->req) {
+ MsH3RequestClose(stream->req);
+ stream->req = ZERO_NULL;
+ }
}
- stream->recv_header_len -= outsize;
- H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
+ break;
+
+ case CF_CTRL_DATA_DONE_SEND:
+ H3BUGF(infof(data, "Curl_quic_done_sending"));
+ stream->upload_done = TRUE;
+ break;
+
+ default:
+ break;
}
- else if(stream->recv_data_len) {
- outsize = buffersize;
- if(stream->recv_data_len < outsize) {
- outsize = stream->recv_data_len;
- }
- memcpy(buf, stream->recv_buf, outsize);
- if(outsize < stream->recv_data_len) {
- memmove(stream->recv_buf, stream->recv_buf + outsize,
- stream->recv_data_len - outsize);
+ return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ bool insecure = !cf->conn->ssl_config.verifypeer;
+
+ H3BUGF(infof(data, "creating new api/connection"));
+
+ ctx->api = MsH3ApiOpen();
+ if(!ctx->api) {
+ failf(data, "can't create msh3 api");
+ return CURLE_FAILED_INIT;
+ }
+
+ ctx->qconn = MsH3ConnectionOpen(ctx->api,
+ cf->conn->host.name,
+ (uint16_t)cf->conn->remote_port,
+ insecure);
+ if(!ctx->qconn) {
+ failf(data, "can't create msh3 connection");
+ if(ctx->api) {
+ MsH3ApiClose(ctx->api);
+ ctx->api = NULL;
}
- stream->recv_data_len -= outsize;
- H3BUGF(infof(data, "returned %zu bytes of data", outsize));
+ return CURLE_FAILED_INIT;
}
- else if(stream->recv_data_complete) {
- H3BUGF(infof(data, "receive complete"));
+
+ return CURLE_OK;
+}
+
+static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct cf_msh3_ctx *ctx = cf->ctx;
+ MSH3_CONNECTION_STATE state;
+ CURLcode result = CURLE_OK;
+
+ (void)blocking;
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
}
- msh3_lock_release(&stream->recv_lock);
+ *done = FALSE;
+ if(!ctx->qconn) {
+ result = cf_connect_start(cf, data);
+ if(result)
+ goto out;
+ }
- return (ssize_t)outsize;
+ state = MsH3ConnectionGetState(ctx->qconn, FALSE);
+ if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
+ failf(data, "failed to connect, state=%u", (uint32_t)state);
+ result = CURLE_COULDNT_CONNECT;
+ goto out;
+ }
+
+ if(state == MSH3_CONN_CONNECTED) {
+ DEBUGF(infof(data, "msh3 established connection"));
+ cf->connected = TRUE;
+ cf->conn->alpn = CURL_HTTP_VERSION_3;
+ *done = TRUE;
+ connkeep(cf->conn, "HTTP/3 default");
+ }
+
+out:
+ return result;
}
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+static void cf_msh3_ctx_clear(struct cf_msh3_ctx *ctx)
{
- struct connectdata *conn = data->conn;
- H3BUGF(infof(data, "Curl_quic_done_sending"));
- if(conn->handler == &msh3_curl_handler_http3) {
- struct HTTP *stream = data->req.p.http;
- stream->upload_done = TRUE;
+ if(ctx) {
+ if(ctx->qconn)
+ MsH3ConnectionClose(ctx->qconn);
+ if(ctx->api)
+ MsH3ApiClose(ctx->api);
+ memset(ctx, 0, sizeof(*ctx));
}
-
- return CURLE_OK;
}
-void Curl_quic_done(struct Curl_easy *data, bool premature)
+static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct HTTP *stream = data->req.p.http;
- (void)premature;
- H3BUGF(infof(data, "Curl_quic_done"));
- if(stream) {
- if(stream->recv_buf) {
- Curl_safefree(stream->recv_buf);
- msh3_lock_uninitialize(&stream->recv_lock);
- }
- if(stream->req) {
- MsH3RequestClose(stream->req);
- stream->req = ZERO_NULL;
- }
+ struct cf_msh3_ctx *ctx = cf->ctx;
+
+ (void)data;
+ if(ctx) {
+ cf_msh3_ctx_clear(ctx);
}
}
-bool Curl_quic_data_pending(const struct Curl_easy *data)
+static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct HTTP *stream = data->req.p.http;
- H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
- return stream->recv_header_len || stream->recv_data_len;
+ struct cf_msh3_ctx *ctx = cf->ctx;
+
+ (void)data;
+ cf_msh3_ctx_clear(ctx);
+ free(ctx);
+ cf->ctx = NULL;
}
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
+static const struct Curl_cftype cft_msh3 = {
+ "HTTP/3-MSH3",
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ cf_msh3_destroy,
+ cf_msh3_connect,
+ cf_msh3_close,
+ Curl_cf_def_get_host,
+ cf_msh3_get_select_socks,
+ cf_msh3_data_pending,
+ cf_msh3_send,
+ cf_msh3_recv,
+ cf_msh3_data_event,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
{
+ struct cf_msh3_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL;
+ CURLcode result;
+
(void)data;
- H3BUGF(infof(data, "Curl_quic_idle"));
- return CURLE_OK;
+ (void)conn;
+ (void)ai; /* TODO: msh3 resolves itself? */
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ result = Curl_cf_create(&cf, &cft_msh3, ctx);
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
+}
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+ (void)data;
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_msh3)
+ return TRUE;
+ if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+ return FALSE;
+ }
+ return FALSE;
}
#endif /* USE_MSH3 */
diff --git a/lib/vquic/msh3.h b/lib/vquic/msh3.h
index ce884d92d..2bad58839 100644
--- a/lib/vquic/msh3.h
+++ b/lib/vquic/msh3.h
@@ -30,10 +30,16 @@
#include <msh3.h>
-struct quicsocket {
- MSH3_API* api;
- MSH3_CONNECTION* conn;
-};
+void Curl_msh3_ver(char *p, size_t len);
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex);
#endif /* USE_MSQUIC */
diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c
index 535a36898..bc5fbd52e 100644
--- a/lib/vquic/ngtcp2.c
+++ b/lib/vquic/ngtcp2.c
@@ -52,6 +52,7 @@
#include "multiif.h"
#include "strcase.h"
#include "cfilters.h"
+#include "cf-socket.h"
#include "connect.h"
#include "strerror.h"
#include "dynbuf.h"
@@ -114,23 +115,78 @@ struct h3out {
#define QUIC_GROUPS "P-256:P-384:P-521"
#endif
+
+/*
+ * Store ngtcp2 version info in this buffer.
+ */
+void Curl_ngtcp2_ver(char *p, size_t len)
+{
+ const ngtcp2_info *ng2 = ngtcp2_version(0);
+ const nghttp3_info *ht3 = nghttp3_version(0);
+ (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
+ ng2->version_str, ht3->version_str);
+}
+
+struct blocked_pkt {
+ const uint8_t *pkt;
+ size_t pktlen;
+ size_t gsolen;
+};
+
+struct cf_ngtcp2_ctx {
+ curl_socket_t sockfd;
+ struct sockaddr_storage local_addr;
+ socklen_t local_addrlen;
+ ngtcp2_conn *qconn;
+ ngtcp2_cid dcid;
+ ngtcp2_cid scid;
+ uint32_t version;
+ ngtcp2_settings settings;
+ ngtcp2_transport_params transport_params;
+ ngtcp2_connection_close_error last_error;
+ ngtcp2_crypto_conn_ref conn_ref;
+#ifdef USE_OPENSSL
+ SSL_CTX *sslctx;
+ SSL *ssl;
+#elif defined(USE_GNUTLS)
+ struct gtls_instance *gtls;
+#elif defined(USE_WOLFSSL)
+ WOLFSSL_CTX *sslctx;
+ WOLFSSL *ssl;
+#endif
+ bool no_gso;
+ uint8_t *pktbuf;
+ size_t pktbuflen;
+ /* the number of entries in blocked_pkt */
+ size_t num_blocked_pkt;
+ /* the number of processed entries in blocked_pkt */
+ size_t num_blocked_pkt_sent;
+ /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
+ struct blocked_pkt blocked_pkt[2];
+
+ nghttp3_conn *h3conn;
+ nghttp3_settings h3settings;
+ int qlogfd;
+};
+
+
/* ngtcp2 default congestion controller does not perform pacing. Limit
the maximum packet burst to MAX_PKT_BURST packets. */
#define MAX_PKT_BURST 10
-static CURLcode ng_process_ingress(struct Curl_easy *data,
- curl_socket_t sockfd,
- struct quicsocket *qs);
-static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd,
- struct quicsocket *qs);
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
uint64_t datalen, void *user_data,
void *stream_user_data);
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
{
- struct quicsocket *qs = conn_ref->user_data;
- return qs->qconn;
+ struct Curl_cfilter *cf = conn_ref->user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ return ctx->qconn;
}
static ngtcp2_tstamp timestamp(void)
@@ -154,24 +210,25 @@ static void quic_printf(void *user_data, const char *fmt, ...)
static void qlog_callback(void *user_data, uint32_t flags,
const void *data, size_t datalen)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
(void)flags;
- if(qs->qlogfd != -1) {
- ssize_t rc = write(qs->qlogfd, data, datalen);
+ if(ctx->qlogfd != -1) {
+ ssize_t rc = write(ctx->qlogfd, data, datalen);
if(rc == -1) {
/* on write error, stop further write attempts */
- close(qs->qlogfd);
- qs->qlogfd = -1;
+ close(ctx->qlogfd);
+ ctx->qlogfd = -1;
}
}
}
-static void quic_settings(struct quicsocket *qs,
- uint64_t stream_buffer_size)
+static void quic_settings(struct cf_ngtcp2_ctx *ctx,
+ struct Curl_easy *data)
{
- ngtcp2_settings *s = &qs->settings;
- ngtcp2_transport_params *t = &qs->transport_params;
+ ngtcp2_settings *s = &ctx->settings;
+ ngtcp2_transport_params *t = &ctx->transport_params;
ngtcp2_settings_default(s);
ngtcp2_transport_params_default(t);
#ifdef DEBUG_NGTCP2
@@ -180,14 +237,14 @@ static void quic_settings(struct quicsocket *qs,
s->log_printf = NULL;
#endif
s->initial_ts = timestamp();
- t->initial_max_stream_data_bidi_local = stream_buffer_size;
+ t->initial_max_stream_data_bidi_local = data->set.buffer_size;
t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS;
t->initial_max_stream_data_uni = QUIC_MAX_STREAMS;
t->initial_max_data = QUIC_MAX_DATA;
t->initial_max_streams_bidi = 1;
t->initial_max_streams_uni = 3;
t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
- if(qs->qlogfd != -1) {
+ if(ctx->qlogfd != -1) {
s->qlog.write = qlog_callback;
}
}
@@ -223,12 +280,12 @@ static void keylog_callback(const WOLFSSL *ssl, const char *line)
#endif
#endif
-static int init_ngh3_conn(struct quicsocket *qs);
+static int init_ngh3_conn(struct Curl_cfilter *cf);
#ifdef USE_OPENSSL
-static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+static SSL_CTX *quic_ssl_ctx(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct connectdata *conn = data->conn;
+ struct connectdata *conn = cf->conn;
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
#ifdef OPENSSL_IS_BORINGSSL
@@ -300,10 +357,11 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
return ssl_ctx;
}
-static CURLcode quic_set_client_cert(struct Curl_easy *data,
- struct quicsocket *qs)
+static CURLcode quic_set_client_cert(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- SSL_CTX *ssl_ctx = qs->sslctx;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ SSL_CTX *ssl_ctx = ctx->sslctx;
const struct ssl_config_data *ssl_config;
ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
@@ -323,64 +381,61 @@ static CURLcode quic_set_client_cert(struct Curl_easy *data,
/** SSL callbacks ***/
-static CURLcode quic_init_ssl(struct quicsocket *qs,
- struct Curl_easy *data,
- struct connectdata *conn)
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
const uint8_t *alpn = NULL;
size_t alpnlen = 0;
- /* this will need some attention when HTTPS proxy over QUIC get fixed */
- const char * const hostname = qs->conn->host.name;
(void)data;
- (void)conn;
- DEBUGASSERT(!qs->ssl);
- qs->ssl = SSL_new(qs->sslctx);
+ DEBUGASSERT(!ctx->ssl);
+ ctx->ssl = SSL_new(ctx->sslctx);
- SSL_set_app_data(qs->ssl, &qs->conn_ref);
- SSL_set_connect_state(qs->ssl);
- SSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
+ SSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+ SSL_set_connect_state(ctx->ssl);
+ SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
if(alpn)
- SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
+ SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
/* set SNI */
- SSL_set_tlsext_host_name(qs->ssl, hostname);
+ SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
return CURLE_OK;
}
#elif defined(USE_GNUTLS)
-static CURLcode quic_init_ssl(struct quicsocket *qs,
- struct Curl_easy *data,
- struct connectdata *conn)
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
CURLcode result;
gnutls_datum_t alpn[2];
/* this will need some attention when HTTPS proxy over QUIC get fixed */
- const char * const hostname = qs->conn->host.name;
+ const char * const hostname = cf->conn->host.name;
long * const pverifyresult = &data->set.ssl.certverifyresult;
int rc;
- DEBUGASSERT(qs->gtls == NULL);
- qs->gtls = calloc(1, sizeof(*(qs->gtls)));
- if(!qs->gtls)
+ DEBUGASSERT(ctx->gtls == NULL);
+ ctx->gtls = calloc(1, sizeof(*(ctx->gtls)));
+ if(!ctx->gtls)
return CURLE_OUT_OF_MEMORY;
- result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl,
- hostname, qs->gtls, pverifyresult);
+ result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl,
+ hostname, ctx->gtls, pverifyresult);
if(result)
return result;
- gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref);
+ gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref);
- if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) {
+ if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) {
H3BUGF(fprintf(stderr,
"ngtcp2_crypto_gnutls_configure_client_session failed\n"));
return CURLE_QUIC_CONNECT_ERROR;
}
- rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL);
+ rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
if(rc < 0) {
H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n",
gnutls_strerror(rc)));
@@ -390,7 +445,7 @@ static CURLcode quic_init_ssl(struct quicsocket *qs,
/* Open the file if a TLS or QUIC backend has not done this before. */
Curl_tls_keylog_open();
if(Curl_tls_keylog_enabled()) {
- gnutls_session_set_keylog_function(qs->gtls->session, keylog_callback);
+ gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback);
}
/* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
@@ -399,15 +454,16 @@ static CURLcode quic_init_ssl(struct quicsocket *qs,
alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1;
alpn[1].size = sizeof(H3_ALPN_H3) - 2;
- gnutls_alpn_set_protocols(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY);
-
+ gnutls_alpn_set_protocols(ctx->gtls->session,
+ alpn, 2, GNUTLS_ALPN_MANDATORY);
return CURLE_OK;
}
#elif defined(USE_WOLFSSL)
-static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- struct connectdata *conn = data->conn;
+ struct connectdata *conn = cf->conn;
WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
@@ -476,31 +532,30 @@ static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
/** SSL callbacks ***/
-static CURLcode quic_init_ssl(struct quicsocket *qs,
- struct Curl_easy *data,
- struct connectdata *conn)
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
const uint8_t *alpn = NULL;
size_t alpnlen = 0;
/* this will need some attention when HTTPS proxy over QUIC get fixed */
- const char * const hostname = qs->conn->host.name;
+ const char * const hostname = cf->conn->host.name;
(void)data;
- (void)conn;
- DEBUGASSERT(!qs->ssl);
- qs->ssl = SSL_new(qs->sslctx);
+ DEBUGASSERT(!ctx->ssl);
+ ctx->ssl = wolfSSL_new(ctx->sslctx);
- wolfSSL_set_app_data(qs->ssl, &qs->conn_ref);
- wolfSSL_set_connect_state(qs->ssl);
- wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
+ wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+ wolfSSL_set_connect_state(ctx->ssl);
+ wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
if(alpn)
- wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
+ wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
/* set SNI */
- wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME,
+ wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
hostname, (unsigned short)strlen(hostname));
return CURLE_OK;
@@ -529,18 +584,19 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags,
const uint8_t *buf, size_t buflen,
void *user_data, void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
nghttp3_ssize nconsumed;
int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
(void)offset;
(void)stream_user_data;
nconsumed =
- nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin);
+ nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin);
if(nconsumed < 0) {
ngtcp2_connection_close_error_set_application_error(
- &qs->last_error, nghttp3_err_infer_quic_app_error_code((int)nconsumed),
- NULL, 0);
+ &ctx->last_error,
+ nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0);
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -558,7 +614,8 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
uint64_t offset, uint64_t datalen, void *user_data,
void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)stream_id;
(void)tconn;
@@ -566,7 +623,7 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
(void)datalen;
(void)stream_user_data;
- rv = nghttp3_conn_add_ack_offset(qs->h3conn, stream_id, datalen);
+ rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen);
if(rv) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -578,7 +635,8 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
int64_t stream_id, uint64_t app_error_code,
void *user_data, void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)tconn;
(void)stream_user_data;
@@ -588,11 +646,11 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
app_error_code = NGHTTP3_H3_NO_ERROR;
}
- rv = nghttp3_conn_close_stream(qs->h3conn, stream_id,
+ rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id,
app_error_code);
if(rv) {
ngtcp2_connection_close_error_set_application_error(
- &qs->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
+ &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -603,14 +661,15 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
uint64_t final_size, uint64_t app_error_code,
void *user_data, void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)tconn;
(void)final_size;
(void)app_error_code;
(void)stream_user_data;
- rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
+ rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
if(rv) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -622,13 +681,14 @@ static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)tconn;
(void)app_error_code;
(void)stream_user_data;
- rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
+ rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
if(rv) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -651,13 +711,14 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
uint64_t max_data, void *user_data,
void *stream_user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)tconn;
(void)max_data;
(void)stream_user_data;
- rv = nghttp3_conn_unblock_stream(qs->h3conn, stream_id);
+ rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id);
if(rv) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -702,14 +763,14 @@ static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level,
void *user_data)
{
- struct quicsocket *qs = (struct quicsocket *)user_data;
+ struct Curl_cfilter *cf = user_data;
(void)tconn;
if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
return 0;
}
- if(init_ngh3_conn(qs) != CURLE_OK) {
+ if(init_ngh3_conn(cf) != CURLE_OK) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -759,257 +820,32 @@ static ngtcp2_callbacks ng_callbacks = {
NULL, /* early_data_rejected */
};
-/*
- * Might be called twice for happy eyeballs.
- */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
- struct connectdata *conn,
- curl_socket_t sockfd,
- int sockindex,
- const struct sockaddr *addr,
- socklen_t addrlen)
-{
- int rc;
- int rv;
- CURLcode result;
- ngtcp2_path path; /* TODO: this must be initialized properly */
- struct quicsocket *qs = &conn->hequic[sockindex];
- char ipbuf[40];
- int port;
- int qfd;
-
- if(qs->conn)
- Curl_quic_disconnect(data, conn, sockindex);
- qs->conn = conn;
-
- /* extract the used address as a string */
- if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
- char buffer[STRERROR_LEN];
- failf(data, "ssrem inet_ntop() failed with errno %d: %s",
- SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- return CURLE_BAD_FUNCTION_ARGUMENT;
- }
-
- infof(data, "Connect socket %d over QUIC to %s:%d",
- sockfd, ipbuf, port);
-
- qs->version = NGTCP2_PROTO_VER_MAX;
-#ifdef USE_OPENSSL
- qs->sslctx = quic_ssl_ctx(data);
- if(!qs->sslctx)
- return CURLE_QUIC_CONNECT_ERROR;
-
- result = quic_set_client_cert(data, qs);
- if(result)
- return result;
-#elif defined(USE_WOLFSSL)
- qs->sslctx = quic_ssl_ctx(data);
- if(!qs->sslctx)
- return CURLE_QUIC_CONNECT_ERROR;
-#endif
-
- result = quic_init_ssl(qs, data, conn);
- if(result)
- return result;
-
- qs->dcid.datalen = NGTCP2_MAX_CIDLEN;
- result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN);
- if(result)
- return result;
-
- qs->scid.datalen = NGTCP2_MAX_CIDLEN;
- result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN);
- if(result)
- return result;
-
- (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
- qs->qlogfd = qfd; /* -1 if failure above */
- quic_settings(qs, data->set.buffer_size);
-
- qs->local_addrlen = sizeof(qs->local_addr);
- rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
- &qs->local_addrlen);
- if(rv == -1)
- return CURLE_QUIC_CONNECT_ERROR;
-
- ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
- qs->local_addrlen);
- ngtcp2_addr_init(&path.remote, addr, addrlen);
-
- rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path,
- NGTCP2_PROTO_VER_V1, &ng_callbacks,
- &qs->settings, &qs->transport_params, NULL, qs);
- if(rc)
- return CURLE_QUIC_CONNECT_ERROR;
-
-#ifdef USE_GNUTLS
- ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session);
-#else
- ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl);
-#endif
-
- ngtcp2_connection_close_error_default(&qs->last_error);
-
-#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
- qs->no_gso = FALSE;
-#else
- qs->no_gso = TRUE;
-#endif
-
- qs->num_blocked_pkt = 0;
- qs->num_blocked_pkt_sent = 0;
- memset(&qs->blocked_pkt, 0, sizeof(qs->blocked_pkt));
-
- qs->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST;
- qs->pktbuf = malloc(qs->pktbuflen);
- if(!qs->pktbuf) {
- ngtcp2_conn_del(qs->qconn);
- qs->qconn = NULL;
- return CURLE_OUT_OF_MEMORY;
- }
-
- qs->conn_ref.get_conn = get_conn;
- qs->conn_ref.user_data = qs;
-
- return CURLE_OK;
-}
-
-/*
- * Store ngtcp2 version info in this buffer.
- */
-void Curl_quic_ver(char *p, size_t len)
-{
- const ngtcp2_info *ng2 = ngtcp2_version(0);
- const nghttp3_info *ht3 = nghttp3_version(0);
- (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
- ng2->version_str, ht3->version_str);
-}
-
-static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
- curl_socket_t *socks)
+static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct SingleRequest *k = &data->req;
- int bitmap = GETSOCK_BLANK;
+ int rv = GETSOCK_BLANK;
struct HTTP *stream = data->req.p.http;
- struct quicsocket *qs = conn->quic;
- socks[0] = conn->sock[FIRSTSOCKET];
+ socks[0] = ctx->sockfd;
- /* in an HTTP/2 connection we can basically always get a frame so we should
+ /* in an HTTP/3 connection we can basically always get a frame so we should
always be ready for one */
- bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ rv |= GETSOCK_READSOCK(0);
/* we're still uploading or the HTTP/2 layer wants to send data */
if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND &&
(!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
- ngtcp2_conn_get_cwnd_left(qs->qconn) &&
- ngtcp2_conn_get_max_data_left(qs->qconn) &&
- nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id))
- bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+ ngtcp2_conn_get_cwnd_left(ctx->qconn) &&
+ ngtcp2_conn_get_max_data_left(ctx->qconn) &&
+ nghttp3_conn_is_stream_writable(ctx->h3conn, stream->stream3_id))
+ rv |= GETSOCK_WRITESOCK(0);
- return bitmap;
+ return rv;
}
-static void qs_disconnect(struct quicsocket *qs)
-{
- char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
- ngtcp2_tstamp ts;
- ngtcp2_ssize rc;
-
- if(!qs->conn) /* already closed */
- return;
- ts = timestamp();
- rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */
- NULL, /* pkt_info */
- (uint8_t *)buffer, sizeof(buffer),
- &qs->last_error, ts);
- if(rc > 0) {
- while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) &&
- SOCKERRNO == EINTR);
- }
-
- qs->conn = NULL;
- if(qs->qlogfd != -1) {
- close(qs->qlogfd);
- qs->qlogfd = -1;
- }
-#ifdef USE_OPENSSL
- if(qs->ssl)
- SSL_free(qs->ssl);
- qs->ssl = NULL;
- SSL_CTX_free(qs->sslctx);
-#elif defined(USE_GNUTLS)
- if(qs->gtls) {
- if(qs->gtls->cred)
- gnutls_certificate_free_credentials(qs->gtls->cred);
- if(qs->gtls->session)
- gnutls_deinit(qs->gtls->session);
- free(qs->gtls);
- qs->gtls = NULL;
- }
-#elif defined(USE_WOLFSSL)
- if(qs->ssl)
- wolfSSL_free(qs->ssl);
- qs->ssl = NULL;
- wolfSSL_CTX_free(qs->sslctx);
-#endif
- free(qs->pktbuf);
- nghttp3_conn_del(qs->h3conn);
- ngtcp2_conn_del(qs->qconn);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- int tempindex)
-{
- (void)data;
- if(conn->transport == TRNSPRT_QUIC)
- qs_disconnect(&conn->hequic[tempindex]);
-}
-
-static CURLcode ng_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool dead_connection)
-{
- (void)dead_connection;
- Curl_quic_disconnect(data, conn, 0);
- Curl_quic_disconnect(data, conn, 1);
- return CURLE_OK;
-}
-
-static unsigned int ng_conncheck(struct Curl_easy *data,
- struct connectdata *conn,
- unsigned int checks_to_perform)
-{
- (void)data;
- (void)conn;
- (void)checks_to_perform;
- return CONNRESULT_NONE;
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
- "HTTPS", /* scheme */
- ZERO_NULL, /* setup_connection */
- Curl_http, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- ng_getsock, /* proto_getsock */
- ng_getsock, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- ng_getsock, /* perform_getsock */
- ng_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- ng_conncheck, /* connection_check */
- ZERO_NULL, /* attach connection */
- PORT_HTTP, /* defport */
- CURLPROTO_HTTPS, /* protocol */
- CURLPROTO_HTTP, /* family */
- PROTOPT_SSL | PROTOPT_STREAM /* flags */
-};
-
static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data)
@@ -1080,13 +916,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
size_t consumed, void *user_data,
void *stream_user_data)
{
- struct quicsocket *qs = user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
(void)conn;
(void)stream_user_data;
(void)stream_id;
- ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, consumed);
- ngtcp2_conn_extend_max_offset(qs->qconn, consumed);
+ ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream_id, consumed);
+ ngtcp2_conn_extend_max_offset(ctx->qconn, consumed);
return 0;
}
@@ -1198,12 +1035,13 @@ static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data)
{
- struct quicsocket *qs = user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)conn;
(void)stream_user_data;
- rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code);
+ rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, stream_id, app_error_code);
if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -1214,12 +1052,14 @@ static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data,
void *stream_user_data) {
- struct quicsocket *qs = user_data;
+ struct Curl_cfilter *cf = user_data;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
(void)conn;
(void)stream_user_data;
- rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code);
+ rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id,
+ app_error_code);
if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
@@ -1244,53 +1084,54 @@ static nghttp3_callbacks ngh3_callbacks = {
NULL /* shutdown */
};
-static int init_ngh3_conn(struct quicsocket *qs)
+static int init_ngh3_conn(struct Curl_cfilter *cf)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
CURLcode result;
int rc;
int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
- if(ngtcp2_conn_get_max_local_streams_uni(qs->qconn) < 3) {
+ if(ngtcp2_conn_get_max_local_streams_uni(ctx->qconn) < 3) {
return CURLE_QUIC_CONNECT_ERROR;
}
- nghttp3_settings_default(&qs->h3settings);
+ nghttp3_settings_default(&ctx->h3settings);
- rc = nghttp3_conn_client_new(&qs->h3conn,
+ rc = nghttp3_conn_client_new(&ctx->h3conn,
&ngh3_callbacks,
- &qs->h3settings,
+ &ctx->h3settings,
nghttp3_mem_default(),
- qs);
+ cf);
if(rc) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
- rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL);
+ rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL);
if(rc) {
result = CURLE_QUIC_CONNECT_ERROR;
goto fail;
}
- rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id);
+ rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id);
if(rc) {
result = CURLE_QUIC_CONNECT_ERROR;
goto fail;
}
- rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL);
+ rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL);
if(rc) {
result = CURLE_QUIC_CONNECT_ERROR;
goto fail;
}
- rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL);
+ rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL);
if(rc) {
result = CURLE_QUIC_CONNECT_ERROR;
goto fail;
}
- rc = nghttp3_conn_bind_qpack_streams(qs->h3conn, qpack_enc_stream_id,
+ rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id,
qpack_dec_stream_id);
if(rc) {
result = CURLE_QUIC_CONNECT_ERROR;
@@ -1303,9 +1144,6 @@ static int init_ngh3_conn(struct quicsocket *qs)
return result;
}
-static Curl_recv ngh3_stream_recv;
-static Curl_send ngh3_stream_send;
-
static size_t drain_overflow_buffer(struct HTTP *stream)
{
size_t overlen = Curl_dyn_len(&stream->overflow);
@@ -1325,22 +1163,19 @@ static size_t drain_overflow_buffer(struct HTTP *stream)
}
/* incoming data frames on the h3 stream */
-static ssize_t ngh3_stream_recv(struct Curl_easy *data,
- int sockindex,
- char *buf,
- size_t buffersize,
- CURLcode *curlcode)
+static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
{
- struct connectdata *conn = data->conn;
- curl_socket_t sockfd = conn->sock[sockindex];
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
- struct quicsocket *qs = conn->quic;
+
+ *err = CURLE_OK;
if(!stream->memlen) {
/* remember where to store incoming data for this stream and how big the
buffer is */
stream->mem = buf;
- stream->len = buffersize;
+ stream->len = len;
}
/* else, there's data in the buffer already */
@@ -1348,28 +1183,27 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data,
as possible to the receive buffer before receiving more */
drain_overflow_buffer(stream);
- if(ng_process_ingress(data, sockfd, qs)) {
- *curlcode = CURLE_RECV_ERROR;
+ if(cf_process_ingress(cf, data)) {
+ *err = CURLE_RECV_ERROR;
return -1;
}
- if(ng_flush_egress(data, sockfd, qs)) {
- *curlcode = CURLE_SEND_ERROR;
+ if(cf_flush_egress(cf, data)) {
+ *err = CURLE_SEND_ERROR;
return -1;
}
if(stream->memlen) {
ssize_t memlen = stream->memlen;
/* data arrived */
- *curlcode = CURLE_OK;
/* reset to allow more data to come */
stream->memlen = 0;
stream->mem = buf;
- stream->len = buffersize;
+ stream->len = len;
/* extend the stream window with the data we're consuming and send out
any additional packets to tell the server that we can receive more */
- extend_stream_window(qs->qconn, stream);
- if(ng_flush_egress(data, sockfd, qs)) {
- *curlcode = CURLE_SEND_ERROR;
+ extend_stream_window(ctx->qconn, stream);
+ if(cf_flush_egress(cf, data)) {
+ *err = CURLE_SEND_ERROR;
return -1;
}
return memlen;
@@ -1381,7 +1215,7 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data,
"HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
")",
stream->stream3_id, stream->error3);
- *curlcode = CURLE_HTTP3;
+ *err = CURLE_HTTP3;
return -1;
}
@@ -1390,16 +1224,15 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data,
"HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
" all response header fields, treated as error",
stream->stream3_id);
- *curlcode = CURLE_HTTP3;
+ *err = CURLE_HTTP3;
return -1;
}
- *curlcode = CURLE_OK;
return 0;
}
infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN");
- *curlcode = CURLE_AGAIN;
+ *err = CURLE_AGAIN;
return -1;
}
@@ -1501,13 +1334,14 @@ static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
field list. */
#define AUTHORITY_DST_IDX 3
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
- size_t len)
+static CURLcode h3_stream_open(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len)
{
- struct connectdata *conn = data->conn;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
size_t nheader;
- struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
nghttp3_nv *nva = NULL;
int64_t stream3_id;
@@ -1515,7 +1349,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
struct h3out *h3out = NULL;
struct h2h3req *hreq = NULL;
- rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL);
+ rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream3_id, NULL);
if(rc) {
failf(data, "can get bidi streams");
result = CURLE_SEND_ERROR;
@@ -1523,7 +1357,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
}
stream->stream3_id = stream3_id;
- stream->h3req = TRUE; /* senf off! */
+ stream->h3req = TRUE;
Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
@@ -1568,7 +1402,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
}
stream->h3out = h3out;
- rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
+ rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
nva, nheader, &data_reader, data);
if(rc) {
result = CURLE_SEND_ERROR;
@@ -1578,7 +1412,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
}
default:
stream->upload_left = 0; /* nothing left to send */
- rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
+ rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
nva, nheader, NULL, data);
if(rc) {
result = CURLE_SEND_ERROR;
@@ -1600,27 +1434,25 @@ fail:
Curl_pseudo_free(hreq);
return result;
}
-static ssize_t ngh3_stream_send(struct Curl_easy *data,
- int sockindex,
- const void *mem,
- size_t len,
- CURLcode *curlcode)
+
+static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
ssize_t sent = 0;
- struct connectdata *conn = data->conn;
- struct quicsocket *qs = conn->quic;
- curl_socket_t sockfd = conn->sock[sockindex];
struct HTTP *stream = data->req.p.http;
+ *err = CURLE_OK;
+
if(stream->closed) {
- *curlcode = CURLE_HTTP3;
+ *err = CURLE_HTTP3;
return -1;
}
if(!stream->h3req) {
- CURLcode result = http_request(data, mem, len);
+ CURLcode result = h3_stream_open(cf, data, buf, len);
if(result) {
- *curlcode = CURLE_SEND_ERROR;
+ *err = CURLE_SEND_ERROR;
return -1;
}
/* Assume that mem of length len only includes HTTP/1.1 style
@@ -1632,18 +1464,18 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
len));
if(!stream->upload_len) {
- stream->upload_mem = mem;
+ stream->upload_mem = buf;
stream->upload_len = len;
- (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
+ (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
}
else {
- *curlcode = CURLE_AGAIN;
+ *err = CURLE_AGAIN;
return -1;
}
}
- if(ng_flush_egress(data, sockfd, qs)) {
- *curlcode = CURLE_SEND_ERROR;
+ if(cf_flush_egress(cf, data)) {
+ *err = CURLE_SEND_ERROR;
return -1;
}
@@ -1660,56 +1492,52 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
stream->upload_len = 0;
if(sent == 0) {
- *curlcode = CURLE_AGAIN;
+ *err = CURLE_AGAIN;
return -1;
}
}
- *curlcode = CURLE_OK;
return sent;
}
-static CURLcode ng_has_connected(struct Curl_easy *data,
- struct connectdata *conn, int tempindex)
+static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
const char *hostname, *disp_hostname;
int port;
char *snihost;
- Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port);
+ Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port);
snihost = Curl_ssl_snihost(data, hostname, NULL);
if(!snihost)
return CURLE_PEER_FAILED_VERIFICATION;
- conn->recv[FIRSTSOCKET] = ngh3_stream_recv;
- conn->send[FIRSTSOCKET] = ngh3_stream_send;
- conn->handler = &Curl_handler_http3;
- conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- conn->httpversion = 30;
- conn->bundle->multiuse = BUNDLE_MULTIPLEX;
- conn->quic = &conn->hequic[tempindex];
+ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ cf->conn->httpversion = 30;
+ cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
- if(conn->ssl_config.verifyhost) {
+ if(cf->conn->ssl_config.verifyhost) {
#ifdef USE_OPENSSL
X509 *server_cert;
- server_cert = SSL_get_peer_certificate(conn->quic->ssl);
+ server_cert = SSL_get_peer_certificate(ctx->ssl);
if(!server_cert) {
return CURLE_PEER_FAILED_VERIFICATION;
}
- result = Curl_ossl_verifyhost(data, conn, server_cert);
+ result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
X509_free(server_cert);
if(result)
return result;
#elif defined(USE_GNUTLS)
- result = Curl_gtls_verifyserver(data, conn->quic->gtls->session,
- &conn->ssl_config, &data->set.ssl,
+ result = Curl_gtls_verifyserver(data, ctx->gtls->session,
+ &cf->conn->ssl_config, &data->set.ssl,
hostname, disp_hostname,
data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
if(result)
return result;
#elif defined(USE_WOLFSSL)
- if(wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)
+ if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE)
return CURLE_PEER_FAILED_VERIFICATION;
#endif
infof(data, "Verified certificate just fine");
@@ -1719,48 +1547,15 @@ static CURLcode ng_has_connected(struct Curl_easy *data,
#ifdef USE_OPENSSL
if(data->set.ssl.certinfo)
/* asked to gather certificate info */
- (void)Curl_ossl_certchain(data, conn->quic->ssl);
+ (void)Curl_ossl_certchain(data, ctx->ssl);
#endif
return result;
}
-/*
- * There can be multiple connection attempts going on in parallel.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- bool *done)
-{
- CURLcode result;
- struct quicsocket *qs = &conn->hequic[sockindex];
- curl_socket_t sockfd = conn->tempsock[sockindex];
-
- result = ng_process_ingress(data, sockfd, qs);
- if(result)
- goto error;
-
- result = ng_flush_egress(data, sockfd, qs);
- if(result)
- goto error;
-
- if(ngtcp2_conn_get_handshake_completed(qs->qconn)) {
- result = ng_has_connected(data, conn, sockindex);
- if(!result)
- *done = TRUE;
- }
-
- return result;
- error:
- (void)qs_disconnect(qs);
- return result;
-
-}
-
-static CURLcode ng_process_ingress(struct Curl_easy *data,
- curl_socket_t sockfd,
- struct quicsocket *qs)
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
ssize_t recvd;
int rv;
uint8_t buf[65536];
@@ -1773,7 +1568,7 @@ static CURLcode ng_process_ingress(struct Curl_easy *data,
for(;;) {
remote_addrlen = sizeof(remote_addr);
- while((recvd = recvfrom(sockfd, (char *)buf, bufsize, 0,
+ while((recvd = recvfrom(ctx->sockfd, (char *)buf, bufsize, 0,
(struct sockaddr *)&remote_addr,
&remote_addrlen)) == -1 &&
SOCKERRNO == EINTR)
@@ -1786,21 +1581,22 @@ static CURLcode ng_process_ingress(struct Curl_easy *data,
return CURLE_RECV_ERROR;
}
- ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
- qs->local_addrlen);
+ ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->local_addr,
+ ctx->local_addrlen);
ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
remote_addrlen);
- rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts);
+ rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, buf, recvd, ts);
if(rv) {
- if(!qs->last_error.error_code) {
+ if(!ctx->last_error.error_code) {
if(rv == NGTCP2_ERR_CRYPTO) {
ngtcp2_connection_close_error_set_transport_error_tls_alert(
- &qs->last_error, ngtcp2_conn_get_tls_alert(qs->qconn), NULL, 0);
+ &ctx->last_error,
+ ngtcp2_conn_get_tls_alert(ctx->qconn), NULL, 0);
}
else {
ngtcp2_connection_close_error_set_transport_error_liberr(
- &qs->last_error, rv, NULL, 0);
+ &ctx->last_error, rv, NULL, 0);
}
}
@@ -1815,14 +1611,15 @@ static CURLcode ng_process_ingress(struct Curl_easy *data,
return CURLE_OK;
}
-static CURLcode do_sendmsg(size_t *sent, struct Curl_easy *data, int sockfd,
- struct quicsocket *qs, const uint8_t *pkt,
- size_t pktlen, size_t gsolen);
+static CURLcode do_sendmsg(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const uint8_t *pkt, size_t pktlen, size_t gsolen,
+ size_t *sent);
-static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data,
- int sockfd, struct quicsocket *qs,
+static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
const uint8_t *pkt, size_t pktlen,
- size_t gsolen)
+ size_t gsolen, size_t *psent)
{
const uint8_t *p, *end = pkt + pktlen;
size_t sent;
@@ -1831,7 +1628,7 @@ static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data,
for(p = pkt; p < end; p += gsolen) {
size_t len = CURLMIN(gsolen, (size_t)(end - p));
- CURLcode curlcode = do_sendmsg(&sent, data, sockfd, qs, p, len, len);
+ CURLcode curlcode = do_sendmsg(cf, data, p, len, len, &sent);
if(curlcode != CURLE_OK) {
return curlcode;
}
@@ -1841,10 +1638,12 @@ static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data,
return CURLE_OK;
}
-static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
- struct quicsocket *qs, const uint8_t *pkt,
- size_t pktlen, size_t gsolen)
+static CURLcode do_sendmsg(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const uint8_t *pkt, size_t pktlen, size_t gsolen,
+ size_t *psent)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
#ifdef HAVE_SENDMSG
struct iovec msg_iov;
struct msghdr msg = {0};
@@ -1876,7 +1675,7 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
#endif
- while((sent = sendmsg(sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
+ while((sent = sendmsg(ctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
;
if(sent == -1) {
@@ -1894,9 +1693,8 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
/* GSO failure */
failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent,
SOCKERRNO);
- qs->no_gso = TRUE;
- return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen,
- gsolen);
+ ctx->no_gso = TRUE;
+ return send_packet_no_gso(cf, data, pkt, pktlen, gsolen, psent);
}
/* FALLTHROUGH */
default:
@@ -1914,7 +1712,7 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
*psent = 0;
- while((sent = send(sockfd, (const char *)pkt, pktlen, 0)) == -1 &&
+ while((sent = send(ctx->sockfd, (const char *)pkt, pktlen, 0)) == -1 &&
SOCKERRNO == EINTR)
;
@@ -1938,44 +1736,49 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
return CURLE_OK;
}
-static CURLcode send_packet(size_t *psent, struct Curl_easy *data, int sockfd,
- struct quicsocket *qs, const uint8_t *pkt,
- size_t pktlen, size_t gsolen)
+static CURLcode send_packet(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const uint8_t *pkt, size_t pktlen, size_t gsolen,
+ size_t *psent)
{
- if(qs->no_gso && pktlen > gsolen) {
- return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, gsolen);
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+ if(ctx->no_gso && pktlen > gsolen) {
+ return send_packet_no_gso(cf, data, pkt, pktlen, gsolen, psent);
}
- return do_sendmsg(psent, data, sockfd, qs, pkt, pktlen, gsolen);
+ return do_sendmsg(cf, data, pkt, pktlen, gsolen, psent);
}
-static void push_blocked_pkt(struct quicsocket *qs, const uint8_t *pkt,
+static void push_blocked_pkt(struct Curl_cfilter *cf, const uint8_t *pkt,
size_t pktlen, size_t gsolen)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct blocked_pkt *blkpkt;
- assert(qs->num_blocked_pkt <
- sizeof(qs->blocked_pkt) / sizeof(qs->blocked_pkt[0]));
+ assert(ctx->num_blocked_pkt <
+ sizeof(ctx->blocked_pkt) / sizeof(ctx->blocked_pkt[0]));
- blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt++];
+ blkpkt = &ctx->blocked_pkt[ctx->num_blocked_pkt++];
blkpkt->pkt = pkt;
blkpkt->pktlen = pktlen;
blkpkt->gsolen = gsolen;
}
-static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd,
- struct quicsocket *qs)
+static CURLcode send_blocked_pkt(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
size_t sent;
CURLcode curlcode;
struct blocked_pkt *blkpkt;
- for(; qs->num_blocked_pkt_sent < qs->num_blocked_pkt;
- ++qs->num_blocked_pkt_sent) {
- blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt_sent];
- curlcode = send_packet(&sent, data, sockfd, qs, blkpkt->pkt,
- blkpkt->pktlen, blkpkt->gsolen);
+ for(; ctx->num_blocked_pkt_sent < ctx->num_blocked_pkt;
+ ++ctx->num_blocked_pkt_sent) {
+ blkpkt = &ctx->blocked_pkt[ctx->num_blocked_pkt_sent];
+ curlcode = send_packet(cf, data, blkpkt->pkt,
+ blkpkt->pktlen, blkpkt->gsolen, &sent);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
@@ -1986,26 +1789,26 @@ static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd,
}
}
- qs->num_blocked_pkt = 0;
- qs->num_blocked_pkt_sent = 0;
+ ctx->num_blocked_pkt = 0;
+ ctx->num_blocked_pkt_sent = 0;
return CURLE_OK;
}
-static CURLcode ng_flush_egress(struct Curl_easy *data,
- int sockfd,
- struct quicsocket *qs)
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
int rv;
size_t sent;
ngtcp2_ssize outlen;
- uint8_t *outpos = qs->pktbuf;
+ uint8_t *outpos = ctx->pktbuf;
size_t max_udp_payload_size =
- ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn);
+ ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn);
size_t path_max_udp_payload_size =
- ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn);
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn);
size_t max_pktcnt =
- CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size);
+ CURLMIN(MAX_PKT_BURST, ctx->pktbuflen / max_udp_payload_size);
size_t pktcnt = 0;
size_t gsolen;
ngtcp2_path_storage ps;
@@ -2020,17 +1823,17 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
uint32_t flags;
CURLcode curlcode;
- rv = ngtcp2_conn_handle_expiry(qs->qconn, ts);
+ rv = ngtcp2_conn_handle_expiry(ctx->qconn, ts);
if(rv) {
failf(data, "ngtcp2_conn_handle_expiry returned error: %s",
ngtcp2_strerror(rv));
- ngtcp2_connection_close_error_set_transport_error_liberr(&qs->last_error,
+ ngtcp2_connection_close_error_set_transport_error_liberr(&ctx->last_error,
rv, NULL, 0);
return CURLE_SEND_ERROR;
}
- if(qs->num_blocked_pkt) {
- curlcode = send_blocked_pkt(data, sockfd, qs);
+ if(ctx->num_blocked_pkt) {
+ curlcode = send_blocked_pkt(cf, data);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
Curl_expire(data, 1, EXPIRE_QUIC);
@@ -2047,14 +1850,14 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
stream_id = -1;
fin = 0;
- if(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) {
- veccnt = nghttp3_conn_writev_stream(qs->h3conn, &stream_id, &fin, vec,
+ if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) {
+ veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec,
sizeof(vec) / sizeof(vec[0]));
if(veccnt < 0) {
failf(data, "nghttp3_conn_writev_stream returned error: %s",
nghttp3_strerror((int)veccnt));
ngtcp2_connection_close_error_set_application_error(
- &qs->last_error,
+ &ctx->last_error,
nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0);
return CURLE_SEND_ERROR;
}
@@ -2062,17 +1865,18 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
flags = NGTCP2_WRITE_STREAM_FLAG_MORE |
(fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0);
- outlen = ngtcp2_conn_writev_stream(qs->qconn, &ps.path, NULL, outpos,
+ outlen = ngtcp2_conn_writev_stream(ctx->qconn, &ps.path, NULL, outpos,
max_udp_payload_size,
&ndatalen, flags, stream_id,
(const ngtcp2_vec *)vec, veccnt, ts);
if(outlen == 0) {
- if(outpos != qs->pktbuf) {
- curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
- outpos - qs->pktbuf, gsolen);
+ if(outpos != ctx->pktbuf) {
+ curlcode = send_packet(cf, data, ctx->pktbuf,
+ outpos - ctx->pktbuf, gsolen, &sent);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
- push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
+ push_blocked_pkt(cf, ctx->pktbuf + sent,
+ outpos - ctx->pktbuf - sent,
gsolen);
Curl_expire(data, 1, EXPIRE_QUIC);
return CURLE_OK;
@@ -2087,15 +1891,15 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
switch(outlen) {
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
assert(ndatalen == -1);
- nghttp3_conn_block_stream(qs->h3conn, stream_id);
+ nghttp3_conn_block_stream(ctx->h3conn, stream_id);
continue;
case NGTCP2_ERR_STREAM_SHUT_WR:
assert(ndatalen == -1);
- nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
+ nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id);
continue;
case NGTCP2_ERR_WRITE_MORE:
assert(ndatalen >= 0);
- rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
+ rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
if(rv) {
failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
nghttp3_strerror(rv));
@@ -2107,12 +1911,12 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
failf(data, "ngtcp2_conn_writev_stream returned error: %s",
ngtcp2_strerror((int)outlen));
ngtcp2_connection_close_error_set_transport_error_liberr(
- &qs->last_error, (int)outlen, NULL, 0);
+ &ctx->last_error, (int)outlen, NULL, 0);
return CURLE_SEND_ERROR;
}
}
else if(ndatalen >= 0) {
- rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
+ rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
if(rv) {
failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
nghttp3_strerror(rv));
@@ -2131,24 +1935,24 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
/* Packet larger than path_max_udp_payload_size is PMTUD probe
packet and it might not be sent because of EMSGSIZE. Send
them separately to minimize the loss. */
- curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
- outpos - outlen - qs->pktbuf, gsolen);
+ curlcode = send_packet(cf, data, ctx->pktbuf,
+ outpos - outlen - ctx->pktbuf, gsolen, &sent);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
- push_blocked_pkt(qs, qs->pktbuf + sent,
- outpos - outlen - qs->pktbuf - sent, gsolen);
- push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
+ push_blocked_pkt(cf, ctx->pktbuf + sent,
+ outpos - outlen - ctx->pktbuf - sent, gsolen);
+ push_blocked_pkt(cf, outpos - outlen, outlen, outlen);
Curl_expire(data, 1, EXPIRE_QUIC);
return CURLE_OK;
}
return curlcode;
}
- curlcode = send_packet(&sent, data, sockfd, qs, outpos - outlen, outlen,
- outlen);
+ curlcode = send_packet(cf, data, outpos - outlen, outlen,
+ outlen, &sent);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
assert(0 == sent);
- push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
+ push_blocked_pkt(cf, outpos - outlen, outlen, outlen);
Curl_expire(data, 1, EXPIRE_QUIC);
return CURLE_OK;
}
@@ -2156,16 +1960,16 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
}
pktcnt = 0;
- outpos = qs->pktbuf;
+ outpos = ctx->pktbuf;
continue;
}
if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) {
- curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
- outpos - qs->pktbuf, gsolen);
+ curlcode = send_packet(cf, data, ctx->pktbuf,
+ outpos - ctx->pktbuf, gsolen, &sent);
if(curlcode) {
if(curlcode == CURLE_AGAIN) {
- push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
+ push_blocked_pkt(cf, ctx->pktbuf + sent, outpos - ctx->pktbuf - sent,
gsolen);
Curl_expire(data, 1, EXPIRE_QUIC);
return CURLE_OK;
@@ -2174,11 +1978,11 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
}
pktcnt = 0;
- outpos = qs->pktbuf;
+ outpos = ctx->pktbuf;
}
}
- expiry = ngtcp2_conn_get_expiry(qs->qconn);
+ expiry = ngtcp2_conn_get_expiry(ctx->qconn);
if(expiry != UINT64_MAX) {
if(expiry <= ts) {
timeout = 0;
@@ -2196,71 +2000,420 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
}
/*
- * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
*/
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
{
- struct connectdata *conn = data->conn;
- DEBUGASSERT(conn);
- if(conn->handler == &Curl_handler_http3) {
- /* only for HTTP/3 transfers */
+ /* We may have received more data than we're able to hold in the receive
+ buffer and allocated an overflow buffer. Since it's possible that
+ there's no more data coming on the socket, we need to keep reading
+ until the overflow buffer is empty. */
+ const struct HTTP *stream = data->req.p.http;
+ (void)cf;
+ return Curl_dyn_len(&stream->overflow) > 0;
+}
+
+static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
+{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
+ (void)arg1;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_DATA_DONE: {
+ struct HTTP *stream = data->req.p.http;
+ Curl_dyn_free(&stream->overflow);
+ free(stream->h3out);
+ break;
+ }
+
+ case CF_CTRL_DATA_DONE_SEND: {
struct HTTP *stream = data->req.p.http;
- struct quicsocket *qs = conn->quic;
stream->upload_done = TRUE;
- (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
+ (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
+ break;
}
- return CURLE_OK;
+ case CF_CTRL_DATA_IDLE:
+ if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) {
+ if(cf_flush_egress(cf, data)) {
+ return CURLE_SEND_ERROR;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return result;
}
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
+static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
{
- (void)premature;
- if(data->conn->handler == &Curl_handler_http3) {
- /* only for HTTP/3 transfers */
- struct HTTP *stream = data->req.p.http;
- Curl_dyn_free(&stream->overflow);
- free(stream->h3out);
+ if(ctx) {
+ if(ctx->qlogfd != -1) {
+ close(ctx->qlogfd);
+ ctx->qlogfd = -1;
+ }
+#ifdef USE_OPENSSL
+ if(ctx->ssl)
+ SSL_free(ctx->ssl);
+ if(ctx->sslctx)
+ SSL_CTX_free(ctx->sslctx);
+#elif defined(USE_GNUTLS)
+ if(ctx->gtls) {
+ if(ctx->gtls->cred)
+ gnutls_certificate_free_credentials(ctx->gtls->cred);
+ if(ctx->gtls->session)
+ gnutls_deinit(ctx->gtls->session);
+ free(ctx->gtls);
+ }
+#elif defined(USE_WOLFSSL)
+ if(ctx->ssl)
+ wolfSSL_free(ctx->ssl);
+ if(ctx->sslctx)
+ wolfSSL_CTX_free(ctx->sslctx);
+#endif
+ free(ctx->pktbuf);
+ nghttp3_conn_del(ctx->h3conn);
+ ngtcp2_conn_del(ctx->qconn);
+
+ memset(ctx, 0, sizeof(*ctx));
}
}
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(const struct Curl_easy *data)
+static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- /* We may have received more data than we're able to hold in the receive
- buffer and allocated an overflow buffer. Since it's possible that
- there's no more data coming on the socket, we need to keep reading
- until the overflow buffer is empty. */
- const struct HTTP *stream = data->req.p.http;
- return Curl_dyn_len(&stream->overflow) > 0;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+ (void)data;
+ if(ctx && ctx->qconn) {
+ char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
+ ngtcp2_tstamp ts;
+ ngtcp2_ssize rc;
+
+ ts = timestamp();
+ rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */
+ NULL, /* pkt_info */
+ (uint8_t *)buffer, sizeof(buffer),
+ &ctx->last_error, ts);
+ if(rc > 0) {
+ while((send(ctx->sockfd, buffer, rc, 0) == -1) &&
+ SOCKERRNO == EINTR);
+ }
+
+ cf_ngtcp2_ctx_clear(ctx);
+ }
+
+ cf->connected = FALSE;
+}
+
+static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+ (void)data;
+ cf_ngtcp2_ctx_clear(ctx);
+ free(ctx);
+ cf->ctx = NULL;
}
/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
+ * Might be called twice for happy eyeballs.
*/
-CURLcode Curl_quic_idle(struct Curl_easy *data)
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- struct connectdata *conn = data->conn;
- curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
- struct quicsocket *qs = conn->quic;
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ int rc;
+ int rv;
+ CURLcode result;
+ ngtcp2_path path; /* TODO: this must be initialized properly */
+ const struct Curl_sockaddr_ex *sockaddr;
+ const char *r_ip;
+ int r_port;
+ int qfd;
- if(ngtcp2_conn_get_expiry(qs->qconn) > timestamp()) {
- return CURLE_OK;
+ result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
+ &sockaddr, &r_ip, &r_port);
+ if(result)
+ return result;
+ DEBUGASSERT(ctx->sockfd != CURL_SOCKET_BAD);
+
+ infof(data, "Connect socket %d over QUIC to %s:%d",
+ ctx->sockfd, r_ip, r_port);
+
+ rc = connect(ctx->sockfd, &sockaddr->sa_addr, sockaddr->addrlen);
+ if(-1 == rc) {
+ return Curl_socket_connect_result(data, r_ip, SOCKERRNO);
}
- if(ng_flush_egress(data, sockfd, qs)) {
- return CURLE_SEND_ERROR;
+ /* QUIC sockets need to be nonblocking */
+ (void)curlx_nonblock(ctx->sockfd, TRUE);
+ switch(sockaddr->family) {
+#if defined(__linux__) && defined(IP_MTU_DISCOVER)
+ case AF_INET: {
+ int val = IP_PMTUDISC_DO;
+ (void)setsockopt(ctx->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
+ sizeof(val));
+ break;
+ }
+#endif
+#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
+ case AF_INET6: {
+ int val = IPV6_PMTUDISC_DO;
+ (void)setsockopt(ctx->sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
+ sizeof(val));
+ break;
+ }
+#endif
+ }
+
+ ctx->version = NGTCP2_PROTO_VER_MAX;
+#ifdef USE_OPENSSL
+ ctx->sslctx = quic_ssl_ctx(cf, data);
+ if(!ctx->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ result = quic_set_client_cert(cf, data);
+ if(result)
+ return result;
+#elif defined(USE_WOLFSSL)
+ ctx->sslctx = quic_ssl_ctx(cf, data);
+ if(!ctx->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+#endif
+
+ result = quic_init_ssl(cf, data);
+ if(result)
+ return result;
+
+ ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
+ result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN);
+ if(result)
+ return result;
+
+ ctx->scid.datalen = NGTCP2_MAX_CIDLEN;
+ result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN);
+ if(result)
+ return result;
+
+ (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
+ ctx->qlogfd = qfd; /* -1 if failure above */
+ quic_settings(ctx, data);
+
+ ctx->local_addrlen = sizeof(ctx->local_addr);
+ rv = getsockname(ctx->sockfd, (struct sockaddr *)&ctx->local_addr,
+ &ctx->local_addrlen);
+ if(rv == -1)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->local_addr,
+ ctx->local_addrlen);
+ ngtcp2_addr_init(&path.remote, &sockaddr->sa_addr, sockaddr->addrlen);
+
+ rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, &path,
+ NGTCP2_PROTO_VER_V1, &ng_callbacks,
+ &ctx->settings, &ctx->transport_params,
+ NULL, cf);
+ if(rc)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+#ifdef USE_GNUTLS
+ ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session);
+#else
+ ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl);
+#endif
+
+ ngtcp2_connection_close_error_default(&ctx->last_error);
+
+#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
+ ctx->no_gso = FALSE;
+#else
+ ctx->no_gso = TRUE;
+#endif
+
+ ctx->num_blocked_pkt = 0;
+ ctx->num_blocked_pkt_sent = 0;
+ memset(&ctx->blocked_pkt, 0, sizeof(ctx->blocked_pkt));
+
+ ctx->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST;
+ ctx->pktbuf = malloc(ctx->pktbuflen);
+ if(!ctx->pktbuf) {
+ ngtcp2_conn_del(ctx->qconn);
+ ctx->qconn = NULL;
+ return CURLE_OUT_OF_MEMORY;
}
+ ctx->conn_ref.get_conn = get_conn;
+ ctx->conn_ref.user_data = cf;
+
return CURLE_OK;
}
+static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ /* Connect the UDP filter first */
+ if(!cf->next->connected) {
+ result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+ if(result || !*done)
+ return result;
+ }
+
+ if(!ctx->qconn) {
+ result = cf_connect_start(cf, data);
+ if(result)
+ goto out;
+ }
+
+ *done = FALSE;
+ result = cf_process_ingress(cf, data);
+ if(result)
+ goto out;
+
+ result = cf_flush_egress(cf, data);
+ if(result)
+ goto out;
+
+ if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) {
+ result = qng_verify_peer(cf, data);
+ if(!result) {
+ cf->connected = TRUE;
+ cf->conn->alpn = CURL_HTTP_VERSION_3;
+ *done = TRUE;
+ connkeep(cf->conn, "HTTP/3 default");
+ }
+ }
+
+out:
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ if(result && result != CURLE_AGAIN) {
+ const struct Curl_sockaddr_ex *sockaddr;
+ const char *r_ip;
+ int r_port;
+
+ result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
+ &sockaddr, &r_ip, &r_port);
+ infof(data, "connect to %s port %u failed: %s",
+ r_ip, r_port, curl_easy_strerror(result));
+ }
+#endif
+ return result;
+}
+
+static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2)
+{
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+ switch(query) {
+ case CF_QUERY_MAX_CONCURRENT: {
+ const ngtcp2_transport_params *rp;
+ DEBUGASSERT(pres1);
+ rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn);
+ if(rp)
+ *pres1 = (rp->initial_max_streams_bidi > INT_MAX)?
+ INT_MAX : (int)rp->initial_max_streams_bidi;
+ else /* not arrived yet? */
+ *pres1 = Curl_multi_max_concurrent_streams(data->multi);
+ return CURLE_OK;
+ }
+ default:
+ break;
+ }
+ return cf->next?
+ cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+ CURLE_UNKNOWN_OPTION;
+}
+
+
+static const struct Curl_cftype cft_qng = {
+ "HTTP/3-NGTCP2",
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ cf_ngtcp2_destroy,
+ cf_ngtcp2_connect,
+ cf_ngtcp2_close,
+ Curl_cf_def_get_host,
+ cf_ngtcp2_get_select_socks,
+ cf_ngtcp2_data_pending,
+ cf_ngtcp2_send,
+ cf_ngtcp2_recv,
+ cf_ngtcp2_data_event,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ cf_ngtcp2_query,
+};
+
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+ struct cf_ngtcp2_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL, *udp_cf;
+ CURLcode result;
+
+ (void)data;
+ (void)conn;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ result = Curl_cf_create(&cf, &cft_qng, ctx);
+ if(result)
+ goto out;
+
+ result = Curl_cf_udp_create(&udp_cf, data, conn, ai);
+ if(result)
+ goto out;
+
+ udp_cf->conn = cf->conn;
+ udp_cf->sockindex = cf->sockindex;
+ cf->next = udp_cf;
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ if(udp_cf)
+ Curl_conn_cf_discard(udp_cf, data);
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
+}
+
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+ (void)data;
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_qng)
+ return TRUE;
+ if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+ return FALSE;
+ }
+ return FALSE;
+}
+
#endif
diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h
index 2265999e2..2546738d4 100644
--- a/lib/vquic/ngtcp2.h
+++ b/lib/vquic/ngtcp2.h
@@ -42,52 +42,20 @@
#include <wolfssl/quic.h>
#endif
-struct gtls_instance;
+struct Curl_cfilter;
-struct blocked_pkt {
- const uint8_t *pkt;
- size_t pktlen;
- size_t gsolen;
-};
-
-struct quicsocket {
- struct connectdata *conn; /* point back to the connection */
- ngtcp2_conn *qconn;
- ngtcp2_cid dcid;
- ngtcp2_cid scid;
- uint32_t version;
- ngtcp2_settings settings;
- ngtcp2_transport_params transport_params;
- ngtcp2_connection_close_error last_error;
- ngtcp2_crypto_conn_ref conn_ref;
-#ifdef USE_OPENSSL
- SSL_CTX *sslctx;
- SSL *ssl;
-#elif defined(USE_GNUTLS)
- struct gtls_instance *gtls;
-#elif defined(USE_WOLFSSL)
- WOLFSSL_CTX *sslctx;
- WOLFSSL *ssl;
-#endif
- struct sockaddr_storage local_addr;
- socklen_t local_addrlen;
- bool no_gso;
- uint8_t *pktbuf;
- size_t pktbuflen;
- /* the number of entries in blocked_pkt */
- size_t num_blocked_pkt;
- /* the number of processed entries in blocked_pkt */
- size_t num_blocked_pkt_sent;
- /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
- struct blocked_pkt blocked_pkt[2];
+#include "urldata.h"
- nghttp3_conn *h3conn;
- nghttp3_settings h3settings;
- int qlogfd;
-};
+void Curl_ngtcp2_ver(char *p, size_t len);
-#include "urldata.h"
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex);
#endif
#endif /* HEADER_CURL_VQUIC_NGTCP2_H */
diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c
index fd9d52a79..d9ed45bcb 100644
--- a/lib/vquic/quiche.c
+++ b/lib/vquic/quiche.c
@@ -29,10 +29,11 @@
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "urldata.h"
+#include "cfilters.h"
+#include "cf-socket.h"
#include "sendf.h"
#include "strdup.h"
#include "rand.h"
-#include "quic.h"
#include "strcase.h"
#include "multiif.h"
#include "connect.h"
@@ -48,136 +49,26 @@
#include "curl_memory.h"
#include "memdebug.h"
-#define DEBUG_HTTP3
-/* #define DEBUG_QUICHE */
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
+#define DEBUG_CF 0
+
+#if DEBUG_CF
+#define CF_DEBUGF(x) x
#else
-#define H3BUGF(x) do { } while(0)
+#define CF_DEBUGF(x) do { } while(0)
#endif
#define QUIC_MAX_STREAMS (256*1024)
#define QUIC_MAX_DATA (1*1024*1024)
#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
-static CURLcode process_ingress(struct Curl_easy *data,
- curl_socket_t sockfd,
- struct quicsocket *qs);
-
-static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd,
- struct quicsocket *qs);
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
- size_t len);
-static Curl_recv h3_stream_recv;
-static Curl_send h3_stream_send;
-
-static int quiche_getsock(struct Curl_easy *data,
- struct connectdata *conn, curl_socket_t *socks)
-{
- struct SingleRequest *k = &data->req;
- int bitmap = GETSOCK_BLANK;
-
- socks[0] = conn->sock[FIRSTSOCKET];
-
- /* in an HTTP/2 connection we can basically always get a frame so we should
- always be ready for one */
- bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-
- /* we're still uploading or the HTTP/2 layer wants to send data */
- if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
- bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
-
- return bitmap;
-}
-
-static CURLcode qs_disconnect(struct Curl_easy *data,
- struct quicsocket *qs)
-{
- DEBUGASSERT(qs);
- if(qs->conn) {
- (void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0);
- /* flushing the egress is not a failsafe way to deliver all the
- outstanding packets, but we also don't want to get stuck here... */
- (void)flush_egress(data, qs->sockfd, qs);
- quiche_conn_free(qs->conn);
- qs->conn = NULL;
- }
- if(qs->h3config)
- quiche_h3_config_free(qs->h3config);
- if(qs->h3c)
- quiche_h3_conn_free(qs->h3c);
- if(qs->cfg) {
- quiche_config_free(qs->cfg);
- qs->cfg = NULL;
- }
- return CURLE_OK;
-}
-
-static CURLcode quiche_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool dead_connection)
-{
- struct quicsocket *qs = conn->quic;
- (void)dead_connection;
- return qs_disconnect(data, qs);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- int tempindex)
-{
- if(conn->transport == TRNSPRT_QUIC)
- qs_disconnect(data, &conn->hequic[tempindex]);
-}
-
-static unsigned int quiche_conncheck(struct Curl_easy *data,
- struct connectdata *conn,
- unsigned int checks_to_perform)
-{
- (void)data;
- (void)conn;
- (void)checks_to_perform;
- return CONNRESULT_NONE;
-}
-
-static CURLcode quiche_do(struct Curl_easy *data, bool *done)
-{
- struct HTTP *stream = data->req.p.http;
- stream->h3req = FALSE; /* not sent */
- return Curl_http(data, done);
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
- "HTTPS", /* scheme */
- ZERO_NULL, /* setup_connection */
- quiche_do, /* do_it */
- Curl_http_done, /* done */
- ZERO_NULL, /* do_more */
- ZERO_NULL, /* connect_it */
- ZERO_NULL, /* connecting */
- ZERO_NULL, /* doing */
- quiche_getsock, /* proto_getsock */
- quiche_getsock, /* doing_getsock */
- ZERO_NULL, /* domore_getsock */
- quiche_getsock, /* perform_getsock */
- quiche_disconnect, /* disconnect */
- ZERO_NULL, /* readwrite */
- quiche_conncheck, /* connection_check */
- ZERO_NULL, /* attach connection */
- PORT_HTTP, /* defport */
- CURLPROTO_HTTPS, /* protocol */
- CURLPROTO_HTTP, /* family */
- PROTOPT_SSL | PROTOPT_STREAM /* flags */
-};
-#ifdef DEBUG_QUICHE
-static void quiche_debug_log(const char *line, void *argp)
+/*
+ * Store quiche version info in this buffer.
+ */
+void Curl_quiche_ver(char *p, size_t len)
{
- (void)argp;
- fprintf(stderr, "%s\n", line);
+ (void)msnprintf(p, len, "quiche/%s", quiche_version());
}
-#endif
static void keylog_callback(const SSL *ssl, const char *line)
{
@@ -233,232 +124,262 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
return ssl_ctx;
}
-static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn)
-{
- /* this will need some attention when HTTPS proxy over QUIC get fixed */
- const char * const hostname = conn->host.name;
-
- DEBUGASSERT(!qs->ssl);
- qs->ssl = SSL_new(qs->sslctx);
-
- SSL_set_app_data(qs->ssl, qs);
+struct quic_handshake {
+ char *buf; /* pointer to the buffer */
+ size_t alloclen; /* size of allocation */
+ size_t len; /* size of content in buffer */
+ size_t nread; /* how many bytes have been read */
+};
- /* set SNI */
- SSL_set_tlsext_host_name(qs->ssl, hostname);
- return 0;
-}
+struct h3_event_node {
+ struct h3_event_node *next;
+ uint64_t stream3_id;
+ quiche_h3_event *ev;
+};
+struct cf_quiche_ctx {
+ curl_socket_t sockfd;
+ struct sockaddr_storage local_addr;
+ socklen_t local_addrlen;
+ quiche_conn *qconn;
+ quiche_config *cfg;
+ quiche_h3_conn *h3c;
+ quiche_h3_config *h3config;
+ uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+ SSL_CTX *sslctx;
+ SSL *ssl;
+ struct h3_event_node *pending;
+ bool h3_recving; /* TRUE when in h3-body-reading state */
+ bool goaway;
+};
-CURLcode Curl_quic_connect(struct Curl_easy *data,
- struct connectdata *conn, curl_socket_t sockfd,
- int sockindex,
- const struct sockaddr *addr, socklen_t addrlen)
-{
- CURLcode result;
- struct quicsocket *qs = &conn->hequic[sockindex];
- char ipbuf[40];
- int port;
- int rv;
#ifdef DEBUG_QUICHE
- /* initialize debug log callback only once */
- static int debug_log_init = 0;
- if(!debug_log_init) {
- quiche_enable_debug_logging(quiche_debug_log, NULL);
- debug_log_init = 1;
- }
+static void quiche_debug_log(const char *line, void *argp)
+{
+ (void)argp;
+ fprintf(stderr, "%s\n", line);
+}
#endif
- (void)addr;
- (void)addrlen;
-
- qs->sockfd = sockfd;
- qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
- if(!qs->cfg) {
- failf(data, "can't create quiche config");
- return CURLE_FAILED_INIT;
+static void h3_clear_pending(struct cf_quiche_ctx *ctx)
+{
+ if(ctx->pending) {
+ struct h3_event_node *node, *next;
+ for(node = ctx->pending; node; node = next) {
+ next = node->next;
+ quiche_h3_event_free(node->ev);
+ free(node);
+ }
+ ctx->pending = NULL;
}
+}
- quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
- quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
- quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
- quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg,
- QUIC_MAX_DATA);
- quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
- quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
- quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
- quiche_config_set_application_protos(qs->cfg,
- (uint8_t *)
- QUICHE_H3_APPLICATION_PROTOCOL,
- sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
- - 1);
-
- qs->sslctx = quic_ssl_ctx(data);
- if(!qs->sslctx)
- return CURLE_QUIC_CONNECT_ERROR;
-
- if(quic_init_ssl(qs, conn))
- return CURLE_QUIC_CONNECT_ERROR;
+static bool h3_has_pending(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct HTTP *stream = data->req.p.http;
+ struct h3_event_node *node;
- result = Curl_rand(data, qs->scid, sizeof(qs->scid));
- if(result)
- return result;
+ for(node = ctx->pending; node; node = node->next) {
+ if(node->stream3_id == stream->stream3_id) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] has data pending"),
+ stream->stream3_id));
+ return TRUE;
+ }
+ }
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] no data pending"),
+ stream->stream3_id));
+ return FALSE;
+}
- qs->local_addrlen = sizeof(qs->local_addr);
- rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
- &qs->local_addrlen);
- if(rv == -1)
- return CURLE_QUIC_CONNECT_ERROR;
+static CURLcode h3_add_event(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ uint64_t stream3_id, quiche_h3_event *ev)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct Curl_easy *mdata;
+ struct h3_event_node *node, **pnext = &ctx->pending;
- qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid,
- sizeof(qs->scid), NULL, 0,
- (struct sockaddr *)&qs->local_addr,
- qs->local_addrlen, addr, addrlen,
- qs->cfg, qs->ssl, false);
- if(!qs->conn) {
- failf(data, "can't create quiche connection");
- return CURLE_OUT_OF_MEMORY;
+ DEBUGASSERT(data->multi);
+ for(mdata = data->multi->easyp; mdata; mdata = mdata->next) {
+ if(mdata->req.p.http && mdata->req.p.http->stream3_id == stream3_id) {
+ break;
+ }
}
- /* Known to not work on Windows */
-#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
- {
- int qfd;
- (void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd);
- if(qfd != -1)
- quiche_conn_set_qlog_fd(qs->conn, qfd,
- "qlog title", "curl qlog");
+ if(!mdata) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "event for unknown stream %u, discarded"),
+ stream3_id));
+ quiche_h3_event_free(ev);
+ return CURLE_OK;
}
-#endif
- result = flush_egress(data, sockfd, qs);
- if(result)
- return result;
-
- /* extract the used address as a string */
- if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
- char buffer[STRERROR_LEN];
- failf(data, "ssrem inet_ntop() failed with errno %d: %s",
- SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- return CURLE_BAD_FUNCTION_ARGUMENT;
+ node = calloc(sizeof(*node), 1);
+ if(!node)
+ return CURLE_OUT_OF_MEMORY;
+ node->stream3_id = stream3_id;
+ node->ev = ev;
+ /* append to process them in order of arrival */
+ while(*pnext) {
+ pnext = &((*pnext)->next);
}
+ *pnext = node;
+ if(!mdata->state.drain) {
+ /* tell the multi handle that this data needs processing */
+ mdata->state.drain = 1;
+ Curl_expire(mdata, 0, EXPIRE_RUN_NOW);
+ }
+ return CURLE_OK;
+}
- infof(data, "Connect socket %d over QUIC to %s:%ld",
- sockfd, ipbuf, port);
-
- Curl_persistconninfo(data, conn, NULL, -1);
-
- {
- unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
- unsigned alpn_len, offset = 0;
+struct h3h1header {
+ char *dest;
+ size_t destlen; /* left to use */
+ size_t nlen; /* used */
+};
- /* Replace each ALPN length prefix by a comma. */
- while(offset < sizeof(alpn_protocols) - 1) {
- alpn_len = alpn_protocols[offset];
- alpn_protocols[offset] = ',';
- offset += 1 + alpn_len;
- }
+static int cb_each_header(uint8_t *name, size_t name_len,
+ uint8_t *value, size_t value_len,
+ void *argp)
+{
+ struct h3h1header *headers = (struct h3h1header *)argp;
+ size_t olen = 0;
- infof(data, "Sent QUIC client Initial, ALPN: %s",
- alpn_protocols + 1);
+ if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
+ msnprintf(headers->dest,
+ headers->destlen, "HTTP/3 %.*s \r\n",
+ (int) value_len, value);
}
-
- return CURLE_OK;
+ else if(!headers->nlen) {
+ return CURLE_HTTP3;
+ }
+ else {
+ msnprintf(headers->dest,
+ headers->destlen, "%.*s: %.*s\r\n",
+ (int)name_len, name, (int) value_len, value);
+ }
+ olen = strlen(headers->dest);
+ headers->destlen -= olen;
+ headers->nlen += olen;
+ headers->dest += olen;
+ return 0;
}
-static CURLcode quiche_has_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- int tempindex)
+static ssize_t h3_process_event(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf, size_t len,
+ uint64_t stream3_id,
+ quiche_h3_event *ev,
+ CURLcode *err)
{
- CURLcode result;
- struct quicsocket *qs = conn->quic = &conn->hequic[tempindex];
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct HTTP *stream = data->req.p.http;
+ ssize_t recvd = -1;
+ ssize_t rcode;
+ int rc;
+ struct h3h1header headers;
- conn->recv[sockindex] = h3_stream_recv;
- conn->send[sockindex] = h3_stream_send;
- conn->handler = &Curl_handler_http3;
- conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
- conn->httpversion = 30;
- conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ DEBUGASSERT(stream3_id == stream->stream3_id);
+
+ switch(quiche_h3_event_type(ev)) {
+ case QUICHE_H3_EVENT_HEADERS:
+ headers.dest = buf;
+ headers.destlen = len;
+ headers.nlen = 0;
+ rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
+ if(rc) {
+ failf(data, "Error in HTTP/3 response header");
+ *err = CURLE_RECV_ERROR;
+ recvd = -1;
+ break;
+ }
+ recvd = headers.nlen;
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] HEADERS len=%d"),
+ stream3_id, (int)recvd));
+ break;
- if(conn->ssl_config.verifyhost) {
- X509 *server_cert;
- server_cert = SSL_get_peer_certificate(qs->ssl);
- if(!server_cert) {
- return CURLE_PEER_FAILED_VERIFICATION;
+ case QUICHE_H3_EVENT_DATA:
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] DATA"), stream3_id));
+ if(!stream->firstbody) {
+ /* add a header-body separator CRLF */
+ buf[0] = '\r';
+ buf[1] = '\n';
+ buf += 2;
+ len -= 2;
+ stream->firstbody = TRUE;
+ recvd = 2; /* two bytes already */
}
- result = Curl_ossl_verifyhost(data, conn, server_cert);
- X509_free(server_cert);
- if(result)
- return result;
- infof(data, "Verified certificate just fine");
- }
- else
- infof(data, "Skipped certificate verification");
+ else
+ recvd = 0;
+ rcode = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream3_id,
+ (unsigned char *)buf, len);
+ if(rcode <= 0) {
+ recvd = -1;
+ *err = CURLE_AGAIN;
+ break;
+ }
+ ctx->h3_recving = TRUE;
+ recvd += rcode;
+ break;
- qs->h3config = quiche_h3_config_new();
- if(!qs->h3config)
- return CURLE_OUT_OF_MEMORY;
+ case QUICHE_H3_EVENT_RESET:
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] RESET"), stream3_id));
+ streamclose(cf->conn, "Stream reset");
+ *err = CURLE_PARTIAL_FILE;
+ recvd = -1;
+ break;
- /* Create a new HTTP/3 connection on the QUIC connection. */
- qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config);
- if(!qs->h3c) {
- result = CURLE_OUT_OF_MEMORY;
- goto fail;
- }
- if(conn->hequic[1-tempindex].cfg) {
- qs = &conn->hequic[1-tempindex];
- quiche_config_free(qs->cfg);
- quiche_conn_free(qs->conn);
- qs->cfg = NULL;
- qs->conn = NULL;
- }
- if(data->set.ssl.certinfo)
- /* asked to gather certificate info */
- (void)Curl_ossl_certchain(data, qs->ssl);
+ case QUICHE_H3_EVENT_FINISHED:
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] FINISHED"), stream3_id));
+ stream->closed = TRUE;
+ streamclose(cf->conn, "End of stream");
+ *err = CURLE_OK;
+ recvd = 0; /* end of stream */
+ break;
- return CURLE_OK;
- fail:
- quiche_h3_config_free(qs->h3config);
- quiche_h3_conn_free(qs->h3c);
- return result;
+ case QUICHE_H3_EVENT_GOAWAY:
+ recvd = -1;
+ *err = CURLE_AGAIN;
+ ctx->goaway = TRUE;
+ break;
+
+ default:
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] unhandled event %d"),
+ stream3_id, quiche_h3_event_type(ev)));
+ break;
+ }
+ return recvd;
}
-/*
- * This function gets polled to check if this QUIC connection has connected.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- bool *done)
+static ssize_t h3_process_pending(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf, size_t len,
+ CURLcode *err)
{
- CURLcode result;
- struct quicsocket *qs = &conn->hequic[sockindex];
- curl_socket_t sockfd = conn->tempsock[sockindex];
-
- result = process_ingress(data, sockfd, qs);
- if(result)
- goto error;
-
- result = flush_egress(data, sockfd, qs);
- if(result)
- goto error;
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct HTTP *stream = data->req.p.http;
+ struct h3_event_node *node = ctx->pending, **pnext = &ctx->pending;
+ ssize_t recvd = -1;
- if(quiche_conn_is_established(qs->conn)) {
- *done = TRUE;
- result = quiche_has_connected(data, conn, 0, sockindex);
- DEBUGF(infof(data, "quiche established connection"));
+ for(; node; pnext = &node->next, node = node->next) {
+ if(node->stream3_id == stream->stream3_id) {
+ recvd = h3_process_event(cf, data, buf, len,
+ node->stream3_id, node->ev, err);
+ quiche_h3_event_free(node->ev);
+ *pnext = node->next;
+ free(node);
+ break;
+ }
}
-
- return result;
- error:
- qs_disconnect(data, qs);
- return result;
+ return recvd;
}
-static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
- struct quicsocket *qs)
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
ssize_t recvd;
uint8_t *buf = (uint8_t *)data->state.buffer;
size_t bufsize = data->set.buffer_size;
@@ -469,12 +390,12 @@ static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
DEBUGASSERT(qs->conn);
/* in case the timeout expired */
- quiche_conn_on_timeout(qs->conn);
+ quiche_conn_on_timeout(ctx->qconn);
do {
from_len = sizeof(from);
- recvd = recvfrom(sockfd, buf, bufsize, 0,
+ recvd = recvfrom(ctx->sockfd, buf, bufsize, 0,
(struct sockaddr *)&from, &from_len);
if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
@@ -482,22 +403,22 @@ static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
if(recvd < 0) {
failf(data, "quiche: recvfrom() unexpectedly returned %zd "
- "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
+ "(errno: %d, socket %d)", recvd, SOCKERRNO, ctx->sockfd);
return CURLE_RECV_ERROR;
}
recv_info.from = (struct sockaddr *) &from;
recv_info.from_len = from_len;
- recv_info.to = (struct sockaddr *) &qs->local_addr;
- recv_info.to_len = qs->local_addrlen;
+ recv_info.to = (struct sockaddr *) &ctx->local_addr;
+ recv_info.to_len = ctx->local_addrlen;
- recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info);
+ recvd = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info);
if(recvd == QUICHE_ERR_DONE)
break;
if(recvd < 0) {
if(QUICHE_ERR_TLS_FAIL == recvd) {
- long verify_ok = SSL_get_verify_result(qs->ssl);
+ long verify_ok = SSL_get_verify_result(ctx->ssl);
if(verify_ok != X509_V_OK) {
failf(data, "SSL certificate problem: %s",
X509_verify_cert_error_string(verify_ok));
@@ -519,16 +440,17 @@ static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
* flush_egress drains the buffers and sends off data.
* Calls failf() on errors.
*/
-static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
- struct quicsocket *qs)
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
ssize_t sent;
uint8_t out[1200];
int64_t timeout_ns;
quiche_send_info send_info;
do {
- sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info);
+ sent = quiche_conn_send(ctx->qconn, out, sizeof(out), &send_info);
if(sent == QUICHE_ERR_DONE)
break;
@@ -537,7 +459,7 @@ static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
return CURLE_SEND_ERROR;
}
- sent = send(sockfd, out, sent, 0);
+ sent = send(ctx->sockfd, out, sent, 0);
if(sent < 0) {
failf(data, "send() returned %zd", sent);
return CURLE_SEND_ERROR;
@@ -545,7 +467,7 @@ static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
} while(1);
/* time until the next timeout event, as nanoseconds. */
- timeout_ns = quiche_conn_timeout_as_nanos(qs->conn);
+ timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
if(timeout_ns)
/* expire uses milliseconds */
Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC);
@@ -553,213 +475,107 @@ static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
return CURLE_OK;
}
-struct h3h1header {
- char *dest;
- size_t destlen; /* left to use */
- size_t nlen; /* used */
-};
-
-static int cb_each_header(uint8_t *name, size_t name_len,
- uint8_t *value, size_t value_len,
- void *argp)
-{
- struct h3h1header *headers = (struct h3h1header *)argp;
- size_t olen = 0;
-
- if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
- msnprintf(headers->dest,
- headers->destlen, "HTTP/3 %.*s\n",
- (int) value_len, value);
- }
- else if(!headers->nlen) {
- return CURLE_HTTP3;
- }
- else {
- msnprintf(headers->dest,
- headers->destlen, "%.*s: %.*s\n",
- (int)name_len, name, (int) value_len, value);
- }
- olen = strlen(headers->dest);
- headers->destlen -= olen;
- headers->nlen += olen;
- headers->dest += olen;
- return 0;
-}
-
-static ssize_t h3_stream_recv(struct Curl_easy *data,
- int sockindex,
- char *buf,
- size_t buffersize,
- CURLcode *curlcode)
+static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
ssize_t recvd = -1;
ssize_t rcode;
- struct connectdata *conn = data->conn;
- struct quicsocket *qs = conn->quic;
- curl_socket_t sockfd = conn->sock[sockindex];
quiche_h3_event *ev;
int rc;
struct h3h1header headers;
struct HTTP *stream = data->req.p.http;
headers.dest = buf;
- headers.destlen = buffersize;
+ headers.destlen = len;
headers.nlen = 0;
- if(process_ingress(data, sockfd, qs)) {
- infof(data, "h3_stream_recv returns on ingress");
- *curlcode = CURLE_RECV_ERROR;
- return -1;
+ CF_DEBUGF(infof(data, CFMSG(cf, "recv[%u]"), stream->stream3_id));
+
+ *err = CURLE_AGAIN;
+ recvd = -1;
+
+ if(cf_process_ingress(cf, data)) {
+ CF_DEBUGF(infof(data, "h3_stream_recv returns on ingress"));
+ *err = CURLE_RECV_ERROR;
+ goto out;
}
- if(qs->h3_recving) {
+ if(ctx->h3_recving) {
/* body receiving state */
- rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id,
- (unsigned char *)buf, buffersize);
+ rcode = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+ (unsigned char *)buf, len);
if(rcode <= 0) {
- recvd = -1;
- qs->h3_recving = FALSE;
+ ctx->h3_recving = FALSE;
/* fall through into the while loop below */
}
- else
+ else {
+ *err = CURLE_OK;
recvd = rcode;
+ goto out;
+ }
+ }
+
+ if(recvd < 0) {
+ recvd = h3_process_pending(cf, data, buf, len, err);
}
while(recvd < 0) {
- int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
- if(s < 0)
+ int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
+ if(stream3_id < 0)
/* nothing more to do */
break;
- if(s != stream->stream3_id) {
- /* another transfer, ignore for now */
- infof(data, "Got h3 for stream %u, expects %u",
- s, stream->stream3_id);
- continue;
- }
-
- switch(quiche_h3_event_type(ev)) {
- case QUICHE_H3_EVENT_HEADERS:
- rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
- if(rc) {
- *curlcode = rc;
- failf(data, "Error in HTTP/3 response header");
- break;
- }
- recvd = headers.nlen;
- break;
- case QUICHE_H3_EVENT_DATA:
- if(!stream->firstbody) {
- /* add a header-body separator CRLF */
- buf[0] = '\r';
- buf[1] = '\n';
- buf += 2;
- buffersize -= 2;
- stream->firstbody = TRUE;
- recvd = 2; /* two bytes already */
- }
- else
- recvd = 0;
- rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf,
- buffersize);
- if(rcode <= 0) {
- recvd = -1;
- break;
+ if(stream3_id != stream->stream3_id) {
+ /* event for another transfer, preserver for later */
+ CF_DEBUGF(infof(data, CFMSG(cf, "h3[%u] queuing event"), stream3_id));
+ if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) {
+ *err = CURLE_OUT_OF_MEMORY;
+ goto out;
}
- qs->h3_recving = TRUE;
- recvd += rcode;
- break;
-
- case QUICHE_H3_EVENT_RESET:
- streamclose(conn, "Stream reset");
- *curlcode = CURLE_PARTIAL_FILE;
- return -1;
-
- case QUICHE_H3_EVENT_FINISHED:
- streamclose(conn, "End of stream");
- recvd = 0; /* end of stream */
- break;
- default:
- break;
}
-
- quiche_h3_event_free(ev);
+ else {
+ recvd = h3_process_event(cf, data, buf, len, stream3_id, ev, err);
+ quiche_h3_event_free(ev);
+ }
}
- if(flush_egress(data, sockfd, qs)) {
- *curlcode = CURLE_SEND_ERROR;
- return -1;
+
+ if(cf_flush_egress(cf, data)) {
+ CF_DEBUGF(infof(data, CFMSG(cf, "recv(), flush egress failed")));
+ *err = CURLE_SEND_ERROR;
+ recvd = -1;
+ goto out;
}
- *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK;
- if(recvd >= 0)
+ if(recvd >= 0) {
/* Get this called again to drain the event queue */
Curl_expire(data, 0, EXPIRE_QUIC);
-
- data->state.drain = (recvd >= 0) ? 1 : 0;
- return recvd;
-}
-
-static ssize_t h3_stream_send(struct Curl_easy *data,
- int sockindex,
- const void *mem,
- size_t len,
- CURLcode *curlcode)
-{
- ssize_t sent;
- struct connectdata *conn = data->conn;
- struct quicsocket *qs = conn->quic;
- curl_socket_t sockfd = conn->sock[sockindex];
- struct HTTP *stream = data->req.p.http;
-
- if(!stream->h3req) {
- CURLcode result = http_request(data, mem, len);
- if(result) {
- *curlcode = CURLE_SEND_ERROR;
- return -1;
- }
- sent = len;
}
- else {
- sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
- (uint8_t *)mem, len, FALSE);
- if(sent == QUICHE_H3_ERR_DONE) {
- sent = 0;
- }
- else if(sent < 0) {
- *curlcode = CURLE_SEND_ERROR;
- return -1;
- }
+ else if(stream->closed) {
+ *err = CURLE_OK;
+ recvd = -1;
}
- if(flush_egress(data, sockfd, qs)) {
- *curlcode = CURLE_SEND_ERROR;
- return -1;
- }
-
- *curlcode = CURLE_OK;
- return sent;
-}
-
-/*
- * Store quiche version info in this buffer.
- */
-void Curl_quic_ver(char *p, size_t len)
-{
- (void)msnprintf(p, len, "quiche/%s", quiche_version());
+out:
+ data->state.drain = (recvd >= 0) ? 1 : 0;
+ CF_DEBUGF(infof(data, CFMSG(cf, "recv[%u] -> %ld, err=%d"),
+ stream->stream3_id, (long)recvd, *err));
+ return recvd;
}
/* Index where :authority header field will appear in request header
field list. */
#define AUTHORITY_DST_IDX 3
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
- size_t len)
+static CURLcode cf_http_request(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len)
{
- struct connectdata *conn = data->conn;
+ struct cf_quiche_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
size_t nheader;
int64_t stream3_id;
quiche_h3_header *nva = NULL;
- struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
struct h2h3req *hreq = NULL;
@@ -796,10 +612,10 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
/* data sending without specifying the data amount up front */
stream->upload_left = -1; /* unknown, but not zero */
- stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
+ stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
stream->upload_left ? FALSE: TRUE);
if((stream3_id >= 0) && data->set.postfields) {
- ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id,
+ ssize_t sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream3_id,
(uint8_t *)data->set.postfields,
stream->upload_left, TRUE);
if(sent <= 0) {
@@ -810,7 +626,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
}
break;
default:
- stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
+ stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
TRUE);
break;
}
@@ -818,14 +634,13 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
Curl_safefree(nva);
if(stream3_id < 0) {
- H3BUGF(infof(data, "quiche_h3_send_request returned %d",
- stream3_id));
+ CF_DEBUGF(infof(data, CFMSG(cf, "quiche_h3_send_request returned %ld"),
+ (long)stream3_id));
result = CURLE_SEND_ERROR;
goto fail;
}
- infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
- stream3_id, (void *)data);
+ CF_DEBUGF(infof(data, CFMSG(cf, "Using HTTP/3 Stream ID: %u"), stream3_id));
stream->stream3_id = stream3_id;
Curl_pseudo_free(hreq);
@@ -837,56 +652,507 @@ fail:
return result;
}
+static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
+{
+ struct cf_quiche_ctx *ctx = ctx;
+ struct HTTP *stream = data->req.p.http;
+ ssize_t sent;
+
+ if(!stream->h3req) {
+ CURLcode result = cf_http_request(cf, data, buf, len);
+ if(result) {
+ *err = CURLE_SEND_ERROR;
+ return -1;
+ }
+ sent = len;
+ }
+ else {
+ sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+ (uint8_t *)buf, len, FALSE);
+ if(sent == QUICHE_H3_ERR_DONE) {
+ sent = 0;
+ }
+ else if(sent < 0) {
+ *err = CURLE_SEND_ERROR;
+ return -1;
+ }
+ }
+
+ if(cf_flush_egress(cf, data)) {
+ *err = CURLE_SEND_ERROR;
+ return -1;
+ }
+
+ *err = CURLE_OK;
+ return sent;
+}
+
+static int cf_quiche_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ struct SingleRequest *k = &data->req;
+ int rv = GETSOCK_BLANK;
+ struct HTTP *stream = data->req.p.http;
+
+ socks[0] = ctx->sockfd;
+
+ /* in an HTTP/3 connection we can basically always get a frame so we should
+ always be ready for one */
+ rv |= GETSOCK_READSOCK(0);
+
+ /* we're still uploading or the HTTP/3 layer wants to send data */
+ if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
+ rv |= GETSOCK_WRITESOCK(0);
+
+ return rv;
+}
+
/*
- * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
*/
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
{
- struct connectdata *conn = data->conn;
- DEBUGASSERT(conn);
- if(conn->handler == &Curl_handler_http3) {
- /* only for HTTP/3 transfers */
- ssize_t sent;
+ struct cf_quiche_ctx *ctx = cf->ctx;
+
+ return h3_has_pending(cf, (struct Curl_easy *)data);
+}
+
+static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
+ (void)arg1;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_DATA_DONE_SEND: {
struct HTTP *stream = data->req.p.http;
- struct quicsocket *qs = conn->quic;
+ ssize_t sent;
stream->upload_done = TRUE;
- sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
+ sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
NULL, 0, TRUE);
if(sent < 0)
return CURLE_SEND_ERROR;
+ break;
+ }
+
+ default:
+ break;
+ }
+ return result;
+}
+
+static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
+ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ cf->conn->httpversion = 30;
+ cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+ if(cf->conn->ssl_config.verifyhost) {
+ X509 *server_cert;
+ server_cert = SSL_get_peer_certificate(ctx->ssl);
+ if(!server_cert) {
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ goto out;
+ }
+ result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+ X509_free(server_cert);
+ if(result)
+ goto out;
+ CF_DEBUGF(infof(data, CFMSG(cf, "Verified certificate just fine")));
+ }
+ else
+ CF_DEBUGF(infof(data, CFMSG(cf, "Skipped certificate verification")));
+
+ ctx->h3config = quiche_h3_config_new();
+ if(!ctx->h3config) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ /* Create a new HTTP/3 connection on the QUIC connection. */
+ ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
+ if(!ctx->h3c) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ if(data->set.ssl.certinfo)
+ /* asked to gather certificate info */
+ (void)Curl_ossl_certchain(data, ctx->ssl);
+
+out:
+ if(result) {
+ if(ctx->h3config) {
+ quiche_h3_config_free(ctx->h3config);
+ ctx->h3config = NULL;
+ }
+ if(ctx->h3c) {
+ quiche_h3_conn_free(ctx->h3c);
+ ctx->h3c = NULL;
+ }
+ }
+ return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ int rc;
+ int rv;
+ CURLcode result;
+ const struct Curl_sockaddr_ex *sockaddr;
+ const char *r_ip;
+ int r_port;
+ int qfd;
+
+ result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
+ &sockaddr, &r_ip, &r_port);
+ if(result)
+ return result;
+ DEBUGASSERT(ctx->sockfd != CURL_SOCKET_BAD);
+
+ infof(data, "Connect socket %d over QUIC to %s:%d",
+ ctx->sockfd, r_ip, r_port);
+
+ rc = connect(ctx->sockfd, &sockaddr->sa_addr, sockaddr->addrlen);
+ if(-1 == rc) {
+ return Curl_socket_connect_result(data, r_ip, SOCKERRNO);
+ }
+
+ /* QUIC sockets need to be nonblocking */
+ (void)curlx_nonblock(ctx->sockfd, TRUE);
+ switch(sockaddr->family) {
+#if defined(__linux__) && defined(IP_MTU_DISCOVER)
+ case AF_INET: {
+ int val = IP_PMTUDISC_DO;
+ (void)setsockopt(ctx->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
+ sizeof(val));
+ break;
+ }
+#endif
+#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
+ case AF_INET6: {
+ int val = IPV6_PMTUDISC_DO;
+ (void)setsockopt(ctx->sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
+ sizeof(val));
+ break;
+ }
+#endif
+ }
+
+#ifdef DEBUG_QUICHE
+ /* initialize debug log callback only once */
+ static int debug_log_init = 0;
+ if(!debug_log_init) {
+ quiche_enable_debug_logging(quiche_debug_log, NULL);
+ debug_log_init = 1;
+ }
+#endif
+
+ ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+ if(!ctx->cfg) {
+ failf(data, "can't create quiche config");
+ return CURLE_FAILED_INIT;
+ }
+ quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
+ quiche_config_set_initial_max_data(ctx->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_bidi_local(
+ ctx->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_bidi_remote(
+ ctx->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_uni(ctx->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
+ quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
+ quiche_config_set_application_protos(ctx->cfg,
+ (uint8_t *)
+ QUICHE_H3_APPLICATION_PROTOCOL,
+ sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
+ - 1);
+
+ DEBUGASSERT(!ctx->ssl);
+ DEBUGASSERT(!ctx->sslctx);
+ ctx->sslctx = quic_ssl_ctx(data);
+ if(!ctx->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+ ctx->ssl = SSL_new(ctx->sslctx);
+ if(!ctx->ssl)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ SSL_set_app_data(ctx->ssl, cf);
+ SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
+
+ result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
+ if(result)
+ return result;
+
+ ctx->local_addrlen = sizeof(ctx->local_addr);
+ rv = getsockname(ctx->sockfd, (struct sockaddr *)&ctx->local_addr,
+ &ctx->local_addrlen);
+ if(rv == -1)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
+ sizeof(ctx->scid), NULL, 0,
+ (struct sockaddr *)&ctx->local_addr,
+ ctx->local_addrlen,
+ &sockaddr->sa_addr, sockaddr->addrlen,
+ ctx->cfg, ctx->ssl, false);
+ if(!ctx->qconn) {
+ failf(data, "can't create quiche connection");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* Known to not work on Windows */
+#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
+ {
+ int qfd;
+ (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
+ if(qfd != -1)
+ quiche_conn_set_qlog_fd(ctx->qconn, qfd,
+ "qlog title", "curl qlog");
+ }
+#endif
+
+ result = cf_flush_egress(cf, data);
+ if(result)
+ return result;
+
+ {
+ unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
+ unsigned alpn_len, offset = 0;
+
+ /* Replace each ALPN length prefix by a comma. */
+ while(offset < sizeof(alpn_protocols) - 1) {
+ alpn_len = alpn_protocols[offset];
+ alpn_protocols[offset] = ',';
+ offset += 1 + alpn_len;
+ }
+
+ CF_DEBUGF(infof(data, CFMSG(cf, "Sent QUIC client Initial, ALPN: %s"),
+ alpn_protocols + 1));
}
return CURLE_OK;
}
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
+static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+ CURLcode result = CURLE_OK;
+
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ /* Connect the UDP filter first */
+ if(!cf->next->connected) {
+ result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+ if(result || !*done)
+ return result;
+ }
+
+ if(!ctx->qconn) {
+ result = cf_connect_start(cf, data);
+ if(result)
+ goto out;
+ }
+
+ *done = FALSE;
+ result = cf_process_ingress(cf, data);
+ if(result)
+ goto out;
+
+ result = cf_flush_egress(cf, data);
+ if(result)
+ goto out;
+
+ if(quiche_conn_is_established(ctx->qconn)) {
+ result = cf_verify_peer(cf, data);
+ if(!result) {
+ DEBUGF(infof(data, "quiche established connection"));
+ cf->connected = TRUE;
+ cf->conn->alpn = CURL_HTTP_VERSION_3;
+ *done = TRUE;
+ connkeep(cf->conn, "HTTP/3 default");
+ }
+ }
+
+out:
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ if(result && result != CURLE_AGAIN) {
+ const struct Curl_sockaddr_ex *sockaddr;
+ const char *r_ip;
+ int r_port;
+
+ result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
+ &sockaddr, &r_ip, &r_port);
+ infof(data, "connect to %s port %u failed: %s",
+ r_ip, r_port, curl_easy_strerror(result));
+ }
+#endif
+ return result;
+}
+
+static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
+{
+ if(ctx) {
+ if(ctx->pending)
+ h3_clear_pending(ctx);
+ if(ctx->qconn)
+ quiche_conn_free(ctx->qconn);
+ if(ctx->h3config)
+ quiche_h3_config_free(ctx->h3config);
+ if(ctx->h3c)
+ quiche_h3_conn_free(ctx->h3c);
+ if(ctx->cfg)
+ quiche_config_free(ctx->cfg);
+ memset(ctx, 0, sizeof(*ctx));
+ }
+}
+
+static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+
(void)data;
- (void)premature;
+ if(ctx) {
+ if(ctx->qconn) {
+ (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
+ /* flushing the egress is not a failsafe way to deliver all the
+ outstanding packets, but we also don't want to get stuck here... */
+ (void)cf_flush_egress(cf, data);
+ }
+ cf_quiche_ctx_clear(ctx);
+ }
}
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(const struct Curl_easy *data)
+static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+
+ cf_quiche_ctx_clear(ctx);
+ free(ctx);
+ cf->ctx = NULL;
+}
+
+static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int query, int *pres1, void **pres2)
{
+ struct cf_quiche_ctx *ctx = cf->ctx;
+
+ switch(query) {
+ case CF_QUERY_MAX_CONCURRENT: {
+ uint64_t in_use = CONN_INUSE(cf->conn);
+ if(ctx->goaway) {
+ *pres1 = in_use;
+ }
+ else {
+ uint64_t bidi_left = quiche_conn_peer_streams_left_bidi(ctx->qconn);
+ if(bidi_left >= (INT_MAX - in_use))
+ *pres1 = INT_MAX;
+ else
+ *pres1 = (long)(bidi_left + in_use);
+ }
+ CF_DEBUGF(infof(data, CFMSG(cf, "query: MAX_CONCURRENT -> %ld"), *pres1));
+ return CURLE_OK;
+ }
+ default:
+ break;
+ }
+ return cf->next?
+ cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+ CURLE_UNKNOWN_OPTION;
+}
+
+
+static const struct Curl_cftype cft_quiche = {
+ "HTTP/3-QUICHE",
+ CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+ cf_quiche_destroy,
+ cf_quiche_connect,
+ cf_quiche_close,
+ Curl_cf_def_get_host,
+ cf_quiche_get_select_socks,
+ cf_quiche_data_pending,
+ cf_quiche_send,
+ cf_quiche_recv,
+ cf_quiche_data_event,
+ Curl_cf_def_conn_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ cf_quiche_query,
+};
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+ struct cf_quiche_ctx *ctx = NULL;
+ struct Curl_cfilter *cf = NULL, *udp_cf;
+ CURLcode result;
+
(void)data;
- return FALSE;
+ (void)conn;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ result = Curl_cf_create(&cf, &cft_quiche, ctx);
+ if(result)
+ goto out;
+
+ result = Curl_cf_udp_create(&udp_cf, data, conn, ai);
+ if(result)
+ goto out;
+
+ udp_cf->conn = cf->conn;
+ udp_cf->sockindex = cf->sockindex;
+ cf->next = udp_cf;
+
+out:
+ *pcf = (!result)? cf : NULL;
+ if(result) {
+ if(udp_cf)
+ Curl_conn_cf_discard(udp_cf, data);
+ Curl_safefree(cf);
+ Curl_safefree(ctx);
+ }
+
+ return result;
}
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex)
{
+ struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
(void)data;
- return CURLE_OK;
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_quiche)
+ return TRUE;
+ if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+ return FALSE;
+ }
+ return FALSE;
}
#endif
diff --git a/lib/vquic/quiche.h b/lib/vquic/quiche.h
index 2da65f5f4..b4e1bf783 100644
--- a/lib/vquic/quiche.h
+++ b/lib/vquic/quiche.h
@@ -31,27 +31,19 @@
#include <quiche.h>
#include <openssl/ssl.h>
-struct quic_handshake {
- char *buf; /* pointer to the buffer */
- size_t alloclen; /* size of allocation */
- size_t len; /* size of content in buffer */
- size_t nread; /* how many bytes have been read */
-};
-
-struct quicsocket {
- quiche_config *cfg;
- quiche_conn *conn;
- quiche_h3_conn *h3c;
- quiche_h3_config *h3config;
- uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
- curl_socket_t sockfd;
- uint32_t version;
- SSL_CTX *sslctx;
- SSL *ssl;
- bool h3_recving; /* TRUE when in h3-body-reading state */
- struct sockaddr_storage local_addr;
- socklen_t local_addrlen;
-};
+struct Curl_cfilter;
+struct Curl_easy;
+
+void Curl_quiche_ver(char *p, size_t len);
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex);
#endif
diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c
index e52a4f301..b9d0933c8 100644
--- a/lib/vquic/vquic.c
+++ b/lib/vquic/vquic.c
@@ -32,6 +32,9 @@
#include "urldata.h"
#include "dynbuf.h"
#include "curl_printf.h"
+#include "msh3.h"
+#include "ngtcp2.h"
+#include "quiche.h"
#include "vquic.h"
#ifdef O_BINARY
@@ -40,6 +43,17 @@
#define QLOGMODE O_WRONLY|O_CREAT
#endif
+void Curl_quic_ver(char *p, size_t len)
+{
+#ifdef USE_NGTCP2
+ Curl_ngtcp2_ver(p, len);
+#elif defined(USE_QUICHE)
+ Curl_quiche_ver(p, len);
+#elif defined(USE_MSH3)
+ Curl_msh3_ver(p, len);
+#endif
+}
+
/*
* If the QLOGDIR environment variable is set, open and return a file
* descriptor to write the log to.
@@ -84,4 +98,41 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
return CURLE_OK;
}
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai)
+{
+#ifdef USE_NGTCP2
+ return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
+#elif defined(USE_QUICHE)
+ return Curl_cf_quiche_create(pcf, data, conn, ai);
+#elif defined(USE_MSH3)
+ return Curl_cf_msh3_create(pcf, data, conn, ai);
+#else
+ *pcf = NULL;
+ (void)data;
+ (void)conn;
+ (void)ai;
+ return CURLE_NOT_BUILT_IN;
+#endif
+}
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex)
+{
+#ifdef USE_NGTCP2
+ return Curl_conn_is_ngtcp2(data, conn, sockindex);
+#elif defined(USE_QUICHE)
+ return Curl_conn_is_quiche(data, conn, sockindex);
+#elif defined(USE_MSH3)
+ return Curl_conn_is_msh3(data, conn, sockindex);
+#else
+ return ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
+ (conn->httpversion == 30));
+#endif
+}
+
#endif
diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h
index 8f599a8f4..45bab9f97 100644
--- a/lib/vquic/vquic.h
+++ b/lib/vquic/vquic.h
@@ -27,10 +27,32 @@
#include "curl_setup.h"
#ifdef ENABLE_QUIC
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_addrinfo;
+
+void Curl_quic_ver(char *p, size_t len);
+
CURLcode Curl_qlogdir(struct Curl_easy *data,
unsigned char *scid,
size_t scidlen,
int *qlogfdp);
-#endif
+
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+ const struct connectdata *conn,
+ int sockindex);
+
+#else /* ENABLE_QUIC */
+
+#define Curl_conn_is_http3(a,b,c) FALSE
+
+#endif /* !ENABLE_QUIC */
#endif /* HEADER_CURL_VQUIC_QUIC_H */
diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h
new file mode 100644
index 000000000..192ac9b59
--- /dev/null
+++ b/lib/vquic/vquic_int.h
@@ -0,0 +1,33 @@
+#ifndef HEADER_CURL_VQUIC_QUIC_INT_H
+#define HEADER_CURL_VQUIC_QUIC_INT_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, 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"
+
+#ifdef ENABLE_QUIC
+
+#endif /* !ENABLE_QUIC */
+
+#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */
diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c
index 12edaf5e8..c74138d70 100644
--- a/lib/vssh/libssh2.c
+++ b/lib/vssh/libssh2.c
@@ -144,7 +144,7 @@ const struct Curl_handler Curl_handler_scp = {
scp_disconnect, /* disconnect */
ZERO_NULL, /* readwrite */
ZERO_NULL, /* connection_check */
- ssh_attach,
+ ssh_attach, /* attach */
PORT_SSH, /* defport */
CURLPROTO_SCP, /* protocol */
CURLPROTO_SCP, /* family */
@@ -173,7 +173,7 @@ const struct Curl_handler Curl_handler_sftp = {
sftp_disconnect, /* disconnect */
ZERO_NULL, /* readwrite */
ZERO_NULL, /* connection_check */
- ssh_attach,
+ ssh_attach, /* attach */
PORT_SSH, /* defport */
CURLPROTO_SFTP, /* protocol */
CURLPROTO_SFTP, /* family */
diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index f7ffb1e0a..2c2ebfff4 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -96,6 +96,10 @@
#include "curl_memory.h"
#include "memdebug.h"
+
+#define DEBUG_ME 0
+
+
/* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS
renegotiations when built with BoringSSL. Renegotiating is non-compliant
with HTTP/2 and "an extremely dangerous protocol feature". Beware.
@@ -706,8 +710,10 @@ static int bio_cf_out_write(BIO *bio, const char *buf, int blen)
DEBUGASSERT(data);
nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
- /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
- blen, (int)nwritten, result)); */
+#if DEBUG_ME
+ DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
+ blen, (int)nwritten, result));
+#endif
BIO_clear_retry_flags(bio);
connssl->backend->io_result = result;
if(nwritten < 0) {
@@ -731,8 +737,10 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen)
return 0;
nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
- /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
- blen, (int)nread, result)); */
+#if DEBUG_ME
+ DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
+ blen, (int)nread, result));
+#endif
BIO_clear_retry_flags(bio);
connssl->backend->io_result = result;
if(nread < 0) {
@@ -2630,13 +2638,13 @@ static void ossl_trace(int direction, int ssl_ver, int content_type,
const void *buf, size_t len, SSL *ssl,
void *userp)
{
- char unknown[32];
- const char *verstr = NULL;
+ const char *verstr = "???";
struct connectdata *conn = userp;
int cf_idx = ossl_get_ssl_cf_index();
struct ssl_connect_data *connssl;
struct Curl_easy *data = NULL;
struct Curl_cfilter *cf;
+ char unknown[32];
DEBUGASSERT(cf_idx >= 0);
cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
@@ -2646,8 +2654,8 @@ static void ossl_trace(int direction, int ssl_ver, int content_type,
DEBUGASSERT(connssl->backend);
data = connssl->call_data;
- if(!conn || !data || !data->set.fdebug ||
- (direction != 0 && direction != 1))
+ if(!conn || !data || !data->set.fdebug
+ || (direction != 0 && direction != 1))
return;
switch(ssl_ver) {
@@ -3448,6 +3456,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
BIO *bio;
+ int cf_idx = ossl_get_ssl_cf_index();
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
bool sni;
@@ -3473,6 +3482,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
DEBUGASSERT(backend);
+ if(cf_idx < 0)
+ return CURLE_FAILED_INIT;
+
/* Make funny stuff to get random input */
result = ossl_seed(data);
if(result)
@@ -3785,6 +3797,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
return CURLE_OUT_OF_MEMORY;
}
+ SSL_set_ex_data(backend->handle, cf_idx, cf);
+
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
!defined(OPENSSL_NO_OCSP)
if(conn_config->verifystatus)
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
index 873ee6bac..d3526785f 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -685,20 +685,6 @@ void Curl_ssl_version(char *buffer, size_t size)
#endif
}
-/*
- * This function tries to determine connection status.
- *
- * Return codes:
- * 1 means the connection is still in place
- * 0 means the connection has been closed
- * -1 means the connection status is unknown
- */
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn)
-{
- struct Curl_cfilter *cf = Curl_ssl_cf_get_ssl(conn->cfilter[FIRSTSOCKET]);
- return cf? Curl_ssl->check_cxn(cf, data) : -1;
-}
-
void Curl_ssl_free_certinfo(struct Curl_easy *data)
{
struct curl_certinfo *ci = &data->info.certs;
@@ -1589,31 +1575,50 @@ static int ssl_cf_get_select_socks(struct Curl_cfilter *cf,
return result;
}
-static void ssl_cf_attach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
+static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ int event, int arg1, void *arg2)
{
- if(Curl_ssl->attach_data) {
- cf_ctx_set_data(cf, data);
- Curl_ssl->attach_data(cf, data);
- cf_ctx_set_data(cf, NULL);
+ (void)arg1;
+ (void)arg2;
+ switch(event) {
+ case CF_CTRL_DATA_ATTACH:
+ if(Curl_ssl->attach_data) {
+ cf_ctx_set_data(cf, data);
+ Curl_ssl->attach_data(cf, data);
+ cf_ctx_set_data(cf, NULL);
+ }
+ break;
+ case CF_CTRL_DATA_DETACH:
+ if(Curl_ssl->detach_data) {
+ cf_ctx_set_data(cf, data);
+ Curl_ssl->detach_data(cf, data);
+ cf_ctx_set_data(cf, NULL);
+ }
+ break;
+ default:
+ break;
}
+ return CURLE_OK;
}
-static void ssl_cf_detach_data(struct Curl_cfilter *cf,
- struct Curl_easy *data)
+static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- if(Curl_ssl->detach_data) {
- cf_ctx_set_data(cf, data);
- Curl_ssl->detach_data(cf, data);
- cf_ctx_set_data(cf, NULL);
- }
+ /*
+ * This function tries to determine connection status.
+ *
+ * Return codes:
+ * 1 means the connection is still in place
+ * 0 means the connection has been closed
+ * -1 means the connection status is unknown
+ */
+ return Curl_ssl->check_cxn(cf, data) != 0;
}
static const struct Curl_cftype cft_ssl = {
"SSL",
CF_TYPE_SSL,
ssl_cf_destroy,
- Curl_cf_def_setup,
ssl_cf_connect,
ssl_cf_close,
Curl_cf_def_get_host,
@@ -1621,15 +1626,16 @@ static const struct Curl_cftype cft_ssl = {
ssl_cf_data_pending,
ssl_cf_send,
ssl_cf_recv,
- ssl_cf_attach_data,
- ssl_cf_detach_data,
+ ssl_cf_cntrl,
+ cf_ssl_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
static const struct Curl_cftype cft_ssl_proxy = {
"SSL-PROXY",
CF_TYPE_SSL,
ssl_cf_destroy,
- Curl_cf_def_setup,
ssl_cf_connect,
ssl_cf_close,
Curl_cf_def_get_host,
@@ -1637,15 +1643,16 @@ static const struct Curl_cftype cft_ssl_proxy = {
ssl_cf_data_pending,
ssl_cf_send,
ssl_cf_recv,
- ssl_cf_attach_data,
- ssl_cf_detach_data,
+ ssl_cf_cntrl,
+ cf_ssl_is_alive,
+ Curl_cf_def_conn_keep_alive,
+ Curl_cf_def_query,
};
-CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex)
+static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data)
{
- struct Curl_cfilter *cf;
+ struct Curl_cfilter *cf = NULL;
struct ssl_connect_data *ctx;
CURLcode result;
@@ -1657,25 +1664,44 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
}
result = Curl_cf_create(&cf, &cft_ssl, ctx);
- if(result)
- goto out;
-
- Curl_conn_cf_add(data, conn, sockindex, cf);
-
- result = CURLE_OK;
out:
if(result)
cf_ctx_free(ctx);
+ *pcf = result? NULL : cf;
return result;
}
-#ifndef CURL_DISABLE_PROXY
-CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex)
+CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
{
struct Curl_cfilter *cf;
+ CURLcode result;
+
+ result = cf_ssl_create(&cf, data);
+ if(!result)
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+ return result;
+}
+
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ result = cf_ssl_create(&cf, data);
+ if(!result)
+ Curl_conn_cf_insert_after(cf_at, cf);
+ return result;
+}
+
+#ifndef CURL_DISABLE_PROXY
+static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf = NULL;
struct ssl_connect_data *ctx;
CURLcode result;
@@ -1686,16 +1712,36 @@ CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
}
result = Curl_cf_create(&cf, &cft_ssl_proxy, ctx);
- if(result)
- goto out;
-
- Curl_conn_cf_add(data, conn, sockindex, cf);
-
- result = CURLE_OK;
out:
if(result)
cf_ctx_free(ctx);
+ *pcf = result? NULL : cf;
+ return result;
+}
+
+CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ result = cf_ssl_proxy_create(&cf, data);
+ if(!result)
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+ return result;
+}
+
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ result = cf_ssl_proxy_create(&cf, data);
+ if(!result)
+ Curl_conn_cf_insert_after(cf_at, cf);
return result;
}
diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h
index 5ad64fcf6..1c121ee0e 100644
--- a/lib/vtls/vtls.h
+++ b/lib/vtls/vtls.h
@@ -53,6 +53,7 @@ struct Curl_ssl_session;
/* Curl_multi SSL backend-specific data; declared differently by each SSL
backend */
struct multi_ssl_backend_data;
+struct Curl_cfilter;
CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
const curl_ssl_backend ***avail);
@@ -95,7 +96,6 @@ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data);
/* init the SSL session ID cache */
CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t);
void Curl_ssl_version(char *buffer, size_t size);
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn);
/* Certificate information list handling. */
@@ -156,6 +156,9 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data);
+
CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
int sockindex);
@@ -163,6 +166,8 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+ struct Curl_easy *data);
#endif /* !CURL_DISABLE_PROXY */
/**
@@ -218,7 +223,6 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
#define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN
#define Curl_ssl_engines_list(x) NULL
#define Curl_ssl_initsessions(x,y) CURLE_OK
-#define Curl_ssl_check_cxn(d,x) 0
#define Curl_ssl_free_certinfo(x) Curl_nop_stmt
#define Curl_ssl_kill_session(x) Curl_nop_stmt
#define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN)
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 9b4d95b11..c15b0a453 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -246,7 +246,7 @@ test2300 test2301 test2302 test2303 test2304 \
\
test2400 test2401 test2402 \
\
-test2500 \
+test2500 test2501 test2502 \
\
test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
diff --git a/tests/data/test2501 b/tests/data/test2501
new file mode 100644
index 000000000..e7013e5ee
--- /dev/null
+++ b/tests/data/test2501
@@ -0,0 +1,70 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP POST
+HTTP/3
+HTTPS
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 201 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Connection: close
+Content-Length: 0
+Funny-head: yesyes
+
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+debug
+http
+http/3
+</features>
+<server>
+http
+http/3
+</server>
+ <name>
+HTTP/3 POST
+ </name>
+<setenv>
+</setenv>
+ <command>
+-k --http3 "https://%HOSTIP:%HTTP3PORT/%TESTNUMBER" -d "moo"
+</command>
+
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<stdout>
+HTTP/3 201
+date: Tue, 09 Nov 2010 14:49:00 GMT
+content-length: 0
+funny-head: yesyes
+via: 1.1 nghttpx
+
+</stdout>
+<protocol nonewline="yes">
+POST https://%HOSTIP:%HTTP3PORT/2501 HTTP/1.1
+Host: %HOSTIP:%HTTP3PORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 3
+Content-Type: application/x-www-form-urlencoded
+Via: 3 nghttpx
+
+moo
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test2502 b/tests/data/test2502
new file mode 100644
index 000000000..a67645a69
--- /dev/null
+++ b/tests/data/test2502
@@ -0,0 +1,104 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP/3
+multi
+verbose logs
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data1 crlf=yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: server.example.com
+Content-Length: 47
+
+file contents should appear once for each file
+</data1>
+<data2>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: server.example.com
+Content-Length: 47
+
+file contents should appear once for each file
+</data2>
+<data3>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: server.example.com
+Content-Length: 47
+
+file contents should appear once for each file
+</data3>
+<data4>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: server.example.com
+Content-Length: 47
+
+file contents should appear once for each file
+</data4>
+</reply>
+
+# Client-side
+<client>
+<features>
+http/3
+</features>
+<server>
+http
+http/3
+</server>
+<tool>
+lib%TESTNUMBER
+</tool>
+ <name>
+HTTP GET multiple over HTTP/3
+ </name>
+ <command>
+https://%HOSTIP:%HTTP3PORT/path/%TESTNUMBER %HOSTIP %HTTP3PORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="yes">
+GET https://localhost:%HTTP3PORT/path/%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Via: 3 nghttpx
+
+GET https://localhost:%HTTP3PORT/path/%TESTNUMBER0002 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Via: 3 nghttpx
+
+GET https://localhost:%HTTP3PORT/path/%TESTNUMBER0003 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Via: 3 nghttpx
+
+GET https://localhost:%HTTP3PORT/path/%TESTNUMBER0004 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Via: 3 nghttpx
+
+</protocol>
+<strip>
+^Host:.*
+</strip>
+<file name="log/stderr%TESTNUMBER" mode="text">
+* Connection #0 to host localhost left intact
+* Connection #0 to host localhost left intact
+* Connection #0 to host localhost left intact
+* Connection #0 to host localhost left intact
+</file>
+<stripfile>
+$_ = '' if (($_ !~ /left intact/) && ($_ !~ /Closing connection/))
+</stripfile>
+</verify>
+</testcase>
diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
index 022ad3663..13f94ab63 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -71,6 +71,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib1945 lib1946 lib1947 lib1948 lib1955 lib1956 lib1957 lib1958 lib1959 \
lib2301 lib2302 lib2304 \
lib2402 \
+ lib2502 \
lib3010 lib3025 lib3026 lib3027 \
lib3100 lib3101
@@ -803,6 +804,10 @@ lib2402_SOURCES = lib2402.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib2402_LDADD = $(TESTUTIL_LIBS)
lib2402_CPPFLAGS = $(AM_CPPFLAGS) -DLIB2402
+lib2502_SOURCES = lib2502.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib2502_LDADD = $(TESTUTIL_LIBS)
+lib2502_CPPFLAGS = $(AM_CPPFLAGS) -DLIB2502
+
lib3010_SOURCES = lib3010.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib3010_LDADD = $(TESTUTIL_LIBS)
lib3010_CPPFLAGS = $(AM_CPPFLAGS)
diff --git a/tests/libtest/lib2502.c b/tests/libtest/lib2502.c
new file mode 100644
index 000000000..426b46b94
--- /dev/null
+++ b/tests/libtest/lib2502.c
@@ -0,0 +1,141 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2013 - 2022, Linus Nielsen Feltzing <linus@haxx.se>
+ *
+ * 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 "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+
+#define NUM_HANDLES 4
+
+int test(char *URL)
+{
+ int res = 0;
+ CURL *curl[NUM_HANDLES] = {0};
+ int running;
+ CURLM *m = NULL;
+ int i;
+ char target_url[256];
+ char dnsentry[256];
+ struct curl_slist *slist = NULL;
+ char *port = libtest_arg3;
+ char *address = libtest_arg2;
+
+ (void)URL;
+
+ msnprintf(dnsentry, sizeof(dnsentry), "localhost:%s:%s",
+ port, address);
+ printf("%s\n", dnsentry);
+ slist = curl_slist_append(slist, dnsentry);
+ if(!slist) {
+ fprintf(stderr, "curl_slist_append() failed\n");
+ goto test_cleanup;
+ }
+
+ start_test_timing();
+
+ global_init(CURL_GLOBAL_ALL);
+
+ multi_init(m);
+
+ multi_setopt(m, CURLMOPT_MAXCONNECTS, 1L);
+
+ /* get NUM_HANDLES easy handles */
+ for(i = 0; i < NUM_HANDLES; i++) {
+ /* get an easy handle */
+ easy_init(curl[i]);
+ /* specify target */
+ msnprintf(target_url, sizeof(target_url),
+ "https://localhost:%s/path/2502%04i",
+ port, i + 1);
+ target_url[sizeof(target_url) - 1] = '\0';
+ easy_setopt(curl[i], CURLOPT_URL, target_url);
+ /* go http2 */
+ easy_setopt(curl[i], CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
+ easy_setopt(curl[i], CURLOPT_CONNECTTIMEOUT_MS, (long)5000);
+ easy_setopt(curl[i], CURLOPT_CAINFO, "./certs/EdelCurlRoot-ca.cacert");
+ /* wait for first connection establised to see if we can share it */
+ easy_setopt(curl[i], CURLOPT_PIPEWAIT, 1L);
+ /* go verbose */
+ easy_setopt(curl[i], CURLOPT_VERBOSE, 1L);
+ /* include headers */
+ easy_setopt(curl[i], CURLOPT_HEADER, 1L);
+
+ easy_setopt(curl[i], CURLOPT_RESOLVE, slist);
+ }
+
+ fprintf(stderr, "Start at URL 0\n");
+
+ for(i = 0; i < NUM_HANDLES; i++) {
+ /* add handle to multi */
+ multi_add_handle(m, curl[i]);
+
+ for(;;) {
+ struct timeval interval;
+ fd_set rd, wr, exc;
+ int maxfd = -99;
+
+ interval.tv_sec = 1;
+ interval.tv_usec = 0;
+
+ multi_perform(m, &running);
+
+ abort_on_test_timeout();
+
+ if(!running)
+ break; /* done */
+
+ FD_ZERO(&rd);
+ FD_ZERO(&wr);
+ FD_ZERO(&exc);
+
+ multi_fdset(m, &rd, &wr, &exc, &maxfd);
+
+ /* At this point, maxfd is guaranteed to be greater or equal than -1. */
+
+ select_test(maxfd + 1, &rd, &wr, &exc, &interval);
+
+ abort_on_test_timeout();
+ }
+ wait_ms(1); /* to ensure different end times */
+ }
+
+test_cleanup:
+
+ /* proper cleanup sequence - type PB */
+
+ for(i = 0; i < NUM_HANDLES; i++) {
+ curl_multi_remove_handle(m, curl[i]);
+ curl_easy_cleanup(curl[i]);
+ }
+
+ curl_slist_free_all(slist);
+
+ curl_multi_cleanup(m);
+ curl_global_cleanup();
+
+ return res;
+}