#include "setup.h"
#ifndef WIN32
#include <sys/time.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if (defined(HAVE_FIONBIO) && defined(__NOVELL_LIBC__))
#include <sys/filio.h>
#endif
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#endif
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#ifdef WIN32
#include <windows.h>
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EISCONN WSAEISCONN
#define ENOTSOCK WSAENOTSOCK
#define ECONNREFUSED WSAECONNREFUSED
#endif
#include "urldata.h"
#include "sendf.h"
#include "if2ip.h"
#include "strerror.h"
#include "connect.h"
#include "memory.h"
#include "select.h"
#include "url.h"
#include "memdebug.h"
static bool verifyconnect(curl_socket_t sockfd, int *error);
static curl_socket_t
singleipconnect(struct connectdata *conn,
Curl_addrinfo *ai,
long timeout_ms,
bool *connected);
int Curl_ourerrno(void)
{
#ifdef WIN32
return (int)GetLastError();
#else
return errno;
#endif
}
int Curl_nonblock(curl_socket_t sockfd,
int nonblock )
{
#undef SETBLOCK
#ifdef HAVE_O_NONBLOCK
int flags;
flags = fcntl(sockfd, F_GETFL, 0);
if (TRUE == nonblock)
return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
else
return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK));
#define SETBLOCK 1
#endif
#ifdef HAVE_FIONBIO
int flags;
flags = nonblock;
return ioctl(sockfd, FIONBIO, &flags);
#define SETBLOCK 2
#endif
#ifdef HAVE_IOCTLSOCKET
unsigned long flags;
flags = nonblock;
return ioctlsocket(sockfd, FIONBIO, &flags);
#define SETBLOCK 3
#endif
#ifdef HAVE_IOCTLSOCKET_CASE
return IoctlSocket(sockfd, FIONBIO, (long)nonblock);
#define SETBLOCK 4
#endif
#ifdef HAVE_SO_NONBLOCK
long b = nonblock ? 1 : 0;
return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
#define SETBLOCK 5
#endif
#ifdef HAVE_DISABLED_NONBLOCKING
return 0;
#define SETBLOCK 6
#endif
#ifndef SETBLOCK
#error "no non-blocking method was found/used/set"
#endif
}
#define WAITCONN_CONNECTED 0
#define WAITCONN_SELECT_ERROR -1
#define WAITCONN_TIMEOUT 1
#define WAITCONN_FDSET_ERROR 2
static
int waitconnect(curl_socket_t sockfd,
long timeout_msec)
{
int rc;
#ifdef mpeix
verifyconnect(sockfd, NULL);
#endif
rc = Curl_select(CURL_SOCKET_BAD, sockfd, (int)timeout_msec);
if(-1 == rc)
return WAITCONN_SELECT_ERROR;
else if(0 == rc)
return WAITCONN_TIMEOUT;
if(rc & CSELECT_ERR)
return WAITCONN_FDSET_ERROR;
return WAITCONN_CONNECTED;
}
static CURLcode bindlocal(struct connectdata *conn,
curl_socket_t sockfd)
{
#ifdef HAVE_INET_NTOA
bool bindworked = FALSE;
struct SessionHandle *data = conn->data;
if (strlen(data->set.device)<255) {
struct Curl_dns_entry *h=NULL;
size_t size;
char myhost[256] = "";
in_addr_t in;
int rc;
bool was_iface = FALSE;
in=inet_addr(data->set.device);
if((in == CURL_INADDR_NONE) &&
Curl_if2ip(data->set.device, myhost, sizeof(myhost))) {
rc = Curl_resolv(conn, myhost, 0, &h);
if(rc == CURLRESOLV_PENDING)
(void)Curl_wait_for_resolv(conn, &h);
if(h)
was_iface = TRUE;
}
if(!was_iface) {
rc = Curl_resolv(conn, data->set.device, 0, &h);
if(rc == CURLRESOLV_PENDING)
(void)Curl_wait_for_resolv(conn, &h);
if(h)
strcpy(myhost, data->set.device);
}
if(! *myhost) {
failf(data, "Couldn't bind to '%s'", data->set.device);
return CURLE_HTTP_PORT_FAILED;
}
infof(data, "We bind local end to %s\n", myhost);
#ifdef SO_BINDTODEVICE
if (was_iface) {
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
data->set.device, strlen(data->set.device)+1) != 0) {
infof(data, "SO_BINDTODEVICE %s failed\n",
data->set.device);
}
}
#endif
in=inet_addr(myhost);
if (CURL_INADDR_NONE != in) {
if ( h ) {
Curl_addrinfo *addr = h->addr;
Curl_resolv_unlock(data, h);
if( bind(sockfd, addr->ai_addr, (socklen_t)addr->ai_addrlen) >= 0) {
#ifdef ENABLE_IPV6
struct sockaddr_in6 add;
#else
struct sockaddr_in add;
#endif
bindworked = TRUE;
size = sizeof(add);
if(getsockname(sockfd, (struct sockaddr *) &add,
(socklen_t *)&size)<0) {
failf(data, "getsockname() failed");
return CURLE_HTTP_PORT_FAILED;
}
}
if(!bindworked) {
data->state.os_errno = Curl_ourerrno();
failf(data, "%s", Curl_strerror(conn, data->state.os_errno));
return CURLE_HTTP_PORT_FAILED;
}
}
else {
failf(data,"could't find my own IP address (%s)", myhost);
return CURLE_HTTP_PORT_FAILED;
}
}
else {
failf(data, "could't find my own IP address (%s)", myhost);
return CURLE_HTTP_PORT_FAILED;
}
return CURLE_OK;
}
#endif
return CURLE_HTTP_PORT_FAILED;
}
static bool verifyconnect(curl_socket_t sockfd, int *error)
{
bool rc = TRUE;
#ifdef SO_ERROR
int err = 0;
socklen_t errSize = sizeof(err);
#ifdef WIN32
#ifdef _WIN32_WCE
Sleep(0);
#else
SleepEx(0, FALSE);
#endif
#endif
if( -1 == getsockopt(sockfd, SOL_SOCKET, SO_ERROR,
(void *)&err, &errSize))
err = Curl_ourerrno();
#ifdef _WIN32_WCE
if(WSAENOPROTOOPT==err)
err=0;
#endif
if ((0 == err) || (EISCONN == err))
rc = TRUE;
else
rc = FALSE;
if (error)
*error = err;
#else
(void)sockfd;
if (error)
*error = Curl_ourerrno();
#endif
return rc;
}
CURLcode Curl_store_ip_addr(struct connectdata *conn)
{
char addrbuf[256];
Curl_printable_address(conn->ip_addr, addrbuf, sizeof(addrbuf));
Curl_safefree(conn->ip_addr_str);
conn->ip_addr_str = strdup(addrbuf);
if(!conn->ip_addr_str)
return CURLE_OUT_OF_MEMORY;
#ifdef PF_INET6
if(conn->ip_addr->ai_family == PF_INET6)
conn->bits.ipv6 = TRUE;
#endif
return CURLE_OK;
}
static bool trynextip(struct connectdata *conn,
int sockindex,
bool *connected)
{
curl_socket_t sockfd;
Curl_addrinfo *ai;
if(sockindex != FIRSTSOCKET)
return TRUE;
ai = conn->ip_addr->ai_next;
while (ai) {
sockfd = singleipconnect(conn, ai, 0L, connected);
if(sockfd != CURL_SOCKET_BAD) {
conn->sock[sockindex] = sockfd;
conn->ip_addr = ai;
Curl_store_ip_addr(conn);
return FALSE;
}
ai = ai->ai_next;
}
return TRUE;
}
CURLcode Curl_is_connected(struct connectdata *conn,
int sockindex,
bool *connected)
{
int rc;
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;
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
if(data->set.timeout && data->set.connecttimeout) {
if (data->set.timeout < data->set.connecttimeout)
allow = data->set.timeout*1000;
else
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 > allow ) {
failf(data, "Connection time-out after %ld ms", has_passed);
return CURLE_OPERATION_TIMEOUTED;
}
if(conn->bits.tcpconnect) {
*connected = TRUE;
return CURLE_OK;
}
rc = waitconnect(sockfd, 0);
if(WAITCONN_CONNECTED == rc) {
int error;
if (verifyconnect(sockfd, &error)) {
*connected = TRUE;
return CURLE_OK;
}
data->state.os_errno = error;
infof(data, "Connection failed\n");
if(trynextip(conn, sockindex, connected)) {
code = CURLE_COULDNT_CONNECT;
}
}
else if(WAITCONN_TIMEOUT != rc) {
int error = 0;
if (WAITCONN_FDSET_ERROR == rc) {
verifyconnect(sockfd, &error);
data->state.os_errno = error;
infof(data, "%s\n",Curl_strerror(conn,error));
}
else
infof(data, "Connection failed\n");
if(trynextip(conn, sockindex, connected)) {
error = Curl_ourerrno();
data->state.os_errno = error;
failf(data, "Failed connect to %s:%d; %s",
conn->host.name, conn->port, Curl_strerror(conn,error));
code = CURLE_COULDNT_CONNECT;
}
}
return code;
}
static void tcpnodelay(struct connectdata *conn,
curl_socket_t sockfd)
{
#ifdef TCP_NODELAY
struct SessionHandle *data= conn->data;
socklen_t onoff = (socklen_t) data->set.tcp_nodelay;
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void *)&onoff,
sizeof(onoff)) < 0)
infof(data, "Could not set TCP_NODELAY: %s\n",
Curl_strerror(conn, Curl_ourerrno()));
else
infof(data,"TCP_NODELAY set\n");
#else
(void)conn;
(void)sockfd;
#endif
}
#ifdef SO_NOSIGPIPE
static void nosigpipe(struct connectdata *conn,
curl_socket_t sockfd)
{
struct SessionHandle *data= conn->data;
int onoff = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
sizeof(onoff)) < 0)
infof(data, "Could not set SO_NOSIGPIPE: %s\n",
Curl_strerror(conn, Curl_ourerrno()));
}
#endif
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;
Curl_printable_address(ai, addr_buf, sizeof(addr_buf));
infof(data, " Trying %s... ", addr_buf);
if(data->set.tcp_nodelay)
tcpnodelay(conn, sockfd);
#ifdef SO_NOSIGPIPE
nosigpipe(conn, sockfd);
#endif
if(conn->data->set.device) {
CURLcode res = bindlocal(conn, sockfd);
if(res) {
sclose(sockfd);
return res;
}
}
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
case EAGAIN:
#endif
rc = waitconnect(sockfd, timeout_ms);
break;
default:
failf(data, "Failed to connect to %s: %s",
addr_buf, Curl_strerror(conn,error));
data->state.os_errno = error;
break;
}
}
if((WAITCONN_TIMEOUT == rc) &&
(data->state.used_interface == Curl_if_multi)) {
return sockfd;
}
conected = verifyconnect(sockfd, &error);
if(!rc && conected) {
*connected = TRUE;
infof(data, "connected\n");
return sockfd;
}
else if(WAITCONN_TIMEOUT == rc)
infof(data, "Timeout\n");
else {
data->state.os_errno = error;
infof(data, "%s\n", Curl_strerror(conn, error));
}
sclose(sockfd);
return CURL_SOCKET_BAD;
}
CURLcode Curl_connecthost(struct connectdata *conn,
struct Curl_dns_entry *remotehost,
curl_socket_t *sockconn,
Curl_addrinfo **addr,
bool *connected)
{
struct SessionHandle *data = conn->data;
curl_socket_t sockfd = CURL_SOCKET_BAD;
int aliasindex;
int num_addr;
Curl_addrinfo *ai;
Curl_addrinfo *curr_addr;
struct timeval after;
struct timeval before = Curl_tvnow();
long timeout_ms= DEFAULT_CONNECT_TIMEOUT;
long timeout_per_addr;
*connected = FALSE;
if(data->set.timeout || data->set.connecttimeout) {
long has_passed;
has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle);
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
if(data->set.timeout && data->set.connecttimeout) {
if (data->set.timeout < data->set.connecttimeout)
timeout_ms = data->set.timeout*1000;
else
timeout_ms = data->set.connecttimeout*1000;
}
else if(data->set.timeout)
timeout_ms = data->set.timeout*1000;
else
timeout_ms = data->set.connecttimeout*1000;
timeout_ms -= has_passed;
if(timeout_ms < 0) {
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEOUTED;
}
}
num_addr = Curl_num_addresses(remotehost->addr);
timeout_per_addr = timeout_ms / num_addr;
ai = remotehost->addr;
if(data->state.used_interface == Curl_if_multi)
timeout_per_addr = 0;
for (curr_addr = ai, aliasindex=0; curr_addr;
curr_addr = curr_addr->ai_next, aliasindex++) {
sockfd = singleipconnect(conn, curr_addr, timeout_per_addr, connected);
if(sockfd != CURL_SOCKET_BAD)
break;
after = Curl_tvnow();
timeout_ms -= Curl_tvdiff(after, before);
if(timeout_ms < 0) {
failf(data, "connect() timed out!");
return CURLE_OPERATION_TIMEOUTED;
}
before = after;
}
if (sockfd == CURL_SOCKET_BAD) {
*sockconn = CURL_SOCKET_BAD;
failf(data, "couldn't connect to host");
return CURLE_COULDNT_CONNECT;
}
if(addr)
*addr = curr_addr;
if(sockconn)
*sockconn = sockfd;
data->info.numconnects++;
return CURLE_OK;
}