From 453ce8cf6017f956b678df6a042d7147c619289c Mon Sep 17 00:00:00 2001 From: hiker Date: Mon, 9 Nov 2015 20:51:00 +1100 Subject: [PATCH] Added LAN server discovery. --- src/network/network.cpp | 112 ++++----------- src/network/network.hpp | 5 +- src/network/network_string.hpp | 6 + src/network/protocol_manager.cpp | 2 +- src/network/protocols/connect_to_peer.cpp | 2 +- src/network/protocols/connect_to_server.cpp | 6 +- src/network/protocols/get_public_address.cpp | 25 ++-- .../protocols/server_lobby_room_protocol.cpp | 5 +- src/network/stk_host.cpp | 40 +++++- src/network/stk_host.hpp | 8 +- src/online/servers_manager.cpp | 133 ++++++++++++++++-- src/online/servers_manager.hpp | 4 +- src/online/xml_request.hpp | 2 +- src/states_screens/create_server_screen.cpp | 6 +- src/states_screens/online_screen.cpp | 2 +- src/states_screens/server_selection.cpp | 2 +- 16 files changed, 232 insertions(+), 128 deletions(-) diff --git a/src/network/network.cpp b/src/network/network.cpp index 4717d6152..4e9ee517b 100755 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -103,113 +103,59 @@ void Network::sendRawPacket(uint8_t* data, int length, false); } // sendRawPacket -// ---------------------------------------------------------------------------- -/** \brief Receives a packet directly from the network interface. - * Receive a packet whithout ENet processing it and returns the - * sender's ip address and port in the TransportAddress structure. - * \param sender : Stores the transport address of the sender of the - * received packet. - * \return A string containing the data of the received packet. - */ -uint8_t* Network::receiveRawPacket(TransportAddress* sender) -{ - const int LEN = 2048; - // max size needed normally (only used for stun) - uint8_t* buffer = new uint8_t[LEN]; - memset(buffer, 0, LEN); - - socklen_t from_len; - struct sockaddr_in addr; - - from_len = sizeof(addr); - int len = recvfrom(m_host->socket, (char*)buffer, LEN, 0, - (struct sockaddr*)(&addr), &from_len ); - - int i = 0; - // wait to receive the message because enet sockets are non-blocking - while(len == -1) // nothing received - { - StkTime::sleep(1); // wait 1 millisecond between two checks - i++; - len = recvfrom(m_host->socket, (char*)buffer, LEN, 0, - (struct sockaddr*)(&addr), &from_len ); - } - if (len == SOCKET_ERROR) - { - Log::error("STKHost", - "Problem with the socket. Please contact the dev team."); - } - // we received the data - sender->setIP( ntohl((uint32_t)(addr.sin_addr.s_addr)) ); - sender->setPort( ntohs(addr.sin_port) ); - - if (addr.sin_family == AF_INET) - { - Log::info("STKHost", "IPv4 Address of the sender was %s", - sender->toString().c_str()); - } - return buffer; -} // receiveRawPacket(TransportAddress* sender) - // ---------------------------------------------------------------------------- /** \brief Receives a packet directly from the network interface and * filter its address. * Receive a packet whithout ENet processing it. Checks that the * sender of the packet is the one that corresponds to the sender * parameter. Does not check the port right now. - * \param sender : Transport address of the original sender of the - * wanted packet. + * \param buffer A buffer to receive the data in. + * \param buf_len Length of the buffer. + * \param[out] sender : Transport address of the original sender of the + * wanted packet. If the ip address is 0, do not check + * the sender's ip address, otherwise wait till a message + * from the specified sender arrives. All other messages + * are discarded. * \param max_tries : Number of times we try to read data from the * socket. This is aproximately the time we wait in * milliseconds. -1 means eternal tries. - * \return A string containing the data of the received packet - * matching the sender's ip address. + * \return Length of the received data, or -1 if no data was received. */ -uint8_t* Network::receiveRawPacket(const TransportAddress& sender, - int max_tries) +int Network::receiveRawPacket(char *buffer, int buf_len, + TransportAddress *sender, int max_tries) { - const int LEN = 2048; - uint8_t* buffer = new uint8_t[LEN]; - memset(buffer, 0, LEN); + memset(buffer, 0, buf_len); - socklen_t from_len; struct sockaddr_in addr; + socklen_t from_len = sizeof(addr); - from_len = sizeof(addr); - int len = recvfrom(m_host->socket, (char*)buffer, LEN, 0, + int len = recvfrom(m_host->socket, buffer, buf_len, 0, (struct sockaddr*)(&addr), &from_len ); int count = 0; - // wait to receive the message because enet sockets are non-blocking - while(len < 0 || addr.sin_addr.s_addr == sender.getIP()) + // wait to receive the message because enet sockets are non-blocking + while(len < 0 && (count=0) - { - Log::info("STKHost", "Message received but the ip address didn't " - "match the expected one."); - } - len = recvfrom(m_host->socket, (char*)buffer, LEN, 0, - (struct sockaddr*)(&addr), &from_len); StkTime::sleep(1); // wait 1 millisecond between two checks - if (count >= max_tries && max_tries != -1) - { - TransportAddress a(m_host->address); - Log::verbose("STKHost", "No answer from the server on %s", - a.toString().c_str()); - delete [] buffer; - return NULL; - } + len = recvfrom(m_host->socket, buffer, buf_len, 0, + (struct sockaddr*)(&addr), &from_len); } + + // No message received + if(len<0) + return -1; + + Network::logPacket(NetworkString(std::string(buffer, len)), true); + sender->setIP(ntohl((uint32_t)(addr.sin_addr.s_addr)) ); + sender->setPort( ntohs(addr.sin_port) ); if (addr.sin_family == AF_INET) { - TransportAddress a(ntohl(addr.sin_addr.s_addr)); - Log::info("STKHost", "IPv4 Address of the sender was %s", - a.toString(false).c_str()); + Log::info("Network", "IPv4 Address of the sender was %s", + sender->toString().c_str()); } - Network::logPacket(NetworkString(std::string((char*)(buffer), len)), true); - return buffer; -} // receiveRawPacket(const TransportAddress& sender, int max_tries) + return len; +} // receiveRawPacket // ---------------------------------------------------------------------------- /** \brief Broadcasts a packet to all peers. diff --git a/src/network/network.hpp b/src/network/network.hpp index 1196f3b25..ad217caae 100644 --- a/src/network/network.hpp +++ b/src/network/network.hpp @@ -62,9 +62,8 @@ public: ENetPeer *connectTo(const TransportAddress &address); void sendRawPacket(uint8_t* data, int length, const TransportAddress& dst); - uint8_t* receiveRawPacket(TransportAddress* sender); - uint8_t* receiveRawPacket(const TransportAddress& sender, - int max_tries = -1); + int receiveRawPacket(char *buffer, int buf_len, + TransportAddress* sender, int max_tries = -1); void broadcastPacket(const NetworkString& data, bool reliable = true); // ------------------------------------------------------------------------ diff --git a/src/network/network_string.hpp b/src/network/network_string.hpp index 4274f1dd5..b68d32534 100644 --- a/src/network/network_string.hpp +++ b/src/network/network_string.hpp @@ -77,6 +77,12 @@ public: m_string = std::vector(value.begin(), value.end()); } // NetworkString + // ------------------------------------------------------------------------ + NetworkString(const char *p, int len) + { + m_string.resize(len); + memcpy(m_string.data(), p, len); + } // NetworkString(char*, int) // ------------------------------------------------------------------------ NetworkString& add(const std::string &s) { diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index 3604d99ee..ea00f59af 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -473,7 +473,7 @@ void ProtocolManager::asynchronousUpdate() } pthread_mutex_unlock(&m_asynchronous_protocols_mutex); - // process queued events for protocols + // Process queued events for protocols // these requests are asynchronous pthread_mutex_lock(&m_requests_mutex); while(m_requests.size()>0) diff --git a/src/network/protocols/connect_to_peer.cpp b/src/network/protocols/connect_to_peer.cpp index 584a0e439..5d5c8cead 100644 --- a/src/network/protocols/connect_to_peer.cpp +++ b/src/network/protocols/connect_to_peer.cpp @@ -129,7 +129,7 @@ void ConnectToPeer::asynchronousUpdate() m_state = CONNECTING; break; } - case CONNECTING: // waiting the peer to connect + case CONNECTING: // waiting for the peer to connect break; case CONNECTED: { diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index 014bb292b..76b690006 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -357,11 +357,13 @@ void ConnectToServer::handleSameLAN() TransportAddress sender; // get the sender - const uint8_t* received_data = host->receiveRawPacket(&sender); + const int LEN=256; + char buffer[LEN]; + int len = host->receiveRawPacket(buffer, LEN, &sender, 2000); host->startListening(); // start listening again const char data[] = "aloha_stk\0"; - if (strcmp(data, (char*)(received_data)) == 0) + if (strcmp(data, buffer) == 0) { Log::info("ConnectToServer", "LAN Server found : %s", sender.toString().c_str()); diff --git a/src/network/protocols/get_public_address.cpp b/src/network/protocols/get_public_address.cpp index c58b2b57c..2e93516b0 100644 --- a/src/network/protocols/get_public_address.cpp +++ b/src/network/protocols/get_public_address.cpp @@ -122,21 +122,24 @@ void GetPublicAddress::createStunRequest() */ std::string GetPublicAddress::parseStunResponse() { - uint8_t* s = m_transaction_host - ->receiveRawPacket(TransportAddress(m_stun_server_ip, - m_stun_server_port), 2000); + TransportAddress sender; + const int LEN = 2048; + char buffer[LEN]; + int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000); - if (!s) + if(sender.getIP()!=m_stun_server_ip) + { + TransportAddress stun(m_stun_server_ip, m_stun_server_port); + Log::warn("GetPublicAddress", + "Received stun response from %s instead of %s.", + sender.toString().c_str(), stun.toString().c_str()); + } + + if (len<0) return "STUN response contains no data at all"; // Convert to network string. - // FIXME: the length is not known (atm 2048 bytes are allocated in - // receiveRawPacket, and it looks like 32 are actually used in a normal - // stun reply - NetworkString datas(std::string((char*)s, 32)); - - // The received data has been copied and can now be deleted - delete s; + NetworkString datas(std::string(buffer, len)); // check that the stun response is a response, contains the magic cookie // and the transaction ID diff --git a/src/network/protocols/server_lobby_room_protocol.cpp b/src/network/protocols/server_lobby_room_protocol.cpp index 41a0b7213..3bcb3bb01 100644 --- a/src/network/protocols/server_lobby_room_protocol.cpp +++ b/src/network/protocols/server_lobby_room_protocol.cpp @@ -57,7 +57,10 @@ void ServerLobbyRoomProtocol::setup() { m_setup = STKHost::get()->setupNewGame(); m_next_id = 0; - m_state = NONE; + + // In case of LAN we don't need our public address or register with the STK + // server, so we can directly go to the working state. + m_state = STKHost::isLAN() ? WORKING : NONE; m_selection_enabled = false; m_in_race = false; m_current_protocol = NULL; diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index 6a9344192..a8c24f1f9 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -21,10 +21,11 @@ #include "config/user_config.hpp" #include "io/file_manager.hpp" #include "network/event.hpp" +#include "network/network_console.hpp" +#include "network/network_string.hpp" #include "network/protocols/connect_to_server.hpp" #include "network/protocols/server_lobby_room_protocol.hpp" #include "network/protocol_manager.hpp" -#include "network/network_console.hpp" #include "network/stk_peer.hpp" #include "utils/log.hpp" #include "utils/time.hpp" @@ -101,7 +102,7 @@ STKHost::NetworkType STKHost::m_network_type = STKHost::NETWORK_NONE; * randomly), and then instantiates STKHost with the id of this server. * STKHost then triggers: * 1) ConnectToServer, which starts the following protocols: - * a) GetPublicAccess: Use STUN to discover the public ip address + * a) GetPublicAddress: Use STUN to discover the public ip address * and port number of this host. * b) Register the client with the STK host ('set' command, into the * table 'client_sessions'). Its public ip address and port will @@ -157,6 +158,7 @@ STKHost::STKHost(const irr::core::stringw &server_name) { m_is_server = true; init(); + m_server_name = server_name; ENetAddress addr; addr.host = STKHost::HOST_ANY; @@ -408,8 +410,42 @@ void* STKHost::mainLoop(void* self) ENetEvent event; STKHost* myself = (STKHost*)(self); ENetHost* host = myself->m_network->getENetHost(); + + // Separate network/socket connection in case of LAN server + // listening to broadcast. + Network *discovery_host = NULL; + + if(myself->isServer() && STKHost::isLAN()) + { + TransportAddress address(0, 2757); + ENetAddress eaddr = address.toEnetAddress(); + discovery_host = new Network(1, 1, 0, 0, &eaddr); + } + + const int LEN=2048; + char buffer[LEN]; while (!myself->mustStopListening()) { + if(discovery_host) + { + TransportAddress sender; + int len= discovery_host->receiveRawPacket(buffer, LEN, &sender, 1); + if(len>0 && std::string(buffer, len)=="stk-server") + { + Log::verbose("STKHost", "Received LAN server query"); + std::string &name = StringUtils::wide_to_utf8( + myself->get()->getServerName().c_str()); + if(name.size()>2) + name = name.substr(0, 255); + NetworkString s; + s.addUInt8((uint8_t)name.size()); + s.addString(name.c_str()); + s.addUInt8(getMaxPlayers()); + s.addUInt8(1); // FIXME: current number of connected players + discovery_host->sendRawPacket(s.getBytes(), s.size(), sender); + } // if message is server-requested + } // if discovery host + while (enet_host_service(host, &event, 20) != 0) { if (event.type == ENET_EVENT_TYPE_NONE) diff --git a/src/network/stk_host.hpp b/src/network/stk_host.hpp index 2360627a4..3ec648fe1 100644 --- a/src/network/stk_host.hpp +++ b/src/network/stk_host.hpp @@ -186,8 +186,6 @@ public: uint32_t max_outgoing_bandwidth); void startListening(); void stopListening(); - uint8_t* receiveRawPacket(const TransportAddress& sender, - int max_tries = -1); bool peerExists(const TransportAddress& peer_address); void removePeer(const STKPeer* peer); bool isConnectedTo(const TransportAddress& peer_address); @@ -201,9 +199,11 @@ public: /** Returns the current game setup. */ GameSetup* getGameSetup() { return m_game_setup; } // -------------------------------------------------------------------- - uint8_t* receiveRawPacket(TransportAddress* sender) + int receiveRawPacket(char *buffer, int buffer_len, + TransportAddress* sender, int max_tries = -1) { - return m_network->receiveRawPacket(sender); + return m_network->receiveRawPacket(buffer, buffer_len, sender, + max_tries); } // receiveRawPacket // -------------------------------------------------------------------- diff --git a/src/online/servers_manager.cpp b/src/online/servers_manager.cpp index 71b6fe355..71521df9a 100644 --- a/src/online/servers_manager.cpp +++ b/src/online/servers_manager.cpp @@ -18,6 +18,7 @@ #include "online/servers_manager.hpp" #include "config/user_config.hpp" +#include "network/stk_host.hpp" #include "utils/translation.hpp" #include "utils/time.hpp" @@ -73,7 +74,121 @@ void ServersManager::cleanUpServers() } // cleanUpServers // ------------------------------------------------------------------------ -XMLRequest* ServersManager::refreshRequest(bool request_now) const +/** Returns a WAN update-list-of-servers request. It queries the + * STK server for an up-to-date list of servers. + */ +XMLRequest* ServersManager::getWANRefreshRequest() const +{ + // ======================================================================== + /** A small local class that triggers an update of the ServersManager + * when the request is finished. */ + class WANRefreshRequest : public XMLRequest + { + public: + WANRefreshRequest() : XMLRequest(/*manage_memory*/false, + /*priority*/100) {} + // -------------------------------------------------------------------- + virtual void callback() + { + ServersManager::get()->refresh(isSuccess(), getXMLData()); + } // callback + // -------------------------------------------------------------------- + }; // RefreshRequest + // ======================================================================== + + XMLRequest* request = new WANRefreshRequest(); + request->setApiURL(API::SERVER_PATH, "get-all"); + + return request; +} // getWANRefreshRequest + +// ------------------------------------------------------------------------ +/** Returns a LAN update-list-of-servers request. It uses UDP broadcasts + * to find LAN servers, and waits for a certain amount of time fr + * answers. + */ +XMLRequest* ServersManager::getLANRefreshRequest() const +{ + /** A simple class that uses LAN broadcasts to find local servers. + * It is based on XML request, but actually does not use any of the + * XML/HTTP based infrastructure, but implements the same interface. + * This way the already existing request thread can be used. + */ + class LANRefreshRequest : public XMLRequest + { + public: + + /** High priority for this request. */ + LANRefreshRequest() : XMLRequest(false, 100) {m_success = false;} + // -------------------------------------------------------------------- + virtual ~LANRefreshRequest() {} + // -------------------------------------------------------------------- + /** Get the downloaded XML tree. + * \pre request has to be executed. + * \return get the complete result from the request reply. */ + virtual const XMLNode * getXMLData() const + { + assert(hasBeenExecuted()); + return NULL; + } // getXMLData + // -------------------------------------------------------------------- + virtual void prepareOperation() OVERRIDE + { + } // prepareOperation + // -------------------------------------------------------------------- + virtual void operation() + { + Network *broadcast = new Network(1, 1, 0, 0); + + NetworkString s(std::string("stk-server")); + TransportAddress broadcast_address(-1, 2757); + broadcast->sendRawPacket(s.getBytes(), s.size(), broadcast_address); + + Log::info("ServersManager", "Sent broadcast message."); + + const int LEN=2048; + char buffer[LEN]; + // Wait for up to 0.5 seconds to receive an answer from + // any local servers. + double start_time = StkTime::getRealTime(); + const double DURATION = 1.0; + while(StkTime::getRealTime() - start_time < DURATION) + { + TransportAddress sender; + int len = broadcast->receiveRawPacket(buffer, LEN, &sender, 1); + if(len>0) + { + NetworkString s(buffer, len); + uint8_t name_len = s.getUInt8(0); + std::string name = s.getString(1, name_len); + irr::core::stringw name_w = + StringUtils::utf8_to_wide(name.c_str()); + uint8_t max_players = s.getUInt8(1+name_len ); + uint8_t players = s.getUInt8(1+name.size()+1); + ServersManager::get() + ->addServer(new Server(name_w, /*lan*/true, + max_players, players)); + m_success = true; + } // if received_data + } // while still waiting + } // operation + // -------------------------------------------------------------------- + /** This function is necessary, otherwise the XML- and HTTP-Request + * functions are called, which will cause a crash. */ + virtual void afterOperation() OVERRIDE {} + // -------------------------------------------------------------------- + }; // LANRefreshRequest + // ======================================================================== + + return new LANRefreshRequest(); + +} // getLANRefreshRequest + +// ------------------------------------------------------------------------ +/** Factory function to create either a LAN or a WAN update-of-server + * requests. The current list of servers is also cleared/ + */ +XMLRequest* ServersManager::getRefreshRequest(bool request_now) { if (StkTime::getRealTime() - m_last_load_time.getAtomic() < SERVER_REFRESH_INTERVAL) @@ -82,18 +197,9 @@ XMLRequest* ServersManager::refreshRequest(bool request_now) const return NULL; } - // ==================================================================== - class RefreshRequest : public XMLRequest - { - virtual void callback() - { - ServersManager::get()->refresh(isSuccess(), getXMLData()); - } // callback - }; // RefreshRequest - - // ==================================================================== - RefreshRequest* request = new RefreshRequest(); - request->setApiURL(API::SERVER_PATH, "get-all"); + cleanUpServers(); + XMLRequest *request = STKHost::isWAN() ? getWANRefreshRequest() + : getLANRefreshRequest(); if (request_now) RequestManager::get()->addRequest(request); @@ -115,7 +221,6 @@ void ServersManager::refresh(bool success, const XMLNode *input) } const XMLNode *servers_xml = input->getNode("servers"); - cleanUpServers(); for (unsigned int i = 0; i < servers_xml->getNumNodes(); i++) { addServer(new Server(*servers_xml->getNode(i), /*is_lan*/false)); diff --git a/src/online/servers_manager.hpp b/src/online/servers_manager.hpp index aa69628bd..6a8e9284a 100644 --- a/src/online/servers_manager.hpp +++ b/src/online/servers_manager.hpp @@ -50,13 +50,15 @@ private: Synchronised m_last_load_time; void refresh(bool success, const XMLNode * input); void cleanUpServers(); + XMLRequest * getWANRefreshRequest() const; + XMLRequest * getLANRefreshRequest() const; public: // Singleton static ServersManager* get(); static void deallocate(); - XMLRequest * refreshRequest(bool request_now = true) const; + XMLRequest * getRefreshRequest(bool request_now = true); void setJoinedServer(uint32_t server_id); void unsetJoinedServer(); void addServer(Server * server); diff --git a/src/online/xml_request.hpp b/src/online/xml_request.hpp index 9f8d06957..e6b0be3f3 100644 --- a/src/online/xml_request.hpp +++ b/src/online/xml_request.hpp @@ -46,10 +46,10 @@ namespace Online * message if a problem occurred). */ irr::core::stringw m_info; + protected: /** True if the request was successful executed on the server. */ bool m_success; - protected: virtual void afterOperation() OVERRIDE; public : diff --git a/src/states_screens/create_server_screen.cpp b/src/states_screens/create_server_screen.cpp index 9a05d4eb7..b38c05845 100644 --- a/src/states_screens/create_server_screen.cpp +++ b/src/states_screens/create_server_screen.cpp @@ -144,6 +144,9 @@ void CreateServerScreen::onUpdate(float delta) } //FIXME If we really want a gui, we need to decide what else to do here + // For now start the (wrong i.e. client) lobby, to prevent to create + // a server more than once. + NetworkingLobby::getInstance()->push(); } // onUpdate // ---------------------------------------------------------------------------- @@ -174,11 +177,10 @@ void CreateServerScreen::createServer() // In case of a LAN game, we can create the new server object now if (STKHost::isLAN()) { + // FIXME Is this actually necessary?? Only in case of WAN, or LAN and WAN? Server *server = new Server(name, /*lan*/true, max_players, /*current_player*/1); ServersManager::get()->addServer(server); - new ServerInfoDialog(server->getServerId(), true); - return; } // In case of a WAN game, we register this server with the diff --git a/src/states_screens/online_screen.cpp b/src/states_screens/online_screen.cpp index 61fe5b4b2..062ec37c1 100644 --- a/src/states_screens/online_screen.cpp +++ b/src/states_screens/online_screen.cpp @@ -183,7 +183,7 @@ void OnlineScreen::onUpdate(float delta) void OnlineScreen::doQuickPlay() { // Refresh server list. - HTTPRequest* refresh_request = ServersManager::get()->refreshRequest(false); + HTTPRequest* refresh_request = ServersManager::get()->getRefreshRequest(false); if (refresh_request != NULL) // consider request done { refresh_request->executeNow(); diff --git a/src/states_screens/server_selection.cpp b/src/states_screens/server_selection.cpp index a7f20abe2..9ae07c881 100644 --- a/src/states_screens/server_selection.cpp +++ b/src/states_screens/server_selection.cpp @@ -62,7 +62,7 @@ void ServerSelection::tearDown() */ void ServerSelection::refresh() { - m_refresh_request = ServersManager::get()->refreshRequest(); + m_refresh_request = ServersManager::get()->getRefreshRequest(); // If the request was created (i.e. no error, and not re-requested within // 5 seconds), clear the list and display the waiting message: if(m_refresh_request)