aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/curl/lib/connect.c
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2024-01-18 11:28:56 +0100
committerGitHub <noreply@github.com>2024-01-18 11:28:56 +0100
commit9d0a3761b3201e0d9db879a7adf91876ebdb0564 (patch)
tree541d11ac878c18efd7ebca81e35112aa0fef995b /contrib/libs/curl/lib/connect.c
parent404ef8886ecc9736bc58ade6da2fbd83b486a408 (diff)
downloadydb-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.c2392
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;
}