summaryrefslogblamecommitdiffstats
path: root/src/client/net/serverdiscovery.cpp
blob: 9f1991c6424d9f7caf39916b0d899b4f124e8c02 (plain) (tree)




















































































































































































                                                                                                                                              

#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)
{
	_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(500);
	_discoveryTimer.setSingleShot(true);
	//
	connect(&_discoveryTimer, SIGNAL(timeout()), this, SLOT(doDiscovery()));
}

/***************************************************************************//**
 * Dtor
 */
ServerDiscovery::~ServerDiscovery()
{
}

/***************************************************************************//**
 * @brief start
 */
void ServerDiscovery::start(const QByteArray& sessionName)
{
	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(500);
	_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);
	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() < 5000)
		_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))
			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)
		{
			// 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);

		// Stop the discovery
		this->stop();
	}
}