diff options
Diffstat (limited to 'src/server/net/discoverylistener.cpp')
-rw-r--r-- | src/server/net/discoverylistener.cpp | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/server/net/discoverylistener.cpp b/src/server/net/discoverylistener.cpp new file mode 100644 index 0000000..051a972 --- /dev/null +++ b/src/server/net/discoverylistener.cpp @@ -0,0 +1,165 @@ +/* + * discoverylistener.cpp + * + * Created on: 25.01.2013 + * Author: sr + */ + +#include "discoverylistener.h" +#include "certmanager.h" +#include "../util/global.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 +++ + +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 (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] = (quint16)qrand(); + data[1] = (quint16)qrand(); + } + quint16 result = 0; + quint16 mod = seed1; + for (quint8 i = 0; i < len; ++i) + { + result = ((result << 1) + data[i]) ^ mod; // because of the shift this algo is not suitable for len(input) > 8 + mod += seed2 + data[i]; + } + return result; +} + +// +++++++++++++++++++++++++++++++++++ + +DiscoveryListener::DiscoveryListener() : + _socket(this), _counterResetPos(0) +{ + if (!_socket.bind(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); +} + +DiscoveryListener::~DiscoveryListener() +{ + // TODO Auto-generated destructor stub +} + +/** + * Overrides + */ + +/** + * Decrease packet counters + */ +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] -= 10; + else if (_packetCounter[_counterResetPos] != 0) + _packetCounter[_counterResetPos] = 0; + } +} + +/** + * Slots + */ + +void DiscoveryListener::onReadyRead() +{ + 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)) + 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(&Global::sessionNameArray(), &salt1, &iplist) != hash) + continue; // did not match local session name + qDebug("Got matching discovery request..."); + QByteArray myiplist(Network::interfaceAddressesToString().toUtf8()); + QSslKey key; + QSslCertificate cert; + CertManager::getPrivateKeyAndCert("manager", key, cert); + QByteArray certhash(cert.digest(QCryptographicHash::Sha1)); + // Reply to client + _packet.reset(); + _packet.setField(_HASH, genSha1(&Global::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); + } +} |