#include "setup.h"
#ifndef WIN32
#ifdef HAVE_SYS_TIME_H
#include <sys/time.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>
#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 "multiif.h"
#include "sockaddr.h"
#include "inet_ntop.h"
#include "inet_pton.h"
#include "memdebug.h"
static bool verifyconnect(curl_socket_t sockfd, int *error);
static curl_socket_t
singleipconnect(struct connectdata *conn,
const Curl_addrinfo *ai,
long timeout_ms,
bool *connected);
int Curl_nonblock(curl_socket_t sockfd,
int nonblock )
{
#undef SETBLOCK
#define SETBLOCK 0
#ifdef HAVE_O_NONBLOCK
int flags;
flags = fcntl(sockfd, F_GETFL, 0);
if (FALSE != nonblock)
return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
else
return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK));
#undef SETBLOCK
#define SETBLOCK 1
#endif
#if defined(HAVE_FIONBIO) && (SETBLOCK == 0)
int flags;
flags = nonblock;
return ioctl(sockfd, FIONBIO, &flags);
#undef SETBLOCK
#define SETBLOCK 2
#endif
#if defined(HAVE_IOCTLSOCKET) && (SETBLOCK == 0)
unsigned long flags;
flags = nonblock;
return ioctlsocket(sockfd, FIONBIO, &flags);
#undef SETBLOCK
#define SETBLOCK 3
#endif
#if defined(HAVE_IOCTLSOCKET_CASE) && (SETBLOCK == 0)
return IoctlSocket(sockfd, FIONBIO, (long)nonblock);
#undef SETBLOCK
#define SETBLOCK 4
#endif
#if defined(HAVE_SO_NONBLOCK) && (SETBLOCK == 0)
long b = nonblock ? 1 : 0;
return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
#undef SETBLOCK
#define SETBLOCK 5
#endif
#ifdef HAVE_DISABLED_NONBLOCKING
return 0;
#undef SETBLOCK
#define SETBLOCK 6
#endif
#if (SETBLOCK == 0)
#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
(void)verifyconnect(sockfd, NULL);
#endif
rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, (int)timeout_msec);
if(-1 == rc)
return WAITCONN_SELECT_ERROR;
else if(0 == rc)
return WAITCONN_TIMEOUT;
if(rc & CURL_CSELECT_ERR)
return WAITCONN_FDSET_ERROR;
return WAITCONN_CONNECTED;
}
static CURLcode bindlocal(struct connectdata *conn,
curl_socket_t sockfd)
{
#ifdef ENABLE_IPV6
char ipv6_addr[16];
#endif
struct SessionHandle *data = conn->data;
struct sockaddr_in me;
struct sockaddr *sock = NULL;
socklen_t socksize;
unsigned short port = data->set.localport;
int portnum = data->set.localportrange;
if (data->set.device && (strlen(data->set.device)<255) ) {
struct Curl_dns_entry *h=NULL;
char myhost[256] = "";
in_addr_t in;
int rc;
bool was_iface = FALSE;
int in6 = -1;
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;
Curl_resolv_unlock(data, h);
}
}
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) {
if(in == CURL_INADDR_NONE)
Curl_inet_ntop(h->addr->ai_addr->sa_family,
&((struct sockaddr_in*)h->addr->ai_addr)->sin_addr,
myhost, sizeof myhost);
else
strcpy(myhost, data->set.device);
Curl_resolv_unlock(data, h);
}
}
if(! *myhost) {
failf(data, "Couldn't bind to '%s'", data->set.device);
return CURLE_HTTP_PORT_FAILED;
}
infof(data, "Bind local address 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);
#ifdef ENABLE_IPV6
in6 = Curl_inet_pton (AF_INET6, myhost, (void *)&ipv6_addr);
#endif
if (CURL_INADDR_NONE == in && -1 == in6) {
failf(data,"couldn't find my own IP address (%s)", myhost);
return CURLE_HTTP_PORT_FAILED;
}
if ( h ) {
Curl_addrinfo *addr = h->addr;
sock = addr->ai_addr;
socksize = addr->ai_addrlen;
}
else
return CURLE_HTTP_PORT_FAILED;
}
else if(port) {
memset(&me, 0, sizeof(struct sockaddr));
me.sin_family = AF_INET;
me.sin_addr.s_addr = INADDR_ANY;
sock = (struct sockaddr *)&me;
socksize = sizeof(struct sockaddr);
}
else
return CURLE_OK;
do {
if(sock->sa_family == AF_INET)
((struct sockaddr_in *)sock)->sin_port = htons(port);
#ifdef ENABLE_IPV6
else
((struct sockaddr_in6 *)sock)->sin6_port = htons(port);
#endif
if( bind(sockfd, sock, socksize) >= 0) {
struct Curl_sockaddr_storage add;
socklen_t size;
size = sizeof(add);
if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
failf(data, "getsockname() failed");
return CURLE_HTTP_PORT_FAILED;
}
if(((struct sockaddr *)&add)->sa_family == AF_INET)
port = ntohs(((struct sockaddr_in *)&add)->sin_port);
#ifdef ENABLE_IPV6
else
port = ntohs(((struct sockaddr_in6 *)&add)->sin6_port);
#endif
infof(data, "Local port: %d\n", port);
return CURLE_OK;
}
if(--portnum > 0) {
infof(data, "Bind to local port %d failed, trying next\n", port);
port++;
}
else
break;
} while(1);
data->state.os_errno = SOCKERRNO;
failf(data, "bind failure: %s",
Curl_strerror(conn, data->state.os_errno));
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 (0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
err = SOCKERRNO;
#ifdef _WIN32_WCE
if (WSAENOPROTOOPT == err) {
SET_SOCKERRNO(0);
err = 0;
}
#endif
if ((0 == err) || (EISCONN == err))
rc = TRUE;
else
rc = FALSE;
if (error)
*error = err;
#else
(void)sockfd;
if (error)
*error = SOCKERRNO;
#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;
sclose(conn->sock[sockindex]);
conn->sock[sockindex] = CURL_SOCKET_BAD;
*connected = FALSE;
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 allow_total = 0;
long has_passed;
DEBUGASSERT(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_total = allow = data->set.timeout;
else
allow = data->set.connecttimeout;
}
else if(data->set.timeout) {
allow_total = allow = data->set.timeout;
}
else if(data->set.connecttimeout) {
allow = data->set.connecttimeout;
}
if(has_passed > allow ) {
failf(data, "Connection time-out after %ld ms", has_passed);
return CURLE_OPERATION_TIMEOUTED;
}
if(conn->bits.tcpconnect) {
Curl_expire(data, allow_total);
*connected = TRUE;
return CURLE_OK;
}
Curl_expire(data, allow);
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) {
(void)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 = SOCKERRNO;
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;
int proto = IPPROTO_TCP;
#ifdef HAVE_GETPROTOBYNAME
struct protoent *pe = getprotobyname("tcp");
if (pe)
proto = pe->p_proto;
#endif
if(setsockopt(sockfd, proto, TCP_NODELAY, (void *)&onoff,
sizeof(onoff)) < 0)
infof(data, "Could not set TCP_NODELAY: %s\n",
Curl_strerror(conn, SOCKERRNO));
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, SOCKERRNO));
}
#else
#define nosigpipe(x,y)
#endif
static curl_socket_t
singleipconnect(struct connectdata *conn,
const Curl_addrinfo *ai,
long timeout_ms,
bool *connected)
{
char addr_buf[128];
int rc;
int error;
bool isconnected;
struct SessionHandle *data = conn->data;
curl_socket_t sockfd;
CURLcode res;
sockfd = socket(ai->ai_family, conn->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);
nosigpipe(conn, sockfd);
if(data->set.fsockopt) {
error = data->set.fsockopt(data->set.sockopt_client,
sockfd,
CURLSOCKTYPE_IPCXN);
if (error) {
sclose(sockfd);
return CURL_SOCKET_BAD;
}
}
res = bindlocal(conn, sockfd);
if(res) {
sclose(sockfd);
return CURL_SOCKET_BAD;
}
Curl_nonblock(sockfd, TRUE);
if(conn->socktype == SOCK_STREAM)
rc = connect(sockfd, ai->ai_addr, ai->ai_addrlen);
else
rc = 0;
if(-1 == rc) {
error = SOCKERRNO;
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;
}
isconnected = verifyconnect(sockfd, &error);
if(!rc && isconnected) {
*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,
const 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;
int timeout_set = 0;
struct timeval after;
struct timeval before = Curl_tvnow();
long timeout_ms;
long timeout_per_addr;
*connected = FALSE;
if (data->set.timeout > 0)
timeout_set += 1;
if (data->set.connecttimeout > 0)
timeout_set += 2;
switch (timeout_set) {
case 1:
timeout_ms = data->set.timeout;
break;
case 2:
timeout_ms = data->set.connecttimeout;
break;
case 3:
if (data->set.timeout < data->set.connecttimeout)
timeout_ms = data->set.timeout;
else
timeout_ms = data->set.connecttimeout;
break;
default:
timeout_ms = DEFAULT_CONNECT_TIMEOUT;
break;
}
if (timeout_set > 0) {
timeout_ms -= Curl_tvdiff(before, data->progress.t_startsingle);
if(timeout_ms < 0) {
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEOUTED;
}
}
Curl_expire(data, timeout_ms);
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;
}