OpenTTD Source 20250524-master-gc366e6a48e
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(std::string_view 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(std::string_view 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] = 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
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 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
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:176
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Definition address.h:175
void ConnectToServer(std::string_view 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 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 sockaddr_storage * GetAddress()
Get the address in its internal representation.
Definition address.cpp:114
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
std::string connection_string
The connection string for this ServerAddress.
Definition address.h:198
ServerAddressType type
The type of this ServerAddress.
Definition address.h:197
"Helper" class for creating TCP connections in a non-blocking manner
Definition tcp.h:74
NetworkAddress bind_address
Address we're binding to, if any.
Definition tcp.h:104
void Kill()
Kill this connecter.
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
Definition tcp.h:101
std::atomic< Status > status
The current status of the connecter.
Definition tcp.h:92
std::string connection_string
Current address we are connecting to (before resolving).
Definition tcp.h:103
static std::vector< std::shared_ptr< TCPConnecter > > connecters
List of connections that are currently being created.
Definition tcp.h:107
std::vector< SOCKET > sockets
Pending connect() attempts.
Definition tcp.h:100
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:98
void Resolve()
Start resolving the hostname.
virtual void OnFailure()
Callback for when the connection attempt failed.
Definition tcp.h:135
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:97
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:96
std::thread resolve_thread
Thread used during resolving.
Definition tcp.h:91
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:93
@ 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.
virtual void OnConnect(SOCKET s)
Callback when the connection succeeded.
Definition tcp.h:130
void Connect(addrinfo *address)
Start a connection to the indicated address.
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
Definition tcp.h:95
int family
Family we are using to connect with.
Definition tcp.h:105
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:162
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:157
#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:555
std::string NormalizeConnectionString(std::string_view connection_string, uint16_t default_port)
Normalize a connection string.
Definition network.cpp:540
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, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition thread.h:47