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 | |
25 | namespace 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 | |