Pool Video Switch v2
Software video switch for distributed remote display in a lecture environment
discoverylistener.cpp
Go to the documentation of this file.
1 /*
2  * discoverylistener.cpp
3  *
4  * Created on: 25.01.2013
5  * Author: sr
6  */
7 
8 #include "discoverylistener.h"
9 #include "certmanager.h"
10 #include "../serverapp/serverapp.h"
11 #include "../../shared/settings.h"
12 #include "../../shared/network.h"
13 #include "../../shared/util.h"
14 #include <QCryptographicHash>
15 #include <QSslCertificate>
16 #include <QSslKey>
17 
18 #define UDPBUFSIZ 9000
19 #define SPAM_CUTOFF 50
20 #define SPAM_MODERATE_INTERVAL 6787
21 #define SPAM_MODERATE_AT_ONCE 100
22 
23 // static objects
24 
25 
26 // +++ static ++++ hash ip address +++
27 
28 
33  : QObject(parent), _socket(this)
34 {
35  if (!_socket.bind(QHostAddress::AnyIPv4, SERVICE_DISCOVERY_PORT)) {
36  qFatal("Could not bind to service discovery port %d", int(SERVICE_DISCOVERY_PORT));
37  }
38  connect(&_socket, &QUdpSocket::readyRead, this, &DiscoveryListener::onReadyRead);
40 }
41 
46 
52 static quint16 hash(const QHostAddress& host)
53 {
54  static quint16 seed1 = 0, seed2 = 0;
55  while (seed1 == 0) { // Make sure the algorithm uses different seeds each time the program is
56  // run to prevent hash collision attacks
57  seed1 = quint16(slxrand() & 0xffff);
58  seed2 = quint16(slxrand() & 0xffff);
59  }
60  quint8 data[16], len;
61  if (host.protocol() == QAbstractSocket::IPv4Protocol) {
62  // IPv4
63  quint32 addr = host.toIPv4Address();
64  len = 4;
65  memcpy(data, &addr, len);
66  } else if (host.protocol() == QAbstractSocket::IPv6Protocol) {
67  // IPv6
68  len = 16;
69  // Fast version (might break with future qt versions)
70  memcpy(data, host.toIPv6Address().c, len);
71  /* // --- Safe version (but better try to figure out a new fast way if above stops working ;))
72  Q_IPV6ADDR addr = host.toIPv6Address();
73  for (int i = 0; i < len; ++i)
74  data[i] = addr[i];
75  */
76  } else {
77  // Durr?
78  len = 2;
79  data[0] = quint8(slxrand());
80  data[1] = quint8(slxrand());
81  }
82  quint16 result = 0;
83  quint16 mod = seed1;
84  for (quint8 i = 0; i < len; ++i) {
85  result = quint16(((result << 1) + data[i]) ^ mod); // because of the shift this algo is not suitable for len(input) > 8
86  mod = quint16(mod + seed2 + data[i]);
87  }
88  return result;
89 }
90 
91 /*
92  * Overrides
93  */
94 
99 void DiscoveryListener::timerEvent(QTimerEvent* /* event */ )
100 {
101  for (int i = 0; i < SPAM_MODERATE_AT_ONCE; ++i) {
103  _counterResetPos = 0;
104  if (_packetCounter[_counterResetPos] > 10) {
106  } else if (_packetCounter[_counterResetPos] != 0) {
108  }
109  }
110 }
111 
112 /*
113  * Slots
114  */
115 
120 {
121  static int certFails = 0;
122  char data[UDPBUFSIZ];
123  QHostAddress addr;
124  quint16 port;
125  while (_socket.hasPendingDatagrams()) {
126  const qint64 size = _socket.readDatagram(data, UDPBUFSIZ, &addr, &port);
127  if (size <= 0)
128  continue;
129  const quint16 bucket = hash(addr) % SD_PACKET_TABLE_SIZE;
130  if (_packetCounter[bucket] > SPAM_CUTOFF) {
131  qDebug() << "SD: Potential (D)DoS from " << addr.toString();
132  // emit some signal and pop up a big warning that someone is flooding/ddosing the PVS SD
133  // ... on the other hand, will the user understand? ;)
134  continue;
135  }
136  ++_packetCounter[bucket];
137  _packet.reset();
138  if (_packet.readMessage(data, quint32(size)) != NM_READ_OK) {
139  qDebug() << "Corrupted service discovery message from" << addr.toString();
140  continue;
141  }
142  // Valid packet, process it:
143  const QByteArray iplist(_packet.getFieldBytes(_IPLIST));
144  const QByteArray hash(_packet.getFieldBytes(_HASH));
145  const QByteArray salt1(_packet.getFieldBytes(_SALT1));
146  const QByteArray salt2(_packet.getFieldBytes(_SALT2));
147  // For security, the salt has to be at least 16 bytes long
148  if (salt1.size() < 16 || salt2.size() < 16)
149  continue; // To make this more secure, you could remember the last X salts used, and ignore new packets using the same
150  // Check if the source IP of the packet matches any of the addresses given in the IP list
151  if (!Network::isAddressInList(QString::fromUtf8(iplist), addr.toString())) {
152  qDebug() << "SD: Client" << addr.toString() << "did not supply IP in list:" << iplist;
153  continue;
154  }
155  // If so, check if the submitted hash seems valid
156  if (genSha1(&serverApp->sessionNameArray(), &salt1, &iplist) != hash &&
157  !(serverApp->getCurrentRoom()->clientPositions.contains(addr.toString()))) {
158  // did not match local session name and client is not in same room.
159  qDebug() << "SD: Mismatch, neither session name match, nor client for current room" << serverApp->getCurrentRoom()->tutorIP;
160  qDebug() << "SD: Allowed clients from room:" << serverApp->getCurrentRoom()->clientPositions.keys();
161  continue;
162  }
163 
164  qDebug("Got matching discovery request...");
165  QByteArray myiplist(Network::interfaceAddressesToString().toUtf8());
166  QSslKey key;
167  QSslCertificate cert;
168  if (!CertManager::getPrivateKeyAndCert("manager2", key, cert)) {
169  if (++certFails > 5) {
171  }
172  continue;
173  }
174  QByteArray certhash(cert.digest(QCryptographicHash::Sha1));
175  // Reply to client
176  _packet.reset();
177  _packet.setField(_HASH, genSha1(&serverApp->sessionNameArray(), &salt2, &myiplist, &CLIENT_PORT_ARRAY, &certhash));
178  _packet.setField(_IPLIST, myiplist);
179  _packet.setField(_PORT, CLIENT_PORT_ARRAY);
180  _packet.setField(_CERT, certhash);
181  _packet.writeMessage(&_socket, addr, port);
182  }
183 }
void fatal()
Definition: certmanager.cpp:70
NetworkMessage _packet
#define serverApp
Definition: serverapp.h:31
bool writeMessage(QAbstractSocket *socket)
#define NM_READ_OK
#define SERVICE_DISCOVERY_PORT
Definition: settings.h:10
static quint16 hash(const QHostAddress &host)
hash
QByteArray genSha1(const QByteArray *a, const QByteArray *b, const QByteArray *c, const QByteArray *d, const QByteArray *e)
Definition: util.cpp:13
#define slxrand()
Definition: util.h:19
bool getPrivateKeyAndCert(const QString &name, QSslKey &key, QSslCertificate &cert)
Definition: certmanager.cpp:41
DiscoveryListener(QObject *parent)
DiscoveryListener::DiscoveryListener.
#define SD_PACKET_TABLE_SIZE
void timerEvent(QTimerEvent *event) override
Decrease packet counters per source IP in our "spam protection" table.
bool isAddressInList(const QString &list, const QString &address)
Definition: network.cpp:42
#define SPAM_MODERATE_INTERVAL
int readMessage(QAbstractSocket *socket)
static const QByteArray CLIENT_PORT_ARRAY(QString::number(CLIENT_PORT).toUtf8())
quint8 _packetCounter[SD_PACKET_TABLE_SIZE]
void onReadyRead()
Incoming UDP packet on service discovery port - handle.
#define SPAM_CUTOFF
QString interfaceAddressesToString()
Returns list of all addresses assigned to the interfaces of this machine.
Definition: network.cpp:22
#define SPAM_MODERATE_AT_ONCE
QByteArray getFieldBytes(const QByteArray &key) const
~DiscoveryListener() override
DiscoveryListener::~DiscoveryListener.
void setField(const QByteArray &key, const QByteArray &value)
#define UDPBUFSIZ