diff options
author | AlexSm <alex@ydb.tech> | 2024-01-18 11:28:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-18 11:28:56 +0100 |
commit | 9d0a3761b3201e0d9db879a7adf91876ebdb0564 (patch) | |
tree | 541d11ac878c18efd7ebca81e35112aa0fef995b /contrib/libs/curl/lib/connect.c | |
parent | 404ef8886ecc9736bc58ade6da2fbd83b486a408 (diff) | |
download | ydb-9d0a3761b3201e0d9db879a7adf91876ebdb0564.tar.gz |
Library import 8 (#1074)
* Library import 8
* Add contrib/libs/cxxsupp/libcxx/include/__verbose_abort
Diffstat (limited to 'contrib/libs/curl/lib/connect.c')
-rw-r--r-- | contrib/libs/curl/lib/connect.c | 2392 |
1 files changed, 1059 insertions, 1333 deletions
diff --git a/contrib/libs/curl/lib/connect.c b/contrib/libs/curl/lib/connect.c index ac007c61b0..ec5ab71d49 100644 --- a/contrib/libs/curl/lib/connect.c +++ b/contrib/libs/curl/lib/connect.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -48,13 +48,6 @@ #include <arpa/inet.h> #endif -#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) -#include <sys/filio.h> -#endif -#ifdef NETWARE -#undef in_addr_t -#define in_addr_t unsigned long -#endif #ifdef __VMS #include <in.h> #include <inet.h> @@ -64,21 +57,26 @@ #include "sendf.h" #include "if2ip.h" #include "strerror.h" +#include "cfilters.h" #include "connect.h" +#include "cf-haproxy.h" +#include "cf-https-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 */ @@ -86,86 +84,9 @@ #include "curl_memory.h" #include "memdebug.h" -static bool verifyconnect(curl_socket_t sockfd, int *error); - -#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 +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #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 @@ -237,383 +158,6 @@ timediff_t Curl_timeleft(struct Curl_easy *data, return timeout_ms; } -static CURLcode bindlocal(struct Curl_easy *data, - curl_socket_t sockfd, int af, unsigned int scope) -{ - struct connectdata *conn = data->conn; - 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 */ - si6->sin6_scope_id = atoi(scope_ptr); -#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, @@ -632,6 +176,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 && 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, @@ -689,708 +255,517 @@ 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 { + curl_off_t 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 + if(conn->connection_id == f->id_tofind) { + f->found = conn; + return 1; + } + return 0; } -/* 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) +/* + * 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) { - /* '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); -} - -/* After a TCP connection to the proxy has been verified, this function does - the next magic steps. If 'done' isn't set TRUE, it is not done yet and - must be called again. - - Note: this function's sub-functions call failf() + DEBUGASSERT(data); -*/ -static CURLcode connect_SOCKS(struct Curl_easy *data, int sockindex, - bool *done) -{ - CURLcode result = CURLE_OK; -#ifndef CURL_DISABLE_PROXY - CURLproxycode pxresult = CURLPX_OK; - struct connectdata *conn = data->conn; - if(conn->bits.socksproxy) { - /* for the secondary socket (FTP), use the "connect to host" - * but ignore the "connect to port" (use the secondary port) - */ - const char * const host = - conn->bits.httpproxy ? - conn->http_proxy.host.name : - conn->bits.conn_to_host ? - conn->conn_to_host.name : - sockindex == SECONDARYSOCKET ? - conn->secondaryhostname : conn->host.name; - const int port = - conn->bits.httpproxy ? (int)conn->http_proxy.port : - sockindex == SECONDARYSOCKET ? conn->secondary_port : - conn->bits.conn_to_port ? conn->conn_to_port : - conn->remote_port; - switch(conn->socks_proxy.proxytype) { - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - pxresult = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd, - host, port, sockindex, data, done); - break; + /* 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; - case CURLPROXY_SOCKS4: - case CURLPROXY_SOCKS4A: - pxresult = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex, - data, done); - break; + 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); - default: - failf(data, "unknown proxytype option given"); - result = CURLE_COULDNT_CONNECT; - } /* switch proxytype */ - if(pxresult) { - result = CURLE_PROXY; - data->info.pxcode = pxresult; + if(!find.found) { + data->state.lastconnect_id = -1; + return CURL_SOCKET_BAD; } - } - else -#else - (void)data; - (void)sockindex; -#endif /* CURL_DISABLE_PROXY */ - *done = TRUE; /* no SOCKS proxy, so consider us connected */ - return result; + c = find.found; + if(connp) + /* only store this if the caller cares for it */ + *connp = c; + return c->sock[FIRSTSOCKET]; + } + return CURL_SOCKET_BAD; } /* - * post_SOCKS() is called after a successful connect to the peer, which - * *could* be a SOCKS proxy + * Curl_conncontrol() marks streams or connection for closure. */ -static void post_SOCKS(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected) +void Curl_conncontrol(struct connectdata *conn, + int ctrl /* see defines in header */ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + , const char *reason +#endif + ) { - conn->bits.tcpconnect[sockindex] = TRUE; - - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_updateconninfo(data, conn, conn->sock[sockindex]); - Curl_verboseconnect(data, conn); - data->info.numconnects++; /* to track the number of connections made */ + /* 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 + 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 */ + } } -/* - * Curl_is_connected() checks if the socket has connected. +/** + * job walking the matching addr infos, creating a sub-cfilter with the + * provided method `cf_create` and running setup/connect on it. */ +struct eyeballer { + const char *name; + const struct Curl_addrinfo *first; /* complete address list, not owned */ + 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 backup for */ + timediff_t delay_ms; /* delay until start */ + struct curltime started; /* start of current attempt */ + timediff_t timeoutms; /* timeout for current attempt */ + expire_id timeout_id; /* ID for Curl_expire() */ + CURLcode result; + int error; + BIT(rewinded); /* if we rewinded the addr list */ + BIT(has_started); /* attempts have started */ + BIT(is_done); /* out of addresses/time */ + BIT(connected); /* cf has connected */ + BIT(inconclusive); /* connect was not a hard failure, we + * might talk to a restarting server */ +}; -CURLcode Curl_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected) -{ - CURLcode result = CURLE_OK; - timediff_t allow; - int error = 0; - struct curltime now; - int rc = 0; - unsigned int i; - - DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); - - *connected = FALSE; /* a very negative world view is best */ - - if(conn->bits.tcpconnect[sockindex]) { - /* we are connected already! */ - *connected = TRUE; - return CURLE_OK; - } - - now = Curl_now(); - - if(SOCKS_STATE(conn->cnnct.state)) { - /* still doing SOCKS */ - result = connect_SOCKS(data, sockindex, connected); - if(!result && *connected) - post_SOCKS(data, conn, sockindex, connected); - return result; - } - 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_SOCKS(data, conn, sockindex, connected); - 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 +typedef enum { + SCFST_INIT, + SCFST_WAITING, + SCFST_DONE +} cf_connect_state; + +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[2]; + struct eyeballer *winner; + struct curltime started; +}; - /* check socket for connect */ - rc = SOCKET_WRITABLE(conn->tempsock[i], 0); - } +/* when there are more than one IP address left to use, this macro returns how + much of the given timeout to spend on *this* attempt */ +#define TIMEOUT_LARGE 600 +#define USETIME(ms) ((ms > TIMEOUT_LARGE) ? (ms / 2) : ms) + +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) +{ + struct eyeballer *baller; - 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; - } + *pballer = NULL; + baller = calloc(1, sizeof(*baller) + 1000); + if(!baller) + return CURLE_OUT_OF_MEMORY; - /* 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); - } - } - 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; + baller->name = ((ai_family == AF_INET)? "ipv4" : ( #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; - } - - /* see if we need to kick off any SOCKS proxy magic once we - connected */ - result = connect_SOCKS(data, sockindex, connected); - if(result || !*connected) - return result; - - post_SOCKS(data, conn, sockindex, connected); - - return CURLE_OK; - } - } - else if(rc & CURL_CSELECT_ERR) { - (void)verifyconnect(conn->tempsock[i], &error); - } - - /* - * 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 -#endif - infof(data, "connect to %s port %u failed: %s", - ipaddress, conn->port, - Curl_strerror(error, buffer, sizeof(buffer))); -#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; - } - - 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)); - - 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 */ - - return result; + (ai_family == AF_INET6)? "ipv6" : +#endif + "ip")); + baller->cf_create = cf_create; + baller->first = baller->addr = addr; + baller->ai_family = ai_family; + baller->primary = primary; + baller->delay_ms = delay_ms; + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)? + USETIME(timeout_ms) : timeout_ms; + baller->timeout_id = timeout_id; + baller->result = CURLE_COULDNT_CONNECT; + + *pballer = baller; + return CURLE_OK; } -static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) +static void baller_close(struct eyeballer *baller, + struct Curl_easy *data) { -#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 + if(baller && baller->cf) { + Curl_conn_cf_discard_chain(&baller->cf, data); + } } -#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_free(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_close(baller, data); + free(baller); } } -#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_rewind(struct eyeballer *baller) { - 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; + baller->rewinded = TRUE; + baller->addr = baller->first; + baller->inconclusive = FALSE; +} - 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 cf_he_ctx *ctx = cf->ctx; + 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, + ctx->transport); if(result) - return result; + goto out; - /* store remote address and port used in this connection attempt */ - if(!Curl_addr2string((struct sockaddr*)&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; + /* 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); - - nosigpipe(data, sockfd); + if(addr_next_match(baller->addr, baller->ai_family)) { + Curl_expire(data, baller->timeoutms, baller->timeout_id); + } - Curl_sndbufset(sockfd); +out: + if(result) { + CURL_TRC_CF(data, cf, "%s failed", baller->name); + baller_close(baller, data); + } + if(cf_prev) + Curl_conn_cf_discard_chain(&cf_prev, data); + baller->result = result; +} - if(is_tcp && data->set.tcp_keepalive) - tcpkeepalive(data, 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, + timediff_t timeoutms) +{ + baller->error = 0; + baller->connected = FALSE; + baller->has_started = TRUE; + + while(baller->addr) { + baller->started = Curl_now(); + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? + USETIME(timeoutms) : timeoutms; + 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, + timediff_t timeoutms) +{ + if(cf->sockindex == FIRSTSOCKET) { + baller_next_addr(baller); + /* If we get inconclusive answers from the server(s), we make + * a second iteration over the address list */ + if(!baller->addr && baller->inconclusive && !baller->rewinded) + baller_rewind(baller); + baller_start(cf, data, baller, timeoutms); + } + 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) +{ + (void)cf; + *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, baller->started) >= baller->timeoutms) { + infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T + "ms, move on!", baller->name, baller->timeoutms); +#if defined(ETIMEDOUT) + baller->error = ETIMEDOUT; #endif - ) { - result = bindlocal(data, sockfd, addr.family, - Curl_ipv6_scope((struct sockaddr*)&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; } - return result; } + else if(baller->result == CURLE_WEIRD_SERVER_REPLY) + baller->inconclusive = TRUE; } + return baller->result; +} - /* set socket non-blocking */ - (void)curlx_nonblock(sockfd, TRUE); +/* + * 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; + struct curltime now; + size_t i; + int ongoing, not_started; + const char *hostname; + + /* 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 < ARRAYSIZE(ctx->baller); 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) { + ++not_started; + continue; + } + baller->result = baller_connect(cf, data, baller, &now, connected); + CURL_TRC_CF(data, cf, "%s connect -> %d, connected=%d", + baller->name, 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); + } + baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + CURL_TRC_CF(data, cf, "%s done", baller->name); + } + else { + /* next attempt was started */ + CURL_TRC_CF(data, cf, "%s trying next", baller->name); + ++ongoing; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } } + } - 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, check the time before we might + * start new ballers or return ok. */ + if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { + failf(data, "Connection timeout after %" CURL_FORMAT_CURL_OFF_T " 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 < ARRAYSIZE(ctx->baller); 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) { + baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + CURL_TRC_CF(data, cf, "%s done", baller->name); + } + else { + CURL_TRC_CF(data, cf, "%s starting (timeout=%" + CURL_FORMAT_TIMEDIFF_T "ms)", + baller->name, baller->timeoutms); + ++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. */ + CURL_TRC_CF(data, cf, "all eyeballers failed"); + result = CURLE_COULDNT_CONNECT; + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller) + continue; + CURL_TRC_CF(data, cf, "%s assess started=%d, result=%d", + baller->name, baller->has_started, baller->result); + if(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 */ @@ -1398,332 +773,683 @@ CURLcode Curl_connecthost(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } - 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; + ctx->started = Curl_now(); + /* 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; - - Curl_expire(data, data->set.happy_eyeballs_timeout, - EXPIRE_HAPPY_EYEBALLS); + CURL_TRC_CF(data, cf, "created %s (timeout %" + CURL_FORMAT_TIMEDIFF_T "ms)", + ctx->baller[0]->name, ctx->baller[0]->timeoutms); + 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_TRC_CF(data, cf, "created %s (timeout %" + CURL_FORMAT_TIMEDIFF_T "ms)", + ctx->baller[1]->name, ctx->baller[1]->timeoutms); + Curl_expire(data, data->set.happy_eyeballs_timeout, + EXPIRE_HAPPY_EYEBALLS); + } return CURLE_OK; } -struct connfind { - long id_tofind; - struct connectdata *found; -}; +static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; -static int conn_is_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) + DEBUGASSERT(ctx); + DEBUGASSERT(data); + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + baller_free(ctx->baller[i], data); + ctx->baller[i] = NULL; + } + baller_free(ctx->winner, data); + ctx->winner = NULL; +} + +static void cf_he_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { - struct connfind *f = (struct connfind *)param; - (void)data; - if(conn->connection_id == f->id_tofind) { - f->found = conn; - return 1; + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + if(!cf->connected) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + Curl_conn_cf_adjust_pollset(baller->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); } - return 0; } -/* - * 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) +static CURLcode cf_he_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) { - DEBUGASSERT(data); + struct cf_he_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; - /* 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(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - 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); + (void)blocking; /* TODO: do we want to support this? */ + DEBUGASSERT(ctx); + *done = FALSE; - if(!find.found) { - data->state.lastconnect_id = -1; - return CURL_SOCKET_BAD; - } + switch(ctx->state) { + case SCFST_INIT: + DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data)); + DEBUGASSERT(!cf->connected); + result = start_connect(cf, data, ctx->remotehost); + if(result) + return result; + ctx->state = SCFST_WAITING; + /* FALLTHROUGH */ + case SCFST_WAITING: + result = is_connected(cf, data, done); + if(!result && *done) { + DEBUGASSERT(ctx->winner); + DEBUGASSERT(ctx->winner->cf); + DEBUGASSERT(ctx->winner->cf->connected); + /* 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); + + if(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: + *done = TRUE; + break; + } + return result; +} - c = find.found; - if(connp) - /* only store this if the caller cares for it */ - *connp = c; - return c->sock[FIRSTSOCKET]; +static void cf_he_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "close"); + cf_he_ctx_clear(cf, data); + cf->connected = FALSE; + ctx->state = SCFST_INIT; + + if(cf->next) { + cf->next->cft->do_close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); } - return CURL_SOCKET_BAD; } -/* - * Check if a connection seems to be alive. - */ -bool Curl_connalive(struct connectdata *conn) +static bool cf_he_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - /* First determine if ssl */ - if(conn->ssl[FIRSTSOCKET].use) { - /* use the SSL context */ - if(!Curl_ssl_check_cxn(conn)) - return false; /* FIN received */ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + if(cf->connected) + return cf->next->cft->has_data_pending(cf->next, data); + + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + if(baller->cf->cft->has_data_pending(baller->cf, data)) + return TRUE; } -/* 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 */ + return FALSE; +} + +static struct curltime get_max_baller_time(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct curltime t, tmax; + size_t i; + + memset(&tmax, 0, sizeof(tmax)); + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + + memset(&t, 0, sizeof(t)); + if(baller && baller->cf && + !baller->cf->cft->query(baller->cf, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + tmax = t; } } -#endif - return true; + return tmax; } -/* - * Close a socket. - * - * 'conn' can be NULL, beware! - */ -int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sock) +static CURLcode cf_he_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) { - 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; + struct cf_he_ctx *ctx = cf->ctx; + + if(!cf->connected) { + switch(query) { + case CF_QUERY_CONNECT_REPLY_MS: { + int reply_ms = -1; + size_t i; + + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + int breply_ms; + + if(baller && baller->cf && + !baller->cf->cft->query(baller->cf, data, query, + &breply_ms, NULL)) { + if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) + reply_ms = breply_ms; + } + } + *pres1 = reply_ms; + CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1); + return CURLE_OK; + } + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); + return CURLE_OK; + } + default: + break; } } - if(conn) - /* tell the multi-socket code about this */ - Curl_multi_closed(data, sock); + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} - sclose(sock); +static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; - return 0; + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + cf_he_ctx_clear(cf, data); + } + /* release any resources held in state */ + Curl_safefree(ctx); } -/* - * 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! - * +struct Curl_cftype Curl_cft_happy_eyeballs = { + "HAPPY-EYEBALLS", + 0, + CURL_LOG_LVL_NONE, + cf_he_destroy, + cf_he_connect, + cf_he_close, + Curl_cf_def_get_host, + cf_he_adjust_pollset, + 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, + cf_he_query, +}; + +/** + * 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_socket(struct Curl_easy *data, - const struct Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd) +static CURLcode +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, + int transport) { - 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. - */ + struct cf_he_ctx *ctx = NULL; + CURLcode result; - 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; + (void)data; + (void)conn; + *pcf = NULL; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - 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); + ctx->transport = transport; + ctx->cf_create = cf_create; + ctx->remotehost = remotehost; + + result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); + +out: + if(result) { + Curl_safefree(*pcf); + Curl_safefree(ctx); } - else - /* opensocket callback not set, so simply create the socket now */ - *sockfd = socket(addr->family, addr->socktype, addr->protocol); + return result; +} - if(*sockfd == CURL_SOCKET_BAD) - /* no socket, no connection */ - return CURLE_COULDNT_CONNECT; +struct transport_provider { + int transport; + cf_ip_connect_create *cf_create; +}; - 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; - } +static +#ifndef DEBUGBUILD +const #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; - } +struct transport_provider transport_providers[] = { + { TRNSPRT_TCP, Curl_cf_tcp_create }, +#ifdef ENABLE_QUIC + { TRNSPRT_QUIC, Curl_cf_quic_create }, +#endif +#ifndef CURL_DISABLE_TFTP + { TRNSPRT_UDP, Curl_cf_udp_create }, +#endif +#ifdef USE_UNIX_SOCKETS + { TRNSPRT_UNIX, Curl_cf_unix_create }, #endif +}; + +static cf_ip_connect_create *get_cf_create(int transport) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) + return transport_providers[i].cf_create; + } + 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) { + CURL_TRC_CF(data, cf_at, "unsupported transport type %d", transport); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = cf_happy_eyeballs_create(&cf, data, cf_at->conn, + cf_create, remotehost, + transport); + 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; + int transport; +}; + +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; + } + + /* 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, ctx->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(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) + && !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 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; + 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; } -#endif + ctx->state = CF_SETUP_DONE; + cf->connected = TRUE; + *done = TRUE; return CURLE_OK; } -/* - * 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 void cf_setup_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { - /* 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 */ + struct cf_setup_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "close"); + cf->connected = FALSE; + ctx->state = CF_SETUP_INIT; + + if(cf->next) { + cf->next->cft->do_close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); } } -/* Data received can be cached at various levels, so check them all here. */ -bool Curl_conn_data_pending(struct connectdata *conn, int sockindex) +static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - int readable; - DEBUGASSERT(conn); + struct cf_setup_ctx *ctx = cf->ctx; + + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + Curl_safefree(ctx); +} + + +struct Curl_cftype Curl_cft_setup = { + "SETUP", + 0, + CURL_LOG_LVL_NONE, + cf_setup_destroy, + cf_setup_connect, + cf_setup_close, + Curl_cf_def_get_host, + Curl_cf_def_adjust_pollset, + 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, +}; + +static CURLcode cf_setup_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf = NULL; + struct cf_setup_ctx *ctx; + CURLcode result = CURLE_OK; - if(Curl_ssl_data_pending(conn, sockindex) || - Curl_recv_has_postponed_data(conn, sockindex)) - return true; + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->state = CF_SETUP_INIT; + ctx->remotehost = remotehost; + ctx->ssl_mode = ssl_mode; + ctx->transport = transport; + + result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); + if(result) + goto out; + ctx = NULL; + +out: + *pcf = result? NULL : cf; + free(ctx); + return result; +} - readable = SOCKET_READABLE(conn->sock[sockindex], 0); - return (readable > 0 && (readable & CURL_CSELECT_IN)); +static CURLcode cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +#ifdef DEBUGBUILD +/* used by unit2600.c */ +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) { + transport_providers[i].cf_create = cf_create; + return; + } + } +} +#endif /* DEBUGBUILD */ + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + return result; +} + +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode) +{ + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + DEBUGASSERT(conn->handler); + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + if(!conn->cfilter[sockindex] && + conn->handler->protocol == CURLPROTO_HTTPS) { + DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); + result = Curl_cf_https_setup(data, conn, sockindex, remotehost); + if(result) + goto out; + } +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ + + /* Still no cfilter set, apply default. */ + if(!conn->cfilter[sockindex]) { + result = cf_setup_add(data, conn, sockindex, remotehost, + conn->transport, ssl_mode); + if(result) + goto out; + } + + DEBUGASSERT(conn->cfilter[sockindex]); +out: + return result; } |