1
0
Fork 0

Merge branch 'topics/mpdiscovery-via-dns' into next

This commit is contained in:
Torsten Dreyer 2016-11-17 14:45:40 +01:00
commit b652758330
8 changed files with 459 additions and 33 deletions

View file

@ -131,6 +131,7 @@
#include <Instrumentation/HUD/HUD.hxx>
#include <Cockpit/cockpitDisplayManager.hxx>
#include <Network/HTTPClient.hxx>
#include <Network/DNSClient.hxx>
#include <Network/fgcom.hxx>
#include <Network/http/httpd.hxx>
#include <Include/version.h>
@ -735,6 +736,7 @@ void fgCreateSubsystems(bool duringReset) {
if (!globals->get_subsystem<FGHTTPClient>()) {
globals->add_new_subsystem<FGHTTPClient>();
}
globals->add_new_subsystem<FGDNSClient>();
////////////////////////////////////////////////////////////////////
// Initialize the flight model subsystem.

View file

@ -3,11 +3,13 @@ include(FlightGearComponent)
set(SOURCES
multiplaymgr.cxx
tiny_xdr.cxx
MPServerResolver.cxx
)
set(HEADERS
multiplaymgr.hxx
tiny_xdr.hxx
MPServerResolver.hxx
)
flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}")
flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}")

View file

@ -0,0 +1,194 @@
/*
MPServerResolver.cxx - mpserver names lookup via DNS
Written and copyright by Torsten Dreyer - November 2016
This file is part of FlightGear.
FlightGear 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 2 of the License, or
(at your option) any later version.
FlightGear 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 FlightGear. If not, see <http://www.gnu.org/licenses/>.
*/#include "MPServerResolver.hxx"
#include <Network/DNSClient.hxx>
#include <Main/fg_props.hxx>
#include <3rdparty/cjson/cJSON.h>
#include <cstdlib>
using namespace simgear;
/**
* Build a name=value map from base64 encoded JSON string
*/
class MPServerProperties : public std::map<string, string> {
public:
MPServerProperties (string b64)
{
std::vector<unsigned char> b64dec;
simgear::strutils::decodeBase64 (b64, b64dec);
auto jsonString = string ((char*) b64dec.data (), b64dec.size ());
cJSON * json = ::cJSON_Parse (jsonString.c_str ());
if (json) {
for (int i = 0; i < ::cJSON_GetArraySize (json); i++) {
cJSON * cj = ::cJSON_GetArrayItem (json, i);
if (cj->string && cj->valuestring)
emplace (cj->string, cj->valuestring);
}
::cJSON_Delete (json);
} else {
SG_LOG(SG_NETWORK,SG_WARN, "MPServerResolver: Can't parse JSON string '" << jsonString << "'" );
}
}
};
class MPServerResolver::MPServerResolver_priv {
public:
enum {
INIT, LOADING_SRV_RECORDS, LOAD_NEXT_TXT_RECORD, LOADING_TXT_RECORDS, DONE,
} _state = INIT;
FGDNSClient * _dnsClient = globals->get_subsystem<FGDNSClient> ();
DNS::Request_ptr _dnsRequest;
PropertyList _serverNodes;
PropertyList::const_iterator _serverNodes_it;
};
MPServerResolver::~MPServerResolver ()
{
delete _priv;
}
MPServerResolver::MPServerResolver () :
_priv (new MPServerResolver_priv ())
{
}
void
MPServerResolver::run ()
{
//SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver::run() with state=" << _priv->_state );
switch (_priv->_state) {
// First call - fire DNS lookup for SRV records
case MPServerResolver_priv::INIT:
if (!_priv->_dnsClient) {
SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: DNS subsystem not available.");
onFailure ();
return;
}
_priv->_dnsRequest = new DNS::SRVRequest (_dnsName, _service, _protocol);
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn());
_priv->_dnsClient->makeRequest (_priv->_dnsRequest);
_priv->_state = MPServerResolver_priv::LOADING_SRV_RECORDS;
break;
// Check if response from SRV Query
case MPServerResolver_priv::LOADING_SRV_RECORDS:
if (_priv->_dnsRequest->isTimeout ()) {
SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn());
onFailure ();
return;
}
if (_priv->_dnsRequest->isComplete ()) {
// Create a child node under _targetNode for each SRV entry of the response
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn());
int idx = 0;
for (DNS::SRVRequest::SRV_ptr entry : dynamic_cast<DNS::SRVRequest*> (_priv->_dnsRequest.get ())->entries) {
SG_LOG(SG_NETWORK, SG_DEBUG,
"MPServerResolver: SRV " << entry->priority << " " << entry->weight << " " << entry->port << " " << entry->target);
if( 0 == entry->port ) {
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: Skipping offline host " << entry->target );
continue;
}
SGPropertyNode * serverNode = _targetNode->getNode ("server", idx++, true);
serverNode->getNode ("hostname", true)->setStringValue (entry->target);
serverNode->getNode ("priority", true)->setIntValue (entry->priority);
serverNode->getNode ("weight", true)->setIntValue (entry->weight);
serverNode->getNode ("port", true)->setIntValue (entry->port);
}
// prepare an iterator over the server-nodes to be used later when loading the TXT records
_priv->_serverNodes = _targetNode->getChildren ("server");
_priv->_serverNodes_it = _priv->_serverNodes.begin ();
if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) {
// No SRV records found - flag failure
SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: no multiplayer servers defined via DNS");
onFailure ();
return;
}
_priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD;
break;
}
break;
// get the next TXT record
case MPServerResolver_priv::LOAD_NEXT_TXT_RECORD:
if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) {
// we are done with all servers
_priv->_state = MPServerResolver_priv::DONE;
break;
}
// send the DNS query for the hostnames TXT record
_priv->_dnsRequest = new DNS::TXTRequest ((*_priv->_serverNodes_it)->getStringValue ("hostname"));
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn());
_priv->_dnsClient->makeRequest (_priv->_dnsRequest);
_priv->_state = MPServerResolver_priv::LOADING_TXT_RECORDS;
break;
// check if response for TXT query
case MPServerResolver_priv::LOADING_TXT_RECORDS:
if (_priv->_dnsRequest->isTimeout ()) {
// on timeout, try proceeding with next server
SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn());
_priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD;
++_priv->_serverNodes_it;
break;
}
if (_priv->_dnsRequest->isComplete ()) {
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn());
// DNS::TXTRequest automatically extracts name=value entries for us, lets retrieve them
auto attributes = dynamic_cast<DNS::TXTRequest*> (_priv->_dnsRequest.get ())->attributes;
auto mpserverAttribute = attributes["flightgear-mpserver"];
if (!mpserverAttribute.empty ()) {
// we are only interested in the 'flightgear-mpserver=something' entry, this is a base64 encoded
// JSON string, convert this into a map<string,string>
MPServerProperties mpserverProperties (mpserverAttribute);
for (auto prop : mpserverProperties) {
// and store each as a node under our servers node.
SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver: TXT record attribute " << prop.first << "=" << prop.second);
// sanitize property name, don't allow dots or forward slash
auto propertyName = prop.first;
std::replace( propertyName.begin(), propertyName.end(), '.', '_');
std::replace( propertyName.begin(), propertyName.end(), '/', '_');
(*_priv->_serverNodes_it)->setStringValue (propertyName, prop.second);
}
} else {
SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: TXT record attributes empty");
}
// procede with the net node
++_priv->_serverNodes_it;
_priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD;
break;
}
break;
case MPServerResolver_priv::DONE:
onSuccess ();
return;
}
// Relinguish control, call me back on the next frame
globals->get_event_mgr ()->addEvent ("MPServerResolver_update", this, &MPServerResolver::run, .0);
}

View file

@ -0,0 +1,83 @@
/*
MPServerResolver.hxx - mpserver names lookup via DNS
Written and copyright by Torsten Dreyer - November 2016
This file is part of FlightGear.
FlightGear 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 2 of the License, or
(at your option) any later version.
FlightGear 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 FlightGear. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FG_MPSERVERRESOLVER_HXX
#define __FG_MPSERVERRESOLVER_HXX
#include <string>
#include <simgear/props/props.hxx>
class MPServerResolver {
public:
MPServerResolver();
virtual ~MPServerResolver();
void run();
/**
* Set the target property where the server-list gets stored
*
* \param value the property node to use as a target
*/
void setTarget( SGPropertyNode_ptr value ) { _targetNode = value; }
/**
* Set the dns domain name to query. This could be either a full qualified name including the
* service and the protocol like _fgms._udp.flightgear.org or just the domain name like
* flightgear.org. Use setService() and setProtocol() in the latter case.
*
* \param value the dnsname to use for the query.
*/
void setDnsName( const std::string & value ) { _dnsName = value; }
/** Set the service name to use for the query. Don't add the underscore, this gets added
* automatically. This builds the fully qualified DNS name to query, together with
* setProtocol() and setDnsName().
*
* \param value the service name to use for the query sans the leading underscore
*/
void setService( const std::string & value ) { _service = value; }
/** Set the protocol name to use for the query. Don't add the underscore, this gets added
* automatically. This builds the fully qualified DNS name to query, together with
* setService() and setDnsName().
*
* \param value the protocol name to use for the query sans the leading underscore
*/
void setProtocol( const std::string & value ) { _protocol = value; }
/** Handler to be called if the resolver process finishes with success. Does nothing by
* default and should be overridden be the user.
*/
virtual void onSuccess() {};
/** Handler to be called if the resolver process terminates with an error. Does nothing by
* default and should be overridden be the user.
*/
virtual void onFailure() {};
private:
class MPServerResolver_priv;
std::string _dnsName;
std::string _service;
std::string _protocol;
SGPropertyNode_ptr _targetNode;
MPServerResolver_priv * _priv;
};
#endif // __FG_MPSERVERRESOLVER_HXX

View file

@ -41,14 +41,14 @@
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/AIMultiplayer.hxx>
#include <Main/fg_props.hxx>
#include <Network/RemoteXMLRequest.hxx>
#include <Network/HTTPClient.hxx>
#include "multiplaymgr.hxx"
#include "mpmessages.hxx"
#include "MPServerResolver.hxx"
#include <FDM/flightProperties.hxx>
using namespace std;
@ -428,40 +428,60 @@ static bool do_multiplayer_disconnect(const SGPropertyNode * arg) {
// none
//
//////////////////////////////////////////////////////////////////////
static bool do_multiplayer_refreshserverlist(const SGPropertyNode * arg) {
FGMultiplayMgr * self = (FGMultiplayMgr*) globals->get_subsystem("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
if (!http) {
SG_LOG(SG_IO, SG_ALERT,
"do_multiplayer.refreshserverlist: HTTP client not running");
return false;
}
static bool
do_multiplayer_refreshserverlist (const SGPropertyNode * arg)
{
using namespace simgear;
string url(
fgGetString("/sim/multiplay/serverlist-url",
"http://liveries.flightgear.org/mpstatus/mpservers.xml"));
FGMultiplayMgr * self = (FGMultiplayMgr*) globals->get_subsystem ("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
if (url.empty()) {
SG_LOG(SG_IO, SG_ALERT,
"do_multiplayer.refreshserverlist: no URL given");
return false;
}
// MPServerResolver implementation to fill the mp server list
// deletes itself when done
class MyMPServerResolver : public MPServerResolver {
public:
MyMPServerResolver () :
MPServerResolver ()
{
setTarget (fgGetNode ("/sim/multiplay/server-list", true));
setDnsName (fgGetString ("/sim/multiplay/dns/query-dn", "flightgear.org"));
setService (fgGetString ("/sim/multiplay/dns/query-srv-service", "fgms"));
setProtocol (fgGetString ("/sim/multiplay/dns/query-srv-protocol", "udp"));
_completeNode->setBoolValue (false);
_failureNode->setBoolValue (false);
}
SGPropertyNode *targetnode = fgGetNode("/sim/multiplay/server-list", true);
SGPropertyNode *completeNode = fgGetNode("/sim/multiplay/got-servers", true);
SGPropertyNode *failureNode = fgGetNode("/sim/multiplay/get-servers-failure", true);
RemoteXMLRequest* req = new RemoteXMLRequest(url, targetnode);
req->setCompletionProp(completeNode);
req->setFailedProp(failureNode);
completeNode->setBoolValue(false);
failureNode->setBoolValue(false);
http->makeRequest(req);
return true;
~MyMPServerResolver ()
{
}
virtual void
onSuccess ()
{
SG_LOG(SG_NETWORK, SG_DEBUG, "MyMPServerResolver: trigger success");
_completeNode->setBoolValue (true);
delete this;
}
virtual void
onFailure ()
{
SG_LOG(SG_NETWORK, SG_DEBUG, "MyMPServerResolver: trigger failure");
_failureNode->setBoolValue (true);
delete this;
}
private:
SGPropertyNode *_completeNode = fgGetNode ("/sim/multiplay/got-servers", true);
SGPropertyNode *_failureNode = fgGetNode ("/sim/multiplay/get-servers-failure", true);
};
MyMPServerResolver * mpServerResolver = new MyMPServerResolver ();
mpServerResolver->run ();
return true;
}
//////////////////////////////////////////////////////////////////////

View file

@ -11,6 +11,7 @@ set(SOURCES
garmin.cxx
generic.cxx
HTTPClient.cxx
DNSClient.cxx
igc.cxx
joyclient.cxx
jsclient.cxx
@ -39,6 +40,7 @@ set(HEADERS
garmin.hxx
generic.hxx
HTTPClient.hxx
DNSClient.hxx
igc.hxx
joyclient.hxx
jsclient.hxx

71
src/Network/DNSClient.cxx Normal file
View file

@ -0,0 +1,71 @@
// HDNSClient.cxx -- Singleton DNS client object
//
// Written by James Turner, started April 2012.
// Based on HTTPClient from James Turner
// Copyright (C) 2012 James Turner
//
// 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 2 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "DNSClient.hxx"
#include <cassert>
#include <Main/fg_props.hxx>
#include <Include/version.h>
#include <simgear/sg_inlines.h>
using namespace simgear;
FGDNSClient::FGDNSClient() :
_inited(false)
{
}
FGDNSClient::~FGDNSClient()
{
}
void FGDNSClient::init()
{
if (_inited) {
return;
}
_dns.reset( new simgear::DNS::Client() );
_inited = true;
}
void FGDNSClient::postinit()
{
}
void FGDNSClient::shutdown()
{
_dns.reset();
_inited = false;
}
void FGDNSClient::update(double)
{
_dns->update();
}
void FGDNSClient::makeRequest(const simgear::DNS::Request_ptr& req)
{
_dns->makeRequest(req);
}

52
src/Network/DNSClient.hxx Normal file
View file

@ -0,0 +1,52 @@
// DNSClient.hxx -- Singleton DNS client object
//
// Written by Torsten Dreyer, started November 2016.
// Based on HTTPClient from James Turner
// Copyright (C) 2012 James Turner
//
// 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 2 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_DNS_CLIENT_HXX
#define FG_DNS_CLIENT_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/DNSClient.hxx>
#include <memory>
class FGDNSClient : public SGSubsystem
{
public:
FGDNSClient();
virtual ~FGDNSClient();
void makeRequest(const simgear::DNS::Request_ptr& req);
// simgear::HTTP::Client* client() { return _http.get(); }
// simgear::HTTP::Client const* client() const { return _http.get(); }
virtual void init();
virtual void postinit();
virtual void shutdown();
virtual void update(double);
static const char* subsystemName() { return "dns"; }
private:
bool _inited;
std::unique_ptr<simgear::DNS::Client> _dns;
};
#endif // FG_DNS_CLIENT_HXX