Commit 02fbc26d authored by Björn Stenberg's avatar Björn Stenberg Committed by Daniel Stenberg
Browse files

connect: Add connection delay to Happy Eyeballs.

This patch adds a 200ms delay between the first and second address
family socket connection attempts.

It also iterates over IP addresses in the order returned by the
system, meaning most dual-stack systems will try IPv6 first.

Additionally, it refactors the connect code, removing most code that
handled synchronous connects. Since all sockets are now non-blocking,
the logic can be made simpler.
parent 0074c9f5
Loading
Loading
Loading
Loading
+74 −125
Original line number Diff line number Diff line
@@ -164,8 +164,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
@@ -534,12 +533,9 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
   more address exists or error */
static CURLcode trynextip(struct connectdata *conn,
                          int sockindex,
                          int tempindex,
                          bool *connected)
                          int tempindex)
{
  curl_socket_t sockfd;
  Curl_addrinfo *ai;
  int family = tempindex ? AF_INET6 : AF_INET;
  CURLcode rc = 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
@@ -547,36 +543,35 @@ static CURLcode trynextip(struct connectdata *conn,
     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;
  *connected = FALSE;

  if(sockindex != FIRSTSOCKET) {
    Curl_closesocket(conn, fd_to_close);
    return CURLE_COULDNT_CONNECT; /* no next */
  }
  if(sockindex == FIRSTSOCKET) {
    Curl_addrinfo *ai;
    int family;

  /* try the next address with same family */
    if(conn->tempaddr[tempindex]) {
      /* find next address in the same protocol family */
      family = conn->tempaddr[tempindex]->ai_family;
      ai = conn->tempaddr[tempindex]->ai_next;
    }
    else {
      /* 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;
    }

    while(ai && ai->ai_family != family)
      ai = ai->ai_next;

  while(ai && ai->ai_family == family) {
    CURLcode res = singleipconnect(conn, ai, &sockfd, connected);
    if(res)
      return res;
    if(sockfd != CURL_SOCKET_BAD) {
      /* store the new socket descriptor */
      conn->tempsock[tempindex] = sockfd;
    if(ai) {
      rc = singleipconnect(conn, ai, &conn->tempsock[tempindex]);
      conn->tempaddr[tempindex] = ai;
      Curl_closesocket(conn, fd_to_close);
      return CURLE_OK;
    }

    do {
      ai = ai->ai_next;
    } while(ai && ai->ai_family != family);
  }

  if(fd_to_close != CURL_SOCKET_BAD)
    Curl_closesocket(conn, fd_to_close);
  return CURLE_COULDNT_CONNECT;

  return rc;
}

/* Copies connection info into the session handle to make it available
@@ -701,7 +696,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
{
  struct SessionHandle *data = conn->data;
  CURLcode code = CURLE_OK;
  long allow = DEFAULT_CONNECT_TIMEOUT;
  long allow;
  int error = 0;
  struct timeval now;
  int result;
@@ -748,6 +743,12 @@ CURLcode Curl_is_connected(struct connectdata *conn,
              conn->timeoutms_per_addr);
        error = ETIMEDOUT;
      }

      /* should we try another protocol family? */
      if(i == 0 && conn->tempaddr[1] == NULL &&
         curlx_tvdiff(now, conn->connecttime) >= HAPPY_EYEBALLS_TIMEOUT) {
        trynextip(conn, sockindex, 1);
      }
    }
    else if(result == CURL_CSELECT_OUT) {
      if(verifyconnect(conn->tempsock[i], &error)) {
@@ -759,13 +760,8 @@ CURLcode Curl_is_connected(struct connectdata *conn,
        conn->ip_addr = conn->tempaddr[i];

        /* close the other socket, if open */
        if(conn->tempsock[other] != CURL_SOCKET_BAD) {
          if(conn->fclosesocket)
            conn->fclosesocket(conn->closesocket_client,
                               conn->tempsock[other]);
          else
            sclose(conn->tempsock[other]);
        }
        if(conn->tempsock[other] != CURL_SOCKET_BAD)
          Curl_closesocket(conn, conn->tempsock[other]);

        /* see if we need to do any proxy magic first once we connected */
        code = Curl_connected_proxy(conn, sockindex);
@@ -797,18 +793,28 @@ CURLcode Curl_is_connected(struct connectdata *conn,
      data->state.os_errno = error;
      SET_SOCKERRNO(error);
      Curl_printable_address(conn->tempaddr[i], ipaddress, MAX_IPADR_LEN);
      infof(data, "connect to %s port %ld: %s\n",
      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;

      code = trynextip(conn, sockindex, i, connected);
      code = trynextip(conn, sockindex, i);
    }
  }

  if(code) {
    /* no more addresses to try */

    /* 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) {
      int rc;
      rc = trynextip(conn, sockindex, 1);
      if(rc == CURLE_OK)
        return CURLE_OK;
    }

    failf(data, "Failed to connect to %s port %ld: %s",
          conn->host.name, conn->port, Curl_strerror(conn, error));
  }
@@ -927,8 +933,7 @@ void Curl_sndbufset(curl_socket_t sockfd)
static CURLcode
singleipconnect(struct connectdata *conn,
                const Curl_addrinfo *ai,
                curl_socket_t *sockp,
                bool *connected)
                curl_socket_t *sockp)
{
  struct Curl_sockaddr_ex addr;
  int rc;
@@ -941,7 +946,6 @@ singleipconnect(struct connectdata *conn,
  long port;

  *sockp = CURL_SOCKET_BAD;
  *connected = FALSE; /* default is not connected */

  res = Curl_socket(conn, ai, &addr, &sockfd);
  if(res)
@@ -1038,14 +1042,12 @@ singleipconnect(struct connectdata *conn,

    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;
      return CURLE_COULDNT_CONNECT;
    }
  }
  else
@@ -1061,23 +1063,13 @@ singleipconnect(struct connectdata *conn,
 */

CURLcode Curl_connecthost(struct connectdata *conn,  /* context */
                          const struct Curl_dns_entry *remotehost,
                          bool *connected)           /* really connected? */
                          const struct Curl_dns_entry *remotehost)
{
  struct SessionHandle *data = conn->data;
  struct timeval after;
  struct timeval before = Curl_tvnow();
  int i;

  /*************************************************************
   * Figure out what maximum time we have left
   *************************************************************/
  long timeout_ms;

  *connected = FALSE; /* default to not connected */
  CURLcode res;

  /* get the timeout left */
  timeout_ms = Curl_timeleft(data, &before, TRUE);
  long timeout_ms = Curl_timeleft(data, &before, TRUE);

  if(timeout_ms < 0) {
    /* a precaution, no need to continue if time already is up */
@@ -1087,70 +1079,27 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */

  conn->num_addr = Curl_num_addresses(remotehost->addr);
  conn->tempaddr[0] = remotehost->addr;
  conn->tempaddr[1] = remotehost->addr;

  /* Below is the loop that attempts to connect to all IP-addresses we
   * know for the given host.
   * One by one, for each protocol, until one IP succeeds.
   */

  for(i=0; i<2; i++) {
    curl_socket_t sockfd = CURL_SOCKET_BAD;
    Curl_addrinfo *ai = conn->tempaddr[i];
    int family = i ? AF_INET6 : AF_INET;

    /* find first address for this address family, if any */
    while(ai && ai->ai_family != family)
      ai = ai->ai_next;

    /*
     * Connecting with a Curl_addrinfo chain
     */
    while(ai) {
      CURLcode res;
  conn->tempaddr[1] = NULL;
  conn->tempsock[0] = CURL_SOCKET_BAD;
  conn->tempsock[1] = CURL_SOCKET_BAD;
  Curl_expire(conn->data,
              HAPPY_EYEBALLS_TIMEOUT + (MULTI_TIMEOUT_INACCURACY/1000));

  /* Max time for the next connection attempt */
      conn->timeoutms_per_addr = ai->ai_next == NULL ?
                                 timeout_ms : timeout_ms / 2;

      /* start connecting to the IP curr_addr points to */
      res = singleipconnect(conn, ai, &sockfd, connected);
      if(res)
  conn->timeoutms_per_addr =
    conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;

  /* start connecting to first IP */
  res = singleipconnect(conn, conn->tempaddr[0], &(conn->tempsock[0]));
  while(res != CURLE_OK &&
        conn->tempaddr[0] &&
        conn->tempaddr[0]->ai_next &&
        conn->tempsock[0] == CURL_SOCKET_BAD)
    res = trynextip(conn, FIRSTSOCKET, 0);

  if(conn->tempsock[0] == CURL_SOCKET_BAD)
    return res;

      if(sockfd != CURL_SOCKET_BAD)
        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;

      /* next addresses */
      do {
        ai = ai->ai_next;
      } while(ai && ai->ai_family != family);
    }  /* end of connect-to-each-address loop */

    conn->tempsock[i] = sockfd;
    conn->tempaddr[i] = ai;
  }

  if((conn->tempsock[0] == CURL_SOCKET_BAD) &&
     (conn->tempsock[1] == 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;
  }

  /* leave the socket in non-blocking mode */

  data->info.numconnects++; /* to track the number of connections made */

  return CURLE_OK;
+3 −3
Original line number Diff line number Diff line
@@ -31,9 +31,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
                           bool *connected);

CURLcode Curl_connecthost(struct connectdata *conn,
                          const struct Curl_dns_entry *host, /* connect to
                                                                this */
                          bool *connected); /* truly connected? */
                          const struct Curl_dns_entry *host);

/* generic function that returns how much time there's left to run, according
   to the timeouts set */
@@ -42,6 +40,8 @@ long Curl_timeleft(struct SessionHandle *data,
                   bool duringconnect);

#define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
#define HAPPY_EYEBALLS_TIMEOUT     200 /* milliseconds to wait between
                                          ipv4/ipv6 connection attempts */

/*
 * Used to extract socket and connectdata struct for the most recent
+2 −14
Original line number Diff line number Diff line
@@ -1884,7 +1884,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
  struct Curl_dns_entry *addr=NULL;
  int rc;
  unsigned short connectport; /* the local port connect() should use! */
  bool connected;
  char *str=&data->state.buffer[4];  /* start on the first letter */

  if((ftpc->count1 == 0) &&
@@ -2038,9 +2037,8 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
    }
  }

  result = Curl_connecthost(conn,
                            addr,
                            &connected);
  conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE;
  result = Curl_connecthost(conn, addr);

  Curl_resolv_unlock(data, addr); /* we're done using this address */

@@ -2051,7 +2049,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
    return result;
  }

  conn->bits.tcpconnect[SECONDARYSOCKET] = connected;

  /*
   * When this is used from the multi interface, this might've returned with
@@ -2063,15 +2060,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
    /* this just dumps information about this second connection */
    ftp_pasv_verbose(conn, conn->ip_addr, ftpc->newhost, connectport);

  if(connected) {
    /* Only do the proxy connection magic if we're actually connected.  We do
       this little trick and send in the same 'connected' variable here again
       and it will be set FALSE by proxy_magic() for when for example the
       CONNECT procedure doesn't complete */
    infof(data, "Connection to proxy confirmed almost instantly\n");
    result = proxy_magic(conn, ftpc->newhost, ftpc->newport, &connected);
  }
  conn->bits.tcpconnect[SECONDARYSOCKET] = connected;
  conn->bits.do_more = TRUE;
  state(conn, FTP_STOP); /* this phase is completed */

+1 −0
Original line number Diff line number Diff line
@@ -874,6 +874,7 @@ CURLMcode curl_multi_wait(CURLM *multi_handle,

  if(nfds) {
    /* wait... */
    infof(data, "Curl_poll(%d ds, %d ms)\n", nfds, timeout_ms);
    i = Curl_poll(ufds, nfds, timeout_ms);

    if(i) {
+2 −67
Original line number Diff line number Diff line
@@ -3256,43 +3256,6 @@ CURLcode Curl_connected_proxy(struct connectdata *conn,
  return CURLE_OK;
}

static CURLcode ConnectPlease(struct SessionHandle *data,
                              struct connectdata *conn,
                              bool *connected)
{
  CURLcode result;
#ifndef CURL_DISABLE_VERBOSE_STRINGS
  char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name;

  infof(data, "About to connect() to %s%s port %ld (#%ld)\n",
        conn->bits.proxy?"proxy ":"",
        hostname, conn->port, conn->connection_id);
#else
  (void)data;
#endif

  /*************************************************************
   * Connect to server/proxy
   *************************************************************/
  result= Curl_connecthost(conn,
                           conn->dns_entry,
                           connected);
  if(CURLE_OK == result) {
    if(*connected) {
      result = Curl_connected_proxy(conn, FIRSTSOCKET);
      if(!result) {
        conn->bits.tcpconnect[FIRSTSOCKET] = TRUE;
        Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
      }
    }
  }

  if(result)
    *connected = FALSE; /* mark it as not connected */

  return result;
}

/*
 * verboseconnect() displays verbose information after a connect
 */
@@ -5600,36 +5563,8 @@ CURLcode Curl_setup_conn(struct connectdata *conn,
    /* loop for CURL_SERVER_CLOSED_CONNECTION */

    if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) {
      /* Try to connect only if not already connected */
      bool connected = FALSE;

      result = ConnectPlease(data, conn, &connected);

      if(result && !conn->ip_addr) {
        /* transport connection failure not related with authentication */
        conn->bits.tcpconnect[FIRSTSOCKET] = FALSE;
        return result;
      }

      if(connected) {
        result = Curl_protocol_connect(conn, protocol_done);
        if(CURLE_OK == result)
          conn->bits.tcpconnect[FIRSTSOCKET] = TRUE;
      }
      else
      conn->bits.tcpconnect[FIRSTSOCKET] = FALSE;

      /* if the connection was closed by the server while exchanging
         authentication informations, retry with the new set
         authentication information */
      if(conn->bits.proxy_connect_closed) {
        /* reset the error buffer */
        if(data->set.errorbuffer)
          data->set.errorbuffer[0] = '\0';
        data->state.errorbuf = FALSE;
        continue;
      }

      result = Curl_connecthost(conn, conn->dns_entry);
      if(CURLE_OK != result)
        return result;
    }