OpenTTD Source: src/network/core/tcp_connect.cpp Source File

OpenTTD Source 12.0-beta1

tcp_connect.cpp
Go to the documentation of this file.
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
12 #include "../../stdafx.h"
13 #include "../../thread.h"
14
15 #include "tcp.h"
16 #include "../network_coordinator.h"
17 #include "../network_internal.h"
18
19 #include <deque>
20
21 #include "../../safeguards.h"
22
24 static std::vector<TCPConnecter *> _tcp_connecters;
25
32 TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address, int family) :
33 bind_address(bind_address),
34 family(family)
35 {
36 this->connection_string = NormalizeConnectionString(connection_string, default_port);
37
38 _tcp_connecters.push_back(this);
39 }
40
46 TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16 default_port) :
47 server_address(ServerAddress::Parse(connection_string, default_port))
48 {
49 switch (this->server_address.type) {
50 case SERVER_ADDRESS_DIRECT:
51 this->connection_string = this->server_address.connection_string;
52 break;
53
54 case SERVER_ADDRESS_INVITE_CODE:
55 this->status = Status::CONNECTING;
56 _network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
57 break;
58
59 default:
60 NOT_REACHED();
61 }
62
63 _tcp_connecters.push_back(this);
64 }
65
66 TCPConnecter::~TCPConnecter()
67 {
68 if (this->resolve_thread.joinable()) {
69 this->resolve_thread.join();
70 }
71
72 for (const auto &socket : this->sockets) {
73 closesocket(socket);
74 }
75 this->sockets.clear();
76 this->sock_to_address.clear();
77
78 if (this->ai != nullptr) freeaddrinfo(this->ai);
79 }
80
85 void TCPConnecter::Kill()
86 {
87 /* Delay the removing of the socket till the next CheckActivity(). */
88 this->killed = true;
89 }
90
95 void TCPConnecter::Connect(addrinfo *address)
96 {
97 SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
98 if (sock == INVALID_SOCKET) {
99 Debug(net, 0, "Could not create {} {} socket: {}", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
100 return;
101 }
102
103 if (!SetReusePort(sock)) {
104 Debug(net, 0, "Setting reuse-port mode failed: {}", NetworkError::GetLast().AsString());
105 }
106
107 if (this->bind_address.GetPort() > 0) {
108 if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
109 Debug(net, 1, "Could not bind socket on {}: {}", this->bind_address.GetAddressAsString(), NetworkError::GetLast().AsString());
110 closesocket(sock);
111 return;
112 }
113 }
114
115 if (!SetNoDelay(sock)) {
116 Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
117 }
118 if (!SetNonBlocking(sock)) {
119 Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
120 }
121
122 NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
123 Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
124
125 int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
126 if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
127 closesocket(sock);
128
129 Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
130 return;
131 }
132
133 this->sock_to_address[sock] = network_address;
134 this->sockets.push_back(sock);
135 }
136
141 bool TCPConnecter::TryNextAddress()
142 {
143 if (this->current_address >= this->addresses.size()) return false;
144
145 this->last_attempt = std::chrono::steady_clock::now();
146 this->Connect(this->addresses[this->current_address++]);
147
148 return true;
149 }
150
155 void TCPConnecter::OnResolved(addrinfo *ai)
156 {
157 std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
158
159 /* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
160
161 /* Detect if IPv6 is likely to succeed or not. */
162 bool seen_ipv6 = false;
163 bool resort = true;
164 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
165 if (runp->ai_family == AF_INET6) {
166 seen_ipv6 = true;
167 } else if (!seen_ipv6) {
168 /* We see an IPv4 before an IPv6; this most likely means there is
169 * no IPv6 available on the system, so keep the order of this
170 * list. */
171 resort = false;
172 break;
173 }
174 }
175
176 /* Convert the addrinfo into NetworkAddresses. */
177 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
178 /* Skip entries if the family is set and it is not matching. */
179 if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue;
180
181 if (resort) {
182 if (runp->ai_family == AF_INET6) {
183 addresses_ipv6.emplace_back(runp);
184 } else {
185 addresses_ipv4.emplace_back(runp);
186 }
187 } else {
188 this->addresses.emplace_back(runp);
189 }
190 }
191
192 /* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
193 * for how ever many (round-robin) DNS entries we have. */
194 if (resort) {
195 while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
196 if (!addresses_ipv6.empty()) {
197 this->addresses.push_back(addresses_ipv6.front());
198 addresses_ipv6.pop_front();
199 }
200 if (!addresses_ipv4.empty()) {
201 this->addresses.push_back(addresses_ipv4.front());
202 addresses_ipv4.pop_front();
203 }
204 }
205 }
206
207 if (_debug_net_level >= 6) {
208 Debug(net, 6, "{} resolved in:", this->connection_string);
209 for (const auto &address : this->addresses) {
210 Debug(net, 6, "- {}", NetworkAddress(address->ai_addr, (int)address->ai_addrlen).GetAddressAsString());
211 }
212 }
213
214 this->current_address = 0;
215 }
216
223 void TCPConnecter::Resolve()
224 {
225 /* Port is already guaranteed part of the connection_string. */
226 NetworkAddress address = ParseConnectionString(this->connection_string, 0);
227
228 addrinfo hints;
229 memset(&hints, 0, sizeof(hints));
230 hints.ai_family = AF_UNSPEC;
231 hints.ai_flags = AI_ADDRCONFIG;
232 hints.ai_socktype = SOCK_STREAM;
233
234 char port_name[6];
235 seprintf(port_name, lastof(port_name), "%u", address.GetPort());
236
237 static bool getaddrinfo_timeout_error_shown = false;
238 auto start = std::chrono::steady_clock::now();
239
240 addrinfo *ai;
241 int error = getaddrinfo(address.GetHostname().c_str(), port_name, &hints, &ai);
242
243 auto end = std::chrono::steady_clock::now();
244 auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
245 if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
246 Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
247 Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
248 getaddrinfo_timeout_error_shown = true;
249 }
250
251 if (error != 0) {
252 Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
253 this->status = Status::FAILURE;
254 return;
255 }
256
257 this->ai = ai;
258 this->OnResolved(ai);
259
260 this->status = Status::CONNECTING;
261 }
262
266 /* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
267 {
268 connecter->Resolve();
269 }
270
275 bool TCPConnecter::CheckActivity()
276 {
277 if (this->killed) return true;
278
279 switch (this->status) {
280 case Status::INIT:
281 /* Start the thread delayed, so the vtable is loaded. This allows classes
282 * to overload functions used by Resolve() (in case threading is disabled). */
283 if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
284 this->status = Status::RESOLVING;
285 return false;
286 }
287
288 /* No threads, do a blocking resolve. */
289 this->Resolve();
290
291 /* Continue as we are either failed or can start the first
292 * connection. The rest of this function handles exactly that. */
293 break;
294
295 case Status::RESOLVING:
296 /* Wait till Resolve() comes back with an answer (in case it runs threaded). */
297 return false;
298
299 case Status::FAILURE:
300 /* Ensure the OnFailure() is called from the game-thread instead of the
301 * resolve-thread, as otherwise we can get into some threading issues. */
302 this->OnFailure();
303 return true;
304
305 case Status::CONNECTING:
306 case Status::CONNECTED:
307 break;
308 }
309
310 /* If there are no attempts pending, connect to the next. */
311 if (this->sockets.empty()) {
312 if (!this->TryNextAddress()) {
313 /* There were no more addresses to try, so we failed. */
314 this->OnFailure();
315 return true;
316 }
317 return false;
318 }
319
320 fd_set write_fd;
321 FD_ZERO(&write_fd);
322 for (const auto &socket : this->sockets) {
323 FD_SET(socket, &write_fd);
324 }
325
326 timeval tv;
327 tv.tv_usec = 0;
328 tv.tv_sec = 0;
329 int n = select(FD_SETSIZE, nullptr, &write_fd, nullptr, &tv);
330 /* select() failed; hopefully next try it doesn't. */
331 if (n < 0) {
332 /* select() normally never fails; so hopefully it works next try! */
333 Debug(net, 1, "select() failed: {}", NetworkError::GetLast().AsString());
334 return false;
335 }
336
337 /* No socket updates. */
338 if (n == 0) {
339 /* Wait 250ms between attempting another address. */
340 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
341
342 /* Try the next address in the list. */
343 if (this->TryNextAddress()) return false;
344
345 /* Wait up to 3 seconds since the last connection we started. */
346 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
347
348 /* More than 3 seconds no socket reported activity, and there are no
349 * more address to try. Timeout the attempt. */
350 Debug(net, 0, "Timeout while connecting to {}", this->connection_string);
351
352 for (const auto &socket : this->sockets) {
353 closesocket(socket);
354 }
355 this->sockets.clear();
356 this->sock_to_address.clear();
357
358 this->OnFailure();
359 return true;
360 }
361
362 /* Check for errors on any of the sockets. */
363 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
364 NetworkError socket_error = GetSocketError(*it);
365 if (socket_error.HasError()) {
366 Debug(net, 1, "Could not connect to {}: {}", this->sock_to_address[*it].GetAddressAsString(), socket_error.AsString());
367 closesocket(*it);
368 this->sock_to_address.erase(*it);
369 it = this->sockets.erase(it);
370 } else {
371 it++;
372 }
373 }
374
375 /* In case all sockets had an error, queue a new one. */
376 if (this->sockets.empty()) {
377 if (!this->TryNextAddress()) {
378 /* There were no more addresses to try, so we failed. */
379 this->OnFailure();
380 return true;
381 }
382 return false;
383 }
384
385 /* At least one socket is connected. The first one that does is the one
386 * we will be using, and we close all other sockets. */
387 SOCKET connected_socket = INVALID_SOCKET;
388 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
389 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
390 connected_socket = *it;
391 } else {
392 closesocket(*it);
393 }
394 this->sock_to_address.erase(*it);
395 it = this->sockets.erase(it);
396 }
397 assert(connected_socket != INVALID_SOCKET);
398
399 Debug(net, 3, "Connected to {}", this->connection_string);
400 if (_debug_net_level >= 5) {
401 Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
402 }
403
404 this->OnConnect(connected_socket);
405 this->status = Status::CONNECTED;
406 return true;
407 }
408
413 bool TCPServerConnecter::CheckActivity()
414 {
415 if (this->killed) return true;
416
417 switch (this->server_address.type) {
418 case SERVER_ADDRESS_DIRECT:
419 return TCPConnecter::CheckActivity();
420
421 case SERVER_ADDRESS_INVITE_CODE:
422 /* Check if a result has come in. */
423 switch (this->status) {
424 case Status::FAILURE:
425 this->OnFailure();
426 return true;
427
428 case Status::CONNECTED:
429 this->OnConnect(this->socket);
430 return true;
431
432 default:
433 break;
434 }
435
436 return false;
437
438 default:
439 NOT_REACHED();
440 }
441 }
442
448 void TCPServerConnecter::SetConnected(SOCKET sock)
449 {
450 this->socket = sock;
451 this->status = Status::CONNECTED;
452 }
453
457 void TCPServerConnecter::SetFailure()
458 {
459 this->status = Status::FAILURE;
460 }
461
468 /* static */ void TCPConnecter::CheckCallbacks()
469 {
470 for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
471 TCPConnecter *cur = *iter;
472
473 if (cur->CheckActivity()) {
474 iter = _tcp_connecters.erase(iter);
475 delete cur;
476 } else {
477 iter++;
478 }
479 }
480 }
481
483 /* static */ void TCPConnecter::KillAll()
484 {
485 for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
486 TCPConnecter *cur = *iter;
487 iter = _tcp_connecters.erase(iter);
488 delete cur;
489 }
490 }
TCPServerConnecter::CheckActivity
bool CheckActivity() override
Check if there was activity for this connecter.
Definition: tcp_connect.cpp:413
TCPConnecter::CheckCallbacks
static void CheckCallbacks()
Check whether we need to call the callback, i.e.
Definition: tcp_connect.cpp:468
TCPConnecter::OnConnect
virtual void OnConnect(SOCKET s)
Callback when the connection succeeded.
Definition: tcp.h:125
TCPConnecter::sockets
std::vector< SOCKET > sockets
Pending connect() attempts.
Definition: tcp.h:97
TCPConnecter::connection_string
std::string connection_string
Current address we are connecting to (before resolving).
Definition: tcp.h:100
TCPConnecter::Status::CONNECTED
@ CONNECTED
The connection is established.
NetworkAddress::GetAddress
const sockaddr_storage * GetAddress()
Get the address in its internal representation.
Definition: address.cpp:114
SetReusePort
bool SetReusePort(SOCKET d)
Try to set the socket to reuse ports.
Definition: os_abstraction.cpp:167
TCPConnecter
"Helper" class for creating TCP connections in a non-blocking manner
Definition: tcp.h:71
TCPConnecter::last_attempt
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
Definition: tcp.h:98
ParseConnectionString
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port)
Convert a string containing either "hostname" or "hostname:ip" to a NetworkAddress.
Definition: network.cpp:538
TCPConnecter::Kill
void Kill()
Kill this connecter.
Definition: tcp_connect.cpp:85
TCPConnecter::family
int family
Family we are using to connect with.
Definition: tcp.h:102
NetworkError::HasError
bool HasError() const
Check whether an error was actually set.
Definition: os_abstraction.cpp:107
TCPConnecter::ai
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
Definition: tcp.h:92
TCPServerConnecter::SetFailure
void SetFailure()
The connection couldn't be established.
Definition: tcp_connect.cpp:457
ServerAddress::type
ServerAddressType type
The type of this ServerAddress.
Definition: address.h:211
NetworkAddress::GetPeerName
static const std::string GetPeerName(SOCKET sock)
Get the peer name of a socket in string format.
Definition: address.cpp:439
TCPConnecter::Resolve
void Resolve()
Start resolving the hostname.
Definition: tcp_connect.cpp:223
ClientNetworkCoordinatorSocketHandler::ConnectToServer
void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
Join a server based on an invite code.
Definition: network_coordinator.cpp:500
_tcp_connecters
static std::vector< TCPConnecter * > _tcp_connecters
List of connections that are currently being created.
Definition: tcp_connect.cpp:24
NormalizeConnectionString
std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port)
Normalize a connection string.
Definition: network.cpp:523
TCPConnecter::Connect
void Connect(addrinfo *address)
Start a connection to the indicated address.
Definition: tcp_connect.cpp:95
TCPConnecter::killed
std::atomic< bool > killed
Whether this connecter is marked as killed.
Definition: tcp.h:90
NetworkError::GetLast
static NetworkError GetLast()
Get the last network error.
Definition: os_abstraction.cpp:116
SERVER_ADDRESS_DIRECT
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Definition: address.h:189
TCPConnecter::Status::RESOLVING
@ RESOLVING
The hostname is being resolved (threaded).
StartNewThread
bool StartNewThread(std::thread *thr, const char *name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition: thread.h:46
NetworkAddress::GetPort
uint16 GetPort() const
Get the port.
Definition: address.cpp:38
NetworkAddress::GetAddressAsString
std::string GetAddressAsString(bool with_family=true)
Get the address as a string, e.g.
Definition: address.cpp:94
TCPConnecter::KillAll
static void KillAll()
Kill all connection attempts.
Definition: tcp_connect.cpp:483
GetSocketError
NetworkError GetSocketError(SOCKET d)
Get the error from a socket, if any.
Definition: os_abstraction.cpp:184
TCPConnecter::addresses
std::vector< addrinfo * > addresses
Addresses we can connect to.
Definition: tcp.h:93
TCPServerConnecter::server_address
ServerAddress server_address
Address we are connecting to.
Definition: tcp.h:145
TCPConnecter::CheckActivity
virtual bool CheckActivity()
Check if there was activity for this connecter.
Definition: tcp_connect.cpp:275
NetworkAddress
Wrapper for (un)resolved network addresses; there's no reason to transform a numeric IP to a string a...
Definition: address.h:30
TCPServerConnecter::SetConnected
void SetConnected(SOCKET sock)
The connection was successfully established.
Definition: tcp_connect.cpp:448
TCPConnecter::current_address
size_t current_address
Current index in addresses we are trying.
Definition: tcp.h:95
tcp.h
_network_coordinator_client
ClientNetworkCoordinatorSocketHandler _network_coordinator_client
The connection to the Game Coordinator.
Definition: network_coordinator.cpp:30
TCPConnecter::sock_to_address
std::map< SOCKET, NetworkAddress > sock_to_address
Mapping of a socket to the real address it is connecting to. USed for DEBUG statements.
Definition: tcp.h:94
NetworkAddress::GetHostname
const std::string & GetHostname()
Get the hostname; in case it wasn't given the IPv4 dotted representation is given.
Definition: address.cpp:23
NetworkError::AsString
const std::string & AsString() const
Get the string representation of the error message.
Definition: os_abstraction.cpp:79
seprintf
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:535
NetworkError
Abstraction of a network error where all implementation details of the error codes are encapsulated i...
Definition: os_abstraction.h:21
TCPConnecter::OnFailure
virtual void OnFailure()
Callback for when the connection attempt failed.
Definition: tcp.h:130
NetworkAddress::SocketTypeAsString
static const char * SocketTypeAsString(int socktype)
Convert the socket type into a string.
Definition: address.cpp:377
SERVER_ADDRESS_INVITE_CODE
@ SERVER_ADDRESS_INVITE_CODE
Server-address is based on an invite code.
Definition: address.h:190
error
void CDECL error(const char *s,...)
Error handling for fatal non-user errors.
Definition: openttd.cpp:132
Debug
#define Debug(name, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
SetNonBlocking
bool SetNonBlocking(SOCKET d)
Try to set the socket into non-blocking mode.
Definition: os_abstraction.cpp:133
TCPConnecter::OnResolved
void OnResolved(addrinfo *ai)
Callback when resolving is done.
Definition: tcp_connect.cpp:155
TCPServerConnecter::socket
SOCKET socket
The socket when a connection is established.
Definition: tcp.h:140
TCPConnecter::Status::FAILURE
@ FAILURE
Resolving failed.
NetworkAddress::AddressFamilyAsString
static const char * AddressFamilyAsString(int family)
Convert the address family into a string.
Definition: address.cpp:392
ServerAddress::connection_string
std::string connection_string
The connection string for this ServerAddress.
Definition: address.h:212
lastof
#define lastof(x)
Get the last element of an fixed size array.
Definition: stdafx.h:394
TCPConnecter::bind_address
NetworkAddress bind_address
Address we're binding to, if any.
Definition: tcp.h:101
SetNoDelay
bool SetNoDelay(SOCKET d)
Try to set the socket to not delay sending.
Definition: os_abstraction.cpp:151
TCPConnecter::Status::CONNECTING
@ CONNECTING
We are currently connecting.
ServerAddress
Address to a game server.
Definition: address.h:198
TCPServerConnecter::TCPServerConnecter
TCPServerConnecter(const std::string &connection_string, uint16 default_port)
Create a new connecter for the server.
Definition: tcp_connect.cpp:46
TCPConnecter::status
std::atomic< Status > status
The current status of the connecter.
Definition: tcp.h:89
TCPConnecter::ResolveThunk
static void ResolveThunk(TCPConnecter *connecter)
Thunk to start Resolve() on the right instance.
Definition: tcp_connect.cpp:266
TCPConnecter::Status::INIT
@ INIT
TCPConnecter is created but resolving hasn't started.
TCPConnecter::resolve_thread
std::thread resolve_thread
Thread used during resolving.
Definition: tcp.h:88
TCPConnecter::TryNextAddress
bool TryNextAddress()
Start the connect() for the next address in the list.
Definition: tcp_connect.cpp:141