First attempt at making the multi interface work when connecting to a host

that resolves to multiple IP addresses.
This commit is contained in:
Daniel Stenberg 2004-06-29 11:20:07 +00:00
parent 964066c0de
commit 6ed5feda2b
2 changed files with 176 additions and 122 deletions

View File

@ -101,8 +101,16 @@
/* The last #include file should be: */ /* The last #include file should be: */
#include "memdebug.h" #include "memdebug.h"
#define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
static bool verifyconnect(curl_socket_t sockfd, int *error); static bool verifyconnect(curl_socket_t sockfd, int *error);
static curl_socket_t
singleipconnect(struct connectdata *conn,
Curl_addrinfo *ai, /* start connecting to this */
long timeout_ms,
bool *connected);
/* /*
* Curl_ourerrno() returns the errno (or equivalent) on this platform to * Curl_ourerrno() returns the errno (or equivalent) on this platform to
* hide platform specific for the function that calls this. * hide platform specific for the function that calls this.
@ -422,43 +430,74 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
return rc; return rc;
} }
/* Used within the multi interface. Try next IP address, return TRUE if no
more address exists */
static bool trynextip(struct connectdata *conn,
int sockindex,
long timeout,
bool *connected)
{
curl_socket_t sockfd;
Curl_addrinfo *ai;
if(sockindex != FIRSTSOCKET)
return TRUE; /* no next */
ai = conn->ip_addr->ai_next;
while (ai) {
sockfd = singleipconnect(conn, ai, timeout, connected);
if(sockfd != CURL_SOCKET_BAD) {
/* store the new socket descriptor */
conn->sock[sockindex] = sockfd;
return FALSE;
}
ai = ai->ai_next;
}
return TRUE;
}
/* /*
* Curl_is_connected() is used from the multi interface to check if the * Curl_is_connected() is used from the multi interface to check if the
* firstsocket has connected. * firstsocket has connected.
*/ */
CURLcode Curl_is_connected(struct connectdata *conn, CURLcode Curl_is_connected(struct connectdata *conn,
curl_socket_t sockfd, int sockindex,
bool *connected) bool *connected)
{ {
int rc; int rc;
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
CURLcode code = CURLE_OK;
curl_socket_t sockfd = conn->sock[sockindex];
long allow = DEFAULT_CONNECT_TIMEOUT;
long has_passed;
curlassert(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
*connected = FALSE; /* a very negative world view is best */ *connected = FALSE; /* a very negative world view is best */
if(data->set.timeout || data->set.connecttimeout) { /* Evaluate in milliseconds how much time that has passed */
/* there is a timeout set */ has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.start);
/* Evaluate in milliseconds how much time that has passed */ /* subtract the most strict timeout of the ones */
long has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.start); if(data->set.timeout && data->set.connecttimeout) {
if (data->set.timeout < data->set.connecttimeout)
/* subtract the most strict timeout of the ones */ allow = data->set.timeout*1000;
if(data->set.timeout && data->set.connecttimeout) {
if (data->set.timeout < data->set.connecttimeout)
has_passed -= data->set.timeout*1000;
else
has_passed -= data->set.connecttimeout*1000;
}
else if(data->set.timeout)
has_passed -= data->set.timeout*1000;
else else
has_passed -= data->set.connecttimeout*1000; allow = data->set.connecttimeout*1000;
}
else if(data->set.timeout) {
allow = data->set.timeout*1000;
}
else if(data->set.connecttimeout) {
allow = data->set.connecttimeout*1000;
}
if(has_passed > 0 ) { if(has_passed > allow ) {
/* time-out, bail out, go home */ /* time-out, bail out, go home */
failf(data, "Connection time-out"); failf(data, "Connection time-out after %ld ms", has_passed);
return CURLE_OPERATION_TIMEOUTED; return CURLE_OPERATION_TIMEOUTED;
}
} }
if(conn->bits.tcpconnect) { if(conn->bits.tcpconnect) {
/* we are connected already! */ /* we are connected already! */
@ -476,21 +515,27 @@ CURLcode Curl_is_connected(struct connectdata *conn,
return CURLE_OK; return CURLE_OK;
} }
/* nope, not connected for real */ /* nope, not connected for real */
failf(data, "Connection failed"); infof(data, "Connection failed\n");
return CURLE_COULDNT_CONNECT; if(trynextip(conn, sockindex, allow-has_passed, connected)) {
code = CURLE_COULDNT_CONNECT;
}
} }
else if(WAITCONN_TIMEOUT != rc) { else if(WAITCONN_TIMEOUT != rc) {
int error = Curl_ourerrno(); /* nope, not connected */
failf(data, "Failed connect to %s:%d; %s", infof(data, "Connection failed\n");
conn->host.name, conn->port, Curl_strerror(conn,error)); if(trynextip(conn, sockindex, allow-has_passed, connected)) {
return CURLE_COULDNT_CONNECT; int error = Curl_ourerrno();
failf(data, "Failed connect to %s:%d; %s",
conn->host.name, conn->port, Curl_strerror(conn,error));
code = CURLE_COULDNT_CONNECT;
}
} }
/* /*
* If the connection failed here, we should attempt to connect to the "next * If the connection failed here, we should attempt to connect to the "next
* address" for the given host. * address" for the given host.
*/ */
return CURLE_OK; return code;
} }
static void tcpnodelay(struct connectdata *conn, static void tcpnodelay(struct connectdata *conn,
@ -511,6 +556,95 @@ static void tcpnodelay(struct connectdata *conn,
#endif #endif
} }
/* singleipconnect() connects to the given IP only, and it may return without
having connected if used from the multi interface. */
static curl_socket_t
singleipconnect(struct connectdata *conn,
Curl_addrinfo *ai,
long timeout_ms,
bool *connected)
{
char addr_buf[128];
int rc;
int error;
bool conected;
struct SessionHandle *data = conn->data;
curl_socket_t sockfd = socket(ai->ai_family, ai->ai_socktype,
ai->ai_protocol);
if (sockfd == CURL_SOCKET_BAD)
return CURL_SOCKET_BAD;
*connected = FALSE; /* default is not connected */
Curl_printable_address(ai, addr_buf, sizeof(addr_buf));
infof(data, " Trying %s... ", addr_buf);
if(data->set.tcp_nodelay)
tcpnodelay(conn, sockfd);
if(conn->data->set.device) {
/* user selected to bind the outgoing socket to a specified "device"
before doing connect */
CURLcode res = bindlocal(conn, sockfd);
if(res)
return res;
}
/* set socket non-blocking */
Curl_nonblock(sockfd, TRUE);
rc = connect(sockfd, ai->ai_addr, ai->ai_addrlen);
if(-1 == rc) {
error = Curl_ourerrno();
switch (error) {
case EINPROGRESS:
case EWOULDBLOCK:
#if defined(EAGAIN) && 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
rc = waitconnect(sockfd, timeout_ms);
break;
default:
/* unknown error, fallthrough and try another address! */
failf(data, "Failed to connect to %s: %s",
addr_buf, Curl_strerror(conn,error));
break;
}
}
/* The 'WAITCONN_TIMEOUT == rc' comes from the waitconnect(), and not from
connect(). We can be sure of this since connect() cannot return 1. */
if((WAITCONN_TIMEOUT == rc) &&
(data->state.used_interface == Curl_if_multi)) {
/* Timeout when running the multi interface */
return sockfd;
}
conected = verifyconnect(sockfd, &error);
if(!rc && conected) {
/* we are connected, awesome! */
*connected = TRUE; /* this is a true connect */
infof(data, "connected\n");
return sockfd;
}
else if(WAITCONN_TIMEOUT == rc)
infof(data, "Timeout\n");
else
infof(data, "%s\n", Curl_strerror(conn, error));
/* connect failed or timed out */
sclose(sockfd);
return CURL_SOCKET_BAD;
}
/* /*
* TCP connect to the given host with timeout, proxy or remote doesn't matter. * 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 * There might be more than one IP address to try out. Fill in the passed
@ -525,11 +659,8 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
{ {
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
curl_socket_t sockfd = CURL_SOCKET_BAD; curl_socket_t sockfd = CURL_SOCKET_BAD;
int rc, error;
int aliasindex; int aliasindex;
int num_addr; int num_addr;
bool conected;
char addr_buf[256];
Curl_addrinfo *ai; Curl_addrinfo *ai;
Curl_addrinfo *curr_addr; Curl_addrinfo *curr_addr;
@ -539,7 +670,7 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
/************************************************************* /*************************************************************
* Figure out what maximum time we have left * Figure out what maximum time we have left
*************************************************************/ *************************************************************/
long timeout_ms=300000; /* milliseconds, default to five minutes total */ long timeout_ms= DEFAULT_CONNECT_TIMEOUT;
long timeout_per_addr; long timeout_per_addr;
*connected = FALSE; /* default to not connected */ *connected = FALSE; /* default to not connected */
@ -583,98 +714,24 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
ai = remotehost->addr; ai = remotehost->addr;
/* Below is the loop that attempts to connect to all IP-addresses we /* Below is the loop that attempts to connect to all IP-addresses we
* know for the given host. One by one until one IP succeedes. * know for the given host. One by one until one IP succeeds.
*/ */
if(data->state.used_interface == Curl_if_multi)
/* don't hang when doing multi */
timeout_per_addr = timeout_ms = 0;
/* /*
* Connecting with a getaddrinfo chain * Connecting with a Curl_addrinfo chain
*/ */
for (curr_addr = ai, aliasindex=0; curr_addr; for (curr_addr = ai, aliasindex=0; curr_addr;
curr_addr = curr_addr->ai_next, aliasindex++) { curr_addr = curr_addr->ai_next, aliasindex++) {
sockfd = socket(curr_addr->ai_family, curr_addr->ai_socktype, /* start connecting to the IP curr_addr points to */
curr_addr->ai_protocol); sockfd = singleipconnect(conn, curr_addr, timeout_per_addr, connected);
if (sockfd == CURL_SOCKET_BAD) {
timeout_per_addr += timeout_per_addr / (num_addr - aliasindex);
continue;
}
Curl_printable_address(curr_addr, addr_buf, sizeof(addr_buf)); if(sockfd != CURL_SOCKET_BAD)
infof(data, " Trying %s... ", addr_buf);
if(data->set.tcp_nodelay)
tcpnodelay(conn, sockfd);
if(conn->data->set.device) {
/* user selected to bind the outgoing socket to a specified "device"
before doing connect */
CURLcode res = bindlocal(conn, sockfd);
if(res)
return res;
}
/* set socket non-blocking */
Curl_nonblock(sockfd, TRUE);
/* do not use #ifdef within the function arguments below, as connect() is
a defined macro on some platforms and some compilers don't like to mix
#ifdefs with macro usage! (AmigaOS is one such platform) */
rc = connect(sockfd, curr_addr->ai_addr, curr_addr->ai_addrlen);
if(-1 == rc) {
error = Curl_ourerrno();
switch (error) {
case EINPROGRESS:
case EWOULDBLOCK:
#if defined(EAGAIN) && 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
/* asynchronous connect, wait for connect or timeout */
if(data->state.used_interface == Curl_if_multi)
/* don't hang when doing multi */
timeout_per_addr = timeout_ms = 0;
rc = waitconnect(sockfd, timeout_per_addr);
break;
default:
/* unknown error, fallthrough and try another address! */
failf(data, "Failed to connect to %s (IP number %d): %s",
addr_buf, aliasindex+1, Curl_strerror(conn,error));
break;
}
}
/* The 'WAITCONN_TIMEOUT == rc' comes from the waitconnect(), and not from
connect(). We can be sure of this since connect() cannot return 1. */
if((WAITCONN_TIMEOUT == rc) &&
(data->state.used_interface == Curl_if_multi)) {
/* Timeout when running the multi interface, we return here with a
CURLE_OK return code. */
rc = 0;
break; break;
}
conected = verifyconnect(sockfd, &error);
if(!rc && conected) {
/* we are connected, awesome! */
*connected = TRUE; /* this is a true connect */
break;
}
if(WAITCONN_TIMEOUT == rc)
infof(data, "Timeout\n");
else
infof(data, "%s\n", Curl_strerror(conn, error));
/* connect failed or timed out */
sclose(sockfd);
sockfd = CURL_SOCKET_BAD;
/* get a new timeout for next attempt */ /* get a new timeout for next attempt */
after = Curl_tvnow(); after = Curl_tvnow();

View File

@ -320,7 +320,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
easy=multi->easy.next; easy=multi->easy.next;
while(easy) { while(easy) {
#ifdef CURLDEBUG #if 0
fprintf(stderr, "HANDLE %p: State: %x\n", fprintf(stderr, "HANDLE %p: State: %x\n",
(char *)easy, easy->state); (char *)easy, easy->state);
#endif #endif
@ -416,8 +416,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
case CURLM_STATE_WAITCONNECT: case CURLM_STATE_WAITCONNECT:
/* awaiting a completion of an asynch connect */ /* awaiting a completion of an asynch connect */
easy->result = Curl_is_connected(easy->easy_conn, easy->result = Curl_is_connected(easy->easy_conn, FIRSTSOCKET,
easy->easy_conn->sock[FIRSTSOCKET],
&connected); &connected);
if(connected) if(connected)
easy->result = Curl_protocol_connect(easy->easy_conn); easy->result = Curl_protocol_connect(easy->easy_conn);
@ -463,10 +462,8 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
/* /*
* First, check if we really are ready to do more. * First, check if we really are ready to do more.
*/ */
easy->result = easy->result = Curl_is_connected(easy->easy_conn, SECONDARYSOCKET,
Curl_is_connected(easy->easy_conn, &connected);
easy->easy_conn->sock[SECONDARYSOCKET],
&connected);
if(connected) { if(connected) {
/* /*
* When we are connected, DO MORE and then go PERFORM * When we are connected, DO MORE and then go PERFORM