Codechange: Update minimum CMake version to 3.16 for all parts. (#13141)
[openttd-github.git] / src / network / core / tcp_connect.cpp
blobf0cf8c3b4f20eae5bb66ce7c54f8f7176222dbfe
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 */
8 /**
9 * @file tcp_connect.cpp Basic functions to create connections without blocking.
12 #include "../../stdafx.h"
13 #include "../../thread.h"
15 #include "tcp.h"
16 #include "../network_coordinator.h"
17 #include "../network_internal.h"
19 #include "../../safeguards.h"
21 /* static */ std::vector<std::shared_ptr<TCPConnecter>> TCPConnecter::connecters;
23 /**
24 * Create a new connecter for the given address.
25 * @param connection_string The address to connect to.
26 * @param default_port If not indicated in connection_string, what port to use.
27 * @param bind_address The local bind address to use. Defaults to letting the OS find one.
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)
33 this->connection_string = NormalizeConnectionString(connection_string, default_port);
36 /**
37 * Create a new connecter for the server.
38 * @param connection_string The address to connect to.
39 * @param default_port If not indicated in connection_string, what port to use.
41 TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16_t default_port) :
42 server_address(ServerAddress::Parse(connection_string, default_port))
44 switch (this->server_address.type) {
45 case SERVER_ADDRESS_DIRECT:
46 this->connection_string = this->server_address.connection_string;
47 break;
49 case SERVER_ADDRESS_INVITE_CODE:
50 this->status = Status::Connecting;
51 _network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
52 break;
54 default:
55 NOT_REACHED();
59 TCPConnecter::~TCPConnecter()
61 if (this->resolve_thread.joinable()) {
62 this->resolve_thread.join();
65 for (const auto &socket : this->sockets) {
66 closesocket(socket);
68 this->sockets.clear();
69 this->sock_to_address.clear();
71 if (this->ai != nullptr) freeaddrinfo(this->ai);
74 /**
75 * Kill this connecter.
76 * It will abort as soon as it can and not call any of the callbacks.
78 void TCPConnecter::Kill()
80 /* Delay the removing of the socket till the next CheckActivity(). */
81 this->killed = true;
84 /**
85 * Start a connection to the indicated address.
86 * @param address The address to connection to.
88 void TCPConnecter::Connect(addrinfo *address)
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;
96 if (!SetReusePort(sock)) {
97 Debug(net, 0, "Setting reuse-port mode failed: {}", NetworkError::GetLast().AsString());
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;
108 if (!SetNoDelay(sock)) {
109 Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
111 if (!SetNonBlocking(sock)) {
112 Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
115 NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
116 Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
118 int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
119 if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
120 closesocket(sock);
122 Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
123 return;
126 this->sock_to_address[sock] = network_address;
127 this->sockets.push_back(sock);
131 * Start the connect() for the next address in the list.
132 * @return True iff a new connect() is attempted.
134 bool TCPConnecter::TryNextAddress()
136 if (this->current_address >= this->addresses.size()) return false;
138 this->last_attempt = std::chrono::steady_clock::now();
139 this->Connect(this->addresses[this->current_address++]);
141 return true;
145 * Callback when resolving is done.
146 * @param ai A linked-list of address information.
148 void TCPConnecter::OnResolved(addrinfo *ai)
150 std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
152 /* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
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;
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;
174 if (resort) {
175 if (runp->ai_family == AF_INET6) {
176 addresses_ipv6.emplace_back(runp);
177 } else {
178 addresses_ipv4.emplace_back(runp);
180 } else {
181 this->addresses.emplace_back(runp);
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();
193 if (!addresses_ipv4.empty()) {
194 this->addresses.push_back(addresses_ipv4.front());
195 addresses_ipv4.pop_front();
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());
211 this->current_address = 0;
215 * Start resolving the hostname.
217 * This function must change "status" to either Status::FAILURE
218 * or Status::CONNECTING before returning.
220 void TCPConnecter::Resolve()
222 /* Port is already guaranteed part of the connection_string. */
223 NetworkAddress address = ParseConnectionString(this->connection_string, 0);
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;
231 std::string port_name = std::to_string(address.GetPort());
233 static bool getaddrinfo_timeout_error_shown = false;
234 auto start = std::chrono::steady_clock::now();
236 addrinfo *ai;
237 int error = getaddrinfo(address.GetHostname().c_str(), port_name.c_str(), &hints, &ai);
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;
247 if (error != 0) {
248 Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
249 this->status = Status::Failure;
250 return;
253 this->ai = ai;
254 this->OnResolved(ai);
256 this->status = Status::Connecting;
260 * Thunk to start Resolve() on the right instance.
262 /* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
264 connecter->Resolve();
268 * Check if there was activity for this connecter.
269 * @return True iff the TCPConnecter is done and can be cleaned up.
271 bool TCPConnecter::CheckActivity()
273 if (this->killed) return true;
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;
284 /* No threads, do a blocking resolve. */
285 this->Resolve();
287 /* Continue as we are either failed or can start the first
288 * connection. The rest of this function handles exactly that. */
289 break;
291 case Status::Resolving:
292 /* Wait till Resolve() comes back with an answer (in case it runs threaded). */
293 return false;
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;
301 case Status::Connecting:
302 case Status::Connected:
303 break;
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;
313 return false;
316 fd_set write_fd;
317 FD_ZERO(&write_fd);
318 for (const auto &socket : this->sockets) {
319 FD_SET(socket, &write_fd);
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;
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;
338 /* Try the next address in the list. */
339 if (this->TryNextAddress()) return false;
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;
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);
348 for (const auto &socket : this->sockets) {
349 closesocket(socket);
351 this->sockets.clear();
352 this->sock_to_address.clear();
354 this->OnFailure();
355 return true;
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;
372 /* No error but writeable means connected. */
373 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
374 connected_socket = *it;
377 it++;
380 /* All the writable sockets were in error state. So nothing is connected yet. */
381 if (connected_socket == INVALID_SOCKET) return false;
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);
388 this->sock_to_address.erase(*it);
389 it = this->sockets.erase(it);
392 Debug(net, 3, "Connected to {}", this->connection_string);
393 if (_debug_net_level >= 5) {
394 Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
397 this->OnConnect(connected_socket);
398 this->status = Status::Connected;
399 return true;
403 * Check if there was activity for this connecter.
404 * @return True iff the TCPConnecter is done and can be cleaned up.
406 bool TCPServerConnecter::CheckActivity()
408 if (this->killed) return true;
410 switch (this->server_address.type) {
411 case SERVER_ADDRESS_DIRECT:
412 return TCPConnecter::CheckActivity();
414 case SERVER_ADDRESS_INVITE_CODE:
415 /* Check if a result has come in. */
416 switch (this->status) {
417 case Status::Failure:
418 this->OnFailure();
419 return true;
421 case Status::Connected:
422 this->OnConnect(this->socket);
423 return true;
425 default:
426 break;
429 return false;
431 default:
432 NOT_REACHED();
437 * The connection was successfully established.
438 * This socket is fully setup and ready to send/recv game protocol packets.
439 * @param sock The socket of the established connection.
441 void TCPServerConnecter::SetConnected(SOCKET sock)
443 assert(sock != INVALID_SOCKET);
445 this->socket = sock;
446 this->status = Status::Connected;
450 * The connection couldn't be established.
452 void TCPServerConnecter::SetFailure()
454 this->status = Status::Failure;
458 * Check whether we need to call the callback, i.e. whether we
459 * have connected or aborted and call the appropriate callback
460 * for that. It's done this way to ease on the locking that
461 * would otherwise be needed everywhere.
463 /* static */ void TCPConnecter::CheckCallbacks()
465 TCPConnecter::connecters.erase(
466 std::remove_if(TCPConnecter::connecters.begin(), TCPConnecter::connecters.end(),
467 [](auto &connecter) { return connecter->CheckActivity(); }),
468 TCPConnecter::connecters.end());
471 /** Kill all connection attempts. */
472 /* static */ void TCPConnecter::KillAll()
474 TCPConnecter::connecters.clear();