diff options
Diffstat (limited to 'libs/libcurl/src/connect.c')
-rw-r--r-- | libs/libcurl/src/connect.c | 850 |
1 files changed, 476 insertions, 374 deletions
diff --git a/libs/libcurl/src/connect.c b/libs/libcurl/src/connect.c index 2b5719d123..3edb71eb72 100644 --- a/libs/libcurl/src/connect.c +++ b/libs/libcurl/src/connect.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at http://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -28,8 +28,10 @@ #ifdef HAVE_SYS_UN_H #include <sys/un.h> /* for sockaddr_un */ #endif -#ifdef HAVE_NETINET_TCP_H -#include <netinet/tcp.h> /* for TCP_NODELAY */ +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> #endif #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> @@ -56,28 +58,27 @@ #include <inet.h> #endif -#define _MPRINTF_REPLACE /* use our functions only */ -#include <curl/mprintf.h> - #include "urldata.h" #include "sendf.h" #include "if2ip.h" #include "strerror.h" #include "connect.h" -#include "curl_memory.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 "sslgen.h" /* for Curl_ssl_check_cxn() */ +#include "vtls/vtls.h" /* for Curl_ssl_check_cxn() */ #include "progress.h" #include "warnless.h" #include "conncache.h" #include "multihandle.h" +#include "system_win32.h" -/* The last #include file should be: */ +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" #include "memdebug.h" #ifdef __SYMBIAN32__ @@ -94,7 +95,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error); #define KEEPALIVE_FACTOR(x) #endif -#if defined(HAVE_WINSOCK_H) && !defined(SIO_KEEPALIVE_VALS) +#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) struct tcp_keepalive { @@ -105,7 +106,7 @@ struct tcp_keepalive { #endif static void -tcpkeepalive(struct SessionHandle *data, +tcpkeepalive(struct Curl_easy *data, curl_socket_t sockfd) { int optval = data->set.tcp_keepalive?1:0; @@ -164,8 +165,7 @@ tcpkeepalive(struct SessionHandle *data, static CURLcode singleipconnect(struct connectdata *conn, const Curl_addrinfo *ai, /* start connecting to this */ - curl_socket_t *sock, - bool *connected); + curl_socket_t *sock); /* * Curl_timeleft() returns the amount of milliseconds left allowed for the @@ -181,13 +181,13 @@ singleipconnect(struct connectdata *conn, * * @unittest: 1303 */ -long Curl_timeleft(struct SessionHandle *data, - struct timeval *nowp, - bool duringconnect) +timediff_t Curl_timeleft(struct Curl_easy *data, + struct curltime *nowp, + bool duringconnect) { int timeout_set = 0; - long timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0; - struct timeval now; + timediff_t timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0; + struct curltime now; /* if a timeout is set, use the most restrictive one */ @@ -196,7 +196,7 @@ long Curl_timeleft(struct SessionHandle *data, if(duringconnect && (data->set.connecttimeout > 0)) timeout_set |= 2; - switch (timeout_set) { + switch(timeout_set) { case 1: timeout_ms = data->set.timeout; break; @@ -220,12 +220,17 @@ long Curl_timeleft(struct SessionHandle *data, } if(!nowp) { - now = Curl_tvnow(); + now = Curl_now(); nowp = &now; } /* subtract elapsed time */ - timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startsingle); + if(duringconnect) + /* since this most recent connect started */ + timeout_ms -= Curl_timediff(*nowp, data->progress.t_startsingle); + else + /* since the entire operation started */ + timeout_ms -= Curl_timediff(*nowp, data->progress.t_startop); if(!timeout_ms) /* avoid returning 0 as that means no timeout! */ return -1; @@ -233,49 +238,10 @@ long Curl_timeleft(struct SessionHandle *data, return timeout_ms; } -/* - * checkconnect() checks for a TCP connect on the given socket. - * It returns: - */ - -enum chkconn_t { - CHKCONN_SELECT_ERROR = -1, - CHKCONN_CONNECTED = 0, - CHKCONN_IDLE = 1, - CHKCONN_FDSET_ERROR = 2 -}; - -static enum chkconn_t -checkconnect(curl_socket_t sockfd) -{ - int rc; -#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(sockfd, NULL); -#endif - - rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, 0); - - if(-1 == rc) - /* error, no connect here, try next */ - return CHKCONN_SELECT_ERROR; - - else if(rc & CURL_CSELECT_ERR) - /* error condition caught */ - return CHKCONN_FDSET_ERROR; - - else if(rc) - return CHKCONN_CONNECTED; - - return CHKCONN_IDLE; -} - static CURLcode bindlocal(struct connectdata *conn, - curl_socket_t sockfd, int af) + curl_socket_t sockfd, int af, unsigned int scope) { - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct Curl_sockaddr_storage sa; struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ @@ -285,19 +251,13 @@ static CURLcode bindlocal(struct connectdata *conn, struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif - struct Curl_dns_entry *h=NULL; + 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; - 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!"; /************************************************************* * Select device to bind socket to @@ -309,6 +269,13 @@ static CURLcode bindlocal(struct connectdata *conn, 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; @@ -320,7 +287,36 @@ static CURLcode bindlocal(struct connectdata *conn, /* interface */ if(!is_host) { - switch(Curl_if2ip(af, conn->scope, dev, myhost, sizeof(myhost))) { +#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, scope, conn->scope_id, dev, + myhost, sizeof(myhost))) { case IF2IP_NOT_FOUND: if(is_interface) { /* Do not fall back to treating it as a host name */ @@ -339,30 +335,6 @@ static CURLcode bindlocal(struct connectdata *conn, infof(data, "Local Interface %s is ip %s using address family %i\n", dev, myhost, af); done = 1; - -#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. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev)+1) != 0) { - error = SOCKERRNO; - infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;" - " will do regular bind\n", - dev, error, Curl_strerror(conn, error)); - /* This is typically "errno 1, error: Operation not permitted" if - you're not running as root or another suitable privileged - user */ - } -#endif break; } } @@ -409,7 +381,7 @@ static CURLcode bindlocal(struct connectdata *conn, if(done > 0) { #ifdef ENABLE_IPV6 - /* ipv6 address */ + /* IPv6 address */ if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID char *scope_ptr = strchr(myhost, '%'); @@ -432,7 +404,7 @@ static CURLcode bindlocal(struct connectdata *conn, } else #endif - /* ipv4 address */ + /* IPv4 address */ if((af == AF_INET) && (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { si4->sin_family = AF_INET; @@ -442,6 +414,10 @@ static CURLcode bindlocal(struct connectdata *conn, } 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; } @@ -573,42 +549,60 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) more address exists or error */ static CURLcode trynextip(struct connectdata *conn, int sockindex, - bool *connected) + int tempindex) { - curl_socket_t sockfd; - Curl_addrinfo *ai; + const int other = tempindex ^ 1; + 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->sock[sockindex]; - conn->sock[sockindex] = CURL_SOCKET_BAD; - *connected = FALSE; + curl_socket_t fd_to_close = conn->tempsock[tempindex]; + conn->tempsock[tempindex] = CURL_SOCKET_BAD; - if(sockindex != FIRSTSOCKET) { - Curl_closesocket(conn, fd_to_close); - return CURLE_COULDNT_CONNECT; /* no next */ - } + if(sockindex == FIRSTSOCKET) { + Curl_addrinfo *ai = NULL; + int family = AF_UNSPEC; - /* try the next address */ - ai = conn->ip_addr->ai_next; - - while(ai) { - CURLcode res = singleipconnect(conn, ai, &sockfd, connected); - if(res) - return res; - if(sockfd != CURL_SOCKET_BAD) { - /* store the new socket descriptor */ - conn->sock[sockindex] = sockfd; - conn->ip_addr = ai; - Curl_closesocket(conn, fd_to_close); - return CURLE_OK; + if(conn->tempaddr[tempindex]) { + /* find next address in the same protocol family */ + family = conn->tempaddr[tempindex]->ai_family; + ai = conn->tempaddr[tempindex]->ai_next; + } +#ifdef ENABLE_IPV6 + else if(conn->tempaddr[0]) { + /* happy eyeballs - try the other protocol family */ + int firstfamily = conn->tempaddr[0]->ai_family; + family = (firstfamily == AF_INET) ? AF_INET6 : AF_INET; + ai = conn->tempaddr[0]->ai_next; + } +#endif + + while(ai) { + if(conn->tempaddr[other]) { + /* we can safely skip addresses of the other protocol family */ + while(ai && ai->ai_family != family) + ai = ai->ai_next; + } + + if(ai) { + result = singleipconnect(conn, ai, &conn->tempsock[tempindex]); + if(result == CURLE_COULDNT_CONNECT) { + ai = ai->ai_next; + continue; + } + + conn->tempaddr[tempindex] = ai; + } + break; } - ai = ai->ai_next; } - Curl_closesocket(conn, fd_to_close); - return CURLE_COULDNT_CONNECT; + + if(fd_to_close != CURL_SOCKET_BAD) + Curl_closesocket(conn, fd_to_close); + + return result; } /* Copies connection info into the session handle to make it available @@ -617,26 +611,29 @@ void Curl_persistconninfo(struct connectdata *conn) { memcpy(conn->data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); memcpy(conn->data->info.conn_local_ip, conn->local_ip, MAX_IPADR_LEN); + conn->data->info.conn_scheme = conn->handler->scheme; + conn->data->info.conn_protocol = conn->handler->protocol; conn->data->info.conn_primary_port = conn->primary_port; conn->data->info.conn_local_port = conn->local_port; } -/* retrieves ip address and port from a sockaddr structure */ -static bool getaddressinfo(struct sockaddr* sa, char* addr, - long* port) +/* retrieves ip address and port from a sockaddr structure. + note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */ +static bool getaddressinfo(struct sockaddr *sa, char *addr, + long *port) { unsigned short us_port; - struct sockaddr_in* si = NULL; + struct sockaddr_in *si = NULL; #ifdef ENABLE_IPV6 - struct sockaddr_in6* si6 = NULL; + struct sockaddr_in6 *si6 = NULL; #endif #if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) - struct sockaddr_un* su = NULL; + struct sockaddr_un *su = NULL; #endif - switch (sa->sa_family) { + switch(sa->sa_family) { case AF_INET: - si = (struct sockaddr_in*) sa; + si = (struct sockaddr_in *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { us_port = ntohs(si->sin_port); @@ -646,7 +643,7 @@ static bool getaddressinfo(struct sockaddr* sa, char* addr, break; #ifdef ENABLE_IPV6 case AF_INET6: - si6 = (struct sockaddr_in6*)sa; + si6 = (struct sockaddr_in6 *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { us_port = ntohs(si6->sin6_port); @@ -668,7 +665,7 @@ static bool getaddressinfo(struct sockaddr* sa, char* addr, addr[0] = '\0'; *port = 0; - + errno = EAFNOSUPPORT; return FALSE; } @@ -676,25 +673,28 @@ static bool getaddressinfo(struct sockaddr* sa, char* addr, connection */ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) { - int error; curl_socklen_t len; struct Curl_sockaddr_storage ssrem; struct Curl_sockaddr_storage ssloc; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; - if(!conn->bits.reuse) { + if(conn->socktype == SOCK_DGRAM) + /* there's no connection! */ + return; + if(!conn->bits.reuse && !conn->bits.tcp_fastopen) { len = sizeof(struct Curl_sockaddr_storage); if(getpeername(sockfd, (struct sockaddr*) &ssrem, &len)) { - error = SOCKERRNO; + int error = SOCKERRNO; failf(data, "getpeername() failed with errno %d: %s", error, Curl_strerror(conn, error)); return; } len = sizeof(struct Curl_sockaddr_storage); + memset(&ssloc, 0, sizeof(ssloc)); if(getsockname(sockfd, (struct sockaddr*) &ssloc, &len)) { - error = SOCKERRNO; + int error = SOCKERRNO; failf(data, "getsockname() failed with errno %d: %s", error, Curl_strerror(conn, error)); return; @@ -702,17 +702,16 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) if(!getaddressinfo((struct sockaddr*)&ssrem, conn->primary_ip, &conn->primary_port)) { - error = ERRNO; failf(data, "ssrem inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); + errno, Curl_strerror(conn, errno)); return; } + memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); if(!getaddressinfo((struct sockaddr*)&ssloc, conn->local_ip, &conn->local_port)) { - error = ERRNO; failf(data, "ssloc inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); + errno, Curl_strerror(conn, errno)); return; } @@ -730,13 +729,13 @@ CURLcode Curl_is_connected(struct connectdata *conn, int sockindex, bool *connected) { - struct SessionHandle *data = conn->data; - CURLcode code = CURLE_OK; - curl_socket_t sockfd = conn->sock[sockindex]; - long allow = DEFAULT_CONNECT_TIMEOUT; + struct Curl_easy *data = conn->data; + CURLcode result = CURLE_OK; + timediff_t allow; int error = 0; - struct timeval now; - enum chkconn_t chk; + struct curltime now; + int rc; + int i; DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); @@ -748,7 +747,7 @@ CURLcode Curl_is_connected(struct connectdata *conn, return CURLE_OK; } - now = Curl_tvnow(); + now = Curl_now(); /* figure out how long time we have left to connect */ allow = Curl_timeleft(data, &now, TRUE); @@ -759,93 +758,139 @@ CURLcode Curl_is_connected(struct connectdata *conn, return CURLE_OPERATION_TIMEDOUT; } - /* check socket for connect */ - chk = checkconnect(sockfd); - if(CHKCONN_IDLE == chk) { - if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { - infof(data, "After %ldms connect time, move on!\n", - conn->timeoutms_per_addr); - goto next; - } + for(i = 0; i<2; i++) { + const int other = i ^ 1; + if(conn->tempsock[i] == CURL_SOCKET_BAD) + continue; - /* not an error, but also no connection yet */ - return code; - } +#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 - if(CHKCONN_CONNECTED == chk) { - if(verifyconnect(sockfd, &error)) { - /* we are connected with TCP, awesome! */ + /* check socket for connect */ + rc = SOCKET_WRITABLE(conn->tempsock[i], 0); - /* see if we need to do any proxy magic first once we connected */ - code = Curl_connected_proxy(conn); - if(code) - return code; + if(rc == 0) { /* no connection yet */ + error = 0; + if(Curl_timediff(now, conn->connecttime) >= conn->timeoutms_per_addr) { + infof(data, "After %ldms connect time, move on!\n", + conn->timeoutms_per_addr); + error = ETIMEDOUT; + } - conn->bits.tcpconnect[sockindex] = TRUE; + /* should we try another protocol family? */ + if(i == 0 && conn->tempaddr[1] == NULL && + Curl_timediff(now, conn->connecttime) >= HAPPY_EYEBALLS_TIMEOUT) { + trynextip(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; +#ifdef ENABLE_IPV6 + conn->bits.ipv6 = (conn->ip_addr->ai_family == AF_INET6)?TRUE:FALSE; +#endif - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_verboseconnect(conn); - Curl_updateconninfo(conn, sockfd); + /* close the other socket, if open */ + if(conn->tempsock[other] != CURL_SOCKET_BAD) { + Curl_closesocket(conn, conn->tempsock[other]); + conn->tempsock[other] = CURL_SOCKET_BAD; + } - return CURLE_OK; + /* see if we need to do any proxy magic first once we connected */ + result = Curl_connected_proxy(conn, sockindex); + if(result) + return result; + + conn->bits.tcpconnect[sockindex] = TRUE; + + *connected = TRUE; + if(sockindex == FIRSTSOCKET) + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + Curl_updateconninfo(conn, conn->sock[sockindex]); + Curl_verboseconnect(conn); + + return CURLE_OK; + } + infof(data, "Connection failed\n"); } - /* nope, not connected for real */ - } - else { - /* nope, not connected */ - if(CHKCONN_FDSET_ERROR == chk) { - (void)verifyconnect(sockfd, &error); - infof(data, "%s\n",Curl_strerror(conn, error)); + 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; + char ipaddress[MAX_IPADR_LEN]; + Curl_printable_address(conn->tempaddr[i], ipaddress, MAX_IPADR_LEN); + infof(data, "connect to %s port %ld failed: %s\n", + ipaddress, conn->port, Curl_strerror(conn, error)); + + conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ? + allow : allow / 2; + + status = trynextip(conn, sockindex, i); + if(status != CURLE_COULDNT_CONNECT + || conn->tempsock[other] == CURL_SOCKET_BAD) + /* the last attempt failed and no other sockets remain open */ + result = status; + } } - else - infof(data, "Connection failed\n"); } - /* - * 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); - } - next: + if(result) { + /* no more addresses to try */ - conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ? - allow : allow / 2; - code = trynextip(conn, sockindex, connected); + const char *hostname; - if(code) { - error = SOCKERRNO; - data->state.os_errno = error; - failf(data, "Failed connect to %s:%ld; %s", - conn->host.name, conn->port, Curl_strerror(conn, error)); + /* if the first address family runs out of addresses to try before + the happy eyeball timeout, go ahead and try the next family now */ + if(conn->tempaddr[1] == NULL) { + result = trynextip(conn, sockindex, 1); + if(!result) + return result; + } + + if(conn->bits.socksproxy) + hostname = conn->socks_proxy.host.name; + else if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else 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 %ld: %s", + hostname, conn->port, Curl_strerror(conn, error)); } - return code; + return result; } -static void tcpnodelay(struct connectdata *conn, - curl_socket_t sockfd) +void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd) { -#ifdef TCP_NODELAY - struct SessionHandle *data= conn->data; - curl_socklen_t onoff = (curl_socklen_t) data->set.tcp_nodelay; +#if defined(TCP_NODELAY) +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + struct Curl_easy *data = conn->data; +#endif + curl_socklen_t onoff = (curl_socklen_t) 1; int level = IPPROTO_TCP; -#if 0 - /* The use of getprotobyname() is disabled since it isn't thread-safe on - numerous systems. On these getprotobyname_r() should be used instead, but - that exists in at least one 4 arg version and one 5 arg version, and - since the proto number rarely changes anyway we now just use the hard - coded number. The "proper" fix would need a configure check for the - correct function much in the same style the gethostbyname_r versions are - detected. */ - struct protoent *pe = getprotobyname("tcp"); - if(pe) - level = pe->p_proto; +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) conn; #endif if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, @@ -853,7 +898,7 @@ static void tcpnodelay(struct connectdata *conn, infof(data, "Could not set TCP_NODELAY: %s\n", Curl_strerror(conn, SOCKERRNO)); else - infof(data,"TCP_NODELAY set\n"); + infof(data, "TCP_NODELAY set\n"); #else (void)conn; (void)sockfd; @@ -868,7 +913,7 @@ static void tcpnodelay(struct connectdata *conn, static void nosigpipe(struct connectdata *conn, curl_socket_t sockfd) { - struct SessionHandle *data= conn->data; + struct Curl_easy *data = conn->data; int onoff = 1; if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, sizeof(onoff)) < 0) @@ -883,7 +928,7 @@ static void nosigpipe(struct connectdata *conn, /* When you run a program that uses the Windows Sockets API, you may experience slow performance when you copy data to a TCP server. - http://support.microsoft.com/kb/823764 + https://support.microsoft.com/kb/823764 Work-around: Make the Socket Send Buffer Size Larger Than the Program Send Buffer Size @@ -902,18 +947,16 @@ void Curl_sndbufset(curl_socket_t sockfd) int curval = 0; int curlen = sizeof(curval); - OSVERSIONINFO osver; static int detectOsState = DETECT_OS_NONE; if(detectOsState == DETECT_OS_NONE) { - memset(&osver, 0, sizeof(osver)); - osver.dwOSVersionInfoSize = sizeof(osver); - detectOsState = DETECT_OS_PREVISTA; - if(GetVersionEx(&osver)) { - if(osver.dwMajorVersion >= 6) - detectOsState = DETECT_OS_VISTA_OR_LATER; - } + if(Curl_verify_windows_version(6, 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; @@ -925,7 +968,6 @@ void Curl_sndbufset(curl_socket_t sockfd) } #endif - /* * singleipconnect() * @@ -935,25 +977,28 @@ void Curl_sndbufset(curl_socket_t sockfd) * singleipconnect() connects to the given IP only, and it may return without * having connected. */ -static CURLcode -singleipconnect(struct connectdata *conn, - const Curl_addrinfo *ai, - curl_socket_t *sockp, - bool *connected) +static CURLcode singleipconnect(struct connectdata *conn, + const Curl_addrinfo *ai, + curl_socket_t *sockp) { struct Curl_sockaddr_ex addr; - int rc; + int rc = -1; int error = 0; bool isconnected = FALSE; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; curl_socket_t sockfd; - CURLcode res = CURLE_OK; + CURLcode result; + char ipaddress[MAX_IPADR_LEN]; + long port; + bool is_tcp; +#ifdef TCP_FASTOPEN_CONNECT + int optval = 1; +#endif *sockp = CURL_SOCKET_BAD; - *connected = FALSE; /* default is not connected */ - res = Curl_socket(conn, ai, &addr, &sockfd); - if(res) + result = Curl_socket(conn, ai, &addr, &sockfd); + if(result) /* Failed to create the socket, but still return OK since we signal the lack of socket as well. This allows the parent function to keep looping over alternative addresses/socket families etc. */ @@ -961,27 +1006,29 @@ singleipconnect(struct connectdata *conn, /* store remote address and port used in this connection attempt */ if(!getaddressinfo((struct sockaddr*)&addr.sa_addr, - conn->primary_ip, &conn->primary_port)) { + ipaddress, &port)) { /* malformed address or bug in inet_ntop, try next address */ - error = ERRNO; failf(data, "sa_addr inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); + errno, Curl_strerror(conn, errno)); Curl_closesocket(conn, sockfd); return CURLE_OK; } - memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); - infof(data, " Trying %s...\n", conn->ip_addr_str); - - Curl_persistconninfo(conn); + infof(data, " Trying %s...\n", ipaddress); - if(data->set.tcp_nodelay) - tcpnodelay(conn, sockfd); +#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) + Curl_tcpnodelay(conn, sockfd); nosigpipe(conn, sockfd); Curl_sndbufset(sockfd); - if(data->set.tcp_keepalive) + if(is_tcp && data->set.tcp_keepalive) tcpkeepalive(data, sockfd); if(data->set.fsockopt) { @@ -999,27 +1046,76 @@ singleipconnect(struct connectdata *conn, } /* possibly bind the local end to an IP, interface or port */ - res = bindlocal(conn, sockfd, addr.family); - if(res) { - Curl_closesocket(conn, sockfd); /* close socket and bail out */ - if(res == CURLE_UNSUPPORTED_PROTOCOL) { - /* The address family is not supported on this interface. - We can continue trying addresses */ - return CURLE_OK; + if(addr.family == AF_INET +#ifdef ENABLE_IPV6 + || addr.family == AF_INET6 +#endif + ) { + result = bindlocal(conn, sockfd, addr.family, + Curl_ipv6_scope((struct sockaddr*)&addr.sa_addr)); + if(result) { + Curl_closesocket(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; + } + return result; } - return res; } /* set socket non-blocking */ - curlx_nonblock(sockfd, TRUE); + (void)curlx_nonblock(sockfd, TRUE); - conn->connecttime = Curl_tvnow(); + conn->connecttime = Curl_now(); if(conn->num_addr > 1) - Curl_expire(data, conn->timeoutms_per_addr); + Curl_expire(data, conn->timeoutms_per_addr, EXPIRE_DNS_PER_NAME); /* Connect TCP sockets, bind UDP */ if(!isconnected && (conn->socktype == SOCK_STREAM)) { - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); + 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); + } + else { + rc = connect(sockfd, &addr.sa_addr, addr.addrlen); + } +# 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\n", sockfd); + else + infof(data, "TCP_FASTOPEN_CONNECT set\n"); + + 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); + } + if(-1 == rc) error = SOCKERRNO; } @@ -1028,12 +1124,8 @@ singleipconnect(struct connectdata *conn, return CURLE_OK; } -#ifdef ENABLE_IPV6 - conn->bits.ipv6 = (addr.family == AF_INET6)?TRUE:FALSE; -#endif - if(-1 == rc) { - switch (error) { + switch(error) { case EINPROGRESS: case EWOULDBLOCK: #if defined(EAGAIN) @@ -1045,25 +1137,25 @@ singleipconnect(struct connectdata *conn, case EAGAIN: #endif #endif - *sockp = sockfd; - return CURLE_OK; + result = CURLE_OK; + break; default: /* unknown error, fallthrough and try another address! */ - failf(data, "Failed to connect to %s: %s", - conn->ip_addr_str, Curl_strerror(conn,error)); + infof(data, "Immediate connect fail for %s: %s\n", + ipaddress, Curl_strerror(conn, error)); data->state.os_errno = error; /* connect failed */ Curl_closesocket(conn, sockfd); - - break; + result = CURLE_COULDNT_CONNECT; } } - else + + if(!result) *sockp = sockfd; - return CURLE_OK; + return result; } /* @@ -1073,29 +1165,13 @@ singleipconnect(struct connectdata *conn, */ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ - const struct Curl_dns_entry *remotehost, - curl_socket_t *sockconn, /* the connected socket */ - Curl_addrinfo **addr, /* the one we used */ - bool *connected) /* really connected? */ + const struct Curl_dns_entry *remotehost) { - struct SessionHandle *data = conn->data; - curl_socket_t sockfd = CURL_SOCKET_BAD; - Curl_addrinfo *ai; - Curl_addrinfo *curr_addr; + struct Curl_easy *data = conn->data; + struct curltime before = Curl_now(); + CURLcode result = CURLE_COULDNT_CONNECT; - struct timeval after; - struct timeval before = Curl_tvnow(); - - /************************************************************* - * Figure out what maximum time we have left - *************************************************************/ - long timeout_ms; - - DEBUGASSERT(sockconn); - *connected = FALSE; /* default to not connected */ - - /* get the timeout left */ - timeout_ms = Curl_timeleft(data, &before, TRUE); + timediff_t timeout_ms = Curl_timeleft(data, &before, TRUE); if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -1104,59 +1180,31 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ } conn->num_addr = Curl_num_addresses(remotehost->addr); - - ai = remotehost->addr; - - /* Below is the loop that attempts to connect to all IP-addresses we - * know for the given host. One by one until one IP succeeds. - */ - - /* - * Connecting with a Curl_addrinfo chain - */ - for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) { - CURLcode res; - - /* Max time for the next address */ - conn->timeoutms_per_addr = curr_addr->ai_next == NULL ? - timeout_ms : timeout_ms / 2; - - /* start connecting to the IP curr_addr points to */ - res = singleipconnect(conn, curr_addr, - &sockfd, connected); - if(res) - return res; - - if(sockfd != CURL_SOCKET_BAD) + conn->tempaddr[0] = remotehost->addr; + conn->tempaddr[1] = NULL; + conn->tempsock[0] = CURL_SOCKET_BAD; + conn->tempsock[1] = CURL_SOCKET_BAD; + + /* Max time for the next connection attempt */ + conn->timeoutms_per_addr = + conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2; + + /* start connecting to first IP */ + while(conn->tempaddr[0]) { + result = singleipconnect(conn, conn->tempaddr[0], &(conn->tempsock[0])); + if(!result) break; - - /* get a new timeout for next attempt */ - after = Curl_tvnow(); - timeout_ms -= Curl_tvdiff(after, before); - if(timeout_ms < 0) { - failf(data, "connect() timed out!"); - return CURLE_OPERATION_TIMEDOUT; - } - before = after; - } /* end of connect-to-each-address loop */ - - *sockconn = sockfd; /* the socket descriptor we've connected */ - - if(sockfd == CURL_SOCKET_BAD) { - /* no good connect was made */ - failf(data, "couldn't connect to %s at %s:%ld", - conn->bits.proxy?"proxy":"host", - conn->bits.proxy?conn->proxy.name:conn->host.name, conn->port); - return CURLE_COULDNT_CONNECT; + conn->tempaddr[0] = conn->tempaddr[0]->ai_next; } - /* leave the socket in non-blocking mode */ - - /* store the address we use */ - if(addr) - *addr = curr_addr; + if(conn->tempsock[0] == CURL_SOCKET_BAD) { + if(!result) + result = CURLE_COULDNT_CONNECT; + return result; + } data->info.numconnects++; /* to track the number of connections made */ + Curl_expire(conn->data, HAPPY_EYEBALLS_TIMEOUT, EXPIRE_HAPPY_EYEBALLS); return CURLE_OK; } @@ -1178,26 +1226,31 @@ static int conn_is_conn(struct connectdata *conn, void *param) /* * Used to extract socket and connectdata struct for the most recent - * transfer on the given SessionHandle. + * transfer on the given Curl_easy. * * The returned socket will be CURL_SOCKET_BAD in case of failure! */ -curl_socket_t Curl_getconnectinfo(struct SessionHandle *data, +curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp) { curl_socket_t sockfd; DEBUGASSERT(data); - /* this only works for an easy handle that has been used for - curl_easy_perform()! */ - if(data->state.lastconnect && data->multi_easy) { + /* 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 && (data->multi_easy || data->multi)) { struct connectdata *c = data->state.lastconnect; struct connfind find; find.tofind = data->state.lastconnect; find.found = FALSE; - Curl_conncache_foreach(data->multi_easy->conn_cache, &find, conn_is_conn); + Curl_conncache_foreach(data, data->multi_easy? + &data->multi_easy->conn_cache: + &data->multi->conn_cache, &find, conn_is_conn); if(!find.found) { data->state.lastconnect = NULL; @@ -1208,24 +1261,6 @@ curl_socket_t Curl_getconnectinfo(struct SessionHandle *data, /* only store this if the caller cares for it */ *connp = c; sockfd = c->sock[FIRSTSOCKET]; - /* we have a socket connected, let's determine if the server shut down */ - /* determine if ssl */ - if(c->ssl[FIRSTSOCKET].use) { - /* use the SSL context */ - if(!Curl_ssl_check_cxn(c)) - return CURL_SOCKET_BAD; /* FIN received */ - } -/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ -#ifdef MSG_PEEK - else { - /* use the socket */ - char buf; - if(recv((RECV_TYPE_ARG1)c->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { - return CURL_SOCKET_BAD; /* FIN received */ - } - } -#endif } else return CURL_SOCKET_BAD; @@ -1234,6 +1269,33 @@ curl_socket_t Curl_getconnectinfo(struct SessionHandle *data, } /* + * Check if a connection seems to be alive. + */ +bool Curl_connalive(struct connectdata *conn) +{ + /* First determine if ssl */ + if(conn->ssl[FIRSTSOCKET].use) { + /* use the SSL context */ + if(!Curl_ssl_check_cxn(conn)) + return false; /* FIN received */ + } +/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ +#ifdef MSG_PEEK + else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) + return false; + else { + /* use the socket */ + char buf; + if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, + (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { + return false; /* FIN received */ + } + } +#endif + return true; +} + +/* * Close a socket. * * 'conn' can be NULL, beware! @@ -1248,15 +1310,18 @@ int Curl_closesocket(struct connectdata *conn, accept, then we MUST NOT call the callback but clear the accepted status */ conn->sock_accepted[SECONDARYSOCKET] = FALSE; - else + else { + Curl_multi_closed(conn, sock); return conn->fclosesocket(conn->closesocket_client, sock); + } } - sclose(sock); if(conn) /* tell the multi-socket code about this */ Curl_multi_closed(conn, sock); + sclose(sock); + return 0; } @@ -1274,7 +1339,7 @@ CURLcode Curl_socket(struct connectdata *conn, struct Curl_sockaddr_ex *addr, curl_socket_t *sockfd) { - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct Curl_sockaddr_ex dummy; if(!addr) @@ -1291,7 +1356,7 @@ CURLcode Curl_socket(struct connectdata *conn, addr->family = ai->ai_family; addr->socktype = conn->socktype; - addr->protocol = conn->socktype==SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; + addr->protocol = conn->socktype == SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; addr->addrlen = ai->ai_addrlen; if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) @@ -1320,12 +1385,49 @@ CURLcode Curl_socket(struct connectdata *conn, return CURLE_COULDNT_CONNECT; #if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(conn->scope && (addr->family == AF_INET6)) { + if(conn->scope_id && (addr->family == AF_INET6)) { struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; - sa6->sin6_scope_id = conn->scope; + sa6->sin6_scope_id = conn->scope_id; } #endif 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 + ) +{ + /* close if a connection, or a stream that isn't multiplexed */ + bool closeit = (ctrl == CONNCTRL_CONNECTION) || + ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM)); + if((ctrl == CONNCTRL_STREAM) && + (conn->handler->flags & PROTOPT_STREAM)) + DEBUGF(infof(conn->data, "Kill stream: %s\n", reason)); + else if(closeit != conn->bits.close) { + DEBUGF(infof(conn->data, "Marked for [%s]: %s\n", + closeit?"closure":"keep alive", reason)); + conn->bits.close = closeit; /* the only place in the source code that + should assign this bit */ + } +} + +/* Data received can be cached at various levels, so check them all here. */ +bool Curl_conn_data_pending(struct connectdata *conn, int sockindex) +{ + int readable; + + if(Curl_ssl_data_pending(conn, sockindex) || + Curl_recv_has_postponed_data(conn, sockindex)) + return true; + + readable = SOCKET_READABLE(conn->sock[sockindex], 0); + return (readable > 0 && (readable & CURL_CSELECT_IN)); +} |