FlightGear next
MPServerResolver.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: MPServerResolver.cxx
3 * SPDX-FileComment: mpserver names lookup via DNS. This file is part of FlightGear.
4 * SPDX-FileCopyrightText: Written and copyright by Torsten Dreyer - November 2016
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <algorithm>
9
10#include <Network/DNSClient.hxx>
11#include <Main/fg_props.hxx>
12#include <cJSON.h>
13#include <cstdlib>
14
15#include "MPServerResolver.hxx"
16
17using namespace simgear;
18
22class MPServerProperties : public std::map<std::string, std::string> {
23public:
24 MPServerProperties (std::string b64)
25 {
26 std::vector<unsigned char> b64dec;
27 simgear::strutils::decodeBase64 (b64, b64dec);
28 auto jsonString = std::string ((char*) b64dec.data (), b64dec.size ());
29 cJSON * json = ::cJSON_Parse (jsonString.c_str ());
30 if (json) {
31 for (int i = 0; i < ::cJSON_GetArraySize (json); i++) {
32 cJSON * cj = ::cJSON_GetArrayItem (json, i);
33 if (cj->string && cj->valuestring)
34 emplace (cj->string, cj->valuestring);
35 }
36 ::cJSON_Delete (json);
37 } else {
38 SG_LOG(SG_NETWORK,SG_WARN, "MPServerResolver: Can't parse JSON string '" << jsonString << "'" );
39 }
40 }
41};
42
44public:
45 enum {
48
50 DNS::Request_ptr _dnsRequest;
51 PropertyList _serverNodes;
52 PropertyList::const_iterator _serverNodes_it;
53};
54
56{
57 if (_priv->_dnsRequest) {
58 _priv->_dnsRequest->cancel();
59 }
60
61 delete _priv;
62}
63
68
69void
71{
72 //SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver::run() with state=" << _priv->_state );
73 switch (_priv->_state) {
74 // First call - fire DNS lookup for SRV records
76 if (!_priv->_dnsClient) {
77 SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: DNS subsystem not available.");
78 onFailure ();
79 return;
80 }
81
82 _priv->_dnsRequest = new DNS::SRVRequest (_dnsName, _service, _protocol);
83 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn());
84 _priv->_dnsClient->makeRequest (_priv->_dnsRequest);
86 break;
87
88 // Check if response from SRV Query
90 if (_priv->_dnsRequest->isTimeout ()) {
91 SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn());
92 onFailure ();
93 return;
94 }
95 if (_priv->_dnsRequest->isComplete ()) {
96 // Create a child node under _targetNode for each SRV entry of the response
97 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn());
98 int idx = 0;
99 for (DNS::SRVRequest::SRV_ptr entry : dynamic_cast<DNS::SRVRequest*> (_priv->_dnsRequest.get ())->entries) {
100 SG_LOG(SG_NETWORK, SG_DEBUG,
101 "MPServerResolver: SRV " << entry->priority << " " << entry->weight << " " << entry->port << " " << entry->target);
102 if( 0 == entry->port ) {
103 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: Skipping offline host " << entry->target );
104 continue;
105 }
106 SGPropertyNode * serverNode = _targetNode->getNode ("server", idx++, true);
107 serverNode->getNode ("hostname", true)->setStringValue (entry->target);
108 serverNode->getNode ("priority", true)->setIntValue (entry->priority);
109 serverNode->getNode ("weight", true)->setIntValue (entry->weight);
110 serverNode->getNode ("port", true)->setIntValue (entry->port);
111 }
112
113 // prepare an iterator over the server-nodes to be used later when loading the TXT records
114 _priv->_serverNodes = _targetNode->getChildren ("server");
115 _priv->_serverNodes_it = _priv->_serverNodes.begin ();
116 if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) {
117 // No SRV records found - flag failure
118 SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: no multiplayer servers defined via DNS");
119 onFailure ();
120 return;
121 }
123 break;
124 }
125 break;
126
127 // get the next TXT record
129 if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) {
130 // we are done with all servers
131 _priv->_state = MPServerResolver_priv::DONE;
132 break;
133 }
134
135 // send the DNS query for the hostnames TXT record
136 _priv->_dnsRequest = new DNS::TXTRequest ((*_priv->_serverNodes_it)->getStringValue ("hostname"));
137 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn());
138 _priv->_dnsClient->makeRequest (_priv->_dnsRequest);
140 break;
141
142 // check if response for TXT query
144 if (_priv->_dnsRequest->isTimeout ()) {
145 // on timeout, try proceeding with next server
146 SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn());
148 ++_priv->_serverNodes_it;
149 break;
150 }
151 if (_priv->_dnsRequest->isComplete ()) {
152 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn());
153 // DNS::TXTRequest automatically extracts name=value entries for us, lets retrieve them
154 auto attributes = dynamic_cast<DNS::TXTRequest*> (_priv->_dnsRequest.get ())->attributes;
155 auto mpserverAttribute = attributes["flightgear-mpserver"];
156 if (!mpserverAttribute.empty ()) {
157 // we are only interested in the 'flightgear-mpserver=something' entry, this is a base64 encoded
158 // JSON string, convert this into a map<string,string>
159 MPServerProperties mpserverProperties (mpserverAttribute);
160 for (auto prop : mpserverProperties) {
161 // and store each as a node under our servers node.
162 SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver: TXT record attribute " << prop.first << "=" << prop.second);
163 // sanitize property name, don't allow dots or forward slash
164 auto propertyName = prop.first;
165 std::replace( propertyName.begin(), propertyName.end(), '.', '_');
166 std::replace( propertyName.begin(), propertyName.end(), '/', '_');
167 (*_priv->_serverNodes_it)->setStringValue (propertyName, prop.second);
168 }
169 } else {
170 SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: TXT record attributes empty");
171 }
172
173 // procede with the net node
174 ++_priv->_serverNodes_it;
176 break;
177 }
178 break;
179
181 _priv->_dnsRequest.clear();
182 onSuccess();
183 return;
184 }
185
186 // Relinguish control, call me back on the next frame
187 globals->get_event_mgr ()->addEvent ("MPServerResolver_update", [this](){ this->run(); }, .0);
188}
189
#define i(x)
Build a name=value map from base64 encoded JSON string.
MPServerProperties(std::string b64)
enum MPServerResolver::MPServerResolver_priv::@147351107166011150165351241222254230171075054234 _state
PropertyList::const_iterator _serverNodes_it
virtual void onSuccess()
Handler to be called if the resolver process finishes with success.
virtual void onFailure()
Handler to be called if the resolver process terminates with an error.
FGGlobals * globals
Definition globals.cxx:142