OpenTTD Source 20260109-master-g241b5fcdfe
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
10#include "../../stdafx.h"
11#include "../../thread.h"
12
13#include "tcp.h"
14#include "../network_coordinator.h"
15#include "../network_internal.h"
16
17#include "../../safeguards.h"
18
19/* static */ std::vector<std::shared_ptr<TCPConnecter>> TCPConnecter::connecters;
20
27TCPConnecter::TCPConnecter(std::string_view connection_string, uint16_t default_port, const NetworkAddress &bind_address, int family) :
28 bind_address(bind_address),
29 family(family)
30{
31 this->connection_string = NormalizeConnectionString(connection_string, default_port);
32}
33
39TCPServerConnecter::TCPServerConnecter(std::string_view connection_string, uint16_t default_port) :
40 server_address(ServerAddress::Parse(connection_string, default_port))
41{
42 switch (this->server_address.type) {
44 this->connection_string = this->server_address.connection_string;
45 break;
46
50 break;
51
52 default:
53 NOT_REACHED();
54 }
55}
56
57TCPConnecter::~TCPConnecter()
58{
59 if (this->resolve_thread.joinable()) {
60 this->resolve_thread.join();
61 }
62
63 for (const auto &socket : this->sockets) {
64 closesocket(socket);
65 }
66 this->sockets.clear();
67 this->sock_to_address.clear();
68
69 if (this->ai != nullptr) freeaddrinfo(this->ai);
70}
71
77{
78 /* Delay the removing of the socket till the next CheckActivity(). */
79 this->killed = true;
80}
81
86void TCPConnecter::Connect(addrinfo *address)
87{
88 SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
89 if (sock == INVALID_SOCKET) {
90 Debug(net, 0, "Could not create {} {} socket: {}", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
91 return;
92 }
93
94 if (!SetReusePort(sock)) {
95 Debug(net, 0, "Setting reuse-port mode failed: {}", NetworkError::GetLast().AsString());
96 }
97
98 if (this->bind_address.GetPort() > 0) {
99 if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
100 Debug(net, 1, "Could not bind socket on {}: {}", this->bind_address.GetAddressAsString(), NetworkError::GetLast().AsString());
101 closesocket(sock);
102 return;
103 }
104 }
105
106 if (!SetNoDelay(sock)) {
107 Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
108 }
109 if (!SetNonBlocking(sock)) {
110 Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
111 }
112
113 NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
114 Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
115
116 int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
117 if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
118 closesocket(sock);
119
120 Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
121 return;
122 }
123
124 this->sock_to_address[sock] = std::move(network_address);
125 this->sockets.push_back(sock);
126}
127
133{
134 if (this->current_address >= this->addresses.size()) return false;
135
136 this->last_attempt = std::chrono::steady_clock::now();
137 this->Connect(this->addresses[this->current_address++]);
138
139 return true;
140}
141
146void TCPConnecter::OnResolved(addrinfo *ai)
147{
148 std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
149
150 /* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
151
152 /* Detect if IPv6 is likely to succeed or not. */
153 bool seen_ipv6 = false;
154 bool resort = true;
155 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
156 if (runp->ai_family == AF_INET6) {
157 seen_ipv6 = true;
158 } else if (!seen_ipv6) {
159 /* We see an IPv4 before an IPv6; this most likely means there is
160 * no IPv6 available on the system, so keep the order of this
161 * list. */
162 resort = false;
163 break;
164 }
165 }
166
167 /* Convert the addrinfo into NetworkAddresses. */
168 for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
169 /* Skip entries if the family is set and it is not matching. */
170 if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue;
171
172 if (resort) {
173 if (runp->ai_family == AF_INET6) {
174 addresses_ipv6.emplace_back(runp);
175 } else {
176 addresses_ipv4.emplace_back(runp);
177 }
178 } else {
179 this->addresses.emplace_back(runp);
180 }
181 }
182
183 /* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
184 * for how ever many (round-robin) DNS entries we have. */
185 if (resort) {
186 while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
187 if (!addresses_ipv6.empty()) {
188 this->addresses.push_back(addresses_ipv6.front());
189 addresses_ipv6.pop_front();
190 }
191 if (!addresses_ipv4.empty()) {
192 this->addresses.push_back(addresses_ipv4.front());
193 addresses_ipv4.pop_front();
194 }
195 }
196 }
197
198 if (_debug_net_level >= 6) {
199 if (this->addresses.empty()) {
200 Debug(net, 6, "{} did not resolve", this->connection_string);
201 } else {
202 Debug(net, 6, "{} resolved in:", this->connection_string);
203 for (const auto &address : this->addresses) {
204 Debug(net, 6, "- {}", NetworkAddress(address->ai_addr, (int)address->ai_addrlen).GetAddressAsString());
205 }
206 }
207 }
208
209 this->current_address = 0;
210}
211
219{
220 /* Port is already guaranteed part of the connection_string. */
222
223 addrinfo hints{};
224 hints.ai_family = AF_UNSPEC;
225 hints.ai_flags = AI_ADDRCONFIG;
226 hints.ai_socktype = SOCK_STREAM;
227
228 std::string port_name = fmt::format("{}", address.GetPort());
229
230 static bool getaddrinfo_timeout_error_shown = false;
231 auto start = std::chrono::steady_clock::now();
232
233 addrinfo *ai;
234 int error = getaddrinfo(address.GetHostname().c_str(), port_name.c_str(), &hints, &ai);
235
236 auto end = std::chrono::steady_clock::now();
237 auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
238 if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
239 Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
240 Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
241 getaddrinfo_timeout_error_shown = true;
242 }
243
244 if (error != 0) {
245 Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
246 this->status = Status::Failure;
247 return;
248 }
249
250 this->ai = ai;
251 this->OnResolved(ai);
252
254}
255
259/* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
260{
261 connecter->Resolve();
262}
263
269{
270 if (this->killed) return true;
271
272 switch (this->status) {
273 case Status::Init:
274 /* Start the thread delayed, so the vtable is loaded. This allows classes
275 * to overload functions used by Resolve() (in case threading is disabled). */
276 if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
278 return false;
279 }
280
281 /* No threads, do a blocking resolve. */
282 this->Resolve();
283
284 /* Continue as we are either failed or can start the first
285 * connection. The rest of this function handles exactly that. */
286 break;
287
289 /* Wait till Resolve() comes back with an answer (in case it runs threaded). */
290 return false;
291
292 case Status::Failure:
293 /* Ensure the OnFailure() is called from the game-thread instead of the
294 * resolve-thread, as otherwise we can get into some threading issues. */
295 this->OnFailure();
296 return true;
297
300 break;
301 }
302
303 /* If there are no attempts pending, connect to the next. */
304 if (this->sockets.empty()) {
305 if (!this->TryNextAddress()) {
306 /* There were no more addresses to try, so we failed. */
307 this->OnFailure();
308 return true;
309 }
310 return false;
311 }
312
313 fd_set write_fd;
314 FD_ZERO(&write_fd);
315 for (const auto &socket : this->sockets) {
316 FD_SET(socket, &write_fd);
317 }
318
319 timeval tv;
320 tv.tv_usec = 0;
321 tv.tv_sec = 0;
322 int n = select(FD_SETSIZE, nullptr, &write_fd, nullptr, &tv);
323 /* select() failed; hopefully next try it doesn't. */
324 if (n < 0) {
325 /* select() normally never fails; so hopefully it works next try! */
326 Debug(net, 1, "select() failed: {}", NetworkError::GetLast().AsString());
327 return false;
328 }
329
330 /* No socket updates. */
331 if (n == 0) {
332 /* Wait 250ms between attempting another address. */
333 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
334
335 /* Try the next address in the list. */
336 if (this->TryNextAddress()) return false;
337
338 /* Wait up to 3 seconds since the last connection we started. */
339 if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
340
341 /* More than 3 seconds no socket reported activity, and there are no
342 * more address to try. Timeout the attempt. */
343 Debug(net, 0, "Timeout while connecting to {}", this->connection_string);
344
345 for (const auto &socket : this->sockets) {
346 closesocket(socket);
347 }
348 this->sockets.clear();
349 this->sock_to_address.clear();
350
351 this->OnFailure();
352 return true;
353 }
354
355 /* If a socket is writeable, it is either in error-state or connected.
356 * Remove all sockets that are in error-state and mark the first that is
357 * not in error-state as the socket we will use for our connection. */
358 SOCKET connected_socket = INVALID_SOCKET;
359 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
360 NetworkError socket_error = GetSocketError(*it);
361 if (socket_error.HasError()) {
362 Debug(net, 1, "Could not connect to {}: {}", this->sock_to_address[*it].GetAddressAsString(), socket_error.AsString());
363 closesocket(*it);
364 this->sock_to_address.erase(*it);
365 it = this->sockets.erase(it);
366 continue;
367 }
368
369 /* No error but writeable means connected. */
370 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
371 connected_socket = *it;
372 }
373
374 it++;
375 }
376
377 /* All the writable sockets were in error state. So nothing is connected yet. */
378 if (connected_socket == INVALID_SOCKET) return false;
379
380 /* Close all sockets except the one we picked for our connection. */
381 for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
382 if (connected_socket != *it) {
383 closesocket(*it);
384 }
385 this->sock_to_address.erase(*it);
386 it = this->sockets.erase(it);
387 }
388
389 Debug(net, 3, "Connected to {}", this->connection_string);
390 if (_debug_net_level >= 5) {
391 Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
392 }
393
394 this->OnConnect(connected_socket);
396 return true;
397}
398
404{
405 if (this->killed) return true;
406
407 switch (this->server_address.type) {
410
412 /* Check if a result has come in. */
413 switch (this->status) {
414 case Status::Failure:
415 this->OnFailure();
416 return true;
417
419 this->OnConnect(this->socket);
420 return true;
421
422 default:
423 break;
424 }
425
426 return false;
427
428 default:
429 NOT_REACHED();
430 }
431}
432
439{
440 assert(sock != INVALID_SOCKET);
441
442 this->socket = sock;
444}
445
453
461{
463 std::remove_if(TCPConnecter::connecters.begin(), TCPConnecter::connecters.end(),
464 [](auto &connecter) { return connecter->CheckActivity(); }),
466}
467
469/* static */ void TCPConnecter::KillAll()
470{
472}
@ 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:72
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.
@ 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: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
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