2018-12-09 23:07:40 +01:00
|
|
|
/*
|
|
|
|
* IXSocketConnect.cpp
|
|
|
|
* Author: Benjamin Sergeant
|
|
|
|
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "IXSocketConnect.h"
|
2018-12-15 01:28:11 +01:00
|
|
|
#include "IXDNSLookup.h"
|
2019-01-06 05:38:43 +01:00
|
|
|
#include "IXNetSystem.h"
|
2018-12-09 23:07:40 +01:00
|
|
|
|
2018-12-10 02:56:20 +01:00
|
|
|
#include <string.h>
|
2018-12-09 23:07:40 +01:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
|
|
|
|
#ifdef ANDROID
|
|
|
|
# include <linux/in.h>
|
|
|
|
# include <linux/tcp.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
void closeSocket(int fd)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
closesocket(fd);
|
|
|
|
#else
|
|
|
|
::close(fd);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
namespace ix
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
2018-12-15 01:28:11 +01:00
|
|
|
//
|
|
|
|
// This function can be cancelled every 50 ms
|
|
|
|
// This is important so that we don't block the main UI thread when shutting down a connection which is
|
|
|
|
// already trying to reconnect, and can be blocked waiting for ::connect to respond.
|
|
|
|
//
|
2019-01-03 05:07:54 +01:00
|
|
|
int SocketConnect::connectToAddress(const struct addrinfo *address,
|
|
|
|
std::string& errMsg,
|
|
|
|
const CancellationRequest& isCancellationRequested)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
2019-01-03 05:07:54 +01:00
|
|
|
errMsg = "no error";
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-01-07 20:18:00 +01:00
|
|
|
int fd = socket(address->ai_family,
|
|
|
|
address->ai_socktype,
|
|
|
|
address->ai_protocol);
|
2018-12-09 23:07:40 +01:00
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
errMsg = "Cannot create a socket";
|
2019-01-03 05:07:54 +01:00
|
|
|
return -1;
|
2018-12-09 23:07:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the socket to non blocking mode, so that slow responses cannot
|
2018-12-15 01:28:11 +01:00
|
|
|
// block us for too long
|
2018-12-09 23:07:40 +01:00
|
|
|
SocketConnect::configure(fd);
|
|
|
|
|
2019-01-07 20:18:00 +01:00
|
|
|
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
|
|
|
|
&& errno != EINPROGRESS)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
2019-01-07 20:18:00 +01:00
|
|
|
closeSocket(fd);
|
|
|
|
errMsg = strerror(errno);
|
|
|
|
return -1;
|
2018-12-09 23:07:40 +01:00
|
|
|
}
|
|
|
|
|
2019-01-03 21:53:44 +01:00
|
|
|
for (;;)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
2019-03-20 22:29:02 +01:00
|
|
|
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
2018-12-10 02:56:20 +01:00
|
|
|
{
|
|
|
|
closeSocket(fd);
|
|
|
|
errMsg = "Cancelled";
|
2019-01-07 03:10:39 +01:00
|
|
|
return -1;
|
2018-12-10 02:56:20 +01:00
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-01-07 20:18:00 +01:00
|
|
|
// Use select to check the status of the new connection
|
|
|
|
struct timeval timeout;
|
|
|
|
timeout.tv_sec = 0;
|
|
|
|
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
|
|
|
fd_set wfds;
|
|
|
|
fd_set efds;
|
|
|
|
|
|
|
|
FD_ZERO(&wfds);
|
|
|
|
FD_SET(fd, &wfds);
|
|
|
|
FD_ZERO(&efds);
|
|
|
|
FD_SET(fd, &efds);
|
|
|
|
|
|
|
|
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
|
|
|
|
(errno == EBADF || errno == EINVAL))
|
|
|
|
{
|
|
|
|
closeSocket(fd);
|
|
|
|
errMsg = std::string("Connect error, select error: ") + strerror(errno);
|
|
|
|
return -1;
|
|
|
|
}
|
2018-12-09 23:07:40 +01:00
|
|
|
|
|
|
|
// Nothing was written to the socket, wait again.
|
|
|
|
if (!FD_ISSET(fd, &wfds)) continue;
|
|
|
|
|
2019-01-06 02:35:50 +01:00
|
|
|
// Something was written to the socket. Check for errors.
|
2018-12-15 01:28:11 +01:00
|
|
|
int optval = -1;
|
2018-12-10 02:56:20 +01:00
|
|
|
socklen_t optlen = sizeof(optval);
|
2018-12-09 23:07:40 +01:00
|
|
|
|
2019-01-06 02:35:50 +01:00
|
|
|
#ifdef _WIN32
|
2019-01-06 05:38:43 +01:00
|
|
|
// On connect error, in async mode, windows will write to the exceptions fds
|
2019-01-06 02:35:50 +01:00
|
|
|
if (FD_ISSET(fd, &efds))
|
|
|
|
#else
|
2018-12-09 23:07:40 +01:00
|
|
|
// getsockopt() puts the errno value for connect into optval so 0
|
|
|
|
// means no-error.
|
|
|
|
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
|
|
|
|
optval != 0)
|
2019-01-06 02:35:50 +01:00
|
|
|
#endif
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
|
|
|
closeSocket(fd);
|
2019-01-07 20:18:00 +01:00
|
|
|
errMsg = strerror(optval);
|
2019-01-03 05:07:54 +01:00
|
|
|
return -1;
|
2018-12-09 23:07:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Success !
|
2019-01-03 05:07:54 +01:00
|
|
|
return fd;
|
2018-12-09 23:07:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
closeSocket(fd);
|
2019-01-07 20:18:00 +01:00
|
|
|
errMsg = "connect timed out after 60 seconds";
|
2019-01-03 05:07:54 +01:00
|
|
|
return -1;
|
2018-12-09 23:07:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int SocketConnect::connect(const std::string& hostname,
|
|
|
|
int port,
|
2018-12-10 02:56:20 +01:00
|
|
|
std::string& errMsg,
|
2018-12-15 01:28:11 +01:00
|
|
|
const CancellationRequest& isCancellationRequested)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
|
|
|
//
|
|
|
|
// First do DNS resolution
|
|
|
|
//
|
2018-12-15 01:28:11 +01:00
|
|
|
DNSLookup dnsLookup(hostname, port);
|
|
|
|
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
|
|
|
|
if (res == nullptr)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sockfd = -1;
|
|
|
|
|
|
|
|
// iterate through the records to find a working peer
|
|
|
|
struct addrinfo *address;
|
|
|
|
for (address = res; address != nullptr; address = address->ai_next)
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// Second try to connect to the remote host
|
|
|
|
//
|
2019-01-03 05:07:54 +01:00
|
|
|
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
|
|
|
|
if (sockfd != -1)
|
2018-12-09 23:07:40 +01:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-12-15 01:28:11 +01:00
|
|
|
|
2018-12-09 23:07:40 +01:00
|
|
|
freeaddrinfo(res);
|
|
|
|
return sockfd;
|
|
|
|
}
|
|
|
|
|
2019-01-03 21:53:44 +01:00
|
|
|
// FIXME: configure is a terrible name
|
2018-12-09 23:07:40 +01:00
|
|
|
void SocketConnect::configure(int sockfd)
|
|
|
|
{
|
2018-12-15 01:28:11 +01:00
|
|
|
// 1. disable Nagle's algorithm
|
2018-12-09 23:07:40 +01:00
|
|
|
int flag = 1;
|
2018-12-15 01:28:11 +01:00
|
|
|
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
|
2018-12-09 23:07:40 +01:00
|
|
|
|
2018-12-15 01:28:11 +01:00
|
|
|
// 2. make socket non blocking
|
2018-12-09 23:07:40 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
unsigned long nonblocking = 1;
|
2019-01-05 02:28:13 +01:00
|
|
|
ioctlsocket(sockfd, FIONBIO, &nonblocking);
|
2018-12-09 23:07:40 +01:00
|
|
|
#else
|
|
|
|
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
|
|
|
#endif
|
|
|
|
|
2018-12-15 01:28:11 +01:00
|
|
|
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
|
2018-12-09 23:07:40 +01:00
|
|
|
#ifdef SO_NOSIGPIPE
|
|
|
|
int value = 1;
|
2019-02-21 03:59:07 +01:00
|
|
|
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
|
2018-12-09 23:07:40 +01:00
|
|
|
(void *)&value, sizeof(value));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|