#include <QNetworkInterface>
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include "serverdiscovery.h"
#include <assert.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)
{
const quint16 port = (quint16)(qrand() % 10000) + 10000;
if (_discoverySocket.bind(QHostAddress::Any, port))
break;
if (tries == 0)
qFatal("Could not bind to any UDP port for server discovery.");
}
// Handle incoming messages
connect(&_discoverySocket, SIGNAL(readyRead()), this, SLOT(onUdpReadyRead()));
/* Setup the discovery timer */
_discoveryTimer.setInterval(_minDiscoveryInterval);
_discoveryTimer.setSingleShot(true);
//
connect(&_discoveryTimer, SIGNAL(timeout()), this, SLOT(doDiscovery()));
}
/***************************************************************************//**
* Dtor
*/
ServerDiscovery::~ServerDiscovery()
{
}
/***************************************************************************//**
* @brief start
*/
void ServerDiscovery::start(const QByteArray& sessionName, 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] = qrand() & 0xff;
_salt2[i] = qrand() & 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() << "Broadcasting 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 port;
while (_discoverySocket.hasPendingDatagrams())
{
// Discard any packets if discovery is stopped
if (!this->isActive()){
_discoverySocket.readDatagram(NULL, 0);
continue;
}
const qint64 size = _discoverySocket.readDatagram(data, UDPBUFSIZ, &addr, &port);
if (size <= 0) //|| _connection != NULL) // TODO CHECK
continue;
_packet.reset();
if (_packet.readMessage(data, (quint32)size) != NM_READ_OK)
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()))
{
++_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
++_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
emit serverDetected(addr.toString(), (quint16)QString::fromUtf8(port).toInt(), _nameBytes, cert, (_mgrIP == addr));
// Stop the discovery
this->stop();
}
}