#include <QNetworkInterface>
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include "serverdiscovery.h"
#include "../util/util.h"
/**
* Ctor
*/
ServerDiscovery::ServerDiscovery(QObject *parent)
: QObject(parent),
_minDiscoveryInterval(500),
_maxDiscoveryInterval(5000)
{
_hashErrorCount = 0;
_ipErrorCount = 0;
/* Try to get a UDP port for server discovery */
int tries = 10;
while (tries-- != 0) {
quint16 port = quint16(16384 + slxrand() % 32768);
if (_discoverySocket.bind(QHostAddress::AnyIPv4, port))
break;
if (tries == 0)
qFatal("Could not bind to any UDP port for server discovery.");
}
// Handle incoming messages
connect(&_discoverySocket, &QUdpSocket::readyRead, this, &ServerDiscovery::onUdpReadyRead);
/* Setup the discovery timer */
_discoveryTimer.setInterval(_minDiscoveryInterval);
_discoveryTimer.setSingleShot(true);
//
connect(&_discoveryTimer, &QTimer::timeout, this, &ServerDiscovery::doDiscovery);
}
/**
* Dtor
*/
ServerDiscovery::~ServerDiscovery() = default;
/**
* @brief start
*/
void ServerDiscovery::start(const QByteArray& sessionName, const QString& mgrIP)
{
if (!mgrIP.isEmpty()) {
_mgrIP.setAddress(mgrIP);
} else {
_mgrIP = QHostAddress::Null;
}
//assert(!this->isActive());
// Set the session which is searched
_nameBytes = sessionName;
// Enable signal emittance
this->blockSignals(false);
// Reset the error counters
_hashErrorCount = _ipErrorCount = 0;
// reset anbd start the discovery timer
_discoveryTimer.setInterval(_minDiscoveryInterval);
_discoveryTimer.start();
}
/**
* @brief stop
*/
void ServerDiscovery::stop()
{
assert(this->isActive());
//Bock further signal emittance
this->blockSignals(true);
_discoveryTimer.stop();
}
/*******************************************************************************
* SLOTS
***************************************************************************//**
* @brief ConnectWindow::doDiscovery
*/
void ServerDiscovery::doDiscovery()
{
// Send discovery
_packet.reset();
QByteArray iplist(Network::interfaceAddressesToString().toUtf8());
// qDebug
QByteArray salt1(SALT_LEN, 0);
if (_salt2.size() < SALT_LEN)
_salt2.resize(SALT_LEN);
for (int i = 0; i < SALT_LEN; ++i) {
salt1[i] = char(slxrand() & 0xff);
_salt2[i] = char(slxrand() & 0xff);
}
_packet.reset();
_packet.setField(_HASH, genSha1(&_nameBytes, &salt1, &iplist));
_packet.setField(_SALT1, salt1);
_packet.setField(_SALT2, _salt2);
_packet.setField(_IPLIST, iplist);
// Check if specifig manager IP is given. If not broadcast in whole network.
if (_mgrIP != QHostAddress::Null) {
qDebug() << "Sending discovery to " << _mgrIP.toString();
if (!_packet.writeMessage(&_discoverySocket, _mgrIP, SERVICE_DISCOVERY_PORT))
qDebug("Failed");
} else {
foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) {
foreach (QNetworkAddressEntry entry, interface.addressEntries()) {
if (!entry.broadcast().isNull() && entry.ip() != QHostAddress::LocalHost && entry.ip() != QHostAddress::LocalHostIPv6) {
qDebug() << "Broadcasting to " << entry.broadcast().toString();
if (!_packet.writeMessage(&_discoverySocket, entry.broadcast(), SERVICE_DISCOVERY_PORT))
qDebug("FAILED");
}
}
}
qDebug("Broadcasting to 255.255.255.255");
if (!_packet.writeMessage(&_discoverySocket, QHostAddress::Broadcast, SERVICE_DISCOVERY_PORT))
qDebug("FAILED");
}
// Start the timer again with a larger interval
if (_discoveryTimer.interval() < _maxDiscoveryInterval)
_discoveryTimer.setInterval(_discoveryTimer.interval() * 2);
_discoveryTimer.start();
}
/**
* Handle incoming service discovery packets.
*/
void ServerDiscovery::onUdpReadyRead()
{
char data[UDPBUFSIZ];
QHostAddress addr;
quint16 peerPort;
while (_discoverySocket.hasPendingDatagrams()) {
// Discard any packets if discovery is stopped
if (!this->isActive()) {
_discoverySocket.readDatagram(nullptr, 0);
continue;
}
const qint64 size = _discoverySocket.readDatagram(data, UDPBUFSIZ, &addr, &peerPort);
if (size <= 0) //|| clientApp->connection() != nullptr) // TODO CHECK
continue;
_packet.reset();
if (_packet.readMessage(data, quint32(size)) != NM_READ_OK) {
qDebug() << "Corrupt discovery reply from" << addr.toString();
continue;
}
// Valid packet, process it:
const QByteArray hash(_packet.getFieldBytes(_HASH));
const QByteArray iplist(_packet.getFieldBytes(_IPLIST));
const QByteArray port(_packet.getFieldBytes(_PORT));
const QByteArray cert(_packet.getFieldBytes(_CERT));
// Check if the source IP of the packet matches any of the addresses given in the IP list
if (!Network::isAddressInList(QString::fromUtf8(iplist), addr.toString())) {
qDebug() << "Received bogus discovery reply from" << addr.toString() << "... Not in" << iplist;
++_ipErrorCount;
emit error(ErrorType::InvalidIpList, _hashErrorCount);
continue;
}
// If so, check if the submitted hash seems valid
if (genSha1(&_nameBytes, &_salt2, &iplist, &port, &cert) != hash && _mgrIP != addr) {
// did not match local session name, or other data was spoofed
qDebug() << "Received bogus session name in discovery reply from" << addr.toString();
++_hashErrorCount;
emit error(ErrorType::InvalidHash, _ipErrorCount);
continue;
}
/* Otherwise it's a valid reply */
qDebug() << "Server detected:"
<< addr.toString() + ":" + QString::fromUtf8(port) + "/" + _nameBytes;
// Tell that a server hs been found
bool ok = false;
const ushort iport = QString::fromUtf8(port).toUShort(&ok);
if (ok) {
emit serverDetected(addr.toString(), quint16(iport), _nameBytes, cert, (_mgrIP == addr));
} else {
qDebug() << "... but server advertises unparsable port" << port;
}
// Stop the discovery
this->stop();
}
}