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/>.
8 /** @file network_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */
10 #include "../stdafx.h"
14 #include "../settings_type.h"
15 #include "../strings_func.h"
16 #include "../window_func.h"
17 #include "../window_type.h"
19 #include "network_coordinator.h"
20 #include "network_gamelist.h"
21 #include "network_gui.h"
22 #include "network_internal.h"
23 #include "network_server.h"
24 #include "network_stun.h"
25 #include "table/strings.h"
27 #include "../safeguards.h"
29 static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES
= std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator.
30 ClientNetworkCoordinatorSocketHandler _network_coordinator_client
; ///< The connection to the Game Coordinator.
31 ConnectionType _network_server_connection_type
= CONNECTION_TYPE_UNKNOWN
; ///< What type of connection the Game Coordinator detected we are on.
32 std::string _network_server_invite_code
= ""; ///< Our invite code as indicated by the Game Coordinator.
34 /** Connect to a game server by IP:port. */
35 class NetworkDirectConnecter
: public TCPConnecter
{
37 std::string token
; ///< Token of this connection.
38 uint8_t tracking_number
; ///< Tracking number of this connection.
42 * Try to establish a direct (hostname:port based) connection.
43 * @param hostname The hostname of the server.
44 * @param port The port of the server.
45 * @param token The token as given by the Game Coordinator to track this connection attempt.
46 * @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt.
48 NetworkDirectConnecter(const std::string
&hostname
, uint16_t port
, const std::string
&token
, uint8_t tracking_number
) : TCPConnecter(hostname
, port
), token(token
), tracking_number(tracking_number
) {}
50 void OnFailure() override
52 _network_coordinator_client
.ConnectFailure(this->token
, this->tracking_number
);
55 void OnConnect(SOCKET s
) override
57 NetworkAddress address
= NetworkAddress::GetPeerAddress(s
);
58 _network_coordinator_client
.ConnectSuccess(this->token
, s
, address
);
62 /** Connecter used after STUN exchange to connect from both sides to each other. */
63 class NetworkReuseStunConnecter
: public TCPConnecter
{
65 std::string token
; ///< Token of this connection.
66 uint8_t tracking_number
; ///< Tracking number of this connection.
67 uint8_t family
; ///< Family of this connection.
71 * Try to establish a STUN-based connection.
72 * @param hostname The hostname of the peer.
73 * @param port The port of the peer.
74 * @param bind_address The local bind address used for this connection.
75 * @param token The connection token.
76 * @param tracking_number The tracking number of the connection.
77 * @param family The family this connection is using.
79 NetworkReuseStunConnecter(const std::string
&hostname
, uint16_t port
, const NetworkAddress
&bind_address
, std::string token
, uint8_t tracking_number
, uint8_t family
) :
80 TCPConnecter(hostname
, port
, bind_address
),
82 tracking_number(tracking_number
),
87 void OnFailure() override
89 /* Close the STUN connection too, as it is no longer of use. */
90 _network_coordinator_client
.CloseStunHandler(this->token
, this->family
);
92 _network_coordinator_client
.ConnectFailure(this->token
, this->tracking_number
);
95 void OnConnect(SOCKET s
) override
97 NetworkAddress address
= NetworkAddress::GetPeerAddress(s
);
98 _network_coordinator_client
.ConnectSuccess(this->token
, s
, address
);
102 /** Connect to the Game Coordinator server. */
103 class NetworkCoordinatorConnecter
: public TCPConnecter
{
106 * Initiate the connecting.
107 * @param connection_string The address of the Game Coordinator server.
109 NetworkCoordinatorConnecter(const std::string
&connection_string
) : TCPConnecter(connection_string
, NETWORK_COORDINATOR_SERVER_PORT
) {}
111 void OnFailure() override
113 _network_coordinator_client
.connecting
= false;
114 _network_coordinator_client
.CloseConnection(true);
117 void OnConnect(SOCKET s
) override
119 assert(_network_coordinator_client
.sock
== INVALID_SOCKET
);
121 _network_coordinator_client
.sock
= s
;
122 _network_coordinator_client
.last_activity
= std::chrono::steady_clock::now();
123 _network_coordinator_client
.connecting
= false;
127 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet
&p
)
129 NetworkCoordinatorErrorType error
= (NetworkCoordinatorErrorType
)p
.Recv_uint8();
130 std::string detail
= p
.Recv_string(NETWORK_ERROR_DETAIL_LENGTH
);
133 case NETWORK_COORDINATOR_ERROR_UNKNOWN
:
134 this->CloseConnection();
137 case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED
:
138 ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED
, INVALID_STRING_ID
, WL_ERROR
);
140 /* To prevent that we constantly try to reconnect, switch to local game. */
141 _settings_client
.network
.server_game_type
= SERVER_GAME_TYPE_LOCAL
;
143 this->CloseConnection();
146 case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE
: {
147 auto connecter_pre_it
= this->connecter_pre
.find(detail
);
148 if (connecter_pre_it
!= this->connecter_pre
.end()) {
149 connecter_pre_it
->second
->SetFailure();
150 this->connecter_pre
.erase(connecter_pre_it
);
153 /* Mark the server as offline. */
154 NetworkGameList
*item
= NetworkGameListAddItem(detail
);
155 item
->status
= NGLS_OFFLINE
;
157 UpdateNetworkGameWindow();
161 case NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE
:
162 ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REUSE_OF_INVITE_CODE
, INVALID_STRING_ID
, WL_ERROR
);
164 /* To prevent that we constantly battle for the same invite-code, switch to local game. */
165 _settings_client
.network
.server_game_type
= SERVER_GAME_TYPE_LOCAL
;
167 this->CloseConnection();
171 Debug(net
, 0, "Invalid error type {} received from Game Coordinator", error
);
172 this->CloseConnection();
177 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet
&p
)
179 /* Schedule sending an update. */
180 this->next_update
= std::chrono::steady_clock::now();
182 _settings_client
.network
.server_invite_code
= p
.Recv_string(NETWORK_INVITE_CODE_LENGTH
);
183 _settings_client
.network
.server_invite_code_secret
= p
.Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH
);
184 _network_server_connection_type
= (ConnectionType
)p
.Recv_uint8();
186 if (_network_server_connection_type
== CONNECTION_TYPE_ISOLATED
) {
187 ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED
, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL
, WL_ERROR
);
190 /* Users can change the invite code in the settings, but this has no effect
191 * on the invite code as assigned by the server. So
192 * _network_server_invite_code contains the current invite code,
193 * and _settings_client.network.server_invite_code contains the one we will
194 * attempt to re-use when registering again. */
195 _network_server_invite_code
= _settings_client
.network
.server_invite_code
;
197 SetWindowDirty(WC_CLIENT_LIST
, 0);
199 if (_network_dedicated
) {
200 std::string connection_type
;
201 switch (_network_server_connection_type
) {
202 case CONNECTION_TYPE_ISOLATED
: connection_type
= "Remote players can't connect"; break;
203 case CONNECTION_TYPE_DIRECT
: connection_type
= "Public"; break;
204 case CONNECTION_TYPE_STUN
: connection_type
= "Behind NAT"; break;
205 case CONNECTION_TYPE_TURN
: connection_type
= "Via relay"; break;
207 case CONNECTION_TYPE_UNKNOWN
: // Never returned from Game Coordinator.
208 default: connection_type
= "Unknown"; break; // Should never happen, but don't fail if it does.
211 std::string game_type
;
212 switch (_settings_client
.network
.server_game_type
) {
213 case SERVER_GAME_TYPE_INVITE_ONLY
: game_type
= "Invite only"; break;
214 case SERVER_GAME_TYPE_PUBLIC
: game_type
= "Public"; break;
216 case SERVER_GAME_TYPE_LOCAL
: // Impossible to register local servers.
217 default: game_type
= "Unknown"; break; // Should never happen, but don't fail if it does.
220 Debug(net
, 3, "----------------------------------------");
221 Debug(net
, 3, "Your server is now registered with the Game Coordinator:");
222 Debug(net
, 3, " Game type: {}", game_type
);
223 Debug(net
, 3, " Connection type: {}", connection_type
);
224 Debug(net
, 3, " Invite code: {}", _network_server_invite_code
);
225 Debug(net
, 3, "----------------------------------------");
227 Debug(net
, 3, "Game Coordinator registered our server with invite code '{}'", _network_server_invite_code
);
233 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet
&p
)
235 uint8_t servers
= p
.Recv_uint16();
237 /* End of list; we can now remove all expired items from the list. */
239 NetworkGameListRemoveExpired();
243 for (; servers
> 0; servers
--) {
244 std::string connection_string
= p
.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH
);
246 /* Read the NetworkGameInfo from the packet. */
247 NetworkGameInfo ngi
= {};
248 DeserializeNetworkGameInfo(p
, ngi
, &this->newgrf_lookup_table
);
250 /* Now we know the connection string, we can add it to our list. */
251 NetworkGameList
*item
= NetworkGameListAddItem(connection_string
);
253 /* Clear any existing GRFConfig chain. */
254 ClearGRFConfigList(&item
->info
.grfconfig
);
255 /* Copy the new NetworkGameInfo info. */
257 /* Check for compatability with the client. */
258 CheckGameCompatibility(item
->info
);
259 /* Mark server as online. */
260 item
->status
= NGLS_ONLINE
;
261 /* Mark the item as up-to-date. */
262 item
->version
= _network_game_list_version
;
265 UpdateNetworkGameWindow();
269 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet
&p
)
271 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
272 std::string invite_code
= p
.Recv_string(NETWORK_INVITE_CODE_LENGTH
);
274 /* Find the connecter based on the invite code. */
275 auto connecter_pre_it
= this->connecter_pre
.find(invite_code
);
276 if (connecter_pre_it
== this->connecter_pre
.end()) {
277 this->CloseConnection();
281 /* Now store it based on the token. */
282 this->connecter
[token
] = {invite_code
, connecter_pre_it
->second
};
283 this->connecter_pre
.erase(connecter_pre_it
);
288 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet
&p
)
290 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
291 this->CloseToken(token
);
296 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet
&p
)
298 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
299 uint8_t tracking_number
= p
.Recv_uint8();
300 std::string hostname
= p
.Recv_string(NETWORK_HOSTNAME_LENGTH
);
301 uint16_t port
= p
.Recv_uint16();
303 /* Ensure all other pending connection attempts are killed. */
304 if (this->game_connecter
!= nullptr) {
305 this->game_connecter
->Kill();
306 this->game_connecter
= nullptr;
309 this->game_connecter
= TCPConnecter::Create
<NetworkDirectConnecter
>(hostname
, port
, token
, tracking_number
);
313 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet
&p
)
315 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
317 this->stun_handlers
[token
][AF_INET6
] = ClientNetworkStunSocketHandler::Stun(token
, AF_INET6
);
318 this->stun_handlers
[token
][AF_INET
] = ClientNetworkStunSocketHandler::Stun(token
, AF_INET
);
322 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet
&p
)
324 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
325 uint8_t tracking_number
= p
.Recv_uint8();
326 uint8_t family
= p
.Recv_uint8();
327 std::string host
= p
.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH
);
328 uint16_t port
= p
.Recv_uint16();
330 /* Check if we know this token. */
331 auto stun_it
= this->stun_handlers
.find(token
);
332 if (stun_it
== this->stun_handlers
.end()) return true;
333 auto family_it
= stun_it
->second
.find(family
);
334 if (family_it
== stun_it
->second
.end()) return true;
336 /* Ensure all other pending connection attempts are killed. */
337 if (this->game_connecter
!= nullptr) {
338 this->game_connecter
->Kill();
339 this->game_connecter
= nullptr;
342 /* We now mark the connection as closed, but we do not really close the
343 * socket yet. We do this when the NetworkReuseStunConnecter is connected.
344 * This prevents any NAT to already remove the route while we create the
345 * second connection on top of the first. */
346 family_it
->second
->CloseConnection(false);
348 /* Connect to our peer from the same local address as we use for the
349 * STUN server. This means that if there is any NAT in the local network,
350 * the public ip:port is still pointing to the local address, and as such
351 * a connection can be established. */
352 this->game_connecter
= TCPConnecter::Create
<NetworkReuseStunConnecter
>(host
, port
, family_it
->second
->local_addr
, token
, tracking_number
, family
);
356 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet
&p
)
358 this->newgrf_lookup_table_cursor
= p
.Recv_uint32();
360 uint16_t newgrfs
= p
.Recv_uint16();
361 for (; newgrfs
> 0; newgrfs
--) {
362 uint32_t index
= p
.Recv_uint32();
363 DeserializeGRFIdentifierWithName(p
, this->newgrf_lookup_table
[index
]);
368 bool ClientNetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet
&p
)
370 std::string token
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
371 uint8_t tracking_number
= p
.Recv_uint8();
372 std::string ticket
= p
.Recv_string(NETWORK_TOKEN_LENGTH
);
373 std::string connection_string
= p
.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH
);
375 /* Ensure all other pending connection attempts are killed. */
376 if (this->game_connecter
!= nullptr) {
377 this->game_connecter
->Kill();
378 this->game_connecter
= nullptr;
381 this->turn_handlers
[token
] = ClientNetworkTurnSocketHandler::Turn(token
, tracking_number
, ticket
, connection_string
);
383 if (!_network_server
) {
384 auto connecter_it
= this->connecter
.find(token
);
385 if (connecter_it
== this->connecter
.end()) {
386 /* Make sure we are still interested in connecting to this server. */
387 this->ConnectFailure(token
, 0);
391 switch (_settings_client
.network
.use_relay_service
) {
393 this->ConnectFailure(token
, 0);
397 ShowNetworkAskRelay(connecter_it
->second
.first
, connection_string
, token
);
401 this->StartTurnConnection(token
);
405 this->StartTurnConnection(token
);
411 void ClientNetworkCoordinatorSocketHandler::StartTurnConnection(std::string
&token
)
413 auto turn_it
= this->turn_handlers
.find(token
);
414 if (turn_it
== this->turn_handlers
.end()) return;
416 turn_it
->second
->Connect();
419 void ClientNetworkCoordinatorSocketHandler::Connect()
421 /* We are either already connected or are trying to connect. */
422 if (this->sock
!= INVALID_SOCKET
|| this->connecting
) return;
426 this->connecting
= true;
427 this->last_activity
= std::chrono::steady_clock::now();
429 TCPConnecter::Create
<NetworkCoordinatorConnecter
>(NetworkCoordinatorConnectionString());
432 NetworkRecvStatus
ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error
)
434 NetworkCoordinatorSocketHandler::CloseConnection(error
);
437 this->connecting
= false;
439 _network_server_connection_type
= CONNECTION_TYPE_UNKNOWN
;
440 this->next_update
= {};
442 this->CloseAllConnections();
444 SetWindowDirty(WC_CLIENT_LIST
, 0);
446 return NETWORK_RECV_STATUS_OKAY
;
450 * Register our server to receive our invite code.
452 void ClientNetworkCoordinatorSocketHandler::Register()
454 _network_server_connection_type
= CONNECTION_TYPE_UNKNOWN
;
455 this->next_update
= {};
457 SetWindowDirty(WC_CLIENT_LIST
, 0);
461 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_SERVER_REGISTER
);
462 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
463 p
->Send_uint8(_settings_client
.network
.server_game_type
);
464 p
->Send_uint16(_settings_client
.network
.server_port
);
465 if (_settings_client
.network
.server_invite_code
.empty() || _settings_client
.network
.server_invite_code_secret
.empty()) {
469 p
->Send_string(_settings_client
.network
.server_invite_code
);
470 p
->Send_string(_settings_client
.network
.server_invite_code_secret
);
473 this->SendPacket(std::move(p
));
477 * Send an update of our server status to the Game Coordinator.
479 void ClientNetworkCoordinatorSocketHandler::SendServerUpdate()
481 Debug(net
, 6, "Sending server update to Game Coordinator");
483 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_SERVER_UPDATE
, TCP_MTU
);
484 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
485 SerializeNetworkGameInfo(*p
, GetCurrentNetworkServerGameInfo(), this->next_update
.time_since_epoch() != std::chrono::nanoseconds::zero());
487 this->SendPacket(std::move(p
));
489 this->next_update
= std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES
;
493 * Request a listing of all public servers.
495 void ClientNetworkCoordinatorSocketHandler::GetListing()
499 _network_game_list_version
++;
501 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_CLIENT_LISTING
);
502 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
503 p
->Send_uint8(NETWORK_GAME_INFO_VERSION
);
504 p
->Send_string(_openttd_revision
);
505 p
->Send_uint32(this->newgrf_lookup_table_cursor
);
507 this->SendPacket(std::move(p
));
511 * Join a server based on an invite code.
512 * @param invite_code The invite code of the server to connect to.
513 * @param connecter The connecter of the request.
515 void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string
&invite_code
, TCPServerConnecter
*connecter
)
517 assert(invite_code
.starts_with("+"));
519 if (this->connecter_pre
.find(invite_code
) != this->connecter_pre
.end()) {
520 /* If someone is hammering the refresh key, one can sent out two
521 * requests for the same invite code. There isn't really a great way
522 * of handling this, so just ignore this request. */
523 connecter
->SetFailure();
527 /* Initially we store based on invite code; on first reply we know the
528 * token, and will start using that key instead. */
529 this->connecter_pre
[invite_code
] = connecter
;
533 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_CLIENT_CONNECT
);
534 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
535 p
->Send_string(invite_code
);
537 this->SendPacket(std::move(p
));
541 * Callback from a Connecter to let the Game Coordinator know the connection failed.
542 * @param token Token of the connecter that failed.
543 * @param tracking_number Tracking number of the connecter that failed.
545 void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string
&token
, uint8_t tracking_number
)
547 /* Connecter will destroy itself. */
548 this->game_connecter
= nullptr;
550 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED
);
551 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
552 p
->Send_string(token
);
553 p
->Send_uint8(tracking_number
);
555 this->SendPacket(std::move(p
));
557 /* We do not close the associated connecter here yet, as the
558 * Game Coordinator might have other methods of connecting available. */
562 * Callback from a Connecter to let the Game Coordinator know the connection
563 * to the game server is established.
564 * @param token Token of the connecter that succeeded.
565 * @param sock The socket that the connecter can now use.
567 void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string
&token
, SOCKET sock
, NetworkAddress
&address
)
569 assert(sock
!= INVALID_SOCKET
);
571 /* Connecter will destroy itself. */
572 this->game_connecter
= nullptr;
574 if (_network_server
) {
575 if (!ServerNetworkGameSocketHandler::ValidateClient(sock
, address
)) return;
576 Debug(net
, 3, "[{}] Client connected from {} on frame {}", ServerNetworkGameSocketHandler::GetName(), address
.GetHostname(), _frame_counter
);
577 ServerNetworkGameSocketHandler::AcceptConnection(sock
, address
);
579 /* The client informs the Game Coordinator about the success. The server
580 * doesn't have to, as it is implied by the client telling. */
581 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_CLIENT_CONNECTED
);
582 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
583 p
->Send_string(token
);
584 this->SendPacket(std::move(p
));
586 /* Find the connecter; it can happen it no longer exist, in cases where
587 * we aborted the connect but the Game Coordinator was already in the
588 * processes of connecting us. */
589 auto connecter_it
= this->connecter
.find(token
);
590 if (connecter_it
!= this->connecter
.end()) {
591 connecter_it
->second
.second
->SetConnected(sock
);
592 this->connecter
.erase(connecter_it
);
596 /* Close all remaining connections. */
597 this->CloseToken(token
);
601 * Callback from the STUN connecter to inform the Game Coordinator about the
602 * result of the STUN.
604 * This helps the Game Coordinator not to wait for a timeout on its end, but
605 * rather react as soon as the client/server knows the result.
607 void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string
&token
, uint8_t family
, bool result
)
609 auto p
= std::make_unique
<Packet
>(PACKET_COORDINATOR_SERCLI_STUN_RESULT
);
610 p
->Send_uint8(NETWORK_COORDINATOR_VERSION
);
611 p
->Send_string(token
);
612 p
->Send_uint8(family
);
613 p
->Send_bool(result
);
614 this->SendPacket(std::move(p
));
618 * Close the STUN handler.
619 * @param token The token used for the STUN handlers.
620 * @param family The family of STUN handlers to close. AF_UNSPEC to close all STUN handlers for this token.
622 void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string
&token
, uint8_t family
)
624 auto stun_it
= this->stun_handlers
.find(token
);
625 if (stun_it
== this->stun_handlers
.end()) return;
627 if (family
== AF_UNSPEC
) {
628 for (auto &[family
, stun_handler
] : stun_it
->second
) {
629 stun_handler
->CloseConnection();
630 stun_handler
->CloseSocket();
633 this->stun_handlers
.erase(stun_it
);
635 auto family_it
= stun_it
->second
.find(family
);
636 if (family_it
== stun_it
->second
.end()) return;
638 family_it
->second
->CloseConnection();
639 family_it
->second
->CloseSocket();
641 stun_it
->second
.erase(family_it
);
646 * Close the TURN handler.
647 * @param token The token used for the TURN handler.
649 void ClientNetworkCoordinatorSocketHandler::CloseTurnHandler(const std::string
&token
)
651 CloseWindowByClass(WC_NETWORK_ASK_RELAY
, NRWCD_HANDLED
);
653 auto turn_it
= this->turn_handlers
.find(token
);
654 if (turn_it
== this->turn_handlers
.end()) return;
656 turn_it
->second
->CloseConnection();
657 turn_it
->second
->CloseSocket();
659 /* We don't remove turn_handler here, as we can be called from within that
660 * turn_handler instance, so our object cannot be free'd yet. Instead, we
661 * check later if the connection is closed, and free the object then. */
665 * Close everything related to this connection token.
666 * @param token The connection token to close.
668 void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string
&token
)
670 /* Close all remaining STUN / TURN connections. */
671 this->CloseStunHandler(token
);
672 this->CloseTurnHandler(token
);
674 /* Close the caller of the connection attempt. */
675 auto connecter_it
= this->connecter
.find(token
);
676 if (connecter_it
!= this->connecter
.end()) {
677 connecter_it
->second
.second
->SetFailure();
678 this->connecter
.erase(connecter_it
);
683 * Close all pending connection tokens.
685 void ClientNetworkCoordinatorSocketHandler::CloseAllConnections()
687 /* Ensure all other pending connection attempts are also killed. */
688 if (this->game_connecter
!= nullptr) {
689 this->game_connecter
->Kill();
690 this->game_connecter
= nullptr;
693 /* Mark any pending connecters as failed. */
694 for (auto &[token
, it
] : this->connecter
) {
695 this->CloseStunHandler(token
);
696 this->CloseTurnHandler(token
);
697 it
.second
->SetFailure();
699 /* Inform the Game Coordinator he can stop trying to connect us to the server. */
700 this->ConnectFailure(token
, 0);
702 this->stun_handlers
.clear();
703 this->turn_handlers
.clear();
704 this->connecter
.clear();
706 /* Also close any pending invite-code requests. */
707 for (auto &[invite_code
, it
] : this->connecter_pre
) {
710 this->connecter_pre
.clear();
714 * Check whether we received/can send some data from/to the Game Coordinator server and
715 * when that's the case handle it appropriately.
717 void ClientNetworkCoordinatorSocketHandler::SendReceive()
719 /* Private games are not listed via the Game Coordinator. */
720 if (_network_server
&& _settings_client
.network
.server_game_type
== SERVER_GAME_TYPE_LOCAL
) {
721 if (this->sock
!= INVALID_SOCKET
) {
722 this->CloseConnection();
727 static int last_attempt_backoff
= 1;
728 static bool first_reconnect
= true;
730 if (this->sock
== INVALID_SOCKET
) {
731 static std::chrono::steady_clock::time_point last_attempt
= {};
733 /* Don't auto-reconnect when we are not a server. */
734 if (!_network_server
) return;
735 /* Don't reconnect if we are connecting. */
736 if (this->connecting
) return;
737 /* Throttle how often we try to reconnect. */
738 if (std::chrono::steady_clock::now() < last_attempt
+ std::chrono::seconds(1) * last_attempt_backoff
) return;
740 last_attempt
= std::chrono::steady_clock::now();
741 /* Delay reconnecting with up to 32 seconds. */
742 if (last_attempt_backoff
< 32) {
743 last_attempt_backoff
*= 2;
746 /* Do not reconnect on the first attempt, but only initialize the
747 * last_attempt variables. Otherwise after an outage all servers
748 * reconnect at the same time, potentially overwhelming the
749 * Game Coordinator. */
750 if (first_reconnect
) {
751 first_reconnect
= false;
755 Debug(net
, 1, "Connection with Game Coordinator lost; reconnecting...");
760 last_attempt_backoff
= 1;
761 first_reconnect
= true;
763 if (_network_server
&& _network_server_connection_type
!= CONNECTION_TYPE_UNKNOWN
&& std::chrono::steady_clock::now() > this->next_update
) {
764 this->SendServerUpdate();
767 if (!_network_server
&& std::chrono::steady_clock::now() > this->last_activity
+ IDLE_TIMEOUT
) {
768 this->CloseConnection();
772 if (this->CanSendReceive()) {
773 if (this->ReceivePackets()) {
774 this->last_activity
= std::chrono::steady_clock::now();
780 for (const auto &[token
, families
] : this->stun_handlers
) {
781 for (const auto &[family
, stun_handler
] : families
) {
782 stun_handler
->SendReceive();
786 /* Check for handlers that are not connecting nor connected. Destroy those objects. */
787 for (auto turn_it
= this->turn_handlers
.begin(); turn_it
!= this->turn_handlers
.end(); /* nothing */) {
788 if (turn_it
->second
->connect_started
&& turn_it
->second
->connecter
== nullptr && !turn_it
->second
->IsConnected()) {
789 turn_it
= this->turn_handlers
.erase(turn_it
);
795 for (const auto &[token
, turn_handler
] : this->turn_handlers
) {
796 turn_handler
->SendReceive();