/* * 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 #include #include #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); } }