Implement support for the HAProxy proxy protocol
[quassel.git] / src / common / remotepeer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <utility>
22
23 #include <QtEndian>
24
25 #include <QHostAddress>
26 #include <QTimer>
27
28 #ifdef HAVE_SSL
29 #    include <QSslSocket>
30 #else
31 #    include <QTcpSocket>
32 #endif
33
34 #include "proxyline.h"
35 #include "remotepeer.h"
36 #include "util.h"
37
38 using namespace Protocol;
39
40 const quint32 maxMessageSize = 64 * 1024
41                                * 1024;  // This is uncompressed size. 64 MB should be enough for any sort of initData or backlog chunk
42
43 RemotePeer::RemotePeer(::AuthHandler* authHandler, QTcpSocket* socket, Compressor::CompressionLevel level, QObject* parent)
44     : Peer(authHandler, parent)
45     , _socket(socket)
46     , _compressor(new Compressor(socket, level, this))
47     , _signalProxy(nullptr)
48     , _proxyLine({})
49     , _useProxyLine(false)
50     , _heartBeatTimer(new QTimer(this))
51     , _heartBeatCount(0)
52     , _lag(0)
53     , _msgSize(0)
54 {
55     socket->setParent(this);
56     connect(socket, &QAbstractSocket::stateChanged, this, &RemotePeer::onSocketStateChanged);
57     connect(socket, selectOverload<QAbstractSocket::SocketError>(&QAbstractSocket::error), this, &RemotePeer::onSocketError);
58     connect(socket, &QAbstractSocket::disconnected, this, &Peer::disconnected);
59
60 #ifdef HAVE_SSL
61     auto* sslSocket = qobject_cast<QSslSocket*>(socket);
62     if (sslSocket) {
63         connect(sslSocket, &QSslSocket::encrypted, this, [this]() { emit secureStateChanged(true); });
64     }
65 #endif
66
67     connect(_compressor, &Compressor::readyRead, this, &RemotePeer::onReadyRead);
68     connect(_compressor, &Compressor::error, this, &RemotePeer::onCompressionError);
69
70     connect(_heartBeatTimer, &QTimer::timeout, this, &RemotePeer::sendHeartBeat);
71 }
72
73 void RemotePeer::onSocketStateChanged(QAbstractSocket::SocketState state)
74 {
75     if (state == QAbstractSocket::ClosingState) {
76         emit statusMessage(tr("Disconnecting..."));
77     }
78 }
79
80 void RemotePeer::onSocketError(QAbstractSocket::SocketError error)
81 {
82     emit socketError(error, socket()->errorString());
83 }
84
85 void RemotePeer::onCompressionError(Compressor::Error error)
86 {
87     close(QString("Compression error %1").arg(error));
88 }
89
90 QString RemotePeer::description() const
91 {
92     return address();
93 }
94
95 QHostAddress RemotePeer::hostAddress() const
96 {
97     if (_useProxyLine) {
98         return _proxyLine.sourceHost;
99     }
100     else if (socket()) {
101         return socket()->peerAddress();
102     }
103
104     return {};
105 }
106
107 QString RemotePeer::address() const
108 {
109     QHostAddress address = hostAddress();
110     if (address.isNull()) {
111         return {};
112     }
113     else {
114         return address.toString();
115     }
116 }
117
118 quint16 RemotePeer::port() const
119 {
120     if (_useProxyLine) {
121         return _proxyLine.sourcePort;
122     }
123     else if (socket()) {
124         return socket()->peerPort();
125     }
126
127     return 0;
128 }
129
130 ::SignalProxy* RemotePeer::signalProxy() const
131 {
132     return _signalProxy;
133 }
134
135 void RemotePeer::setSignalProxy(::SignalProxy* proxy)
136 {
137     if (proxy == _signalProxy)
138         return;
139
140     if (!proxy) {
141         _heartBeatTimer->stop();
142         disconnect(signalProxy(), nullptr, this, nullptr);
143         _signalProxy = nullptr;
144         if (isOpen())
145             close();
146     }
147     else {
148         if (signalProxy()) {
149             qWarning() << Q_FUNC_INFO << "Setting another SignalProxy not supported, ignoring!";
150             return;
151         }
152         _signalProxy = proxy;
153         connect(proxy, &SignalProxy::heartBeatIntervalChanged, this, &RemotePeer::changeHeartBeatInterval);
154         _heartBeatTimer->setInterval(proxy->heartBeatInterval() * 1000);
155         _heartBeatTimer->start();
156     }
157 }
158
159 void RemotePeer::changeHeartBeatInterval(int secs)
160 {
161     if (secs <= 0)
162         _heartBeatTimer->stop();
163     else {
164         _heartBeatTimer->setInterval(secs * 1000);
165         _heartBeatTimer->start();
166     }
167 }
168
169 int RemotePeer::lag() const
170 {
171     return _lag;
172 }
173
174 QTcpSocket* RemotePeer::socket() const
175 {
176     return _socket;
177 }
178
179 bool RemotePeer::isSecure() const
180 {
181     if (socket()) {
182         if (isLocal())
183             return true;
184 #ifdef HAVE_SSL
185         auto* sslSocket = qobject_cast<QSslSocket*>(socket());
186         if (sslSocket && sslSocket->isEncrypted())
187             return true;
188 #endif
189     }
190     return false;
191 }
192
193 bool RemotePeer::isLocal() const
194 {
195     return hostAddress() == QHostAddress::LocalHost ||
196            hostAddress() == QHostAddress::LocalHostIPv6;
197 }
198
199 bool RemotePeer::isOpen() const
200 {
201     return socket() && socket()->state() == QTcpSocket::ConnectedState;
202 }
203
204 void RemotePeer::close(const QString& reason)
205 {
206     if (!reason.isEmpty()) {
207         qWarning() << "Disconnecting:" << reason;
208     }
209
210     if (socket() && socket()->state() != QTcpSocket::UnconnectedState) {
211         socket()->disconnectFromHost();
212     }
213 }
214
215 void RemotePeer::onReadyRead()
216 {
217     QByteArray msg;
218     while (readMessage(msg)) {
219         if (SignalProxy::current())
220             SignalProxy::current()->setSourcePeer(this);
221
222         processMessage(msg);
223
224         if (SignalProxy::current())
225             SignalProxy::current()->setSourcePeer(nullptr);
226     }
227 }
228
229 bool RemotePeer::readMessage(QByteArray& msg)
230 {
231     if (_msgSize == 0) {
232         if (_compressor->bytesAvailable() < 4)
233             return false;
234         _compressor->read((char*) &_msgSize, 4);
235         _msgSize = qFromBigEndian<quint32>(_msgSize);
236
237         if (_msgSize > maxMessageSize) {
238             close("Peer tried to send package larger than max package size!");
239             return false;
240         }
241
242         if (_msgSize == 0) {
243             close("Peer tried to send an empty message!");
244             return false;
245         }
246     }
247
248     if (_compressor->bytesAvailable() < _msgSize) {
249         emit transferProgress(socket()->bytesAvailable(), _msgSize);
250         return false;
251     }
252
253     emit transferProgress(_msgSize, _msgSize);
254
255     msg.resize(_msgSize);
256     qint64 bytesRead = _compressor->read(msg.data(), _msgSize);
257     if (bytesRead != _msgSize) {
258         close("Premature end of data stream!");
259         return false;
260     }
261
262     _msgSize = 0;
263     return true;
264 }
265
266 void RemotePeer::writeMessage(const QByteArray& msg)
267 {
268     auto size = qToBigEndian<quint32>(msg.size());
269     _compressor->write((const char*)&size, 4, Compressor::NoFlush);
270     _compressor->write(msg.constData(), msg.size());
271 }
272
273 void RemotePeer::handle(const HeartBeat& heartBeat)
274 {
275     dispatch(HeartBeatReply(heartBeat.timestamp));
276 }
277
278 void RemotePeer::handle(const HeartBeatReply& heartBeatReply)
279 {
280     _heartBeatCount = 0;
281     emit lagUpdated(heartBeatReply.timestamp.msecsTo(QDateTime::currentDateTime().toUTC()) / 2);
282 }
283
284 void RemotePeer::sendHeartBeat()
285 {
286     if (signalProxy()->maxHeartBeatCount() > 0 && _heartBeatCount >= signalProxy()->maxHeartBeatCount()) {
287         qWarning() << "Disconnecting peer:" << description() << "(didn't receive a heartbeat for over"
288                    << _heartBeatCount * _heartBeatTimer->interval() / 1000 << "seconds)";
289         socket()->close();
290         _heartBeatTimer->stop();
291         return;
292     }
293
294     if (_heartBeatCount > 0) {
295         _lag = _heartBeatCount * _heartBeatTimer->interval();
296         emit lagUpdated(_lag);
297     }
298
299     dispatch(HeartBeat(QDateTime::currentDateTime().toUTC()));
300     ++_heartBeatCount;
301 }
302
303 void RemotePeer::setProxyLine(ProxyLine proxyLine)
304 {
305     _proxyLine = std::move(proxyLine);
306
307     if (socket()) {
308         if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
309             QList<QString> subnets = Quassel::optionValue("proxy-cidr").split(",");
310             for (const QString& subnet : subnets) {
311                 if (socket()->peerAddress().isInSubnet(QHostAddress::parseSubnet(subnet))) {
312                     _useProxyLine = true;
313                     return;
314                 }
315             }
316         }
317     }
318     _useProxyLine = false;
319 }