summaryrefslogblamecommitdiffstats
path: root/src/server/net/discoverylistener.cpp
blob: cfeef82d8d2aadcc70ccae9439efdb6f05f03c94 (plain) (tree)
1
2
3
4
5
6
7
8
9








                              
                                   
















                                      






                                                                                
                                                                         
                                                                                                   

















                                                                                                


                                             

                                                                                                     

                                                  

                             
                                                               



                                                    
                                                                      








                                                                                                              
                

                        

                                          


                            
                                          

                                                                                                                                       



                      
  


            


                                                                                
   
                                                             
 
                                                         

                                                               
                                                            
                                                                                                         
                                                                   
                                                             
                 


         
  


        

                                                                                
   

                                     
                                 


                             
                                               



                                                                                        
                                                           






                                                                                                                
                                                                           












                                                                                                                                              

                                                                                                    



                                                                                           



                                                                                    





                                                                               


                                                                           
                                                                                                                                   





                                                           
/*
 * discoverylistener.cpp
 *
 *  Created on: 25.01.2013
 *      Author: sr
 */

#include "discoverylistener.h"
#include "certmanager.h"
#include "../serverapp/serverapp.h"
#include "../../shared/settings.h"
#include "../../shared/network.h"
#include "../../shared/util.h"
#include <QCryptographicHash>
#include <QSslCertificate>
#include <QSslKey>

#define UDPBUFSIZ 9000
#define SPAM_CUTOFF 50
#define SPAM_MODERATE_INTERVAL 6787
#define SPAM_MODERATE_AT_ONCE 100

// static objects


// +++ static ++++ hash ip address +++


/***************************************************************************//**
 * @brief DiscoveryListener::DiscoveryListener
 */
DiscoveryListener::DiscoveryListener() :
	_socket(this), _counterResetPos(0)
{
	if (!_socket.bind(QHostAddress::AnyIPv4, SERVICE_DISCOVERY_PORT))
		qFatal("Could not bind to service discovery port %d", int(SERVICE_DISCOVERY_PORT));
	connect(&_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
	for (int i = 0; i < SD_PACKET_TABLE_SIZE; ++i)
		_packetCounter[i] = 0;
	startTimer((SPAM_MODERATE_AT_ONCE * SPAM_MODERATE_INTERVAL) / SD_PACKET_TABLE_SIZE + 1);
}

/***************************************************************************//**
 * @brief DiscoveryListener::~DiscoveryListener
 */
DiscoveryListener::~DiscoveryListener()
{
}

/***************************************************************************//**
 * @brief hash
 * @param host
 * @return
 */
static quint16 hash(const QHostAddress& host)
{
	static quint16 seed1 = 0, seed2 = 0;
	while (seed1 == 0) { // Make sure the algorithm uses different seeds each time the program is
		// run to prevent hash collision attacks
		seed1 = quint16(qrand() & 0xffff);
		seed2 = quint16(qrand() & 0xffff);
	}
	quint8 data[16], len;
	if (host.protocol() == QAbstractSocket::IPv4Protocol) {
		// IPv4
		quint32 addr = host.toIPv4Address();
		len = 4;
		memcpy(data, &addr, len);
	} else if (host.protocol() == QAbstractSocket::IPv6Protocol) {
		// IPv6
		len = 16;
		// Fast version (might break with future qt versions)
		memcpy(data, host.toIPv6Address().c, len);
		/* // --- Safe version (but better try to figure out a new fast way if above stops working ;))
		 Q_IPV6ADDR addr = host.toIPv6Address();
		 for (int i = 0; i < len; ++i)
		 data[i] = addr[i];
		 */
	} else {
		// Durr?
		len = 2;
		data[0] = quint8(qrand());
		data[1] = quint8(qrand());
	}
	quint16 result = 0;
	quint16 mod = seed1;
	for (quint8 i = 0; i < len; ++i) {
		result = quint16(((result << 1) + data[i]) ^ mod); // because of the shift this algo is not suitable for len(input) > 8
		mod = quint16(mod + seed2 + data[i]);
	}
	return result;
}

/*
 * Overrides
 */

/***************************************************************************//**
 * @brief Decrease packet counters per source IP in our "spam protection" table.
 * @param event
 */
void DiscoveryListener::timerEvent(QTimerEvent* /* event */ )
{
	for (int i = 0; i < SPAM_MODERATE_AT_ONCE; ++i) {
		if (++_counterResetPos >= SD_PACKET_TABLE_SIZE)
			_counterResetPos = 0;
		if (_packetCounter[_counterResetPos] > 10) {
			_packetCounter[_counterResetPos] = quint8(_packetCounter[_counterResetPos] - 10);
		} else if (_packetCounter[_counterResetPos] != 0) {
			_packetCounter[_counterResetPos] = 0;
		}
	}
}

/*
 * Slots
 */

/***************************************************************************//**
 * @brief Incoming UDP packet on service discovery port - handle.
 */
void DiscoveryListener::onReadyRead()
{
	static int certFails = 0;
	char data[UDPBUFSIZ];
	QHostAddress addr;
	quint16 port;
	while (_socket.hasPendingDatagrams()) {
		const qint64 size = _socket.readDatagram(data, UDPBUFSIZ, &addr, &port);
		if (size <= 0)
			continue;
		const quint16 bucket = hash(addr) % SD_PACKET_TABLE_SIZE;
		if (_packetCounter[bucket] > SPAM_CUTOFF) {
			qDebug() << "SD: Potential (D)DoS from " << _socket.peerAddress().toString();
			// emit some signal and pop up a big warning that someone is flooding/ddosing the PVS SD
			// ... on the other hand, will the user understand? ;)
			continue;
		}
		++_packetCounter[bucket];
		_packet.reset();
		if (_packet.readMessage(data, quint32(size)) != NM_READ_OK)
			continue;
		// Valid packet, process it:
		const QByteArray iplist(_packet.getFieldBytes(_IPLIST));
		const QByteArray hash(_packet.getFieldBytes(_HASH));
		const QByteArray salt1(_packet.getFieldBytes(_SALT1));
		const QByteArray salt2(_packet.getFieldBytes(_SALT2));
		// For security, the salt has to be at least 16 bytes long
		if (salt1.size() < 16 || salt2.size() < 16)
			continue; // To make this more secure, you could remember the last X salts used, and ignore new packets using the same
		// 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()))
			continue;
		// If so, check if the submitted hash seems valid
		if (genSha1(&serverApp->sessionNameArray(), &salt1, &iplist) != hash &&
		        !(serverApp->getCurrentRoom()->clientPositions.contains(addr.toString()))) {
			// did not match local session name and client is not in same room.
			continue;
		}

		qDebug("Got matching discovery request...");
		QByteArray myiplist(Network::interfaceAddressesToString().toUtf8());
		QSslKey key;
		QSslCertificate cert;
		if (!CertManager::getPrivateKeyAndCert("manager", key, cert)) {
			if (++certFails > 5) {
				CertManager::fatal();
			}
			continue;
		}
		QByteArray certhash(cert.digest(QCryptographicHash::Sha1));
		// Reply to client
		_packet.reset();
		_packet.setField(_HASH, genSha1(&serverApp->sessionNameArray(), &salt2, &myiplist, &CLIENT_PORT_ARRAY, &certhash));
		_packet.setField(_IPLIST, myiplist);
		_packet.setField(_PORT, CLIENT_PORT_ARRAY);
		_packet.setField(_CERT, certhash);
		_packet.writeMessage(&_socket, addr, port);
	}
}