From c5fdeef41d50d5207c52507e17eadc1ba1e4c5f0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 1 Oct 2001 08:59:17 +0000 Subject: introduced non-blocking connects --- lib/Makefile.am | 2 +- lib/connect.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/connect.h | 29 ++++++ lib/url.c | 115 ++---------------------- 4 files changed, 313 insertions(+), 108 deletions(-) create mode 100644 lib/connect.c create mode 100644 lib/connect.h (limited to 'lib') diff --git a/lib/Makefile.am b/lib/Makefile.am index 900836270..a8d4f5019 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -59,7 +59,7 @@ escape.c mprintf.c telnet.c \ escape.h getpass.c netrc.c telnet.h \ getinfo.c getinfo.h transfer.c strequal.c strequal.h easy.c \ security.h security.c krb4.c krb4.h memdebug.c memdebug.h inet_ntoa_r.h \ -http_chunks.c http_chunks.h strtok.c strtok.h +http_chunks.c http_chunks.h strtok.c strtok.h connect.c connect.h noinst_HEADERS = setup.h transfer.h diff --git a/lib/connect.c b/lib/connect.c new file mode 100644 index 000000000..4bff6d699 --- /dev/null +++ b/lib/connect.c @@ -0,0 +1,275 @@ +/***************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2001, Daniel Stenberg, , et al. + * + * In order to be useful for every potential user, curl and libcurl are + * dual-licensed under the MPL and the MIT/X-derivate licenses. + * + * 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 + * furnished to do so, under the terms of the MPL or the MIT/X-derivate + * licenses. You may pick one of these licenses. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + *****************************************************************************/ + +#include "setup.h" + +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#endif +#include +#include + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifdef WIN32 +#define HAVE_IOCTLSOCKET +#include +#include +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif + +#include "urldata.h" +#include "sendf.h" + +/* The last #include file should be: */ +#ifdef MALLOCDEBUG +#include "memdebug.h" +#endif + +/************************************************************************* + * Curl_nonblock + * + * Description: + * Set the socket to either blocking or non-blocking mode. + */ +static +int nonblock(int socket, /* operate on this */ + int nonblock /* TRUE or FALSE */) +{ +#undef SETBLOCK +#ifdef HAVE_O_NONBLOCK + int flags; + + flags = fcntl(socket, F_GETFL, 0); + if (TRUE == nonblock) + return fcntl(socket, F_SETFL, flags | O_NONBLOCK); + else + return fcntl(socket, F_SETFL, flags & (~O_NONBLOCK)); +#define SETBLOCK 1 +#endif + +#ifdef HAVE_FIONBIO + int flags; + + flags = nonblock; + return ioctl(socket, FIONBIO, &flags); +#define SETBLOCK 2 +#endif + +#ifdef HAVE_IOCTLSOCKET + int flags; + flags = nonblock; + return ioctlsocket(socket, FIONBIO, &flags); +#define SETBLOCK 3 +#endif + +#ifdef HAVE_IOCTLSOCKET_CASE + return IoctlSocket(socket, FIONBIO, (long)nonblock); +#define SETBLOCK 4 +#endif + +#ifdef HAVE_DISABLED_NONBLOCKING + return 0; /* returns success */ +#define SETBLOCK 5 +#endif + +#ifndef SETBLOCK +#error "no non-blocking method was found/used/set" +#endif +} + +/* + * Return 0 on fine connect, -1 on error and 1 on timeout. + */ +static +int waitconnect(int sockfd, /* socket */ + int timeout_msec) +{ + fd_set fd; + struct timeval interval; + int rc; + + /* now select() until we get connect or timeout */ + FD_ZERO(&fd); + FD_SET(sockfd, &fd); + + interval.tv_sec = timeout_msec/1000; + timeout_msec -= interval.tv_sec*1000; + + interval.tv_usec = timeout_msec*1000; + + rc = select(sockfd+1, NULL, &fd, NULL, &interval); + if(-1 == rc) + /* error, no connect here, try next */ + return -1; + + else if(0 == rc) + /* timeout, no connect today */ + return 1; + + /* we have a connect! */ + return 0; +} + +/* + * 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. + */ + +CURLcode Curl_connecthost(struct connectdata *conn, + int sockfd, /* input socket, or -1 if none */ + int *socket) +{ + struct SessionHandle *data = conn->data; + int rc; + +#ifdef ENABLE_IPV6 + /* + * Connecting with IPv6 support is so much easier and cleanly done + */ + if(sockfd != -1) + /* don't use any previous one, it might be of wrong type */ + sclose(sockfd); + sockfd = -1; /* none! */ + for (ai = conn->hp; ai; ai = ai->ai_next) { + sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sockfd < 0) + continue; + + /* set socket non-blocking */ + nonblock(sockfd, TRUE); + + rc = connect(sockfd, ai->ai_addr, ai->ai_addrlen); + + if(0 == rc) + /* direct connect, awesome! */ + break; + + /* asynchronous connect, wait for connect or timeout */ + rc = waitconnect(sockfd, timeout); + if(0 != rc) { + /* connect failed or timed out */ + sclose(sockfd); + sockfd = -1; + continue; + } + + /* now disable the non-blocking mode again */ + nonblock(sockfd, FALSE); + break; + } + conn->ai = ai; + if (sockfd < 0) { + failf(data, strerror(errno)); + return CURLE_COULDNT_CONNECT; + } +#else + /* + * Connecting with IPv4-only support + */ + int aliasindex; + int timeout_ms = 10000; /* while testing */ + + /* non-block socket */ + nonblock(sockfd, TRUE); + + /* This is the loop that attempts to connect to all IP-addresses we + know for the given host. One by one. */ + for(rc=-1, aliasindex=0; + rc && (struct in_addr *)conn->hp->h_addr_list[aliasindex]; + aliasindex++) { + + /* copy this particular name info to the conn struct as it might + be used later in the krb4 "system" */ + memset((char *) &conn->serv_addr, '\0', sizeof(conn->serv_addr)); + memcpy((char *)&(conn->serv_addr.sin_addr), + (struct in_addr *)conn->hp->h_addr_list[aliasindex], + sizeof(struct in_addr)); + conn->serv_addr.sin_family = conn->hp->h_addrtype; + conn->serv_addr.sin_port = htons(conn->port); + + rc = connect(sockfd, (struct sockaddr *)&(conn->serv_addr), + sizeof(conn->serv_addr)); + + if(-1 == rc) { + int error; +#ifdef WIN32 + error = (int)GetLastError(); +#else + error = errno; +#endif + 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 */ + rc = waitconnect(sockfd, timeout_ms); + break; + default: + /* unknown error, fallthrough and try another address! */ + break; + } + } + + if(0 != rc) + continue; /* try next address */ + else + break; + } + if(-1 == rc) { + /* no good connect was made */ + sclose(sockfd); + *socket = -1; + failf(data, "Couldn't connect to (any) IP address"); + return CURLE_COULDNT_CONNECT; + } + + /* now disable the non-blocking mode again */ + nonblock(sockfd, FALSE); + +#endif + + *socket = sockfd; /* pass this to our parent */ + + return CURLE_OK; +} + diff --git a/lib/connect.h b/lib/connect.h new file mode 100644 index 000000000..10fd46353 --- /dev/null +++ b/lib/connect.h @@ -0,0 +1,29 @@ +#ifndef __NONBLOCK_H +#define __NONBLOCK_H +/***************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2001, Daniel Stenberg, , et al. + * + * In order to be useful for every potential user, curl and libcurl are + * dual-licensed under the MPL and the MIT/X-derivate licenses. + * + * 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 + * furnished to do so, under the terms of the MPL or the MIT/X-derivate + * licenses. You may pick one of these licenses. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + *****************************************************************************/ + +CURLcode Curl_connecthost(struct connectdata *conn, + int sockfd, /* input socket, or -1 if none */ + int *socket); +#endif diff --git a/lib/url.c b/lib/url.c index 65443d7a8..ba6c36a6b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -107,6 +107,7 @@ #include "file.h" #include "ldap.h" #include "url.h" +#include "connect.h" #include @@ -1123,12 +1124,6 @@ static CURLcode ConnectPlease(struct SessionHandle *data, #ifndef ENABLE_IPV6 conn->firstsocket = socket(AF_INET, SOCK_STREAM, 0); - - memset((char *) &conn->serv_addr, '\0', sizeof(conn->serv_addr)); - memcpy((char *)&(conn->serv_addr.sin_addr), - conn->hp->h_addr, conn->hp->h_length); - conn->serv_addr.sin_family = conn->hp->h_addrtype; - conn->serv_addr.sin_port = htons(conn->port); #else /* IPv6-style */ struct addrinfo *ai; @@ -1259,108 +1254,14 @@ static CURLcode ConnectPlease(struct SessionHandle *data, /************************************************************* * Connect to server/proxy *************************************************************/ -#ifdef ENABLE_IPV6 - conn->firstsocket = -1; - for (ai = conn->hp; ai; ai = ai->ai_next) { - conn->firstsocket = socket(ai->ai_family, - ai->ai_socktype, - ai->ai_protocol); - if (conn->firstsocket < 0) - continue; - - if (connect(conn->firstsocket, ai->ai_addr, ai->ai_addrlen) < 0) { - sclose(conn->firstsocket); - conn->firstsocket = -1; - continue; - } - - break; - } - conn->ai = ai; - if (conn->firstsocket < 0) { - failf(data, strerror(errno)); - return CURLE_COULDNT_CONNECT; - } -#else - /* non-zero nonblock value sets socket as nonblocking under Win32 */ -#if defined(WIN32) - FD_ZERO (&connectfd); - FD_SET(conn->firstsocket, &connectfd); - if (conn->data->set.connecttimeout > 0) { - nonblock = 1; - } - ioctlsocket(conn->firstsocket, FIONBIO, &nonblock); -#endif - if (connect(conn->firstsocket, - (struct sockaddr *) &(conn->serv_addr), - sizeof(conn->serv_addr) - ) < 0) { -#if defined(WIN32) - conntimeout.tv_sec = conn->data->set.connecttimeout; - conntimeout.tv_usec = 0; - if(-1 != select (conn->firstsocket + 1, NULL, &connectfd, NULL, &conntimeout)) { - if (FD_ISSET(conn->firstsocket, &connectfd)) { - /* shut off non-blocking again */ - nonblock = 0; - ioctlsocket(conn->firstsocket, FIONBIO, &nonblock); - return CURLE_OK; - } - else - errno = EINTR; - } -#endif - switch(errno) { -#ifdef ECONNREFUSED - /* this should be made nicer */ - case ECONNREFUSED: - failf(data, "Connection refused"); - break; - case EFAULT: - failf(data, "Invalid socket address: %d",errno); - break; - case EISCONN: - failf(data, "Socket already connected: %d",errno); - break; - case ETIMEDOUT: - failf(data, "Timeout while accepting connection, server busy: %d",errno); - break; - case ENETUNREACH: - failf(data, "Network is unreachable: %d",errno); - break; - case EADDRINUSE: - failf(data, "Local address already in use: %d",errno); - break; - case EINPROGRESS: - failf(data, "Socket is nonblocking and connection can not be completed immediately: %d",errno); - break; - case EALREADY: - failf(data, "Socket is nonblocking and a previous connection attempt not completed: %d",errno); - break; - case EAGAIN: - failf(data, "No more free local ports: %d",errno); - break; - case EACCES: - case EPERM: - failf(data, "Attempt to connect to broadcast address without socket broadcast flag or local firewall rule violated: %d",errno); - break; -#endif - case EINTR: - failf(data, "Connection timed out"); - break; - default: - failf(data, "Can't connect to server: %d", errno); - break; - } - return CURLE_COULDNT_CONNECT; - } -#endif - - return CURLE_OK; + return Curl_connecthost(conn, + conn->firstsocket, /* might be bind()ed */ + &conn->firstsocket); } -static CURLcode Connect(struct SessionHandle *data, - struct connectdata **in_connect, - bool allow_port) /* allow data->set.use_port ? */ +static CURLcode CreateConnection(struct SessionHandle *data, + struct connectdata **in_connect, + bool allow_port) /* allow set.use_port? */ { char *tmp; char *buf; @@ -2296,7 +2197,7 @@ CURLcode Curl_connect(struct SessionHandle *data, struct connectdata *conn; /* call the stuff that needs to be called */ - code = Connect(data, in_connect, allow_port); + code = CreateConnection(data, in_connect, allow_port); if(CURLE_OK != code) { /* We're not allowed to return failure with memory left allocated -- cgit v1.2.3