From 1458f3ef8e99dbd58270da42deb78de4ca6c8ff3 Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 22 Feb 2018 15:10:30 +0800 Subject: [PATCH] Fix wan connection, move get public address from stun to stk host --- src/main.cpp | 4 +- src/main_loop.cpp | 37 +- src/network/protocols/connect_to_peer.cpp | 151 +++----- src/network/protocols/connect_to_peer.hpp | 23 +- src/network/protocols/connect_to_server.cpp | 322 ++++++++---------- src/network/protocols/connect_to_server.hpp | 16 +- src/network/protocols/get_peer_address.cpp | 16 +- src/network/protocols/get_peer_address.hpp | 5 +- src/network/protocols/get_public_address.cpp | 88 ++--- src/network/protocols/get_public_address.hpp | 17 +- src/network/protocols/hide_public_address.cpp | 2 +- src/network/protocols/ping_protocol.cpp | 9 +- src/network/protocols/ping_protocol.hpp | 5 +- src/network/protocols/server_lobby.cpp | 37 +- src/network/protocols/server_lobby.hpp | 3 - src/network/stk_host.cpp | 263 ++++++++++++-- src/network/stk_host.hpp | 23 +- src/network/transport_address.hpp | 2 + 18 files changed, 574 insertions(+), 449 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3453cc9e3..03e5dbd64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1027,6 +1027,7 @@ int handleCmdLine() while (true) { Online::RequestManager::get()->update(0.0f); + StkTime::sleep(1); if (PlayerManager::getCurrentOnlineState() == PlayerProfile::OS_SIGNED_IN) { break; @@ -1074,9 +1075,6 @@ int handleCmdLine() if(CommandLine::has("--password", &s)) password = s.c_str(); - if (CommandLine::has("--my-address", &s)) - GetPublicAddress::setMyIPAddress(s); - /** Disable detection of LAN connection when connecting via WAN. This is * mostly a debugging feature to force using WAN connection. */ if (CommandLine::has("--disable-lan")) diff --git a/src/main_loop.cpp b/src/main_loop.cpp index b5d473b68..352325e3d 100644 --- a/src/main_loop.cpp +++ b/src/main_loop.cpp @@ -302,31 +302,32 @@ void MainLoop::run() ->addNextTimeStep(World::getWorld()->getTime(), dt); } - if (!m_abort && !ProfileWorld::isNoGraphics()) + if (!m_abort) { float frame_duration = num_steps * dt; + if (!ProfileWorld::isNoGraphics()) + { + // Render the previous frame, and also handle all user input. + PROFILER_PUSH_CPU_MARKER("IrrDriver update", 0x00, 0x00, 0x7F); + irr_driver->update(frame_duration); + PROFILER_POP_CPU_MARKER(); - // Render the previous frame, and also handle all user input. - PROFILER_PUSH_CPU_MARKER("IrrDriver update", 0x00, 0x00, 0x7F); - irr_driver->update(frame_duration); - PROFILER_POP_CPU_MARKER(); - - PROFILER_PUSH_CPU_MARKER("Input/GUI", 0x7F, 0x00, 0x00); -#ifdef ENABLE_WIIUSE - wiimote_manager->update(); -#endif - input_manager->update(frame_duration); - GUIEngine::update(frame_duration); - PROFILER_POP_CPU_MARKER(); - - PROFILER_PUSH_CPU_MARKER("Music", 0x7F, 0x00, 0x00); - SFXManager::get()->update(); - PROFILER_POP_CPU_MARKER(); + PROFILER_PUSH_CPU_MARKER("Input/GUI", 0x7F, 0x00, 0x00); + #ifdef ENABLE_WIIUSE + wiimote_manager->update(); + #endif + input_manager->update(frame_duration); + GUIEngine::update(frame_duration); + PROFILER_POP_CPU_MARKER(); + PROFILER_PUSH_CPU_MARKER("Music", 0x7F, 0x00, 0x00); + SFXManager::get()->update(); + PROFILER_POP_CPU_MARKER(); + } + // Some protocols in network will use RequestManager PROFILER_PUSH_CPU_MARKER("Database polling update", 0x00, 0x7F, 0x7F); Online::RequestManager::get()->update(frame_duration); PROFILER_POP_CPU_MARKER(); - } for(int i=0; igetType() == EVENT_TYPE_CONNECTED) @@ -97,93 +91,81 @@ void ConnectToPeer::asynchronousUpdate() { case NONE: { - m_current_protocol = std::make_shared(m_peer_id, this); + m_current_protocol = std::make_shared(m_peer_id); m_current_protocol->requestStart(); - - // Pause this protocol till we receive an answer - // The GetPeerAddress protocol will change the state and - // unpause this protocol - requestPause(); m_state = RECEIVED_PEER_ADDRESS; break; } case RECEIVED_PEER_ADDRESS: { - if (m_peer_address.getIP() == 0 || m_peer_address.getPort() == 0) + // Wait until we have peer address + auto get_peer_address = + std::dynamic_pointer_cast(m_current_protocol); + assert(get_peer_address); + if (get_peer_address->getAddress().isUnset()) + return; + m_peer_address.copy(get_peer_address->getAddress()); + m_current_protocol = nullptr; + if (m_peer_address.isUnset()) { Log::error("ConnectToPeer", "The peer you want to connect to has hidden his address."); m_state = DONE; break; } - m_current_protocol = nullptr; - // Now we know the peer address. If it's a non-local host, start - // the Ping protocol to keep the port available. We can't rely on - // STKHost::isLAN(), since we might get a LAN connection even if - // the server itself accepts connections from anywhere. - if ( (!m_is_lan && - m_peer_address.getIP() != - NetworkConfig::get()->getMyAddress().getIP() ) || - NetworkConfig::m_disable_lan ) - { - m_current_protocol = std::make_shared(m_peer_address, - /*time-between-ping*/2.0); - ProtocolManager::lock()->requestStart(m_current_protocol); - m_state = CONNECTING; - } - else - { - m_broadcast_count = 0; - // Make sure we trigger the broadcast operation next - m_time_last_broadcast = float(StkTime::getRealTime()-100.0f); - m_state = WAIT_FOR_LAN; - } + m_state = WAIT_FOR_CONNECTION; + resetTimer(); break; } - case WAIT_FOR_LAN: + case WAIT_FOR_CONNECTION: { - // Broadcast once per second - if (StkTime::getRealTime() < m_time_last_broadcast + 1.0f) + // Each 2 second for a ping or broadcast + if (m_timer > m_timer + std::chrono::seconds(2)) { - break; - } - m_time_last_broadcast = float(StkTime::getRealTime()); - m_broadcast_count++; - if (m_broadcast_count > 100) - { - // Not much we can do about if we don't receive the client - // connection - it could have stopped, lost network, ... - // Terminate this protocol. - Log::error("ConnectToPeer", "Time out trying to connect to %s", - m_peer_address.toString().c_str()); - requestTerminate(); - } + resetTimer(); + // Now we know the peer address. If it's a non-local host, start + // the Ping protocol to keep the port available. We can't rely + // on STKHost::isLAN(), since we might get a LAN connection even + // if the server itself accepts connections from anywhere. + if ((!m_is_lan && + m_peer_address.getIP() != + STKHost::get()->getPublicAddress().getIP()) || + NetworkConfig::m_disable_lan) + { + BareNetworkString data; + data.addUInt8(0); + STKHost::get()->sendRawPacket(data, m_peer_address); + } - // Otherwise we are in the same LAN (same public ip address). - // Just send a broadcast packet with the string aloha_stk inside, - // the client will know our ip address and will connect - TransportAddress broadcast_address; - if(NetworkConfig::get()->isWAN()) - { - broadcast_address.setIP(-1); // 255.255.255.255 - broadcast_address.setPort(m_peer_address.getPort()); - } - else + // Send a broadcast packet with the string aloha_stk inside, + // the client will know our ip address and will connect + // The wan remote should already start its ping message to us now + // so we can send packet directly to it. + TransportAddress broadcast_address; broadcast_address.copy(m_peer_address); + BareNetworkString aloha(std::string("aloha_stk")); + STKHost::get()->sendRawPacket(aloha, broadcast_address); + Log::info("ConnectToPeer", "Broadcast aloha sent."); + StkTime::sleep(1); - broadcast_address.copy(m_peer_address); + broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost) + broadcast_address.setPort(m_peer_address.getPort()); + STKHost::get()->sendRawPacket(aloha, broadcast_address); + Log::info("ConnectToPeer", "Broadcast aloha to self."); - BareNetworkString aloha(std::string("aloha_stk")); - STKHost::get()->sendRawPacket(aloha, broadcast_address); - Log::info("ConnectToPeer", "Broadcast aloha sent."); - StkTime::sleep(1); - - broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost) - broadcast_address.setPort(m_peer_address.getPort()); - STKHost::get()->sendRawPacket(aloha, broadcast_address); - Log::info("ConnectToPeer", "Broadcast aloha to self."); + // 30 seconds timeout + if (m_tried_connection++ > 15) + { + // Not much we can do about if we don't receive the client + // connection - it could have stopped, lost network, ... + // Terminate this protocol. + Log::error("ConnectToPeer", "Time out trying to connect to %s", + m_peer_address.toString().c_str()); + requestTerminate(); + } + } break; } case CONNECTING: // waiting for the peer to connect @@ -193,14 +175,6 @@ void ConnectToPeer::asynchronousUpdate() break; case CONNECTED: { - // If the ping protocol is there for NAT traversal terminate it. - // Ping is not running when connecting to a LAN peer. - if (m_current_protocol) - { - // Kill the ping protocol because we're connected - m_current_protocol->requestTerminate(); - m_current_protocol = nullptr; - } m_state = DONE; break; } @@ -212,16 +186,3 @@ void ConnectToPeer::asynchronousUpdate() break; } } // asynchronousUpdate - -// ---------------------------------------------------------------------------- -/** Callback from the GetPeerAddress protocol. It copies the received peer - * address so that it can be used in the next states of the connection - * protocol. - */ -void ConnectToPeer::callback(Protocol *protocol) -{ - assert(m_state==RECEIVED_PEER_ADDRESS); - m_peer_address.copy( ((GetPeerAddress*)protocol)->getAddress() ); - // Reactivate this protocol - requestUnpause(); -} // callback diff --git a/src/network/protocols/connect_to_peer.hpp b/src/network/protocols/connect_to_peer.hpp index 87854b88c..483b36cf5 100644 --- a/src/network/protocols/connect_to_peer.hpp +++ b/src/network/protocols/connect_to_peer.hpp @@ -23,49 +23,54 @@ #include "network/transport_address.hpp" #include "utils/cpp2011.hpp" +#include + /** One instance of this is started for every peer who tries to * connect to this server. */ -class ConnectToPeer : public Protocol, public CallbackObject +class ConnectToPeer : public Protocol { protected: TransportAddress m_peer_address; uint32_t m_peer_id; - /** Pointer to the protocol which is monitored for state changes. */ + /** Pointer to the protocol which is monitored for state changes, this + * need to be shared_ptr because we need to get the result from + * \ref GetPeerAddress, otherwise when it terminated the result will be + * gone. */ std::shared_ptr m_current_protocol; /** True if this is a LAN connection. */ bool m_is_lan; - /** We might need to broadcast several times (in case the client is not - * ready in time). This keep track of broadcastst. */ - float m_time_last_broadcast; + /** Timer use for tracking broadcast. */ + std::chrono::system_clock::time_point m_timer; - int m_broadcast_count; + unsigned m_tried_connection = 0; enum STATE { NONE, RECEIVED_PEER_ADDRESS, - WAIT_FOR_LAN, + WAIT_FOR_CONNECTION, CONNECTING, CONNECTED, DONE, EXITING } m_state; + void resetTimer() { m_timer = std::chrono::system_clock::now(); } + public: ConnectToPeer(uint32_t peer_id); ConnectToPeer(const TransportAddress &address); virtual ~ConnectToPeer(); virtual bool notifyEventAsynchronous(Event* event) OVERRIDE; - virtual void setup() OVERRIDE; + virtual void setup() OVERRIDE {} virtual void update(float dt) OVERRIDE {} virtual void asynchronousUpdate() OVERRIDE; - virtual void callback(Protocol *protocol) OVERRIDE; }; // class ConnectToPeer #endif // CONNECT_TO_SERVER_HPP diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index c950939be..0b4c47c37 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -21,11 +21,9 @@ #include "config/player_manager.hpp" #include "network/event.hpp" #include "network/network_config.hpp" -#include "network/protocols/get_public_address.hpp" #include "network/protocols/get_peer_address.hpp" #include "network/protocols/hide_public_address.hpp" #include "network/protocols/request_connection.hpp" -#include "network/protocols/ping_protocol.hpp" #include "network/protocols/client_lobby.hpp" #include "network/protocol_manager.hpp" #include "network/servers_manager.hpp" @@ -82,45 +80,21 @@ ConnectToServer::~ConnectToServer() void ConnectToServer::setup() { Log::info("ConnectToServer", "SETUP"); - m_current_protocol = nullptr; + m_current_protocol.reset(); // In case of LAN we already have the server's and our ip address, // so we can immediately start requesting a connection. - m_state = NetworkConfig::get()->isLAN() ? GOT_SERVER_ADDRESS : NONE; + m_state = NetworkConfig::get()->isLAN() ? GOT_SERVER_ADDRESS : + REGISTER_SELF_ADDRESS; + } // setup -// ---------------------------------------------------------------------------- -/** Sets the server transport address. This is used in case of LAN networking, - * when we do not query the stk server and instead have the address from the - * LAN server directly. - * \param address Address of server to connect to. - */ -void ConnectToServer::setServerAddress(const TransportAddress &address) -{ -} // setServerAddress - // ---------------------------------------------------------------------------- void ConnectToServer::asynchronousUpdate() { switch(m_state) { - case NONE: + case REGISTER_SELF_ADDRESS: { - Log::info("ConnectToServer", "Protocol starting"); - // This protocol will write the public address of this - // instance to STKHost. - m_current_protocol = std::make_shared(this); - m_current_protocol->requestStart(); - // This protocol will be unpaused in the callback from - // GetPublicAddress - requestPause(); - m_state = GETTING_SELF_ADDRESS; - break; - } - case GETTING_SELF_ADDRESS: - { - // drop GetPublicAddress - m_current_protocol = nullptr; - registerWithSTKServer(); // Register us with STK server if (m_quick_join) @@ -139,135 +113,125 @@ void ConnectToServer::asynchronousUpdate() case GOT_SERVER_ADDRESS: { assert(!m_quick_join); - m_current_protocol = nullptr; Log::info("ConnectToServer", "Server's address known"); - - // we're in the same lan (same public ip address) !! - if (m_server_address.getIP() == - NetworkConfig::get()->getMyAddress().getIP()) - { - Log::info("ConnectToServer", - "Server appears to be in the same LAN."); - } m_state = REQUESTING_CONNECTION; - m_current_protocol = std::make_shared(m_server_id); - m_current_protocol->requestStart(); + auto request_connection = + std::make_shared(m_server_id); + request_connection->requestStart(); + m_current_protocol = request_connection; + // Reset timer for next usage + resetTimer(); break; } case REQUESTING_CONNECTION: - // In case of a LAN server, m_crrent_protocol is NULL - if (!m_current_protocol || - m_current_protocol->getState() == PROTOCOL_STATE_TERMINATED) + if (!m_current_protocol.expired()) { - m_current_protocol = nullptr; - // Server knows we want to connect - Log::info("ConnectToServer", "Connection request made"); - if (m_server_address.getIP() == 0 || - m_server_address.getPort() == 0 ) - { - // server data not correct, hide address and stop - m_state = HIDING_ADDRESS; - Log::error("ConnectToServer", "Server address is %s", - m_server_address.toString().c_str()); - m_current_protocol = std::make_shared(); - m_current_protocol->requestStart(); - return; - } - if( ( !NetworkConfig::m_disable_lan && - m_server_address.getIP() - == NetworkConfig::get()->getMyAddress().getIP() ) || - NetworkConfig::get()->isLAN() ) + return; + } + + // Server knows we want to connect + Log::info("ConnectToServer", "Connection request made"); + if (m_server_address.isUnset()) + { + // server data not correct, hide address and stop + m_state = HIDING_ADDRESS; + Log::error("ConnectToServer", "Server address is %s", + m_server_address.toString().c_str()); + auto hide_address = std::make_shared(); + hide_address->requestStart(); + m_current_protocol = hide_address; + return; + } + if (m_tried_connection++ > 5) + { + Log::error("ConnectToServer", "Timeout waiting for aloha"); + m_state = NetworkConfig::get()->isWAN() ? + HIDING_ADDRESS : DONE; + } + if ((!NetworkConfig::m_disable_lan && + m_server_address.getIP() == + STKHost::get()->getPublicAddress().getIP()) || + NetworkConfig::get()->isLAN()) + { + // We're in the same lan (same public ip address). + // The state will change to CONNECTING + waitingAloha(false/*is_wan*/); + } + else + { + // Send a 1-byte datagram, the remote host can simply ignore + // this datagram, to keep the port open (2 second each) + if (m_timer > m_timer + std::chrono::seconds(2)) { - // We're in the same lan (same public ip address). - // The state will change to CONNECTING - handleSameLAN(); - } - else - { - m_state = CONNECTING; - m_current_protocol = std::make_shared(m_server_address, 2.0); - m_current_protocol->requestStart(); + resetTimer(); + BareNetworkString data; + data.addUInt8(0); + STKHost::get()->sendRawPacket(data, m_server_address); } + waitingAloha(true/*is_wan*/); } break; case CONNECTING: // waiting the server to answer our connection + { + if (m_timer > m_timer + std::chrono::seconds(5)) // every 5 seconds { - static double timer = 0; - if (StkTime::getRealTime() > timer+5.0) // every 5 seconds + STKHost::get()->connect(m_server_address); + resetTimer(); + Log::info("ConnectToServer", "Trying to connect to %s", + m_server_address.toString().c_str()); + if (m_tried_connection++ > 3) { - STKHost::get()->connect(m_server_address); - timer = StkTime::getRealTime(); - Log::info("ConnectToServer", "Trying to connect to %s", - m_server_address.toString().c_str()); + Log::error("ConnectToServer", "Timeout connect to %s", + m_server_address.toString().c_str()); + m_state = NetworkConfig::get()->isWAN() ? + HIDING_ADDRESS : DONE; } - break; } + break; + } case CONNECTED: { Log::info("ConnectToServer", "Connected"); - if(m_current_protocol) - { - // Kill the ping protocol because we're connected - m_current_protocol->requestTerminate(); - } - m_current_protocol = nullptr; // LAN networking does not use the stk server tables. - if(NetworkConfig::get()->isWAN()) + if (NetworkConfig::get()->isWAN()) { - m_current_protocol = std::make_shared(); - m_current_protocol->requestStart(); + auto hide_address = std::make_shared(); + hide_address->requestStart(); + m_current_protocol = hide_address; } m_state = HIDING_ADDRESS; break; } case HIDING_ADDRESS: // Wait till we have hidden our address - if (!m_current_protocol || - m_current_protocol->getState() == PROTOCOL_STATE_TERMINATED) + if (!m_current_protocol.expired()) { - if(m_current_protocol) - { - m_current_protocol = nullptr; - Log::info("ConnectToServer", "Address hidden"); - } - m_state = DONE; - // lobby room protocol if we're connected only - if(STKHost::get()->getPeers()[0]->isConnected()) - { - auto cl = LobbyProtocol::create(); - cl->setAddress(m_server_address); - cl->requestStart(); - } + return; + } + m_state = DONE; + // lobby room protocol if we're connected only + if (STKHost::get()->getPeers()[0]->isConnected() && + !m_server_address.isUnset()) + { + auto cl = LobbyProtocol::create(); + cl->setAddress(m_server_address); + cl->requestStart(); } break; case DONE: requestTerminate(); m_state = EXITING; + if (STKHost::get()->getPeerCount() == 0) + { + // Shutdown STKHost (go back to online menu too) + STKHost::get()->requestShutdown(); + } break; case EXITING: break; } } // asynchronousUpdate - // ---------------------------------------------------------------------------- -/** Called when the GetPeerAddress protocol terminates. - */ -void ConnectToServer::callback(Protocol *protocol) -{ - switch(m_state) - { - case GETTING_SELF_ADDRESS: - // The GetPublicAddress protocol stores our address in - // STKHost, so we only need to unpause this protocol - requestUnpause(); - break; - default: - Log::error("ConnectToServer", - "Received unexpected callback while in state %d.", - m_state); - } // case m_state -} // callback - // ---------------------------------------------------------------------------- /** Register this client with the STK server. */ @@ -275,14 +239,13 @@ void ConnectToServer::registerWithSTKServer() { // Our public address is now known, register details with // STK server. - const TransportAddress& addr = NetworkConfig::get()->getMyAddress(); + const TransportAddress& addr = STKHost::get()->getPublicAddress(); Online::XMLRequest *request = new Online::XMLRequest(); PlayerManager::setUserDetails(request, "set", Online::API::SERVER_PATH); request->addParameter("address", addr.getIP()); request->addParameter("port", addr.getPort()); - request->addParameter("private_port", - NetworkConfig::get()->getClientPort()); + request->addParameter("private_port", STKHost::get()->getPrivatePort()); Log::info("ConnectToServer", "Registering addr %s", addr.toString().c_str()); @@ -318,7 +281,6 @@ void ConnectToServer::handleQuickConnect() request->executeNow(); const XMLNode * result = request->getXMLData(); - delete request; std::string success; if(result->get("success", &success) && success=="yes") @@ -330,7 +292,7 @@ void ConnectToServer::handleQuickConnect() uint16_t port; // If we are using a LAN connection, we need the private (local) port if (m_server_address.getIP() == - NetworkConfig::get()->getMyAddress().getIP()) + STKHost::get()->getPublicAddress().getIP()) { result->get("private_port", &port); } @@ -345,13 +307,15 @@ void ConnectToServer::handleQuickConnect() { Log::error("GetPeerAddress", "Failed to get address."); } + delete request; } // handleQuickConnect // ---------------------------------------------------------------------------- /** Called when the server is on the same LAN. It uses broadcast to - * find and conntect to the server. + * find and conntect to the server. For WAN game, it makes sure server recieve + * request from stk addons first before continuing. */ -void ConnectToServer::handleSameLAN() +void ConnectToServer::waitingAloha(bool is_wan) { // just send a broadcast packet, the client will know our // ip address and will connect @@ -379,65 +343,70 @@ void ConnectToServer::handleSameLAN() std::string aloha("aloha_stk"); if (received==aloha) { - Log::info("ConnectToServer", "LAN Server found : %s", + Log::info("ConnectToServer", "Server found : %s", sender.toString().c_str()); #ifndef WIN32 - // just check if the ip is ours : if so, - // then just use localhost (127.0.0.1) - struct ifaddrs *ifap, *ifa; - struct sockaddr_in *sa; - getifaddrs(&ifap); // get the info - for (ifa = ifap; ifa; ifa = ifa->ifa_next) + if (!is_wan) { - if (ifa->ifa_addr->sa_family == AF_INET) + // just check if the ip is ours : if so, + // then just use localhost (127.0.0.1) + struct ifaddrs *ifap, *ifa; + struct sockaddr_in *sa; + getifaddrs(&ifap); // get the info + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { - sa = (struct sockaddr_in *) ifa->ifa_addr; + if (ifa->ifa_addr->sa_family == AF_INET) + { + sa = (struct sockaddr_in *) ifa->ifa_addr; - // This interface is ours - if (ntohl(sa->sin_addr.s_addr) == sender.getIP()) - sender.setIP(0x7f000001); // 127.0.0.1 + // This interface is ours + if (ntohl(sa->sin_addr.s_addr) == sender.getIP()) + sender.setIP(0x7f000001); // 127.0.0.1 + } } - } - freeifaddrs(ifap); + freeifaddrs(ifap); #else - // Query the list of all IP addresses on the local host - // First call to GetIpAddrTable with 0 bytes buffer - // will return insufficient buffer error, and size - // will contain the number of bytes needed for all - // data. Repeat the process of querying the size - // using GetIpAddrTable in a while loop since it - // can happen that an interface comes online between - // the previous call to GetIpAddrTable and the next - // call. - MIB_IPADDRTABLE *table = NULL; - unsigned long size = 0; - int error = GetIpAddrTable(table, &size, 0); - // Also add a count to limit the while loop - in - // case that something strange is going on. - int count = 0; - while (error == ERROR_INSUFFICIENT_BUFFER && count < 10) - { - delete[] table; // deleting NULL is legal - table = (MIB_IPADDRTABLE*)new char[size]; - error = GetIpAddrTable(table, &size, 0); - count++; - } // while insufficient buffer - for (unsigned int i = 0; i < table->dwNumEntries; i++) - { - unsigned int ip = ntohl(table->table[i].dwAddr); - if (sender.getIP() == ip) // this interface is ours + // Query the list of all IP addresses on the local host + // First call to GetIpAddrTable with 0 bytes buffer + // will return insufficient buffer error, and size + // will contain the number of bytes needed for all + // data. Repeat the process of querying the size + // using GetIpAddrTable in a while loop since it + // can happen that an interface comes online between + // the previous call to GetIpAddrTable and the next + // call. + MIB_IPADDRTABLE *table = NULL; + unsigned long size = 0; + int error = GetIpAddrTable(table, &size, 0); + // Also add a count to limit the while loop - in + // case that something strange is going on. + int count = 0; + while (error == ERROR_INSUFFICIENT_BUFFER && count < 10) { - sender.setIP(0x7f000001); // 127.0.0.1 - break; + delete[] table; // deleting NULL is legal + table = (MIB_IPADDRTABLE*)new char[size]; + error = GetIpAddrTable(table, &size, 0); + count++; + } // while insufficient buffer + for (unsigned int i = 0; i < table->dwNumEntries; i++) + { + unsigned int ip = ntohl(table->table[i].dwAddr); + if (sender.getIP() == ip) // this interface is ours + { + sender.setIP(0x7f000001); // 127.0.0.1 + break; + } } - } - delete[] table; - + delete[] table; #endif - m_server_address.copy(sender); + m_server_address.copy(sender); + } m_state = CONNECTING; + // Reset timer for next usage + resetTimer(); + m_tried_connection = 0; } -} // handleSameLAN +} // waitingAloha // ---------------------------------------------------------------------------- @@ -448,7 +417,6 @@ bool ConnectToServer::notifyEventAsynchronous(Event* event) Log::info("ConnectToServer", "The Connect To Server protocol has " "received an event notifying that he's connected to the peer."); m_state = CONNECTED; // we received a message, we are connected - Server *server = ServersManager::get()->getJoinedServer(); } return true; } // notifyEventAsynchronous diff --git a/src/network/protocols/connect_to_server.hpp b/src/network/protocols/connect_to_server.hpp index 42690fef0..86f6bd0e4 100644 --- a/src/network/protocols/connect_to_server.hpp +++ b/src/network/protocols/connect_to_server.hpp @@ -22,27 +22,28 @@ #include "network/protocol.hpp" #include "network/transport_address.hpp" #include "utils/cpp2011.hpp" +#include #include -class ConnectToServer : public Protocol, public CallbackObject +class ConnectToServer : public Protocol { private: + std::chrono::system_clock::time_point m_timer; TransportAddress m_server_address; uint32_t m_server_id; uint32_t m_host_id; + unsigned m_tried_connection = 0; /** Protocol currently being monitored. */ - std::shared_ptr m_current_protocol; + std::weak_ptr m_current_protocol; bool m_quick_join; /** State for finite state machine. */ enum { - NONE, - GETTING_SELF_ADDRESS, + REGISTER_SELF_ADDRESS, GOT_SERVER_ADDRESS, REQUESTING_CONNECTION, - QUICK_JOIN, CONNECTING, CONNECTED, HIDING_ADDRESS, @@ -52,7 +53,8 @@ private: void registerWithSTKServer(); void handleQuickConnect(); - void handleSameLAN(); + void waitingAloha(bool is_wan); + void resetTimer() { m_timer = std::chrono::system_clock::now(); } public: ConnectToServer(); @@ -62,9 +64,7 @@ public: virtual bool notifyEventAsynchronous(Event* event) OVERRIDE; virtual void setup() OVERRIDE; virtual void asynchronousUpdate() OVERRIDE; - virtual void callback(Protocol *protocol) OVERRIDE; virtual void update(float dt) OVERRIDE {} - void setServerAddress(const TransportAddress &address); }; // class ConnectToServer #endif // CONNECT_TO_SERVER_HPP diff --git a/src/network/protocols/get_peer_address.cpp b/src/network/protocols/get_peer_address.cpp index 2ef5a6a96..d1033aea9 100644 --- a/src/network/protocols/get_peer_address.cpp +++ b/src/network/protocols/get_peer_address.cpp @@ -21,13 +21,12 @@ #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "network/network_config.hpp" -#include "network/protocol_manager.hpp" +#include "network/stk_host.hpp" #include "online/request_manager.hpp" #include "utils/log.hpp" -GetPeerAddress::GetPeerAddress(uint32_t peer_id, - CallbackObject* callback_object) - : Protocol(PROTOCOL_SILENT, callback_object) +GetPeerAddress::GetPeerAddress(uint32_t peer_id) + : Protocol(PROTOCOL_SILENT, NULL) { m_peer_id = peer_id; } // GetPeerAddress @@ -41,7 +40,6 @@ GetPeerAddress::~GetPeerAddress() void GetPeerAddress::setup() { m_address.clear(); - m_request = new Online::XMLRequest(); PlayerManager::setUserDetails(m_request, "get", Online::API::SERVER_PATH); @@ -65,7 +63,7 @@ void GetPeerAddress::asynchronousUpdate() m_address.setIP(ip); uint16_t port; - uint32_t my_ip = NetworkConfig::get()->getMyAddress().getIP(); + uint32_t my_ip = STKHost::get()->getPublicAddress().getIP(); if (m_address.getIP() == my_ip && !NetworkConfig::m_disable_lan) result->get("private_port", &port); else @@ -84,9 +82,3 @@ void GetPeerAddress::asynchronousUpdate() m_request = NULL; } } // asynchronousUpdate - -// ---------------------------------------------------------------------------- -void GetPeerAddress::setPeerID(uint32_t peer_id) -{ - m_peer_id = peer_id; -} // setPeerID diff --git a/src/network/protocols/get_peer_address.hpp b/src/network/protocols/get_peer_address.hpp index 094639953..02b5f8d2a 100644 --- a/src/network/protocols/get_peer_address.hpp +++ b/src/network/protocols/get_peer_address.hpp @@ -35,13 +35,12 @@ private: * to get the result. */ TransportAddress m_address; public: - GetPeerAddress(uint32_t peer_id, CallbackObject* callback_object); + GetPeerAddress(uint32_t peer_id); virtual ~GetPeerAddress(); virtual void setup() OVERRIDE; virtual void asynchronousUpdate() OVERRIDE; - void setPeerID(uint32_t m_peer_id); - + void setPeerID(uint32_t peer_id) { m_peer_id = peer_id; } // ------------------------------------------------------------------------ /** Returns the address found. */ const TransportAddress &getAddress() const { return m_address; } diff --git a/src/network/protocols/get_public_address.cpp b/src/network/protocols/get_public_address.cpp index 5bd89c8e6..934fb34a2 100644 --- a/src/network/protocols/get_public_address.cpp +++ b/src/network/protocols/get_public_address.cpp @@ -19,6 +19,7 @@ #include "network/protocols/get_public_address.hpp" #include "config/user_config.hpp" +#include "guiengine/message_queue.hpp" #include "network/network.hpp" #include "network/network_config.hpp" #include "network/network_string.hpp" @@ -28,6 +29,8 @@ #include "utils/string_utils.hpp" #include +#include +#include #include #ifdef __MINGW32__ @@ -48,36 +51,16 @@ const uint32_t GetPublicAddress::m_stun_magic_cookie = 0x2112A442; TransportAddress GetPublicAddress::m_my_address(0, 0); -void GetPublicAddress::setMyIPAddress(const std::string &s) -{ - std::vector l = StringUtils::split(s, ':'); - if (l.size() != 2) - { - Log::fatal("Invalid IP address '%s'.", s.c_str()); - } - std::vector ip = StringUtils::split(l[0], '.'); - if (ip.size() != 4) - { - Log::fatal("Invalid IP address '%s'.", s.c_str()); - } - uint32_t u = 0; - for (unsigned int i = 0; i < 4; i++) - { - int k; - StringUtils::fromString(ip[i], k); - u = (u << 8) + k; - } - m_my_address.setIP(u); - int p; - StringUtils::fromString(l[1], p); - m_my_address.setPort(p); -} // setMyIPAddress // ============================================================================ -GetPublicAddress::GetPublicAddress(CallbackObject *callback) - : Protocol(PROTOCOL_SILENT, callback) +GetPublicAddress::GetPublicAddress() + : Protocol(PROTOCOL_SILENT, NULL) { - m_state = NOTHING_DONE; + m_untried_server = UserConfigParams::m_stun_servers; + // Generate random list of stun servers + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(m_untried_server.begin(), m_untried_server.end(), g); } // GetPublicAddress // ---------------------------------------------------------------------------- @@ -85,15 +68,22 @@ GetPublicAddress::GetPublicAddress(CallbackObject *callback) * the list stored in the config file. See * https://tools.ietf.org/html/rfc5389#section-6 * for details on the message structure. - * The request is send through m_transaction_host, from which the answer + * The request is send through transaction_host, from which the answer * will be retrieved by parseStunResponse() */ -void GetPublicAddress::createStunRequest() +Network* GetPublicAddress::createStunRequest() { - // Pick a random stun server - std::vector stun_servers = UserConfigParams::m_stun_servers; + if (m_untried_server.empty()) + { + // Notice: MessageQueue is thread safe to add + MessageQueue::add(MessageQueue::MT_ERROR, + _("Failed to get public address from stun server.")); + requestTerminate(); + return NULL; + } - const char* server_name = stun_servers[rand() % stun_servers.size()].c_str(); + // Pick last element in untried servers + const char* server_name = m_untried_server.back().c_str(); Log::debug("GetPublicAddress", "Using STUN server %s", server_name); struct addrinfo hints, *res; @@ -106,10 +96,12 @@ void GetPublicAddress::createStunRequest() int status = getaddrinfo(server_name, NULL, &hints, &res); if (status != 0) { - Log::error("GetPublicAddress", "Error in getaddrinfo: %s", - gai_strerror(status)); - return; + Log::error("GetPublicAddress", "Error in getaddrinfo for stun server" + " %s: %s", server_name, gai_strerror(status)); + m_untried_server.pop_back(); + return NULL; } + m_untried_server.pop_back(); // documentation says it points to "one or more addrinfo structures" assert(res != NULL); struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr); @@ -119,7 +111,7 @@ void GetPublicAddress::createStunRequest() ENetAddress addr; addr.host = STKHost::HOST_ANY; addr.port = STKHost::PORT_ANY; - m_transaction_host = new Network(1, 1, 0, 0, &addr); + Network* transaction_host = new Network(1, 1, 0, 0, &addr); // Assemble the message for the stun server BareNetworkString s(20); @@ -138,11 +130,11 @@ void GetPublicAddress::createStunRequest() m_stun_tansaction_id[i] = random_byte; } - m_transaction_host->sendRawPacket(s, + transaction_host->sendRawPacket(s, TransportAddress(m_stun_server_ip, m_stun_server_port) ); freeaddrinfo(res); - m_state = STUN_REQUEST_SENT; + return transaction_host; } // createStunRequest // ---------------------------------------------------------------------------- @@ -151,12 +143,13 @@ void GetPublicAddress::createStunRequest() * then parses the answer into address and port * \return "" if the address could be parsed or an error message */ -std::string GetPublicAddress::parseStunResponse() +std::string GetPublicAddress::parseStunResponse(Network* transaction_host) { TransportAddress sender; const int LEN = 2048; char buffer[LEN]; - int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000); + int len = transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000); + delete transaction_host; if(sender.getIP()!=m_stun_server_ip) { @@ -199,7 +192,6 @@ std::string GetPublicAddress::parseStunResponse() // Those are the port and the address to be detected - int pos = 20; while (true) { int type = datas.getUInt16(); @@ -239,35 +231,27 @@ void GetPublicAddress::asynchronousUpdate() if (m_my_address.getIP() != 0 && m_my_address.getPort() != 0) { NetworkConfig::get()->setMyAddress(m_my_address); - m_state = EXITING; requestTerminate(); } //#define LAN_TEST #ifdef LAN_TEST TransportAddress address(0x7f000001, 4); NetworkConfig::get()->setMyAddress(address); - m_state = EXITING; requestTerminate(); return; #endif - if (m_state == NOTHING_DONE) + Network* transaction_host = createStunRequest(); + if (transaction_host) { - createStunRequest(); - } - if (m_state == STUN_REQUEST_SENT) - { - std::string message = parseStunResponse(); - delete m_transaction_host; + std::string message = parseStunResponse(transaction_host); if (message != "") { Log::warn("GetPublicAddress", "%s", message.c_str()); - m_state = NOTHING_DONE; // try again } else { // The address and the port are known, so the connection can be closed - m_state = EXITING; requestTerminate(); } } diff --git a/src/network/protocols/get_public_address.hpp b/src/network/protocols/get_public_address.hpp index d759f29a5..545a7c4df 100644 --- a/src/network/protocols/get_public_address.hpp +++ b/src/network/protocols/get_public_address.hpp @@ -30,8 +30,10 @@ class Network; class GetPublicAddress : public Protocol { private: - void createStunRequest(); - std::string parseStunResponse(); + Network* createStunRequest(); + std::string parseStunResponse(Network* transaction_host); + + std::vector m_untried_server; // Constants static const uint32_t m_stun_magic_cookie; @@ -41,20 +43,13 @@ private: * unnecessary (though that means that the user has to take care of * opening the firewall). */ static TransportAddress m_my_address; - enum State - { - NOTHING_DONE, - STUN_REQUEST_SENT, - EXITING - } m_state; uint8_t m_stun_tansaction_id[12]; uint32_t m_stun_server_ip; Network* m_transaction_host; public: - static void setMyIPAddress(const std::string &s); - GetPublicAddress(CallbackObject *callback = NULL); + GetPublicAddress(); virtual ~GetPublicAddress() {} virtual void asynchronousUpdate() OVERRIDE; @@ -65,7 +60,7 @@ public: // ------------------------------------------------------------------------ virtual bool notifyEventAsynchronous(Event* event) OVERRIDE { return true; } // ------------------------------------------------------------------------ - virtual void setup() { m_state = NOTHING_DONE; } + virtual void setup() { } // ------------------------------------------------------------------------ }; // class GetPublicAddress diff --git a/src/network/protocols/hide_public_address.cpp b/src/network/protocols/hide_public_address.cpp index 766dd95b2..47bc29bcc 100644 --- a/src/network/protocols/hide_public_address.cpp +++ b/src/network/protocols/hide_public_address.cpp @@ -56,7 +56,7 @@ void HidePublicAddress::asynchronousUpdate() { if(rec_success == "yes") { - Log::debug("HidePublicAddress", "Address hidden successfully."); + Log::info("HidePublicAddress", "Address hidden successfully."); } else { diff --git a/src/network/protocols/ping_protocol.cpp b/src/network/protocols/ping_protocol.cpp index 1d0b46666..1398f451f 100644 --- a/src/network/protocols/ping_protocol.cpp +++ b/src/network/protocols/ping_protocol.cpp @@ -26,11 +26,12 @@ * \param delay_between_pings: How often to ping. */ PingProtocol::PingProtocol(const TransportAddress& ping_dst, - double delay_between_pings) + double delay_between_pings, double timeout) : Protocol(PROTOCOL_SILENT) { m_ping_dst.copy(ping_dst); m_delay_between_pings = delay_between_pings; + m_timeout = timeout; } // PingProtocol // ---------------------------------------------------------------------------- @@ -41,7 +42,7 @@ PingProtocol::~PingProtocol() // ---------------------------------------------------------------------------- void PingProtocol::setup() { - m_last_ping_time = 0; + m_last_ping_time = 0.0; } // setup // ---------------------------------------------------------------------------- @@ -49,6 +50,10 @@ void PingProtocol::asynchronousUpdate() { if (StkTime::getRealTime() > m_last_ping_time+m_delay_between_pings) { + if (m_last_ping_time == 0.0) + m_timeout = StkTime::getRealTime() + m_timeout; + else if (StkTime::getRealTime() > m_timeout) + requestTerminate(); m_last_ping_time = StkTime::getRealTime(); BareNetworkString data; data.addUInt8(0); diff --git a/src/network/protocols/ping_protocol.hpp b/src/network/protocols/ping_protocol.hpp index fdb66adf2..1a9f71c09 100644 --- a/src/network/protocols/ping_protocol.hpp +++ b/src/network/protocols/ping_protocol.hpp @@ -16,9 +16,12 @@ private: /** Time of last ping. */ double m_last_ping_time; + + /** If longer than this, terminate this protocol. */ + double m_timeout; public: PingProtocol(const TransportAddress& ping_dst, - double delay_between_pings); + double delay_between_pings, double timeout = 60.0); virtual ~PingProtocol(); virtual void asynchronousUpdate() OVERRIDE; diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index df45a08ff..c19c253b9 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -115,7 +115,6 @@ void ServerLobby::setup() m_state = NetworkConfig::get()->isLAN() ? ACCEPTING_CLIENTS : INIT_WAN; m_selection_enabled = false; - m_current_protocol = nullptr; Log::info("ServerLobby", "Starting the protocol."); // Initialise the data structures to detect if all clients and @@ -201,27 +200,20 @@ void ServerLobby::update(float dt) switch (m_state.load()) { case INIT_WAN: + { // Start the protocol to find the public ip address. - m_current_protocol = std::make_shared(this); - m_current_protocol->requestStart(); m_state = GETTING_PUBLIC_ADDRESS; - // The callback from GetPublicAddress will wake this protocol up - requestPause(); break; + } case GETTING_PUBLIC_ADDRESS: { - Log::debug("ServerLobby", "Public address known."); - // Free GetPublicAddress protocol - m_current_protocol = nullptr; - // Register this server with the STK server. This will block // this thread, but there is no need for the protocol manager // to react to any requests before the server is registered. registerServer(); - Log::info("ServerLobby", "Server registered."); m_state = ACCEPTING_CLIENTS; + break; } - break; case ACCEPTING_CLIENTS: { // Only poll the STK server if this is a WAN server. @@ -306,15 +298,6 @@ void ServerLobby::update(float dt) } } // update -//----------------------------------------------------------------------------- -/** Callback when the GetPublicAddress terminates. It will unpause this - * protocol, which triggers the next state of the finite state machine. - */ -void ServerLobby::callback(Protocol *protocol) -{ - requestUnpause(); -} // callback - //----------------------------------------------------------------------------- /** Register this server (i.e. its public address) with the STK server * so that clients can find it. It blocks till a response from the @@ -325,12 +308,12 @@ void ServerLobby::callback(Protocol *protocol) void ServerLobby::registerServer() { Online::XMLRequest *request = new Online::XMLRequest(); - const TransportAddress& addr = NetworkConfig::get()->getMyAddress(); + const TransportAddress& addr = STKHost::get()->getPublicAddress(); PlayerManager::setUserDetails(request, "create", Online::API::SERVER_PATH); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort() ); request->addParameter("private_port", - NetworkConfig::get()->getServerPort() ); + STKHost::get()->getPrivatePort() ); request->addParameter("name", NetworkConfig::get()->getServerName() ); request->addParameter("max_players", UserConfigParams::m_server_max_players ); @@ -350,9 +333,11 @@ void ServerLobby::registerServer() { irr::core::stringc error(request->getInfo().c_str()); Log::error("RegisterServer", "%s", error.c_str()); - STKHost::get()->setErrorMessage(_("Failed to register server: %s", error.c_str())); + STKHost::get()->setErrorMessage(_("Failed to register server: %s", + error.c_str())); + STKHost::get()->requestShutdown(); } - + delete request; } // registerServer //----------------------------------------------------------------------------- @@ -444,7 +429,7 @@ void ServerLobby::checkIncomingConnectionRequests() PlayerManager::setUserDetails(request, "poll-connection-requests", Online::API::SERVER_PATH); - const TransportAddress &addr = NetworkConfig::get()->getMyAddress(); + const TransportAddress &addr = STKHost::get()->getPublicAddress(); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort()); @@ -469,7 +454,7 @@ void ServerLobby::checkIncomingConnectionRequests() Log::debug("ServerLobby", "User with id %d wants to connect.", id); std::make_shared(id)->requestStart(); - } + } delete request; } // checkIncomingConnectionRequests diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 82dbf379c..8e99909ca 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -9,7 +9,6 @@ #include class ServerLobby : public LobbyProtocol - , public CallbackObject { public: /* The state for a small finite state machine. */ @@ -57,7 +56,6 @@ private: * seconds), which is the real time at which the server should start. */ double m_server_delay; - std::shared_ptr m_current_protocol; bool m_selection_enabled; /** Counts how many players are ready to go on. */ @@ -98,7 +96,6 @@ public: void checkRaceFinished(); void finishedLoadingWorld(); ServerState getCurrentState() const { return m_state.load(); } - virtual void callback(Protocol *protocol) OVERRIDE; }; // class ServerLobby diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index a8f889fda..ff8cbd6a4 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -43,7 +43,24 @@ # include # include #endif + +#ifdef __MINGW32__ +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x501 +#endif + +#ifdef WIN32 +# include +# include +#else +# include +#endif +#include + +#include #include +#include +#include STKHost *STKHost::m_stk_host = NULL; bool STKHost::m_enable_console = false; @@ -244,10 +261,10 @@ STKHost::STKHost(uint32_t server_id, uint32_t host_id) // server is made. m_host_id = 0; init(); - TransportAddress a; - a.setIP(0); - a.setPort(NetworkConfig::get()->getClientPort()); - ENetAddress ea = a.toEnetAddress(); + + ENetAddress ea; + ea.host = STKHost::HOST_ANY; + ea.port = STKHost::PORT_ANY; m_network = new Network(/*peer_count*/1, /*channel_limit*/2, /*max_in_bandwidth*/0, /*max_out_bandwidth*/0, &ea); @@ -257,7 +274,16 @@ STKHost::STKHost(uint32_t server_id, uint32_t host_id) "an ENet client host."); } - std::make_shared(server_id, host_id)->requestStart(); + setPrivatePort(); + if (NetworkConfig::get()->isWAN()) + { + setPublicAddress(); + } + // Don't connect to server if no public address in WAN game + if (!m_public_address.isUnset() || NetworkConfig::get()->isLAN()) + { + std::make_shared(server_id, host_id)->requestStart(); + } } // STKHost // ---------------------------------------------------------------------------- @@ -274,7 +300,7 @@ STKHost::STKHost(const irr::core::stringw &server_name) ENetAddress addr; addr.host = STKHost::HOST_ANY; - addr.port = NetworkConfig::get()->getServerPort(); + addr.port = STKHost::PORT_ANY; m_network= new Network(NetworkConfig::get()->getMaxPlayers(), /*channel_limit*/2, @@ -286,8 +312,18 @@ STKHost::STKHost(const irr::core::stringw &server_name) "ENet server host."); } - startListening(); - ProtocolManager::lock()->requestStart(LobbyProtocol::create()); + setPrivatePort(); + if (NetworkConfig::get()->isWAN()) + { + setPublicAddress(); + } + // Don't construct server if no public address in WAN game + if (!m_public_address.isUnset() || NetworkConfig::get()->isLAN()) + { + startListening(); + ProtocolManager::lock() + ->requestStart(LobbyProtocol::create()); + } } // STKHost(server_name) @@ -364,6 +400,192 @@ void STKHost::shutdown() destroy(); } // shutdown +//----------------------------------------------------------------------------- +/** Set the public address using stun protocol. + */ +void STKHost::setPublicAddress() +{ + std::vector untried_server = UserConfigParams::m_stun_servers; + // Generate random list of stun servers + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(untried_server.begin(), untried_server.end(), g); + while (!untried_server.empty()) + { + // Pick last element in untried servers + const char* server_name = untried_server.back().c_str(); + Log::debug("STKHost", "Using STUN server %s", server_name); + + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version + hints.ai_socktype = SOCK_STREAM; + + // Resolve the stun server name so we can send it a STUN request + int status = getaddrinfo(server_name, NULL, &hints, &res); + if (status != 0) + { + Log::error("STKHost", "Error in getaddrinfo for stun server" + " %s: %s", server_name, gai_strerror(status)); + untried_server.pop_back(); + continue; + } + untried_server.pop_back(); + // documentation says it points to "one or more addrinfo structures" + assert(res != NULL); + struct sockaddr_in* current_interface = (struct sockaddr_in*)(res->ai_addr); + uint32_t stun_server_ip = ntohl(current_interface->sin_addr.s_addr); + + // Assemble the message for the stun server + BareNetworkString s(20); + + // bytes 0-1: the type of the message + // bytes 2-3: message length added to header (attributes) + uint16_t message_type = 0x0001; // binding request + uint16_t message_length = 0x0000; + s.addUInt16(message_type).addUInt16(message_length) + .addUInt32(0x2112A442); + uint8_t stun_tansaction_id[12]; + // bytes 8-19: the transaction id + for (int i = 0; i < 12; i++) + { + uint8_t random_byte = rand() % 256; + s.addUInt8(random_byte); + stun_tansaction_id[i] = random_byte; + } + + m_network->sendRawPacket(s, TransportAddress(stun_server_ip, 3478)); + freeaddrinfo(res); + + // Recieve now + TransportAddress sender; + const int LEN = 2048; + char buffer[LEN]; + int len = m_network->receiveRawPacket(buffer, LEN, &sender, 2000); + + if (sender.getIP() != stun_server_ip) + { + TransportAddress stun(stun_server_ip, 3478); + Log::warn("STKHost", + "Received stun response from %s instead of %s.", + sender.toString().c_str(), stun.toString().c_str()); + } + + if (len < 0) + { + Log::error("STKHost", "STUN response contains no data at all"); + continue; + } + + // Convert to network string. + BareNetworkString datas(buffer, len); + + // check that the stun response is a response, contains the magic cookie + // and the transaction ID + if (datas.getUInt16() != 0x0101) + { + Log::error("STKHost", "STUN response doesn't contain the magic " + "cookie"); + continue; + } + int message_size = datas.getUInt16(); + if (datas.getUInt32() != 0x2112A442) + { + Log::error("STKHost", "STUN response doesn't contain the magic " + "cookie"); + continue; + } + + for (int i = 0; i < 12; i++) + { + if (datas.getUInt8() != stun_tansaction_id[i]) + { + Log::error("STKHost", "STUN response doesn't contain the " + "transaction ID"); + continue; + } + } + + Log::debug("GetPublicAddress", + "The STUN server responded with a valid answer"); + + // The stun message is valid, so we parse it now: + if (message_size == 0) + { + Log::error("STKHost", "STUN response does not contain any " + "information."); + continue; + } + // Cannot even read the size + if (message_size < 4) + { + Log::error("STKHost", "STUN response is too short."); + continue; + } + // Those are the port and the address to be detected + bool found = false; + while (true) + { + int type = datas.getUInt16(); + int size = datas.getUInt16(); + if (type == 0 || type == 1) + { + assert(size == 8); + datas.getUInt8(); // skip 1 byte +#ifdef DEBUG + uint8_t skip = datas.getUInt8(); + // Family IPv4 only + assert(skip == 0x01); +#else + datas.getUInt8(); +#endif + m_public_address.setPort(datas.getUInt16()); + m_public_address.setIP(datas.getUInt32()); + // finished parsing, we know our public transport address + Log::debug("STKHost", "The public address has been found: %s", + m_public_address.toString().c_str()); + found = true; + break; + } // type = 0 or 1 + datas.skip(4 + size); + message_size -= 4 + size; + if (message_size == 0) + { + Log::error("STKHost", "STUN response is invalid."); + break; + } + // Cannot even read the size + if (message_size < 4) + { + Log::error("STKHost", "STUN response is invalid."); + break; + } + } // while true + // Found public address and port + if (found) + untried_server.clear(); + } + // We shutdown next frame if no public address + if (m_public_address.isUnset()) + requestShutdown(); +} // setPublicAddress + +//----------------------------------------------------------------------------- +void STKHost::setPrivatePort() +{ + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + ENetHost *host = m_network->getENetHost(); + if (getsockname(host->socket, (struct sockaddr *)&sin, &len) == -1) + { + Log::error("STKHost", "Error while using getsockname()."); + m_private_port = 0; + } + else + m_private_port = ntohs(sin.sin_port); +} // setPrivatePort + //----------------------------------------------------------------------------- /** A previous GameSetup is deletea and a new one is created. * \return Newly create GameSetup object. @@ -495,9 +717,7 @@ void STKHost::mainLoop() // A separate network connection (socket) to handle LAN requests. Network* lan_network = NULL; - - if (NetworkConfig::get()->isServer() && - (NetworkConfig::get()->isLAN() || NetworkConfig::get()->isPublicServer()) ) + if (NetworkConfig::get()->isLAN()) { TransportAddress address(0, NetworkConfig::get()->getServerDiscoveryPort()); ENetAddress eaddr = address.toEnetAddress(); @@ -610,6 +830,13 @@ void STKHost::handleDirectSocketRequest(Network* lan_network) { // In case of a LAN connection, we only allow connections from // a LAN address (192.168*, ..., and 127.*). + if (!sender.isLAN()) + { + Log::error("STKHost", "Client trying to connect from '%s'", + sender.toString().c_str()); + Log::error("STKHost", "which is outside of LAN - rejected."); + return; + } std::make_shared(sender)->requestStart(); } else @@ -742,20 +969,6 @@ void STKHost::removePeer(const STKPeer* peer) m_peers.size()); } // removePeer -//----------------------------------------------------------------------------- - -uint16_t STKHost::getPort() const -{ - struct sockaddr_in sin; - socklen_t len = sizeof(sin); - ENetHost *host = m_network->getENetHost(); - if (getsockname(host->socket, (struct sockaddr *)&sin, &len) == -1) - Log::error("STKHost", "Error while using getsockname()."); - else - return ntohs(sin.sin_port); - return 0; -} // getPort - //----------------------------------------------------------------------------- /** Sends data to all peers except the specified one. * \param peer Peer which will not receive the message. diff --git a/src/network/stk_host.hpp b/src/network/stk_host.hpp index ac78aa3a2..5092df90e 100644 --- a/src/network/stk_host.hpp +++ b/src/network/stk_host.hpp @@ -103,12 +103,22 @@ private: * in the GUI. */ irr::core::stringw m_error_message; + /** The public address found by stun (if WAN is used). */ + TransportAddress m_public_address; + + /** The private port enet socket is bound. */ + uint16_t m_private_port; + + /** An error message, which is set by a protocol to be displayed + * in the GUI. */ + STKHost(uint32_t server_id, uint32_t host_id); STKHost(const irr::core::stringw &server_name); virtual ~STKHost(); void init(); void handleDirectSocketRequest(Network* lan_network); - + // ------------------------------------------------------------------------ + void setPublicAddress(); // ------------------------------------------------------------------------ void mainLoop(); @@ -140,7 +150,15 @@ public: // ------------------------------------------------------------------------ /** Checks if the STKHost has been created. */ static bool existHost() { return m_stk_host != NULL; } - + // ------------------------------------------------------------------------ + const TransportAddress& getPublicAddress() const + { return m_public_address; } + // ------------------------------------------------------------------------ + uint16_t getPrivatePort() const + { return m_private_port; } + // ------------------------------------------------------------------------ + void setPrivatePort(); + // ------------------------------------------------------------------------ virtual GameSetup* setupNewGame(); void abort(); void deleteAllPeers(); @@ -175,7 +193,6 @@ public: STKPeer *getPeer(ENetPeer *enet_peer); STKPeer *getServerPeerForClient() const; std::vector getMyPlayerProfiles(); - uint16_t getPort() const; void setErrorMessage(const irr::core::stringw &message); bool isAuthorisedToControl() const; const irr::core::stringw& diff --git a/src/network/transport_address.hpp b/src/network/transport_address.hpp index 2e33fb01f..3a5cab8bd 100644 --- a/src/network/transport_address.hpp +++ b/src/network/transport_address.hpp @@ -90,6 +90,8 @@ private: public: bool isLAN() const; // ------------------------------------------------------------------------ + bool isUnset() const { return m_ip == 0 || m_port == 0; } + // ------------------------------------------------------------------------ /** A copy function (to replace the copy constructor which is disabled * using NoCopy): it copies the data from the argument into this object.*/ void copy(const TransportAddress &other)