/* * This file is part of OpenTTD. * 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. * 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. * 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 . */ /** @file network_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */ #include "../stdafx.h" #include "../debug.h" #include "../error.h" #include "../rev.h" #include "../settings_type.h" #include "../strings_func.h" #include "../window_func.h" #include "../window_type.h" #include "network.h" #include "network_coordinator.h" #include "network_gamelist.h" #include "network_gui.h" #include "network_internal.h" #include "network_server.h" #include "network_stun.h" #include "table/strings.h" #include "../safeguards.h" static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator. ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator. ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on. std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator. /** Connect to a game server by IP:port. */ class NetworkDirectConnecter : public TCPConnecter { private: std::string token; ///< Token of this connection. uint8_t tracking_number; ///< Tracking number of this connection. public: /** * Try to establish a direct (hostname:port based) connection. * @param hostname The hostname of the server. * @param port The port of the server. * @param token The token as given by the Game Coordinator to track this connection attempt. * @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt. */ 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) {} void OnFailure() override { _network_coordinator_client.ConnectFailure(this->token, this->tracking_number); } void OnConnect(SOCKET s) override { NetworkAddress address = NetworkAddress::GetPeerAddress(s); _network_coordinator_client.ConnectSuccess(this->token, s, address); } }; /** Connecter used after STUN exchange to connect from both sides to each other. */ class NetworkReuseStunConnecter : public TCPConnecter { private: std::string token; ///< Token of this connection. uint8_t tracking_number; ///< Tracking number of this connection. uint8_t family; ///< Family of this connection. public: /** * Try to establish a STUN-based connection. * @param hostname The hostname of the peer. * @param port The port of the peer. * @param bind_address The local bind address used for this connection. * @param token The connection token. * @param tracking_number The tracking number of the connection. * @param family The family this connection is using. */ NetworkReuseStunConnecter(const std::string &hostname, uint16_t port, const NetworkAddress &bind_address, std::string token, uint8_t tracking_number, uint8_t family) : TCPConnecter(hostname, port, bind_address), token(token), tracking_number(tracking_number), family(family) { } void OnFailure() override { /* Close the STUN connection too, as it is no longer of use. */ _network_coordinator_client.CloseStunHandler(this->token, this->family); _network_coordinator_client.ConnectFailure(this->token, this->tracking_number); } void OnConnect(SOCKET s) override { NetworkAddress address = NetworkAddress::GetPeerAddress(s); _network_coordinator_client.ConnectSuccess(this->token, s, address); } }; /** Connect to the Game Coordinator server. */ class NetworkCoordinatorConnecter : public TCPConnecter { public: /** * Initiate the connecting. * @param connection_string The address of the Game Coordinator server. */ NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {} void OnFailure() override { _network_coordinator_client.connecting = false; _network_coordinator_client.CloseConnection(true); } void OnConnect(SOCKET s) override { assert(_network_coordinator_client.sock == INVALID_SOCKET); _network_coordinator_client.sock = s; _network_coordinator_client.last_activity = std::chrono::steady_clock::now(); _network_coordinator_client.connecting = false; } }; bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet &p) { NetworkCoordinatorErrorType error = (NetworkCoordinatorErrorType)p.Recv_uint8(); std::string detail = p.Recv_string(NETWORK_ERROR_DETAIL_LENGTH); switch (error) { case NETWORK_COORDINATOR_ERROR_UNKNOWN: this->CloseConnection(); return false; case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED: ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, INVALID_STRING_ID, WL_ERROR); /* To prevent that we constantly try to reconnect, switch to local game. */ _settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL; this->CloseConnection(); return false; case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: { auto connecter_pre_it = this->connecter_pre.find(detail); if (connecter_pre_it != this->connecter_pre.end()) { connecter_pre_it->second->SetFailure(); this->connecter_pre.erase(connecter_pre_it); } /* Mark the server as offline. */ NetworkGameList *item = NetworkGameListAddItem(detail); item->status = NGLS_OFFLINE; UpdateNetworkGameWindow(); return true; } case NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE: ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REUSE_OF_INVITE_CODE, INVALID_STRING_ID, WL_ERROR); /* To prevent that we constantly battle for the same invite-code, switch to local game. */ _settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL; this->CloseConnection(); return false; default: DEBUG(net, 0, "Invalid error type %u received from Game Coordinator", error); this->CloseConnection(); return false; } } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet &p) { /* Schedule sending an update. */ this->next_update = std::chrono::steady_clock::now(); _settings_client.network.server_invite_code = p.Recv_string(NETWORK_INVITE_CODE_LENGTH); _settings_client.network.server_invite_code_secret = p.Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH); _network_server_connection_type = (ConnectionType)p.Recv_uint8(); if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) { ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR); } /* Users can change the invite code in the settings, but this has no effect * on the invite code as assigned by the server. So * _network_server_invite_code contains the current invite code, * and _settings_client.network.server_invite_code contains the one we will * attempt to re-use when registering again. */ _network_server_invite_code = _settings_client.network.server_invite_code; SetWindowDirty(WC_CLIENT_LIST, 0); if (_network_dedicated) { std::string connection_type; switch (_network_server_connection_type) { case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break; case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break; case CONNECTION_TYPE_STUN: connection_type = "Behind NAT"; break; case CONNECTION_TYPE_TURN: connection_type = "Via relay"; break; case CONNECTION_TYPE_UNKNOWN: // Never returned from Game Coordinator. default: connection_type = "Unknown"; break; // Should never happen, but don't fail if it does. } std::string game_type; switch (_settings_client.network.server_game_type) { case SERVER_GAME_TYPE_INVITE_ONLY: game_type = "Invite only"; break; case SERVER_GAME_TYPE_PUBLIC: game_type = "Public"; break; case SERVER_GAME_TYPE_LOCAL: // Impossible to register local servers. default: game_type = "Unknown"; break; // Should never happen, but don't fail if it does. } DEBUG(net, 3, "----------------------------------------"); DEBUG(net, 3, "Your server is now registered with the Game Coordinator:"); DEBUG(net, 3, " Game type: %s", game_type.c_str()); DEBUG(net, 3, " Connection type: %s", connection_type.c_str()); DEBUG(net, 3, " Invite code: %s", _network_server_invite_code.c_str()); DEBUG(net, 3, "----------------------------------------"); } else { DEBUG(net, 3, "Game Coordinator registered our server with invite code '%s'", _network_server_invite_code.c_str()); } return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet &p) { uint8_t servers = p.Recv_uint16(); /* End of list; we can now remove all expired items from the list. */ if (servers == 0) { NetworkGameListRemoveExpired(); return true; } for (; servers > 0; servers--) { std::string connection_string = p.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); /* Read the NetworkGameInfo from the packet. */ NetworkGameInfo ngi = {}; DeserializeNetworkGameInfo(p, ngi, &this->newgrf_lookup_table); /* Now we know the connection string, we can add it to our list. */ NetworkGameList *item = NetworkGameListAddItem(connection_string); /* Clear any existing GRFConfig chain. */ ClearGRFConfigList(&item->info.grfconfig); /* Copy the new NetworkGameInfo info. */ item->info = ngi; /* Check for compatability with the client. */ CheckGameCompatibility(item->info); /* Mark server as online. */ item->status = NGLS_ONLINE; /* Mark the item as up-to-date. */ item->version = _network_game_list_version; } UpdateNetworkGameWindow(); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); std::string invite_code = p.Recv_string(NETWORK_INVITE_CODE_LENGTH); /* Find the connecter based on the invite code. */ auto connecter_pre_it = this->connecter_pre.find(invite_code); if (connecter_pre_it == this->connecter_pre.end()) { this->CloseConnection(); return false; } /* Now store it based on the token. */ this->connecter[token] = {invite_code, connecter_pre_it->second}; this->connecter_pre.erase(connecter_pre_it); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); this->CloseToken(token); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); uint8_t tracking_number = p.Recv_uint8(); std::string hostname = p.Recv_string(NETWORK_HOSTNAME_LENGTH); uint16_t port = p.Recv_uint16(); /* Ensure all other pending connection attempts are killed. */ if (this->game_connecter != nullptr) { this->game_connecter->Kill(); this->game_connecter = nullptr; } this->game_connecter = TCPConnecter::Create(hostname, port, token, tracking_number); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); this->stun_handlers[token][AF_INET6] = ClientNetworkStunSocketHandler::Stun(token, AF_INET6); this->stun_handlers[token][AF_INET] = ClientNetworkStunSocketHandler::Stun(token, AF_INET); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); uint8_t tracking_number = p.Recv_uint8(); uint8_t family = p.Recv_uint8(); std::string host = p.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); uint16_t port = p.Recv_uint16(); /* Check if we know this token. */ auto stun_it = this->stun_handlers.find(token); if (stun_it == this->stun_handlers.end()) return true; auto family_it = stun_it->second.find(family); if (family_it == stun_it->second.end()) return true; /* Ensure all other pending connection attempts are killed. */ if (this->game_connecter != nullptr) { this->game_connecter->Kill(); this->game_connecter = nullptr; } /* We now mark the connection as closed, but we do not really close the * socket yet. We do this when the NetworkReuseStunConnecter is connected. * This prevents any NAT to already remove the route while we create the * second connection on top of the first. */ family_it->second->CloseConnection(false); /* Connect to our peer from the same local address as we use for the * STUN server. This means that if there is any NAT in the local network, * the public ip:port is still pointing to the local address, and as such * a connection can be established. */ this->game_connecter = TCPConnecter::Create(host, port, family_it->second->local_addr, token, tracking_number, family); return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet &p) { this->newgrf_lookup_table_cursor = p.Recv_uint32(); uint16_t newgrfs = p.Recv_uint16(); for (; newgrfs> 0; newgrfs--) { uint32_t index = p.Recv_uint32(); DeserializeGRFIdentifierWithName(p, this->newgrf_lookup_table[index]); } return true; } bool ClientNetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet &p) { std::string token = p.Recv_string(NETWORK_TOKEN_LENGTH); uint8_t tracking_number = p.Recv_uint8(); std::string ticket = p.Recv_string(NETWORK_TOKEN_LENGTH); std::string connection_string = p.Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); /* Ensure all other pending connection attempts are killed. */ if (this->game_connecter != nullptr) { this->game_connecter->Kill(); this->game_connecter = nullptr; } this->turn_handlers[token] = ClientNetworkTurnSocketHandler::Turn(token, tracking_number, ticket, connection_string); if (!_network_server) { auto connecter_it = this->connecter.find(token); if (connecter_it == this->connecter.end()) { /* Make sure we are still interested in connecting to this server. */ this->ConnectFailure(token, 0); return true; } switch (_settings_client.network.use_relay_service) { case URS_NEVER: this->ConnectFailure(token, 0); break; case URS_ASK: ShowNetworkAskRelay(connecter_it->second.first, connection_string, token); break; case URS_ALLOW: this->StartTurnConnection(token); break; } } else { this->StartTurnConnection(token); } return true; } void ClientNetworkCoordinatorSocketHandler::StartTurnConnection(std::string &token) { auto turn_it = this->turn_handlers.find(token); if (turn_it == this->turn_handlers.end()) return; turn_it->second->Connect(); } void ClientNetworkCoordinatorSocketHandler::Connect() { /* We are either already connected or are trying to connect. */ if (this->sock != INVALID_SOCKET || this->connecting) return; this->Reopen(); this->connecting = true; this->last_activity = std::chrono::steady_clock::now(); TCPConnecter::Create(NetworkCoordinatorConnectionString()); } NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error) { NetworkCoordinatorSocketHandler::CloseConnection(error); this->CloseSocket(); this->connecting = false; _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; this->next_update = {}; this->CloseAllConnections(); SetWindowDirty(WC_CLIENT_LIST, 0); return NETWORK_RECV_STATUS_OKAY; } /** * Register our server to receive our invite code. */ void ClientNetworkCoordinatorSocketHandler::Register() { _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; this->next_update = {}; SetWindowDirty(WC_CLIENT_LIST, 0); this->Connect(); auto p = std::make_unique(PACKET_COORDINATOR_SERVER_REGISTER); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_uint8(_settings_client.network.server_game_type); p->Send_uint16(_settings_client.network.server_port); if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) { p->Send_string(""); p->Send_string(""); } else { p->Send_string(_settings_client.network.server_invite_code); p->Send_string(_settings_client.network.server_invite_code_secret); } this->SendPacket(std::move(p)); } /** * Send an update of our server status to the Game Coordinator. */ void ClientNetworkCoordinatorSocketHandler::SendServerUpdate() { DEBUG(net, 6, "Sending server update to Game Coordinator"); auto p = std::make_unique(PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU); p->Send_uint8(NETWORK_COORDINATOR_VERSION); SerializeNetworkGameInfo(*p, GetCurrentNetworkServerGameInfo(), this->next_update.time_since_epoch() != std::chrono::nanoseconds::zero()); this->SendPacket(std::move(p)); this->next_update = std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES; } /** * Request a listing of all public servers. */ void ClientNetworkCoordinatorSocketHandler::GetListing() { this->Connect(); _network_game_list_version++; auto p = std::make_unique(PACKET_COORDINATOR_CLIENT_LISTING); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_uint8(NETWORK_GAME_INFO_VERSION); p->Send_string(_openttd_revision); p->Send_uint32(this->newgrf_lookup_table_cursor); this->SendPacket(std::move(p)); } /** * Join a server based on an invite code. * @param invite_code The invite code of the server to connect to. * @param connecter The connecter of the request. */ void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter) { assert(invite_code.starts_with("+")); if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) { /* If someone is hammering the refresh key, one can sent out two * requests for the same invite code. There isn't really a great way * of handling this, so just ignore this request. */ connecter->SetFailure(); return; } /* Initially we store based on invite code; on first reply we know the * token, and will start using that key instead. */ this->connecter_pre[invite_code] = connecter; this->Connect(); auto p = std::make_unique(PACKET_COORDINATOR_CLIENT_CONNECT); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_string(invite_code); this->SendPacket(std::move(p)); } /** * Callback from a Connecter to let the Game Coordinator know the connection failed. * @param token Token of the connecter that failed. * @param tracking_number Tracking number of the connecter that failed. */ void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8_t tracking_number) { /* Connecter will destroy itself. */ this->game_connecter = nullptr; auto p = std::make_unique(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_string(token); p->Send_uint8(tracking_number); this->SendPacket(std::move(p)); /* We do not close the associated connecter here yet, as the * Game Coordinator might have other methods of connecting available. */ } /** * Callback from a Connecter to let the Game Coordinator know the connection * to the game server is established. * @param token Token of the connecter that succeeded. * @param sock The socket that the connecter can now use. */ void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address) { assert(sock != INVALID_SOCKET); /* Connecter will destroy itself. */ this->game_connecter = nullptr; if (_network_server) { if (!ServerNetworkGameSocketHandler::ValidateClient(sock, address)) return; DEBUG(net, 3, "[%s] Client connected from %s on frame %u", ServerNetworkGameSocketHandler::GetName(), address.GetHostname(), _frame_counter); ServerNetworkGameSocketHandler::AcceptConnection(sock, address); } else { /* The client informs the Game Coordinator about the success. The server * doesn't have to, as it is implied by the client telling. */ auto p = std::make_unique(PACKET_COORDINATOR_CLIENT_CONNECTED); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_string(token); this->SendPacket(std::move(p)); /* Find the connecter; it can happen it no longer exist, in cases where * we aborted the connect but the Game Coordinator was already in the * processes of connecting us. */ auto connecter_it = this->connecter.find(token); if (connecter_it != this->connecter.end()) { connecter_it->second.second->SetConnected(sock); this->connecter.erase(connecter_it); } } /* Close all remaining connections. */ this->CloseToken(token); } /** * Callback from the STUN connecter to inform the Game Coordinator about the * result of the STUN. * * This helps the Game Coordinator not to wait for a timeout on its end, but * rather react as soon as the client/server knows the result. */ void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string &token, uint8_t family, bool result) { auto p = std::make_unique(PACKET_COORDINATOR_SERCLI_STUN_RESULT); p->Send_uint8(NETWORK_COORDINATOR_VERSION); p->Send_string(token); p->Send_uint8(family); p->Send_bool(result); this->SendPacket(std::move(p)); } /** * Close the STUN handler. * @param token The token used for the STUN handlers. * @param family The family of STUN handlers to close. AF_UNSPEC to close all STUN handlers for this token. */ void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string &token, uint8_t family) { auto stun_it = this->stun_handlers.find(token); if (stun_it == this->stun_handlers.end()) return; if (family == AF_UNSPEC) { for (auto &[family, stun_handler] : stun_it->second) { stun_handler->CloseConnection(); stun_handler->CloseSocket(); } this->stun_handlers.erase(stun_it); } else { auto family_it = stun_it->second.find(family); if (family_it == stun_it->second.end()) return; family_it->second->CloseConnection(); family_it->second->CloseSocket(); stun_it->second.erase(family_it); } } /** * Close the TURN handler. * @param token The token used for the TURN handler. */ void ClientNetworkCoordinatorSocketHandler::CloseTurnHandler(const std::string &token) { CloseWindowByClass(WC_NETWORK_ASK_RELAY, NRWCD_HANDLED); auto turn_it = this->turn_handlers.find(token); if (turn_it == this->turn_handlers.end()) return; turn_it->second->CloseConnection(); turn_it->second->CloseSocket(); /* We don't remove turn_handler here, as we can be called from within that * turn_handler instance, so our object cannot be free'd yet. Instead, we * check later if the connection is closed, and free the object then. */ } /** * Close everything related to this connection token. * @param token The connection token to close. */ void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token) { /* Close all remaining STUN / TURN connections. */ this->CloseStunHandler(token); this->CloseTurnHandler(token); /* Close the caller of the connection attempt. */ auto connecter_it = this->connecter.find(token); if (connecter_it != this->connecter.end()) { connecter_it->second.second->SetFailure(); this->connecter.erase(connecter_it); } } /** * Close all pending connection tokens. */ void ClientNetworkCoordinatorSocketHandler::CloseAllConnections() { /* Ensure all other pending connection attempts are also killed. */ if (this->game_connecter != nullptr) { this->game_connecter->Kill(); this->game_connecter = nullptr; } /* Mark any pending connecters as failed. */ for (auto &[token, it] : this->connecter) { this->CloseStunHandler(token); this->CloseTurnHandler(token); it.second->SetFailure(); /* Inform the Game Coordinator he can stop trying to connect us to the server. */ this->ConnectFailure(token, 0); } this->stun_handlers.clear(); this->turn_handlers.clear(); this->connecter.clear(); /* Also close any pending invite-code requests. */ for (auto &[invite_code, it] : this->connecter_pre) { it->SetFailure(); } this->connecter_pre.clear(); } /** * Check whether we received/can send some data from/to the Game Coordinator server and * when that's the case handle it appropriately. */ void ClientNetworkCoordinatorSocketHandler::SendReceive() { /* Private games are not listed via the Game Coordinator. */ if (_network_server && _settings_client.network.server_game_type == SERVER_GAME_TYPE_LOCAL) { if (this->sock != INVALID_SOCKET) { this->CloseConnection(); } return; } static int last_attempt_backoff = 1; static bool first_reconnect = true; if (this->sock == INVALID_SOCKET) { static std::chrono::steady_clock::time_point last_attempt = {}; /* Don't auto-reconnect when we are not a server. */ if (!_network_server) return; /* Don't reconnect if we are connecting. */ if (this->connecting) return; /* Throttle how often we try to reconnect. */ if (std::chrono::steady_clock::now() < last_attempt + std::chrono::seconds(1) * last_attempt_backoff) return; last_attempt = std::chrono::steady_clock::now(); /* Delay reconnecting with up to 32 seconds. */ if (last_attempt_backoff < 32) { last_attempt_backoff *= 2; } /* Do not reconnect on the first attempt, but only initialize the * last_attempt variables. Otherwise after an outage all servers * reconnect at the same time, potentially overwhelming the * Game Coordinator. */ if (first_reconnect) { first_reconnect = false; return; } DEBUG(net, 1, "Connection with Game Coordinator lost; reconnecting..."); this->Register(); return; } last_attempt_backoff = 1; first_reconnect = true; if (_network_server && _network_server_connection_type != CONNECTION_TYPE_UNKNOWN && std::chrono::steady_clock::now() > this->next_update) { this->SendServerUpdate(); } if (!_network_server && std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) { this->CloseConnection(); return; } if (this->CanSendReceive()) { if (this->ReceivePackets()) { this->last_activity = std::chrono::steady_clock::now(); } } this->SendPackets(); for (const auto &[token, families] : this->stun_handlers) { for (const auto &[family, stun_handler] : families) { stun_handler->SendReceive(); } } /* Check for handlers that are not connecting nor connected. Destroy those objects. */ for (auto turn_it = this->turn_handlers.begin(); turn_it != this->turn_handlers.end(); /* nothing */) { if (turn_it->second->connect_started && turn_it->second->connecter == nullptr && !turn_it->second->IsConnected()) { turn_it = this->turn_handlers.erase(turn_it); } else { turn_it++; } } for (const auto &[token, turn_handler] : this->turn_handlers) { turn_handler->SendReceive(); } }