Commit 7d7df831 authored by Björn Stenberg's avatar Björn Stenberg Committed by Daniel Stenberg
Browse files

Add "Happy Eyeballs" for IPv4/IPv6.

This patch invokes two socket connect()s nearly simultaneously, and
the socket that is first connected "wins" and is subsequently used for
the connection. The other is terminated.

There is a very slight IPv4 preference, in that if both sockets connect
simultaneously IPv4 is checked first and thus will win.
parent 7de4cc35
Loading
Loading
Loading
Loading
+149 −149
Original line number Diff line number Diff line
@@ -233,45 +233,6 @@ 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)
{
@@ -573,17 +534,19 @@ 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)
{
  curl_socket_t sockfd;
  Curl_addrinfo *ai;
  int family = tempindex ? AF_INET6 : AF_INET;

  /* 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;
  curl_socket_t fd_to_close = conn->tempsock[tempindex];
  conn->tempsock[tempindex] = CURL_SOCKET_BAD;
  *connected = FALSE;

  if(sockindex != FIRSTSOCKET) {
@@ -591,21 +554,26 @@ static CURLcode trynextip(struct connectdata *conn,
    return CURLE_COULDNT_CONNECT; /* no next */
  }

  /* try the next address */
  ai = conn->ip_addr->ai_next;
  /* try the next address with same family */
  ai = conn->tempaddr[tempindex]->ai_next;
  while(ai && ai->ai_family != family)
    ai = ai->ai_next;

  while(ai) {
  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->sock[sockindex] = sockfd;
      conn->ip_addr = ai;
      conn->tempsock[tempindex] = sockfd;
      conn->tempaddr[tempindex] = ai;
      Curl_closesocket(conn, fd_to_close);
      return CURLE_OK;
    }

    do {
      ai = ai->ai_next;
    } while(ai && ai->ai_family == family);
  }
  Curl_closesocket(conn, fd_to_close);
  return CURLE_COULDNT_CONNECT;
@@ -707,6 +675,7 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd)
            error, Curl_strerror(conn, error));
      return;
    }
    memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN);

    if(!getaddressinfo((struct sockaddr*)&ssloc,
                       conn->local_ip, &conn->local_port)) {
@@ -732,11 +701,11 @@ CURLcode Curl_is_connected(struct connectdata *conn,
{
  struct SessionHandle *data = conn->data;
  CURLcode code = CURLE_OK;
  curl_socket_t sockfd = conn->sock[sockindex];
  long allow = DEFAULT_CONNECT_TIMEOUT;
  int error = 0;
  struct timeval now;
  enum chkconn_t chk;
  int result;
  int i;

  DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);

@@ -759,22 +728,46 @@ CURLcode Curl_is_connected(struct connectdata *conn,
    return CURLE_OPERATION_TIMEDOUT;
  }

  for(i=0; i<2; i++) {
    if(conn->tempsock[i] == CURL_SOCKET_BAD)
      continue;

#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

    /* check socket for connect */
  chk = checkconnect(sockfd);
  if(CHKCONN_IDLE == chk) {
    result = Curl_socket_ready(CURL_SOCKET_BAD, conn->tempsock[i], 0);

    switch(result) {
    case 0: /* no connection yet */
      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;
    }

    /* not an error, but also no connection yet */
    return code;
        break;
      }
      return CURLE_OK;

  if(CHKCONN_CONNECTED == chk) {
    if(verifyconnect(sockfd, &error)) {
    case CURL_CSELECT_OUT:
      if(verifyconnect(conn->tempsock[i], &error)) {
        /* we are connected with TCP, awesome! */
        int other = i ^ 1;

        /* use this socket from now on */
        conn->sock[sockindex] = conn->tempsock[i];
        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]);
        }

        /* see if we need to do any proxy magic first once we connected */
        code = Curl_connected_proxy(conn, sockindex);
@@ -786,21 +779,22 @@ CURLcode Curl_is_connected(struct connectdata *conn,
        *connected = TRUE;
        if(sockindex == FIRSTSOCKET)
          Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
        Curl_updateconninfo(conn, conn->sock[sockindex]);
        Curl_verboseconnect(conn);
      Curl_updateconninfo(conn, sockfd);

        return CURLE_OK;
      }
    /* 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
        infof(data, "Connection failed\n");
      break;

    case CURL_CSELECT_ERR|CURL_CSELECT_OUT:
      (void)verifyconnect(conn->tempsock[i], &error);
      break;

    default:
      infof(data, "Whut?\n");
      return CURLE_OK;
    }

    /*
@@ -811,11 +805,10 @@ CURLcode Curl_is_connected(struct connectdata *conn,
      data->state.os_errno = error;
      SET_SOCKERRNO(error);
    }
  next:

  conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ?
    conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ?
                               allow : allow / 2;
  code = trynextip(conn, sockindex, connected);
    code = trynextip(conn, sockindex, i, connected);

    if(code) {
      error = SOCKERRNO;
@@ -823,6 +816,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
      failf(data, "Failed connect to %s:%ld; %s",
            conn->host.name, conn->port, Curl_strerror(conn, error));
    }
  }

  return code;
}
@@ -948,6 +942,8 @@ singleipconnect(struct connectdata *conn,
  struct SessionHandle *data = conn->data;
  curl_socket_t sockfd;
  CURLcode res = CURLE_OK;
  char ipaddress[MAX_IPADR_LEN];
  long port;

  *sockp = CURL_SOCKET_BAD;
  *connected = FALSE; /* default is not connected */
@@ -961,7 +957,7 @@ 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",
@@ -969,10 +965,7 @@ singleipconnect(struct connectdata *conn,
    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);
@@ -1074,24 +1067,18 @@ 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? */
{
  struct SessionHandle *data = conn->data;
  curl_socket_t sockfd = CURL_SOCKET_BAD;
  Curl_addrinfo *ai;
  Curl_addrinfo *curr_addr;

  struct timeval after;
  struct timeval before = Curl_tvnow();
  int i;

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

  DEBUGASSERT(sockconn);
  *connected = FALSE; /* default to not connected */

  /* get the timeout left */
@@ -1104,26 +1091,35 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
  }

  conn->num_addr = Curl_num_addresses(remotehost->addr);

  ai = 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 until one IP succeeds.
   * 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
     */
  for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) {
    while(ai) {
      CURLcode res;

    /* Max time for the next address */
    conn->timeoutms_per_addr = curr_addr->ai_next == NULL ?
      /* 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, curr_addr,
                          &sockfd, connected);
      res = singleipconnect(conn, ai, &sockfd, connected);
      if(res)
        return res;

@@ -1138,11 +1134,19 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
        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 */

  *sockconn = sockfd;    /* the socket descriptor we've connected */
    conn->tempsock[i] = sockfd;
    conn->tempaddr[i] = ai;
  }

  if(sockfd == CURL_SOCKET_BAD) {
  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",
@@ -1152,10 +1156,6 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */

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

  /* store the address we use */
  if(addr)
    *addr = curr_addr;

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

  return CURLE_OK;
+0 −2
Original line number Diff line number Diff line
@@ -33,8 +33,6 @@ CURLcode Curl_is_connected(struct connectdata *conn,
CURLcode Curl_connecthost(struct connectdata *conn,
                          const struct Curl_dns_entry *host, /* connect to
                                                                this */
                          curl_socket_t *sockconn, /* not set if error */
                          Curl_addrinfo **addr, /* the one we used */
                          bool *connected); /* truly connected? */

/* generic function that returns how much time there's left to run, according
+1 −4
Original line number Diff line number Diff line
@@ -1881,7 +1881,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
  struct ftp_conn *ftpc = &conn->proto.ftpc;
  CURLcode result;
  struct SessionHandle *data=conn->data;
  Curl_addrinfo *conninfo;
  struct Curl_dns_entry *addr=NULL;
  int rc;
  unsigned short connectport; /* the local port connect() should use! */
@@ -2041,8 +2040,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,

  result = Curl_connecthost(conn,
                            addr,
                            &conn->sock[SECONDARYSOCKET],
                            &conninfo,
                            &connected);

  Curl_resolv_unlock(data, addr); /* we're done using this address */
@@ -2064,7 +2061,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,

  if(data->set.verbose)
    /* this just dumps information about this second connection */
    ftp_pasv_verbose(conn, conninfo, ftpc->newhost, connectport);
    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
+11 −2
Original line number Diff line number Diff line
@@ -621,17 +621,26 @@ static int waitconnect_getsock(struct connectdata *conn,
                               curl_socket_t *sock,
                               int numsocks)
{
  int i;
  int s=0;
  int rc=0;

  if(!numsocks)
    return GETSOCK_BLANK;

  sock[0] = conn->sock[FIRSTSOCKET];
  for(i=0; i<2; i++) {
    if(conn->tempsock[i] != CURL_SOCKET_BAD) {
      sock[s] = conn->tempsock[i];
      rc |= GETSOCK_WRITESOCK(s++);
    }
  }

  /* when we've sent a CONNECT to a proxy, we should rather wait for the
     socket to become readable to be able to get the response headers */
  if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
    return GETSOCK_READSOCK(0);

  return GETSOCK_WRITESOCK(0);
  return rc;
}

static int domore_getsock(struct connectdata *conn,
+1 −7
Original line number Diff line number Diff line
@@ -3260,7 +3260,6 @@ static CURLcode ConnectPlease(struct SessionHandle *data,
                              bool *connected)
{
  CURLcode result;
  Curl_addrinfo *addr;
#ifndef CURL_DISABLE_VERBOSE_STRINGS
  char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name;

@@ -3276,13 +3275,8 @@ static CURLcode ConnectPlease(struct SessionHandle *data,
   *************************************************************/
  result= Curl_connecthost(conn,
                           conn->dns_entry,
                           &conn->sock[FIRSTSOCKET],
                           &addr,
                           connected);
  if(CURLE_OK == result) {
    /* All is cool, we store the current information */
    conn->ip_addr = addr;

    if(*connected) {
      result = Curl_connected_proxy(conn, FIRSTSOCKET);
      if(!result) {
@@ -5643,8 +5637,8 @@ CURLcode Curl_setup_conn(struct connectdata *conn,
      Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
      conn->bits.tcpconnect[FIRSTSOCKET] = TRUE;
      *protocol_done = TRUE;
      Curl_verboseconnect(conn);
      Curl_updateconninfo(conn, conn->sock[FIRSTSOCKET]);
      Curl_verboseconnect(conn);
    }
    /* Stop the loop now */
    break;
Loading