OpenTTD Source 20260311-master-g511d3794ce
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
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] = std::move(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
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 hints.ai_family = AF_UNSPEC;
227 hints.ai_flags = AI_ADDRCONFIG;
228 hints.ai_socktype = SOCK_STREAM;
229
230 std::string port_name = fmt::format("{}", address.GetPort());
231
232 static bool getaddrinfo_timeout_error_shown = false;
233 auto start = std::chrono::steady_clock::now();
234
235 addrinfo *ai;
236 int error = getaddrinfo(address.GetHostname().c_str(), port_name.c_str(), &hints, &ai);
237
238 auto end = std::chrono::steady_clock::now();
239 auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
240 if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
241 Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
242 Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
243 getaddrinfo_timeout_error_shown = true;
244 }
245
246 if (error != 0) {
247 Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
248 this->status = Status::Failure;
249 return;
250 }
251
252 this->ai = ai;
253 this->OnResolved(ai);
254
256}
257
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: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:101
void Kill()
Kill this connecter.
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
Definition tcp.h:98
std::atomic< Status > status
The current status of the connecter.
Definition tcp.h:89
std::string connection_string
Current address we are connecting to (before resolving).
Definition tcp.h:100
static std::vector< std::shared_ptr< TCPConnecter > > connecters
List of connections that are currently being created.
Definition tcp.h:104
std::vector< SOCKET > sockets
Pending connect() attempts.
Definition tcp.h:97
static void CheckCallbacks()
Check whether we need to call the callback, i.e.
virtual ~TCPConnecter()
Wait until the resolving is done, then release the sockets.
size_t current_address
Current index in addresses we are trying.
Definition tcp.h:95
void Resolve()
Start resolving the hostname.
virtual void OnFailure()
Callback for when the connection attempt failed.
Definition tcp.h:132
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:94
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:93
std::thread resolve_thread
Thread used during resolving.
Definition tcp.h:88
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:90
@ Connected
The connection is established.
Definition tcp.h:85
@ Resolving
The hostname is being resolved (threaded).
Definition tcp.h:82
@ Init
TCPConnecter is created but resolving hasn't started.
Definition tcp.h:81
@ Failure
Resolving failed.
Definition tcp.h:83
@ Connecting
We are currently connecting.
Definition tcp.h:84
virtual void OnConnect(SOCKET s)
Callback when the connection succeeded.
Definition tcp.h:127
void Connect(addrinfo *address)
Start a connection to the indicated address.
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
Definition tcp.h:92
int family
Family we are using to connect with.
Definition tcp.h:102
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:550
std::string NormalizeConnectionString(std::string_view connection_string, uint16_t default_port)
Normalize a connection string.
Definition network.cpp:535
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