#include "setup.h"
#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_SYS_UN_H
#include <sys/un.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_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE))
#include <sys/filio.h>
#endif
#ifdef NETWARE
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#include "urldata.h"
#include "sendf.h"
#include "if2ip.h"
#include "strerror.h"
#include "connect.h"
#include "curl_memory.h"
#include "select.h"
#include "url.h"
#include "multiif.h"
#include "sockaddr.h"
#include "inet_ntop.h"
#include "inet_pton.h"
#include "sslgen.h"
#include "memdebug.h"
#ifdef __SYMBIAN32__
#undef SO_NOSIGPIPE
#endif
struct Curl_sockaddr_ex {
int family;
int socktype;
int protocol;
unsigned int addrlen;
union {
struct sockaddr addr;
struct Curl_sockaddr_storage buff;
} _sa_ex_u;
};
#define sa_addr _sa_ex_u.addr
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);
long Curl_timeleft(struct connectdata *conn,
struct timeval *nowp,
bool duringconnect)
{
struct SessionHandle *data = conn->data;
int timeout_set = 0;
long timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0;
struct timeval now;
if(data->set.timeout > 0)
timeout_set |= 1;
if(duringconnect && (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:
if(!duringconnect)
return 0;
break;
}
if(!nowp) {
now = Curl_tvnow();
nowp = &now;
}
timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startsingle);
return timeout_ms;
}
#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, int af)
{
struct SessionHandle *data = conn->data;
struct Curl_sockaddr_storage sa;
struct sockaddr *sock = (struct sockaddr *)&sa;
curl_socklen_t sizeof_sa = 0;
struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
#ifdef ENABLE_IPV6
struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
#endif
struct Curl_dns_entry *h=NULL;
unsigned short port = data->set.localport;
int portnum = data->set.localportrange;
const char *dev = data->set.str[STRING_DEVICE];
int error;
char myhost[256] = "";
int done = 0;
if ( !dev && !port )
return CURLE_OK;
memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
if(dev && (strlen(dev)<255) ) {
if(Curl_if2ip(af, dev, myhost, sizeof(myhost))) {
infof(data, "Local Interface %s is ip %s using address family %i\n",
dev, myhost, af);
done = 1;
#ifdef SO_BINDTODEVICE
if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
dev, (curl_socklen_t)strlen(dev)+1) != 0) {
error = SOCKERRNO;
infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;"
" will do regular bind\n",
dev, error, Curl_strerror(conn, error));
}
#endif
}
else {
long ipver = data->set.ip_version;
int rc;
if (af == AF_INET)
data->set.ip_version = CURL_IPRESOLVE_V4;
#ifdef ENABLE_IPV6
else if (af == AF_INET6)
data->set.ip_version = CURL_IPRESOLVE_V6;
#endif
rc = Curl_resolv(conn, dev, 0, &h);
if(rc == CURLRESOLV_PENDING)
(void)Curl_wait_for_resolv(conn, &h);
data->set.ip_version = ipver;
if(h) {
Curl_printable_address(h->addr, myhost, sizeof(myhost));
infof(data, "Name '%s' family %i resolved to '%s' family %i\n",
dev, af, myhost, h->addr->ai_family);
Curl_resolv_unlock(data, h);
done = 1;
}
else {
done = -1;
}
}
if(done > 0) {
#ifdef ENABLE_IPV6
if((af == AF_INET6) &&
(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0)) {
si6->sin6_family = AF_INET6;
si6->sin6_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in6);
}
else
#endif
if((af == AF_INET) &&
(Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
si4->sin_family = AF_INET;
si4->sin_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in);
}
}
if(done < 1) {
failf(data, "Couldn't bind to '%s'", dev);
return CURLE_INTERFACE_FAILED;
}
}
else {
#ifdef ENABLE_IPV6
if ( af == AF_INET6 ) {
si6->sin6_family = AF_INET6;
si6->sin6_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in6);
}
else
#endif
if ( af == AF_INET ) {
si4->sin_family = AF_INET;
si4->sin_port = htons(port);
sizeof_sa = sizeof(struct sockaddr_in);
}
}
do {
if( bind(sockfd, sock, sizeof_sa) >= 0) {
struct Curl_sockaddr_storage add;
curl_socklen_t size = sizeof(add);
memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
data->state.os_errno = error = SOCKERRNO;
failf(data, "getsockname() failed with errno %d: %s",
error, Curl_strerror(conn, error));
return CURLE_INTERFACE_FAILED;
}
infof(data, "Local port: %d\n", port);
conn->bits.bound = TRUE;
return CURLE_OK;
}
if(--portnum > 0) {
infof(data, "Bind to local port %d failed, trying next\n", port);
port++;
if(sock->sa_family == AF_INET)
si4->sin_port = ntohs(port);
#ifdef ENABLE_IPV6
else
si6->sin6_port = ntohs(port);
#endif
}
else
break;
} while(1);
data->state.os_errno = error = SOCKERRNO;
failf(data, "bind failed with errno %d: %s",
error, Curl_strerror(conn, error));
return CURLE_INTERFACE_FAILED;
}
static bool verifyconnect(curl_socket_t sockfd, int *error)
{
bool rc = TRUE;
#ifdef SO_ERROR
int err = 0;
curl_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
#ifdef __minix
if(EBADIOCTL == 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;
}
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;
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;
DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
*connected = FALSE;
if(conn->bits.tcpconnect) {
long allow_total = 0;
if(data->set.timeout)
allow_total = data->set.timeout;
Curl_expire(data, allow_total);
*connected = TRUE;
return CURLE_OK;
}
allow = Curl_timeleft(conn, NULL, TRUE);
if(allow < 0) {
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEDOUT;
}
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)) {
failf(data, "Failed connect to %s:%d; %s",
conn->host.name, conn->port, Curl_strerror(conn, error));
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;
curl_socklen_t onoff = (curl_socklen_t) data->set.tcp_nodelay;
int proto = IPPROTO_TCP;
#if 0
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
#ifdef WIN32
void Curl_sndbufset(curl_socket_t sockfd)
{
int val = CURL_MAX_WRITE_SIZE + 32;
int curval = 0;
int curlen = sizeof(curval);
if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
if (curval > val)
return;
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
}
#endif
static curl_socket_t
singleipconnect(struct connectdata *conn,
const Curl_addrinfo *ai,
long timeout_ms,
bool *connected)
{
struct Curl_sockaddr_ex addr;
char addr_buf[128];
int rc;
int error;
bool isconnected;
struct SessionHandle *data = conn->data;
curl_socket_t sockfd;
CURLcode res;
const void *iptoprint;
struct sockaddr_in * const sa4 = (void *)&addr.sa_addr;
#ifdef ENABLE_IPV6
struct sockaddr_in6 * const sa6 = (void *)&addr.sa_addr;
#endif
addr.family = ai->ai_family;
addr.socktype = conn->socktype;
addr.protocol = ai->ai_protocol;
addr.addrlen = ai->ai_addrlen;
if(addr.addrlen > sizeof(struct Curl_sockaddr_storage))
addr.addrlen = sizeof(struct Curl_sockaddr_storage);
memcpy(&addr.sa_addr, ai->ai_addr, addr.addrlen);
*connected = FALSE;
if(data->set.fopensocket)
sockfd = data->set.fopensocket(data->set.opensocket_client,
CURLSOCKTYPE_IPCXN,
(struct curl_sockaddr *)&addr);
else
sockfd = socket(addr.family, addr.socktype, addr.protocol);
if(sockfd == CURL_SOCKET_BAD)
return CURL_SOCKET_BAD;
#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
if (conn->scope && (addr.family == AF_INET6))
sa6->sin6_scope_id = conn->scope;
#endif
#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX)
if(addr.family == AF_UNIX) {
infof(data, " Trying %s... ",
((const struct sockaddr_un*)(&addr.sa_addr))->sun_path);
snprintf(data->info.ip, MAX_IPADR_LEN, "%s",
((const struct sockaddr_un*)(&addr.sa_addr))->sun_path);
strcpy(conn->ip_addr_str, data->info.ip);
}
else
#endif
{
#ifdef ENABLE_IPV6
if(addr.family == AF_INET6) {
iptoprint = &sa6->sin6_addr;
conn->bits.ipv6 = TRUE;
}
else
#endif
{
iptoprint = &sa4->sin_addr;
}
if(Curl_inet_ntop(addr.family, iptoprint, addr_buf,
sizeof(addr_buf)) != NULL) {
infof(data, " Trying %s... ", addr_buf);
snprintf(data->info.ip, MAX_IPADR_LEN, "%s", addr_buf);
strcpy(conn->ip_addr_str, data->info.ip);
}
}
if(data->set.tcp_nodelay)
tcpnodelay(conn, sockfd);
nosigpipe(conn, sockfd);
Curl_sndbufset(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, addr.family);
if(res) {
sclose(sockfd);
return CURL_SOCKET_BAD;
}
curlx_nonblock(sockfd, TRUE);
if(conn->socktype == SOCK_STREAM)
rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
else
rc = 0;
if(-1 == rc) {
error = SOCKERRNO;
switch (error) {
case EINPROGRESS:
case EWOULDBLOCK:
#if defined(EAGAIN)
#if (EAGAIN) != (EWOULDBLOCK)
case EAGAIN:
#endif
#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;
struct timeval after;
struct timeval before = Curl_tvnow();
long timeout_ms;
long timeout_per_addr;
DEBUGASSERT(sockconn);
*connected = FALSE;
timeout_ms = Curl_timeleft(conn, &before, TRUE);
if(timeout_ms < 0) {
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEDOUT;
}
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_TIMEDOUT;
}
before = after;
}
*sockconn = sockfd;
if(sockfd == CURL_SOCKET_BAD) {
failf(data, "couldn't connect to host");
return CURLE_COULDNT_CONNECT;
}
if(addr)
*addr = curr_addr;
data->info.numconnects++;
return CURLE_OK;
}
CURLcode Curl_getconnectinfo(struct SessionHandle *data,
long *param_longp,
struct connectdata **connp)
{
if((data->state.lastconnect != -1) &&
(data->state.connc->connects[data->state.lastconnect] != NULL)) {
struct connectdata *c =
data->state.connc->connects[data->state.lastconnect];
if(connp)
*connp = c;
*param_longp = c->sock[FIRSTSOCKET];
if(c->ssl[FIRSTSOCKET].use) {
if(!Curl_ssl_check_cxn(c))
*param_longp = -1;
}
#ifdef MSG_PEEK
else {
char buf;
if(recv((RECV_TYPE_ARG1)c->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf,
(RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
*param_longp = -1;
}
}
#endif
}
else
*param_longp = -1;
return CURLE_OK;
}