diff options
| author | Dark1-dev <shansarkar272@gmail.com> | 2023-03-01 21:30:57 +0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-01 21:30:57 +0600 |
| commit | 60a301a93b6057bb2c54ac04a7c38c38389037b3 (patch) | |
| tree | b09c5f8bc0045828c660654d8ed6744663856202 /src/UdpTasks/BeaconServer/Receive | |
| parent | c784240d1af68dbd8d0466822b34fd05d6ccdda1 (diff) | |
| download | Masterserver-Qt5-60a301a93b6057bb2c54ac04a7c38c38389037b3.tar.gz Masterserver-Qt5-60a301a93b6057bb2c54ac04a7c38c38389037b3.zip | |
Add files via upload
Diffstat (limited to 'src/UdpTasks/BeaconServer/Receive')
| -rw-r--r-- | src/UdpTasks/BeaconServer/Receive/heartbeatgamespy0.cpp | 186 | ||||
| -rw-r--r-- | src/UdpTasks/BeaconServer/Receive/replyquery.cpp | 113 | ||||
| -rw-r--r-- | src/UdpTasks/BeaconServer/Receive/udponread.cpp | 31 | ||||
| -rw-r--r-- | src/UdpTasks/BeaconServer/Receive/udpontimeout.cpp | 27 |
4 files changed, 357 insertions, 0 deletions
diff --git a/src/UdpTasks/BeaconServer/Receive/heartbeatgamespy0.cpp b/src/UdpTasks/BeaconServer/Receive/heartbeatgamespy0.cpp new file mode 100644 index 0000000..657330d --- /dev/null +++ b/src/UdpTasks/BeaconServer/Receive/heartbeatgamespy0.cpp @@ -0,0 +1,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) ); +} diff --git a/src/UdpTasks/BeaconServer/Receive/replyquery.cpp b/src/UdpTasks/BeaconServer/Receive/replyquery.cpp new file mode 100644 index 0000000..7df6549 --- /dev/null +++ b/src/UdpTasks/BeaconServer/Receive/replyquery.cpp @@ -0,0 +1,113 @@ +#include "../beaconserver.h" + +QStringList BeaconServer::replyQuery(const QMultiHash<QString, QString> &query) +{ + // initialise output + QStringList queryResponse; + + // gamespy uses incrementing query ids in the messages + _queryId = ( (_queryId > 99) ? 1 : _queryId + 1 ); + int querySubId = 1; + + // secure response + if ( query.contains("secure") and _coreObject->SupportedGames.contains( TYPE_GAMENAME)) + { + // sanity checks and cast to byte array + QByteArray secure = query.value("secure", "").toLatin1(); + QByteArray cipher = _coreObject->SupportedGames.value(TYPE_GAMENAME).cipher.toLatin1(); + int enctype = query.value("enctype", "0").toInt(); + QString validate = returnValidate(cipher, secure, enctype); + + queryResponse.append( + QStringLiteral("\\validate\\%1\\queryid\\%2.%3") + .arg(validate, QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // basic + if ( query.contains("basic") or query.contains("status") ) + { + queryResponse.append( + QStringLiteral("\\gamename\\%1" + "\\gamever\\%2" + "\\location\\0" + "\\queryid\\%3.%4") + .arg(TYPE_GAMENAME, SHORT_VER, QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // info + if ( query.contains("info") or query.contains("status") ) + { + // cast server statistics as player info + QString selectStats = "SELECT SUM(num_direct) AS serv_direct, " + "SUM(num_total) AS serv_total " + "FROM gameinfo "; + QSqlQuery statQuery; + statQuery.prepare(selectStats); + if ( ! statQuery.exec() ) + reportQuery(statQuery); // do NOT return/die! + + // get values + int serv_direct = -1; + int serv_total = -1; + if ( statQuery.next() ) + { + serv_direct = statQuery.value("serv_direct").toInt(); + serv_total = statQuery.value("serv_total").toInt(); + } + + queryResponse.append( + QStringLiteral("\\hostname\\%1" + "\\hostport\\%2" + "\\gametype\\%3" + "\\mapname\\333networks" + "\\numplayers\\%4" + "\\maxplayers\\%5" + "\\gamemode\\openplaying" + "\\queryid\\%6.%7") + .arg( _coreObject->Settings.PublicInformationSettings.hostname, + QString::number(_coreObject->Settings.ListenServerSettings.listenPort), + "Masterserver", // replace with "bare masterserver" or "integrated beacon/website checker", based on the settings + QString::number(serv_direct), + QString::number(serv_total), + QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // rules + if ( query.contains("rules") or query.contains("status") ) + { + // compile information about the specific software + QString mutators; + mutators.append( QStringLiteral("buildtype: %1, buildtime: %2, Qt version: %3, author: %4") + .arg(BUILD_TYPE, + BUILD_TIME, + qVersion(), + BUILD_AUTHOR) ); + + queryResponse.append( + QStringLiteral("\\mutators\\%1" + "\\AdminName\\%2" + "\\AdminEMail\\%3" + "\\queryid\\%4.%5") + .arg( mutators, + _coreObject->Settings.PublicInformationSettings.adminName, + _coreObject->Settings.PublicInformationSettings.contact, + QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // echo: reply with echo_reply + if (query.contains("echo")) + { + queryResponse.append(QStringLiteral("\\echo_reply\\%1\\queryid\\%2.%3") + .arg(query.value("echo"), QString::number(_queryId), QString::number(querySubId++)) + ); + } + + // end query with final + queryResponse.append("\\final\\"); + + return queryResponse; +} diff --git a/src/UdpTasks/BeaconServer/Receive/udponread.cpp b/src/UdpTasks/BeaconServer/Receive/udponread.cpp new file mode 100644 index 0000000..b6d93d5 --- /dev/null +++ b/src/UdpTasks/BeaconServer/Receive/udponread.cpp @@ -0,0 +1,31 @@ +#include "../beaconserver.h" + +void BeaconServer::onUdpRead() +{ + while ( _udpSocket.hasPendingDatagrams() ) + { + // get sender and payload + QNetworkDatagram datagram = _udpSocket.receiveDatagram(); + QString senderAddress = QHostAddress( datagram.senderAddress().toIPv4Address() ).toString(); + int senderPort = datagram.senderPort(); + QString receiveBuffer = datagram.data(); + receiveBuffer = receiveBuffer.toLatin1(); + + // shorthand label + QString senderAddressLabel = QStringLiteral("%1:%2").arg(senderAddress, QString::number(senderPort)); + _coreObject->Log.logEvent("udp", QStringLiteral("%1 sent '%2'").arg(senderAddressLabel, receiveBuffer ) ); + + // ignore empty data packets (query port forwarded to a game port) + if (receiveBuffer.length() <= 0) + continue; + + // determine protocol and response based on the first character (backslash, byte value, ... ) + unsigned short protocol_chooser = receiveBuffer.at(0).unicode(); + if (protocol_chooser != 92) + continue; + + // Protocol is GameSpy v0 (\key\value format) used by Unreal/UT, Postal 2, Rune, Deus Ex, Serious Sam, others + processHeartbeatGamespy0(datagram, senderAddress, senderPort, receiveBuffer); + + } // while pending datagrams +} diff --git a/src/UdpTasks/BeaconServer/Receive/udpontimeout.cpp b/src/UdpTasks/BeaconServer/Receive/udpontimeout.cpp new file mode 100644 index 0000000..7767029 --- /dev/null +++ b/src/UdpTasks/BeaconServer/Receive/udpontimeout.cpp @@ -0,0 +1,27 @@ +#include "../beaconserver.h" + +// soft timeout. every <timeout>, remove all servers older than <timeout> from the list. +// that means a server has a maximum wait/response time of 2*<timeout> +void BeaconServer::onUdpTimedOut() +{ + // iterate through the server list + QHashIterator<QString, UdpData> list(_beaconList); + while (list.hasNext()) + { + // select + list.next(); + + // check passed time: add date < remove date? + qint64 currentTime = QDateTime::currentSecsSinceEpoch(); + if ( list.value().time < currentTime - (_timeOutTime_ms / 1000) ) + { + // if timeout has passed, remove the server from the list + _coreObject->Log.logEvent("udp", QStringLiteral("%1 timed out").arg(list.key())); + _beaconList.remove(list.key()); + } + } + + // periodically emit readyread signal to avoid issues similar to StatusChecker + // readyread function will handle any inconsistencies + emit _udpSocket.readyRead(); +} |
