1/*
2 * IXSocketConnect.cpp
3 * Author: Benjamin Sergeant
4 * Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
5 */
6
7#include "IXSocketConnect.h"
8
9#include "IXDNSLookup.h"
10#include "IXNetSystem.h"
11#include "IXSelectInterrupt.h"
12#include "IXSocket.h"
13#include "IXUniquePtr.h"
14#include <fcntl.h>
15#include <string.h>
16#include <sys/types.h>
17
18// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
19#ifdef ANDROID
20#include <linux/in.h>
21#include <linux/tcp.h>
22#endif
23#include <ixwebsocket/IXSelectInterruptFactory.h>
24
25namespace ix
26{
27 //
28 // This function can be cancelled every 50 ms
29 // This is important so that we don't block the main UI thread when shutting down a
30 // connection which is already trying to reconnect, and can be blocked waiting for
31 // ::connect to respond.
32 //
33 int SocketConnect::connectToAddress(const struct addrinfo* address,
34 std::string& errMsg,
35 const CancellationRequest& isCancellationRequested)
36 {
37 errMsg = "no error";
38
39 socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
40 if (fd < 0)
41 {
42 errMsg = "Cannot create a socket";
43 return -1;
44 }
45
46 // Set the socket to non blocking mode, so that slow responses cannot
47 // block us for too long
48 SocketConnect::configure(fd);
49
50 int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
51
52 if (res == -1 && !Socket::isWaitNeeded())
53 {
54 errMsg = strerror(Socket::getErrno());
55 Socket::closeSocket(fd);
56 return -1;
57 }
58
59 for (;;)
60 {
61 if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
62 {
63 Socket::closeSocket(fd);
64 errMsg = "Cancelled";
65 return -1;
66 }
67
68 int timeoutMs = 10;
69 bool readyToRead = false;
70 SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
71 PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
72
73 if (pollResult == PollResultType::Timeout)
74 {
75 continue;
76 }
77 else if (pollResult == PollResultType::Error)
78 {
79 Socket::closeSocket(fd);
80 errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
81 return -1;
82 }
83 else if (pollResult == PollResultType::ReadyForWrite)
84 {
85 return fd;
86 }
87 else
88 {
89 Socket::closeSocket(fd);
90 errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
91 return -1;
92 }
93 }
94 }
95
96 int SocketConnect::connect(const std::string& hostname,
97 int port,
98 std::string& errMsg,
99 const CancellationRequest& isCancellationRequested)
100 {
101 //
102 // First do DNS resolution
103 //
104 auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
105 struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
106 if (res == nullptr)
107 {
108 return -1;
109 }
110
111 int sockfd = -1;
112
113 // iterate through the records to find a working peer
114 struct addrinfo* address;
115 for (address = res; address != nullptr; address = address->ai_next)
116 {
117 //
118 // Second try to connect to the remote host
119 //
120 sockfd = connectToAddress(address, errMsg, isCancellationRequested);
121 if (sockfd != -1)
122 {
123 break;
124 }
125 }
126
127 freeaddrinfo(res);
128 return sockfd;
129 }
130
131 // FIXME: configure is a terrible name
132 void SocketConnect::configure(int sockfd)
133 {
134 // 1. disable Nagle's algorithm
135 int flag = 1;
136 setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
137
138 // 2. make socket non blocking
139#ifdef _WIN32
140 unsigned long nonblocking = 1;
141 ioctlsocket(sockfd, FIONBIO, &nonblocking);
142#else
143 fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
144#endif
145
146 // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
147#ifdef SO_NOSIGPIPE
148 int value = 1;
149 setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
150#endif
151 }
152} // namespace ix
153