OpenTTD Source 20241224-master-gf74b0cf984
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 "../../safeguards.h"
20
21/* static */ std::vector<std::shared_ptr<TCPConnecter>> TCPConnecter::connecters;
22
29TCPConnecter::TCPConnecter(const std::string &connection_string, uint16_t default_port, const NetworkAddress &bind_address, int family) :
30 bind_address(bind_address),
31 family(family)
32{
33 this->connection_string = NormalizeConnectionString(connection_string, default_port);
34}
35
41TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16_t default_port) :
42 server_address(ServerAddress::Parse(connection_string, default_port))
43{
44 switch (this->server_address.type) {
46 this->connection_string = this->server_address.connection_string;
47 break;
48
52 break;
53
54 default:
55 NOT_REACHED();
56 }
57}
58
59TCPConnecter::~TCPConnecter()
60{
61 if (this->resolve_thread.joinable()) {
62 this->resolve_thread.join();
63 }
64
65 for (const auto &socket : this->sockets) {
66 closesocket(socket);
67 }
68 this->sockets.clear();
69 this->sock_to_address.clear();
70
71 if (this->ai != nullptr) freeaddrinfo(this->ai);
72}
73
79{
80 /* Delay the removing of the socket till the next CheckActivity(). */
81 this->killed = true;
82}
83
88void TCPConnecter::Connect(addrinfo *address)
89{
90 SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
91 if (sock == INVALID_SOCKET) {
92 Debug(net, 0, "Could not create {} {} socket: {}", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
93 return;
94 }
95
96 if (!SetReusePort(sock)) {
97 Debug(net, 0, "Setting reuse-port mode failed: {}", NetworkError::GetLast().AsString());
98 }
99
100 if (this->bind_address.GetPort() > 0) {
101 if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
102 Debug(net, 1, "Could not bind socket on {}: {}", this->bind_address.GetAddressAsString(), NetworkError::GetLast().AsString());
103 closesocket(sock);
104 return;
105 }
106 }
107
108 if (!SetNoDelay(sock)) {
109 Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
110 }
111 if (!SetNonBlocking(sock)) {
112 Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
113 }
114
115 NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
116 Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
117
118 int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
119 if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
120 closesocket(sock);
121
122 Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
123 return;
124 }
125
126 this->sock_to_address[sock] = network_address;
127 this->sockets.push_back(sock);
128}
129
135{
136 if (this->current_address >= this->addresses.size()) return false;
137
138 this->last_attempt = std::chrono::steady_clock::now();
139 this->Connect(this->addresses[this->current_address++]);
140
141 return true;
142}
143
148void TCPConnecter::OnResolved(addrinfo *ai)
149{
150 std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
151
152 /* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
153
154 /* Detect if IPv6 is likely to succeed or not. */
155 bool seen_ipv6 = false;
156 bool resort = true;
157 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
158 if (runp->ai_family == AF_INET6) {
159 seen_ipv6 = true;
160 } else if (!seen_ipv6) {
161 /* We see an IPv4 before an IPv6; this most likely means there is
162 * no IPv6 available on the system, so keep the order of this
163 * list. */
164 resort = false;
165 break;
166 }
167 }
168
169 /* Convert the addrinfo into NetworkAddresses. */
170 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
171 /* Skip entries if the family is set and it is not matching. */
172 if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue;
173
174 if (resort) {
175 if (runp->ai_family == AF_INET6) {
176 addresses_ipv6.emplace_back(runp);
177 } else {
178 addresses_ipv4.emplace_back(runp);
179 }
180 } else {
181 this->addresses.emplace_back(runp);
182 }
183 }
184
185 /* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
186 * for how ever many (round-robin) DNS entries we have. */
187 if (resort) {
188 while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
189 if (!addresses_ipv6.empty()) {
190 this->addresses.push_back(addresses_ipv6.front());
191 addresses_ipv6.pop_front();
192 }
193 if (!addresses_ipv4.empty()) {
194 this->addresses.push_back(addresses_ipv4.front());
195 addresses_ipv4.pop_front();
196 }
197 }
198 }
199
200 if (_debug_net_level >= 6) {
201 if (this->addresses.empty()) {
202 Debug(net, 6, "{} did not resolve", this->connection_string);
203 } else {
204 Debug(net, 6, "{} resolved in:", this->connection_string);
205 for (const auto &address : this->addresses) {
206 Debug(net, 6, "- {}", NetworkAddress(address->ai_addr, (int)address->ai_addrlen).GetAddressAsString());
207 }
208 }
209 }
210
211 this->current_address = 0;
212}
213
221{
222 /* Port is already guaranteed part of the connection_string. */
224
225 addrinfo hints;
226 memset(&hints, 0, sizeof(hints));
227 hints.ai_family = AF_UNSPEC;
228 hints.ai_flags = AI_ADDRCONFIG;
229 hints.ai_socktype = SOCK_STREAM;
230
231 std::string port_name = std::to_string(address.GetPort());
232
233 static bool getaddrinfo_timeout_error_shown = false;
234 auto start = std::chrono::steady_clock::now();
235
236 addrinfo *ai;
237 int error = getaddrinfo(address.GetHostname().c_str(), port_name.c_str(), &hints, &ai);
238
239 auto end = std::chrono::steady_clock::now();
240 auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
241 if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
242 Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
243 Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
244 getaddrinfo_timeout_error_shown = true;
245 }
246
247 if (error != 0) {
248 Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
249 this->status = Status::Failure;
250 return;
251 }
252
253 this->ai = ai;
254 this->OnResolved(ai);
255
257}
258
262/* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
263{
264 connecter->Resolve();
265}
266
272{
273 if (this->killed) return true;
274
275 switch (this->status) {
276 case Status::Init:
277 /* Start the thread delayed, so the vtable is loaded. This allows classes
278 * to overload functions used by Resolve() (in case threading is disabled). */
279 if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
281 return false;
282 }
283
284 /* No threads, do a blocking resolve. */
285 this->Resolve();
286
287 /* Continue as we are either failed or can start the first
288 * connection. The rest of this function handles exactly that. */
289 break;
290
292 /* Wait till Resolve() comes back with an answer (in case it runs threaded). */
293 return false;
294
295 case Status::Failure:
296 /* Ensure the OnFailure() is called from the game-thread instead of the
297 * resolve-thread, as otherwise we can get into some threading issues. */
298 this->OnFailure();
299 return true;
300
303 break;
304 }
305
306 /* If there are no attempts pending, connect to the next. */
307 if (this->sockets.empty()) {
308 if (!this->TryNextAddress()) {
309 /* There were no more addresses to try, so we failed. */
310 this->OnFailure();
311 return true;
312 }
313 return false;
314 }
315
316 fd_set write_fd;
317 FD_ZERO(&write_fd);
318 for (const auto &socket : this->sockets) {
319 FD_SET(socket, &write_fd);
320 }
321
322 timeval tv;
323 tv.tv_usec = 0;
324 tv.tv_sec = 0;
325 int n = select(FD_SETSIZE, nullptr, &write_fd, nullptr, &tv);
326 /* select() failed; hopefully next try it doesn't. */
327 if (n < 0) {
328 /* select() normally never fails; so hopefully it works next try! */
329 Debug(net, 1, "select() failed: {}", NetworkError::GetLast().AsString());
330 return false;
331 }
332
333 /* No socket updates. */
334 if (n == 0) {
335 /* Wait 250ms between attempting another address. */
336 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
337
338 /* Try the next address in the list. */
339 if (this->TryNextAddress()) return false;
340
341 /* Wait up to 3 seconds since the last connection we started. */
342 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
343
344 /* More than 3 seconds no socket reported activity, and there are no
345 * more address to try. Timeout the attempt. */
346 Debug(net, 0, "Timeout while connecting to {}", this->connection_string);
347
348 for (const auto &socket : this->sockets) {
349 closesocket(socket);
350 }
351 this->sockets.clear();
352 this->sock_to_address.clear();
353
354 this->OnFailure();
355 return true;
356 }
357
358 /* If a socket is writeable, it is either in error-state or connected.
359 * Remove all sockets that are in error-state and mark the first that is
360 * not in error-state as the socket we will use for our connection. */
361 SOCKET connected_socket = INVALID_SOCKET;
362 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
363 NetworkError socket_error = GetSocketError(*it);
364 if (socket_error.HasError()) {
365 Debug(net, 1, "Could not connect to {}: {}", this->sock_to_address[*it].GetAddressAsString(), socket_error.AsString());
366 closesocket(*it);
367 this->sock_to_address.erase(*it);
368 it = this->sockets.erase(it);
369 continue;
370 }
371
372 /* No error but writeable means connected. */
373 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
374 connected_socket = *it;
375 }
376
377 it++;
378 }
379
380 /* All the writable sockets were in error state. So nothing is connected yet. */
381 if (connected_socket == INVALID_SOCKET) return false;
382
383 /* Close all sockets except the one we picked for our connection. */
384 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
385 if (connected_socket != *it) {
386 closesocket(*it);
387 }
388 this->sock_to_address.erase(*it);
389 it = this->sockets.erase(it);
390 }
391
392 Debug(net, 3, "Connected to {}", this->connection_string);
393 if (_debug_net_level >= 5) {
394 Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
395 }
396
397 this->OnConnect(connected_socket);
399 return true;
400}
401
407{
408 if (this->killed) return true;
409
410 switch (this->server_address.type) {
413
415 /* Check if a result has come in. */
416 switch (this->status) {
417 case Status::Failure:
418 this->OnFailure();
419 return true;
420
422 this->OnConnect(this->socket);
423 return true;
424
425 default:
426 break;
427 }
428
429 return false;
430
431 default:
432 NOT_REACHED();
433 }
434}
435
442{
443 assert(sock != INVALID_SOCKET);
444
445 this->socket = sock;
447}
448
456
464{
466 std::remove_if(TCPConnecter::connecters.begin(), TCPConnecter::connecters.end(),
467 [](auto &connecter) { return connecter->CheckActivity(); }),
469}
470
472/* static */ void TCPConnecter::KillAll()
473{
475}
@ SERVER_ADDRESS_INVITE_CODE
Server-address is based on an invite code.
Definition address.h:188
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Definition address.h:187
void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
Join a server based on an invite code.
Wrapper for (un)resolved network addresses; there's no reason to transform a numeric IP to a string a...
Definition address.h:28
static const char * AddressFamilyAsString(int family)
Convert the address family into a string.
Definition address.cpp:388
static const char * SocketTypeAsString(int socktype)
Convert the socket type into a string.
Definition address.cpp:373
static const std::string GetPeerName(SOCKET sock)
Get the peer name of a socket in string format.
Definition address.cpp:435
uint16_t GetPort() const
Get the port.
Definition address.cpp:38
const sockaddr_storage * GetAddress()
Get the address in its internal representation.
Definition address.cpp:113
const std::string & GetHostname()
Get the hostname; in case it wasn't given the IPv4 dotted representation is given.
Definition address.cpp:23
std::string GetAddressAsString(bool with_family=true)
Get the address as a string, e.g.
Definition address.cpp:94
Abstraction of a network error where all implementation details of the error codes are encapsulated i...
bool HasError() const
Check whether an error was actually set.
const std::string & AsString() const
Get the string representation of the error message.
static NetworkError GetLast()
Get the last network error.
Address to a game server.
Definition address.h:196
std::string connection_string
The connection string for this ServerAddress.
Definition address.h:210
ServerAddressType type
The type of this ServerAddress.
Definition address.h:209
"Helper" class for creating TCP connections in a non-blocking manner
Definition tcp.h:70
NetworkAddress bind_address
Address we're binding to, if any.
Definition tcp.h:100
void Kill()
Kill this connecter.
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
Definition tcp.h:97
std::atomic< Status > status
The current status of the connecter.
Definition tcp.h:88
std::string connection_string
Current address we are connecting to (before resolving).
Definition tcp.h:99
static std::vector< std::shared_ptr< TCPConnecter > > connecters
List of connections that are currently being created.
Definition tcp.h:103
std::vector< SOCKET > sockets
Pending connect() attempts.
Definition tcp.h:96
static void CheckCallbacks()
Check whether we need to call the callback, i.e.
@ Connected
The connection is established.
@ Resolving
The hostname is being resolved (threaded).
@ Init
TCPConnecter is created but resolving hasn't started.
@ Failure
Resolving failed.
@ Connecting
We are currently connecting.
size_t current_address
Current index in addresses we are trying.
Definition tcp.h:94
void Resolve()
Start resolving the hostname.
virtual void OnFailure()
Callback for when the connection attempt failed.
Definition tcp.h:131
void OnResolved(addrinfo *ai)
Callback when resolving is done.
virtual bool CheckActivity()
Check if there was activity for this connecter.
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:93
static void ResolveThunk(TCPConnecter *connecter)
Thunk to start Resolve() on the right instance.
std::vector< addrinfo * > addresses
Addresses we can connect to.
Definition tcp.h:92
std::thread resolve_thread
Thread used during resolving.
Definition tcp.h:87
static void KillAll()
Kill all connection attempts.
bool TryNextAddress()
Start the connect() for the next address in the list.
std::atomic< bool > killed
Whether this connecter is marked as killed.
Definition tcp.h:89
virtual void OnConnect(SOCKET s)
Callback when the connection succeeded.
Definition tcp.h:126
void Connect(addrinfo *address)
Start a connection to the indicated address.
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
Definition tcp.h:91
int family
Family we are using to connect with.
Definition tcp.h:101
TCPServerConnecter(const std::string &connection_string, uint16_t default_port)
Create a new connecter for the server.
void SetConnected(SOCKET sock)
The connection was successfully established.
bool CheckActivity() override
Check if there was activity for this connecter.
ServerAddress server_address
Address we are connecting to.
Definition tcp.h:158
void SetFailure()
The connection couldn't be established.
SOCKET socket
The socket when a connection is established.
Definition tcp.h:153
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
std::string NormalizeConnectionString(const std::string &connection_string, uint16_t default_port)
Normalize a connection string.
Definition network.cpp:541
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16_t default_port)
Convert a string containing either "hostname" or "hostname:ip" to a NetworkAddress.
Definition network.cpp:556
ClientNetworkCoordinatorSocketHandler _network_coordinator_client
The connection to the Game Coordinator.
bool SetReusePort(SOCKET d)
Try to set the socket to reuse ports.
bool SetNoDelay(SOCKET d)
Try to set the socket to not delay sending.
bool SetNonBlocking(SOCKET d)
Try to set the socket into non-blocking mode.
NetworkError GetSocketError(SOCKET d)
Get the error from a socket, if any.
Basic functions to receive and send TCP packets.
bool StartNewThread(std::thread *thr, const char *name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition thread.h:47