459 lines
16 KiB
C++
459 lines
16 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2013-2015 SuperTuxKart-Team
|
|
//
|
|
// This program 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; either version 3
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program 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 this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "network/protocols/connect_to_server.hpp"
|
|
|
|
#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_room_protocol.hpp"
|
|
#include "network/protocol_manager.hpp"
|
|
#include "network/servers_manager.hpp"
|
|
#include "network/stk_host.hpp"
|
|
#include "network/stk_peer.hpp"
|
|
#include "utils/time.hpp"
|
|
#include "utils/log.hpp"
|
|
|
|
#ifdef WIN32
|
|
# include <iphlpapi.h>
|
|
#else
|
|
#include <ifaddrs.h>
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Connects to a server. This is the quick connect constructor, which
|
|
* will pick a server randomly.
|
|
*/
|
|
ConnectToServer::ConnectToServer() : Protocol(PROTOCOL_CONNECTION)
|
|
{
|
|
m_server_id = 0;
|
|
m_host_id = 0;
|
|
m_quick_join = true;
|
|
m_server_address.clear();
|
|
} // ConnectToServer()
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Specify server to connect to.
|
|
* \param server_id Id of server to connect to.
|
|
* \param host_id Id of host.
|
|
*/
|
|
ConnectToServer::ConnectToServer(uint32_t server_id, uint32_t host_id)
|
|
: Protocol(PROTOCOL_CONNECTION)
|
|
{
|
|
m_server_id = server_id;
|
|
m_host_id = host_id;
|
|
m_quick_join = false;
|
|
const Server *server = ServersManager::get()->getServerByID(server_id);
|
|
m_server_address.copy(server->getAddress());
|
|
|
|
} // ConnectToServer(server, host)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Destructor.
|
|
*/
|
|
ConnectToServer::~ConnectToServer()
|
|
{
|
|
} // ~ConnectToServer
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Initialise the protocol.
|
|
*/
|
|
void ConnectToServer::setup()
|
|
{
|
|
Log::info("ConnectToServer", "SETUP");
|
|
m_current_protocol = NULL;
|
|
// 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;
|
|
} // 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:
|
|
{
|
|
Log::info("ConnectToServer", "Protocol starting");
|
|
// This protocol will write the public address of this
|
|
// instance to STKHost.
|
|
m_current_protocol = new GetPublicAddress(this);
|
|
m_current_protocol->requestStart();
|
|
// This protocol will be unpaused in the callback from
|
|
// GetPublicAddress
|
|
ProtocolManager::getInstance()->pauseProtocol(this);
|
|
m_state = GETTING_SELF_ADDRESS;
|
|
break;
|
|
}
|
|
case GETTING_SELF_ADDRESS:
|
|
{
|
|
delete m_current_protocol; // delete GetPublicAddress
|
|
m_current_protocol = NULL;
|
|
|
|
registerWithSTKServer(); // Register us with STK server
|
|
|
|
if (m_quick_join)
|
|
{
|
|
handleQuickConnect();
|
|
// Quick connect will give us the server details,
|
|
// so we can immediately try to connect to the server
|
|
m_state = REQUESTING_CONNECTION;
|
|
}
|
|
else
|
|
{
|
|
// No quick connect, so we have a server to connect to.
|
|
// Find its address
|
|
m_current_protocol = new GetPeerAddress(m_host_id, this);
|
|
m_current_protocol->requestStart();
|
|
m_state = GOT_SERVER_ADDRESS;
|
|
// Pause this protocol till GetPeerAddress finishes.
|
|
// The callback then will unpause this protocol/
|
|
ProtocolManager::getInstance()->pauseProtocol(this);
|
|
}
|
|
}
|
|
break;
|
|
case GOT_SERVER_ADDRESS:
|
|
{
|
|
assert(!m_quick_join);
|
|
delete m_current_protocol;
|
|
m_current_protocol = NULL;
|
|
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 = new RequestConnection(m_server_id);
|
|
m_current_protocol->requestStart();
|
|
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)
|
|
{
|
|
delete m_current_protocol;
|
|
m_current_protocol = NULL;
|
|
// 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 = new HidePublicAddress();
|
|
m_current_protocol->requestStart();
|
|
return;
|
|
}
|
|
if (m_server_address.getIP()
|
|
== NetworkConfig::get()->getMyAddress().getIP())
|
|
{
|
|
// We're in the same lan (same public ip address).
|
|
// The state will change to CONNECTING
|
|
handleSameLAN();
|
|
}
|
|
else
|
|
{
|
|
m_state = CONNECTING;
|
|
m_current_protocol = new PingProtocol(m_server_address, 2.0);
|
|
m_current_protocol->requestStart();
|
|
}
|
|
}
|
|
break;
|
|
case CONNECTING: // waiting the server to answer our connection
|
|
{
|
|
static double timer = 0;
|
|
if (StkTime::getRealTime() > timer+5.0) // every 5 seconds
|
|
{
|
|
STKHost::get()->connect(m_server_address);
|
|
timer = StkTime::getRealTime();
|
|
Log::info("ConnectToServer", "Trying to connect to %s",
|
|
m_server_address.toString().c_str());
|
|
}
|
|
break;
|
|
}
|
|
case CONNECTED:
|
|
{
|
|
Log::info("ConnectToServer", "Connected");
|
|
if(m_current_protocol)
|
|
{
|
|
// Kill the ping protocol because we're connected
|
|
m_current_protocol->requestTerminate();
|
|
}
|
|
delete m_current_protocol;
|
|
m_current_protocol = NULL;
|
|
// LAN networking does not use the stk server tables.
|
|
if(NetworkConfig::get()->isWAN())
|
|
{
|
|
m_current_protocol = new HidePublicAddress();
|
|
m_current_protocol->requestStart();
|
|
}
|
|
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)
|
|
{
|
|
delete m_current_protocol;
|
|
m_current_protocol = NULL;
|
|
Log::info("ConnectToServer", "Address hidden");
|
|
}
|
|
m_state = DONE;
|
|
// lobby room protocol if we're connected only
|
|
if(STKHost::get()->getPeers()[0]->isConnected())
|
|
{
|
|
Protocol *p = new ClientLobbyRoomProtocol(m_server_address);
|
|
p->requestStart();
|
|
}
|
|
}
|
|
break;
|
|
case DONE:
|
|
requestTerminate();
|
|
m_state = EXITING;
|
|
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
|
|
ProtocolManager::getInstance()->unpauseProtocol(this);
|
|
break;
|
|
case GOT_SERVER_ADDRESS:
|
|
// Get the server address from the protocol.
|
|
m_server_address.copy(((GetPeerAddress*)protocol)->getAddress());
|
|
ProtocolManager::getInstance()->unpauseProtocol(this);
|
|
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.
|
|
*/
|
|
void ConnectToServer::registerWithSTKServer()
|
|
{
|
|
// Our public address is now known, register details with
|
|
// STK server.
|
|
const TransportAddress& addr = NetworkConfig::get()->getMyAddress();
|
|
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()->getPrivatePort());
|
|
|
|
Log::info("ConnectToServer", "Registering addr %s",
|
|
addr.toString().c_str());
|
|
|
|
// This can be done blocking: till we are registered with the
|
|
// stk server, there is no need to to react to any other
|
|
// network requests
|
|
request->executeNow();
|
|
|
|
const XMLNode * result = request->getXMLData();
|
|
|
|
std::string success;
|
|
if(result->get("success", &success) && success == "yes")
|
|
{
|
|
Log::debug("ConnectToServer", "Address registered successfully.");
|
|
}
|
|
else
|
|
{
|
|
Log::error("ConnectToServer", "Failed to register address.");
|
|
}
|
|
delete request;
|
|
|
|
} // registerWithSTKServer
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Called to request a quick connect from the STK server.
|
|
*/
|
|
void ConnectToServer::handleQuickConnect()
|
|
{
|
|
Online::XMLRequest *request = new Online::XMLRequest();
|
|
PlayerManager::setUserDetails(request, "quick-join",
|
|
Online::API::SERVER_PATH);
|
|
request->executeNow();
|
|
|
|
const XMLNode * result = request->getXMLData();
|
|
delete request;
|
|
std::string success;
|
|
|
|
if(result->get("success", &success) && success=="yes")
|
|
{
|
|
uint32_t ip;
|
|
result->get("ip", &ip);
|
|
m_server_address.setIP(ip);
|
|
|
|
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())
|
|
{
|
|
result->get("private_port", &port);
|
|
}
|
|
else
|
|
result->get("port", &port);
|
|
|
|
m_server_address.setPort(port);
|
|
|
|
Log::debug("GetPeerAddress", "Address gotten successfully.");
|
|
}
|
|
else
|
|
{
|
|
Log::error("GetPeerAddress", "Failed to get address.");
|
|
}
|
|
} // handleQuickConnect
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Called when the server is on the same LAN. It uses broadcast to
|
|
* find and conntect to the server.
|
|
*/
|
|
void ConnectToServer::handleSameLAN()
|
|
{
|
|
// just send a broadcast packet, the client will know our
|
|
// ip address and will connect
|
|
STKHost* host = STKHost::get();
|
|
host->stopListening(); // stop the listening
|
|
|
|
Log::info("ConnectToServer", "Waiting broadcast message.");
|
|
|
|
TransportAddress sender;
|
|
// get the 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, buffer) == 0)
|
|
{
|
|
Log::info("ConnectToServer", "LAN 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 (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
|
|
}
|
|
}
|
|
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
|
|
{
|
|
sender.setIP(0x7f000001); // 127.0.0.1
|
|
break;
|
|
}
|
|
}
|
|
delete[] table;
|
|
|
|
#endif
|
|
m_server_address.copy(sender);
|
|
m_state = CONNECTING;
|
|
}
|
|
} // handleSameLAN
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool ConnectToServer::notifyEventAsynchronous(Event* event)
|
|
{
|
|
if (event->getType() == EVENT_TYPE_CONNECTED)
|
|
{
|
|
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
|
|
|