OpenTTD Source 20260218-master-g2123fca5ea
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
261/* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
262{
263 connecter->Resolve();
264}
265
271{
272 if (this->killed) return true;
273
274 switch (this->status) {
275 case Status::Init:
276 /* Start the thread delayed, so the vtable is loaded. This allows classes
277 * to overload functions used by Resolve() (in case threading is disabled). */
278 if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
280 return false;
281 }
282
283 /* No threads, do a blocking resolve. */
284 this->Resolve();
285
286 /* Continue as we are either failed or can start the first
287 * connection. The rest of this function handles exactly that. */
288 break;
289
291 /* Wait till Resolve() comes back with an answer (in case it runs threaded). */
292 return false;
293
294 case Status::Failure:
295 /* Ensure the OnFailure() is called from the game-thread instead of the
296 * resolve-thread, as otherwise we can get into some threading issues. */
297 this->OnFailure();
298 return true;
299
302 break;
303 }
304
305 /* If there are no attempts pending, connect to the next. */
306 if (this->sockets.empty()) {
307 if (!this->TryNextAddress()) {
308 /* There were no more addresses to try, so we failed. */
309 this->OnFailure();
310 return true;
311 }
312 return false;
313 }
314
315 fd_set write_fd;
316 FD_ZERO(&write_fd);
317 for (const auto &socket : this->sockets) {
318 FD_SET(socket, &write_fd);
319 }
320
321 timeval tv;
322 tv.tv_usec = 0;
323 tv.tv_sec = 0;
324 int n = select(FD_SETSIZE, nullptr, &write_fd, nullptr, &tv);
325 /* select() failed; hopefully next try it doesn't. */
326 if (n < 0) {
327 /* select() normally never fails; so hopefully it works next try! */
328 Debug(net, 1, "select() failed: {}", NetworkError::GetLast().AsString());
329 return false;
330 }
331
332 /* No socket updates. */
333 if (n == 0) {
334 /* Wait 250ms between attempting another address. */
335 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
336
337 /* Try the next address in the list. */
338 if (this->TryNextAddress()) return false;
339
340 /* Wait up to 3 seconds since the last connection we started. */
341 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
342
343 /* More than 3 seconds no socket reported activity, and there are no
344 * more address to try. Timeout the attempt. */
345 Debug(net, 0, "Timeout while connecting to {}", this->connection_string);
346
347 for (const auto &socket : this->sockets) {
348 closesocket(socket);
349 }
350 this->sockets.clear();
351 this->sock_to_address.clear();
352
353 this->OnFailure();
354 return true;
355 }
356
357 /* If a socket is writeable, it is either in error-state or connected.
358 * Remove all sockets that are in error-state and mark the first that is
359 * not in error-state as the socket we will use for our connection. */
360 SOCKET connected_socket = INVALID_SOCKET;
361 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
362 NetworkError socket_error = GetSocketError(*it);
363 if (socket_error.HasError()) {
364 Debug(net, 1, "Could not connect to {}: {}", this->sock_to_address[*it].GetAddressAsString(), socket_error.AsString());
365 closesocket(*it);
366 this->sock_to_address.erase(*it);
367 it = this->sockets.erase(it);
368 continue;
369 }
370
371 /* No error but writeable means connected. */
372 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
373 connected_socket = *it;
374 }
375
376 it++;
377 }
378
379 /* All the writable sockets were in error state. So nothing is connected yet. */
380 if (connected_socket == INVALID_SOCKET) return false;
381
382 /* Close all sockets except the one we picked for our connection. */
383 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
384 if (connected_socket != *it) {
385 closesocket(*it);
386 }
387 this->sock_to_address.erase(*it);
388 it = this->sockets.erase(it);
389 }
390
391 Debug(net, 3, "Connected to {}", this->connection_string);
392 if (_debug_net_level >= 5) {
393 Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
394 }
395
396 this->OnConnect(connected_socket);
398 return true;
399}
400
406{
407 if (this->killed) return true;
408
409 switch (this->server_address.type) {
412
414 /* Check if a result has come in. */
415 switch (this->status) {
416 case Status::Failure:
417 this->OnFailure();
418 return true;
419
421 this->OnConnect(this->socket);
422 return true;
423
424 default:
425 break;
426 }
427
428 return false;
429
430 default:
431 NOT_REACHED();
432 }
433}
434
441{
442 assert(sock != INVALID_SOCKET);
443
444 this->socket = sock;
446}
447
455
463{
465 std::remove_if(TCPConnecter::connecters.begin(), TCPConnecter::connecters.end(),
466 [](auto &connecter) { return connecter->CheckActivity(); }),
468}
469
471/* static */ void TCPConnecter::KillAll()
472{
474}
@ SERVER_ADDRESS_INVITE_CODE
Server-address is based on an invite code.
Definition address.h:177
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Definition address.h:176
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:185
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