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