1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
#include "../beaconserver.h"
// heartbeat processing for different protocol types
void BeaconServer::processHeartbeatGamespy0(const QNetworkDatagram &datagram,
const QString &senderAddress,
const unsigned short &senderPort,
const QString &receiveBuffer)
{
// parse key/value pairs and create a readable server label
QMultiHash<QString, QString> receiveData = parseGameSpy0Buffer(receiveBuffer);
QString senderAddressLabel = QStringLiteral("%1:%2").arg(senderAddress, QString::number(senderPort));
/*
* Receive a heartbeat.
* Update or insert the server in the database. If not authenticated, send a secure/validate challenge.
*/
if ( receiveData.contains("heartbeat") )
{
// store heartbeat and request authentication
UdpData newBeacon;
newBeacon.ip = senderAddress;
newBeacon.port = receiveData.value("heartbeat", "0").toUShort();
newBeacon.gamename = receiveData.value("gamename", "unknown");
// sanity check: known game(name)
if ( _coreObject->SupportedGames.contains( newBeacon.gamename ) )
{
// valid port and/or default port available?
if (newBeacon.port == 0)
{
// override with default port if possible
if ( _coreObject->SupportedGames.value(newBeacon.gamename).port > 0 )
{
newBeacon.port = _coreObject->SupportedGames.value( newBeacon.gamename ).port;
}
else // no valid port available. log and abort.
{
_coreObject->Log.logEvent("heartbeat", QStringLiteral("%1 invalid port for %2")
.arg(newBeacon.ip, newBeacon.gamename) );
return;
}
}
}
else // unknown game. log and abort.
{
_coreObject->Log.logEvent("unsupported", QStringLiteral("%1:%2 for unsupported game %3")
.arg(newBeacon.ip, QString::number(newBeacon.port), newBeacon.gamename) );
return;
}
// if this server already exists, update it
if ( updateServer(newBeacon.ip, newBeacon.port, newBeacon.gamename, true, false) )
{
// log update
_coreObject->Log.logEvent("heartbeat", QStringLiteral("%1:%2 for %3")
.arg(newBeacon.ip, QString::number(newBeacon.port), newBeacon.gamename) );
// no further tasks for this datagram
return;
}
// else:
// add to database
insertServer(newBeacon.ip, newBeacon.port, newBeacon.gamename, true);
// log type "uplink" for first heartbeat. from now on, this server is logged with type "heartbeat"
_coreObject->Log.logEvent("new", QStringLiteral("%1:%2 for %3")
.arg(newBeacon.ip, QString::number(newBeacon.port),newBeacon.gamename) );
/*
* Set up secure/validate challenge
*/
// some games are incompatible with secure/validate (within this protocol)
if ( _overrideValidateBeacon.contains(newBeacon.gamename) )
return;
// generate new challenge
newBeacon.secure = genChallengeString(6, false);
// store heartbeat in temporary list
_beaconList.remove(senderAddressLabel); // remove potential old challenges first
_beaconList.insert(senderAddressLabel, newBeacon);
// request authentication of remote server
_udpSocket.writeDatagram( datagram.makeReply( QStringLiteral("\\secure\\%1").arg(newBeacon.secure).toLatin1() ) );
// no further tasks for this datagram
return;
}
/*
* received response to authentication request
*/
if ( receiveData.contains("validate") )
{
// load existing information
UdpData valBeacon = _beaconList.value(senderAddressLabel);
// empty heartbeat UdpData? then received validate timed out and previous UdpData was removed
if (QHostAddress(valBeacon.ip).isNull() || valBeacon.gamename.length() <= 0)
{
_coreObject->Log.logEvent("secure", QStringLiteral("unexpected validate from %1").arg(senderAddress));
return;
}
// get response
AuthResult authResult = validateGamename(true, // this is a beacon
valBeacon.gamename,
receiveData.value("validate",""),
_coreObject->SupportedGames.value(valBeacon.gamename).cipher,
valBeacon.secure,
receiveData.value("enctype", "0").toInt() );
// compare with received response
if ( authResult.auth )
{
// server authenticated - log and add to database
_coreObject->Log.logEvent("secure", QStringLiteral("successful validate from %1:%2 for %3")
.arg(valBeacon.ip, QString::number(valBeacon.port),valBeacon.gamename) );
// update the existing entry that was already added in the initial heartbeat
updateServer(valBeacon.ip, valBeacon.port, valBeacon.gamename, false, true);
// remove from temporary/pending list
_beaconList.remove(senderAddressLabel);
}
else // log failed validate
{
// set validate false (but update last response time)
updateServer(valBeacon.ip, valBeacon.port, valBeacon.gamename, false, false);
_coreObject->Log.logEvent("secure", QStringLiteral("failed validate from %1:%2 for %3")
.arg(valBeacon.ip, QString::number(valBeacon.port),valBeacon.gamename) );
_coreObject->Log.logEvent("secure", QStringLiteral("secure: '%1', gamename: '%2', validate: '%3', expected: '%4'")
.arg(valBeacon.secure, valBeacon.gamename, receiveData.value("validate", "null"), authResult.validate ));
}
return;
}
/*
* status queries directed at masterserver
*/
if (receiveData.contains("secure") or
receiveData.contains("basic") or
receiveData.contains("info") or
receiveData.contains("rules") or
receiveData.contains("status") or
receiveData.contains("echo") )
{
// parse response query
QStringList response = replyQuery(receiveData);
// return response
_udpSocket.writeDatagram( datagram.makeReply( response.join("").toLatin1() ) );
// log incoming query
_coreObject->Log.logEvent("query", QStringLiteral("%1 queried us with %2")
.arg(senderAddress, receiveData.keys().join(", ") ) );
// log echo separately
if ( receiveData.contains("echo") )
_coreObject->Log.logEvent("echo", QStringLiteral("%1: '%2'")
.arg(senderAddress, receiveData.value("echo") ) );
return;
}
// ignore trailing queryid+final
if (receiveData.size() > 0)
{
// receive queryid and final from the query
receiveData.remove("queryid");
receiveData.remove("final");
// nothing remains? then ignore. otherwise, proceed to "unknown query"
if ( receiveData.size() <= 0)
return;
}
// received another type of query?
_coreObject->Log.logEvent("unknown", QStringLiteral("received unknown udp uplink %1 from %2")
.arg(receiveBuffer, senderAddress) );
}
|