summaryrefslogtreecommitdiffstats
path: root/src/server/net/discoverylistener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/net/discoverylistener.cpp')
-rw-r--r--src/server/net/discoverylistener.cpp165
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);
+ }
+}