OpenTTD Source  20241121-master-g67a0fccfad
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 
29 TCPConnecter::TCPConnecter(const std::string &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 
41 TCPServerConnecter::TCPServerConnecter(const std::string &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 
50  this->status = Status::Connecting;
52  break;
53 
54  default:
55  NOT_REACHED();
56  }
57 }
58 
59 TCPConnecter::~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 
88 void 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] = 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 
148 void 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  memset(&hints, 0, sizeof(hints));
227  hints.ai_family = AF_UNSPEC;
228  hints.ai_flags = AI_ADDRCONFIG;
229  hints.ai_socktype = SOCK_STREAM;
230 
231  std::string port_name = std::to_string(address.GetPort());
232 
233  static bool getaddrinfo_timeout_error_shown = false;
234  auto start = std::chrono::steady_clock::now();
235 
236  addrinfo *ai;
237  int error = getaddrinfo(address.GetHostname().c_str(), port_name.c_str(), &hints, &ai);
238 
239  auto end = std::chrono::steady_clock::now();
240  auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
241  if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
242  Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
243  Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
244  getaddrinfo_timeout_error_shown = true;
245  }
246 
247  if (error != 0) {
248  Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
249  this->status = Status::Failure;
250  return;
251  }
252 
253  this->ai = ai;
254  this->OnResolved(ai);
255 
256  this->status = Status::Connecting;
257 }
258 
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)) {
280  this->status = Status::Resolving;
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 
291  case Status::Resolving:
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 
301  case Status::Connecting:
302  case Status::Connected:
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);
398  this->status = Status::Connected;
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 
421  case Status::Connected:
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;
446  this->status = Status::Connected;
447 }
448 
453 {
454  this->status = Status::Failure;
455 }
456 
463 /* static */ void TCPConnecter::CheckCallbacks()
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 {
474  TCPConnecter::connecters.clear();
475 }
@ SERVER_ADDRESS_INVITE_CODE
Server-address is based on an invite code.
Definition: address.h:188
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Definition: address.h:187
void ConnectToServer(const std::string &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 const char * AddressFamilyAsString(int family)
Convert the address family into a string.
Definition: address.cpp:388
static const char * SocketTypeAsString(int socktype)
Convert the socket type into a string.
Definition: address.cpp:373
static const std::string GetPeerName(SOCKET sock)
Get the peer name of a socket in string format.
Definition: address.cpp:435
uint16_t GetPort() const
Get the port.
Definition: address.cpp:38
const sockaddr_storage * GetAddress()
Get the address in its internal representation.
Definition: address.cpp:113
const std::string & GetHostname()
Get the hostname; in case it wasn't given the IPv4 dotted representation is given.
Definition: address.cpp:23
std::string GetAddressAsString(bool with_family=true)
Get the address as a string, e.g.
Definition: address.cpp:94
Abstraction of a network error where all implementation details of the error codes are encapsulated i...
bool HasError() const
Check whether an error was actually set.
const std::string & AsString() const
Get the string representation of the error message.
static NetworkError GetLast()
Get the last network error.
Address to a game server.
Definition: address.h:196
std::string connection_string
The connection string for this ServerAddress.
Definition: address.h:210
ServerAddressType type
The type of this ServerAddress.
Definition: address.h:209
"Helper" class for creating TCP connections in a non-blocking manner
Definition: tcp.h:70
NetworkAddress bind_address
Address we're binding to, if any.
Definition: tcp.h:100
void Kill()
Kill this connecter.
Definition: tcp_connect.cpp:78
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
Definition: tcp.h:97
virtual void OnConnect([[maybe_unused]] SOCKET s)
Callback when the connection succeeded.
Definition: tcp.h:126
std::atomic< Status > status
The current status of the connecter.
Definition: tcp.h:88
std::string connection_string
Current address we are connecting to (before resolving).
Definition: tcp.h:99
static std::vector< std::shared_ptr< TCPConnecter > > connecters
List of connections that are currently being created.
Definition: tcp.h:103
std::vector< SOCKET > sockets
Pending connect() attempts.
Definition: tcp.h:96
static void CheckCallbacks()
Check whether we need to call the callback, i.e.
@ 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.
size_t current_address
Current index in addresses we are trying.
Definition: tcp.h:94
void Resolve()
Start resolving the hostname.
virtual void OnFailure()
Callback for when the connection attempt failed.
Definition: tcp.h:131
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:93
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:92
std::thread resolve_thread
Thread used during resolving.
Definition: tcp.h:87
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:89
void Connect(addrinfo *address)
Start a connection to the indicated address.
Definition: tcp_connect.cpp:88
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
Definition: tcp.h:91
int family
Family we are using to connect with.
Definition: tcp.h:101
TCPServerConnecter(const std::string &connection_string, uint16_t default_port)
Create a new connecter for the server.
Definition: tcp_connect.cpp:41
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:158
void SetFailure()
The connection couldn't be established.
SOCKET socket
The socket when a connection is established.
Definition: tcp.h:153
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
std::string NormalizeConnectionString(const std::string &connection_string, uint16_t default_port)
Normalize a connection string.
Definition: network.cpp:541
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16_t default_port)
Convert a string containing either "hostname" or "hostname:ip" to a NetworkAddress.
Definition: network.cpp:556
ClientNetworkCoordinatorSocketHandler _network_coordinator_client
The connection to the Game Coordinator.
bool SetReusePort(SOCKET d)
Try to set the socket to reuse ports.
bool SetNonBlocking([[maybe_unused]] SOCKET d)
Try to set the socket into non-blocking mode.
NetworkError GetSocketError(SOCKET d)
Get the error from a socket, if any.
bool SetNoDelay([[maybe_unused]] SOCKET d)
Try to set the socket to not delay sending.
Basic functions to receive and send TCP packets.
bool StartNewThread(std::thread *thr, const char *name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition: thread.h:47