From 8f92b3f08df9f4eb8fd243ccec6aa9d4b563ec23 Mon Sep 17 00:00:00 2001 From: Janne Koschinski Date: Tue, 19 Mar 2019 12:53:46 +0100 Subject: [PATCH] Implement a metrics daemon to expose Prometheus metrics - logs IRC and client sessions, IRC traffic in/out, login attempts per user - logs certificate expiry time --- src/common/quassel.cpp | 3 + src/core/CMakeLists.txt | 1 + src/core/core.cpp | 16 ++ src/core/core.h | 3 + src/core/coreauthhandler.cpp | 7 + src/core/coreauthhandler.h | 2 + src/core/corenetwork.cpp | 27 +++ src/core/corenetwork.h | 1 + src/core/coresession.cpp | 17 ++ src/core/coresession.h | 2 + src/core/metricsserver.cpp | 408 +++++++++++++++++++++++++++++++++++ src/core/metricsserver.h | 86 ++++++++ src/core/sslserver.cpp | 13 ++ src/core/sslserver.h | 8 + 14 files changed, 594 insertions(+) create mode 100644 src/core/metricsserver.cpp create mode 100644 src/core/metricsserver.h diff --git a/src/common/quassel.cpp b/src/common/quassel.cpp index 0c6b8630..94c5ba2f 100644 --- a/src/common/quassel.cpp +++ b/src/common/quassel.cpp @@ -366,6 +366,9 @@ void Quassel::setupCliParser() {"require-ssl", tr("Require SSL for remote (non-loopback) client connections.")}, {"ssl-cert", tr("Specify the path to the SSL certificate."), tr("path"), "configdir/quasselCert.pem"}, {"ssl-key", tr("Specify the path to the SSL key."), tr("path"), "ssl-cert-path"}, + {"metrics-daemon", tr("Enable metrics API.")}, + {"metrics-port", tr("The port quasselcore will listen at for metrics requests. Only meaningful with --metrics-daemon."), tr("port"), "9558"}, + {"metrics-listen", tr("The address(es) quasselcore will listen on for metrics requests. Same format as --listen."), tr("
[,...]"), "::1,127.0.0.1"} #endif }; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a4cc8099..230f370d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(${TARGET} PRIVATE eventstringifier.cpp identserver.cpp ircparser.cpp + metricsserver.cpp netsplit.cpp oidentdconfiggenerator.cpp postgresqlstorage.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index a4133eb1..b0c02c12 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -213,6 +213,14 @@ void Core::init() _identServer = new IdentServer(this); } + if (Quassel::isOptionSet("metrics-daemon")) { + _metricsServer = new MetricsServer(this); +#ifdef HAVE_SSL + _server.setMetricsServer(_metricsServer); + _v6server.setMetricsServer(_metricsServer); +#endif + } + Quassel::registerReloadHandler([]() { // Currently, only reloading SSL certificates and the sysident cache is supported if (Core::instance()) { @@ -674,6 +682,10 @@ bool Core::startListening() _identServer->startListening(); } + if (_metricsServer) { + _metricsServer->startListening(); + } + return success; } @@ -683,6 +695,10 @@ void Core::stopListening(const QString& reason) _identServer->stopListening(reason); } + if (_metricsServer) { + _metricsServer->stopListening(reason); + } + bool wasListening = false; if (_server.isListening()) { wasListening = true; diff --git a/src/core/core.h b/src/core/core.h index ef04b77d..b2c6c1ef 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -45,6 +45,7 @@ #include "deferredptr.h" #include "identserver.h" #include "message.h" +#include "metricsserver.h" #include "oidentdconfiggenerator.h" #include "sessionthread.h" #include "singleton.h" @@ -655,6 +656,7 @@ public: inline OidentdConfigGenerator* oidentdConfigGenerator() const { return _oidentdConfigGenerator; } inline IdentServer* identServer() const { return _identServer; } + inline MetricsServer* metricsServer() const { return _metricsServer; } static const int AddClientEventId; @@ -785,6 +787,7 @@ private: QDateTime _startTime; IdentServer* _identServer{nullptr}; + MetricsServer* _metricsServer{nullptr}; bool _initialized{false}; bool _configured{false}; diff --git a/src/core/coreauthhandler.cpp b/src/core/coreauthhandler.cpp index d8f81abe..ad46352b 100644 --- a/src/core/coreauthhandler.cpp +++ b/src/core/coreauthhandler.cpp @@ -29,6 +29,7 @@ CoreAuthHandler::CoreAuthHandler(QTcpSocket* socket, QObject* parent) : AuthHandler(parent) , _peer(nullptr) + , _metricsServer(Core::instance()->metricsServer()) , _magicReceived(false) , _legacy(false) , _clientRegistered(false) @@ -247,9 +248,15 @@ void CoreAuthHandler::handle(const Protocol::Login& msg) qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(socket()->peerAddress().toString(), msg.user)); _peer->dispatch(Protocol::LoginFailed(tr( "Invalid username or password!
The username/password combination you supplied could not be found in the database."))); + if (_metricsServer) { + _metricsServer->addLoginAttempt(msg.user, false); + } return; } _peer->dispatch(Protocol::LoginSuccess()); + if (_metricsServer) { + _metricsServer->addLoginAttempt(uid, true); + } qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).") .arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt()))); diff --git a/src/core/coreauthhandler.h b/src/core/coreauthhandler.h index 0cb9a962..e47be38b 100644 --- a/src/core/coreauthhandler.h +++ b/src/core/coreauthhandler.h @@ -22,6 +22,7 @@ #define COREAUTHHANDLER_H #include "authhandler.h" +#include "metricsserver.h" #include "peerfactory.h" #include "remotepeer.h" #include "types.h" @@ -60,6 +61,7 @@ private slots: private: RemotePeer* _peer; + MetricsServer* _metricsServer; bool _magicReceived; bool _legacy; diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index e6699c12..45443975 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -37,6 +37,7 @@ CoreNetwork::CoreNetwork(const NetworkId& networkid, CoreSession* session) : Network(networkid, session) , _coreSession(session) , _userInputHandler(new CoreUserInputHandler(this)) + , _metricsServer(Core::instance()->metricsServer()) , _autoReconnectCount(0) , _quitRequested(false) , _disconnectExpected(false) @@ -184,6 +185,10 @@ void CoreNetwork::connectToIrc(bool reconnecting) _socketId = Core::instance()->identServer()->addWaitingSocket(); } + if (_metricsServer) { + _metricsServer->addNetwork(userId()); + } + if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) { _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000); if (unlimitedReconnectRetries()) @@ -290,6 +295,9 @@ void CoreNetwork::disconnectFromIrc(bool requested, const QString& reason, bool } disablePingTimeout(); _msgQueue.clear(); + if (_metricsServer) { + _metricsServer->messageQueue(userId(), 0); + } IrcUser* me_ = me(); if (me_) { @@ -366,6 +374,9 @@ void CoreNetwork::putRawLine(const QByteArray& s, bool prepend) // Add to back, waiting in order _msgQueue.append(s); } + if (_metricsServer) { + _metricsServer->messageQueue(userId(), _msgQueue.size()); + } } } @@ -505,6 +516,9 @@ void CoreNetwork::onSocketHasData() { while (socket.canReadLine()) { QByteArray s = socket.readLine(); + if (_metricsServer) { + _metricsServer->receiveDataNetwork(userId(), s.size()); + } if (s.endsWith("\r\n")) s.chop(2); else if (s.endsWith("\n")) @@ -605,6 +619,9 @@ void CoreNetwork::onSocketDisconnected() { disablePingTimeout(); _msgQueue.clear(); + if (_metricsServer) { + _metricsServer->messageQueue(userId(), 0); + } _autoWhoCycleTimer.stop(); _autoWhoTimer.stop(); @@ -644,6 +661,10 @@ void CoreNetwork::onSocketDisconnected() else _autoReconnectTimer.start(); } + + if (_metricsServer) { + _metricsServer->removeNetwork(userId()); + } } void CoreNetwork::onSocketStateChanged(QAbstractSocket::SocketState socketState) @@ -1503,6 +1524,9 @@ void CoreNetwork::fillBucketAndProcessQueue() // As long as there's tokens available and messages remaining, sending messages from the queue while (!_msgQueue.empty() && _tokenBucket > 0) { writeToSocket(_msgQueue.takeFirst()); + if (_metricsServer) { + _metricsServer->messageQueue(userId(), _msgQueue.size()); + } } } @@ -1515,6 +1539,9 @@ void CoreNetwork::writeToSocket(const QByteArray& data) } socket.write(data); socket.write("\r\n"); + if (_metricsServer) { + _metricsServer->transmitDataNetwork(userId(), data.size() + 2); + } if (!_skipMessageRates) { // Only subtract from the token bucket if message rate limiting is enabled _tokenBucket--; diff --git a/src/core/corenetwork.h b/src/core/corenetwork.h index bcab1df7..01d71edc 100644 --- a/src/core/corenetwork.h +++ b/src/core/corenetwork.h @@ -521,6 +521,7 @@ private: qint64 _socketId{0}; CoreUserInputHandler* _userInputHandler; + MetricsServer* _metricsServer; QHash _channelKeys; // stores persistent channels and their passwords, if any diff --git a/src/core/coresession.cpp b/src/core/coresession.cpp index 119d73d7..f4196b0a 100644 --- a/src/core/coresession.cpp +++ b/src/core/coresession.cpp @@ -82,6 +82,7 @@ CoreSession::CoreSession(UserId uid, bool restoreState, bool strictIdentEnabled, , _processMessages(false) , _ignoreListManager(this) , _highlightRuleManager(this) + , _metricsServer(Core::instance()->metricsServer()) { SignalProxy* p = signalProxy(); p->setHeartBeatInterval(30); @@ -151,6 +152,10 @@ CoreSession::CoreSession(UserId uid, bool restoreState, bool strictIdentEnabled, restoreSessionState(); emit initialized(); + + if (_metricsServer) { + _metricsServer->addSession(user(), Core::instance()->strictSysIdent(_user)); + } } void CoreSession::shutdown() @@ -171,6 +176,10 @@ void CoreSession::shutdown() // Nothing to do, suicide so the core can shut down deleteLater(); } + + if (_metricsServer) { + _metricsServer->removeSession(user()); + } } void CoreSession::onNetworkDisconnected(NetworkId networkId) @@ -257,6 +266,10 @@ void CoreSession::addClient(RemotePeer* peer) _coreInfo->setConnectedClientData(signalProxy()->peerCount(), signalProxy()->peerData()); signalProxy()->setTargetPeer(nullptr); + + if (_metricsServer) { + _metricsServer->addClient(user()); + } } void CoreSession::addClient(InternalPeer* peer) @@ -271,6 +284,10 @@ void CoreSession::removeClient(Peer* peer) if (p) qInfo() << qPrintable(tr("Client")) << p->description() << qPrintable(tr("disconnected (UserId: %1).").arg(user().toInt())); _coreInfo->setConnectedClientData(signalProxy()->peerCount(), signalProxy()->peerData()); + + if (_metricsServer) { + _metricsServer->removeClient(user()); + } } QHash CoreSession::persistentChannels(NetworkId id) const diff --git a/src/core/coresession.h b/src/core/coresession.h index 99cd5932..cfa04e3f 100644 --- a/src/core/coresession.h +++ b/src/core/coresession.h @@ -32,6 +32,7 @@ #include "coreignorelistmanager.h" #include "coreinfo.h" #include "message.h" +#include "metricsserver.h" #include "peer.h" #include "protocol.h" #include "storage.h" @@ -279,6 +280,7 @@ private: bool _processMessages; CoreIgnoreListManager _ignoreListManager; CoreHighlightRuleManager _highlightRuleManager; + MetricsServer* _metricsServer{nullptr}; }; struct NetworkInternalMessage diff --git a/src/core/metricsserver.cpp b/src/core/metricsserver.cpp new file mode 100644 index 00000000..c0f4bbe6 --- /dev/null +++ b/src/core/metricsserver.cpp @@ -0,0 +1,408 @@ +/*************************************************************************** + * Copyright (C) 2005-2019 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "metricsserver.h" + +#include + +#include +#include +#include +#include +#include + +#include "core.h" +#include "corenetwork.h" + +MetricsServer::MetricsServer(QObject* parent) + : QObject(parent) +{ + connect(&_server, &QTcpServer::newConnection, this, &MetricsServer::incomingConnection); + connect(&_v6server, &QTcpServer::newConnection, this, &MetricsServer::incomingConnection); +} + +bool MetricsServer::startListening() +{ + bool success = false; + + uint16_t port = Quassel::optionValue("metrics-port").toUShort(); + + const QString listen = Quassel::optionValue("metrics-listen"); + const QStringList listen_list = listen.split(",", QString::SkipEmptyParts); + for (const QString& listen_term : listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully + QHostAddress addr; + if (!addr.setAddress(listen_term)) { + qCritical() << qPrintable( + tr("Invalid listen address %1") + .arg(listen_term) + ); + } + else { + switch (addr.protocol()) { + case QAbstractSocket::IPv6Protocol: + if (_v6server.listen(addr, port)) { + qInfo() << qPrintable( + tr("Listening for metrics requests on IPv6 %1 port %2") + .arg(addr.toString()) + .arg(_v6server.serverPort()) + ); + success = true; + } + else + qWarning() << qPrintable( + tr("Could not open IPv6 interface %1:%2: %3") + .arg(addr.toString()) + .arg(port) + .arg(_v6server.errorString())); + break; + case QAbstractSocket::IPv4Protocol: + if (_server.listen(addr, port)) { + qInfo() << qPrintable( + tr("Listening for metrics requests on IPv4 %1 port %2") + .arg(addr.toString()) + .arg(_server.serverPort()) + ); + success = true; + } + else { + // if v6 succeeded on Any, the port will be already in use - don't display the error then + if (!success || _server.serverError() != QAbstractSocket::AddressInUseError) + qWarning() << qPrintable( + tr("Could not open IPv4 interface %1:%2: %3") + .arg(addr.toString()) + .arg(port) + .arg(_server.errorString())); + } + break; + default: + qCritical() << qPrintable( + tr("Invalid listen address %1, unknown network protocol") + .arg(listen_term) + ); + break; + } + } + } + + if (!success) { + qWarning() << qPrintable(tr("Metrics could not open any network interfaces to listen on! No metrics functionality will be available")); + } + + return success; +} + +void MetricsServer::stopListening(const QString& msg) +{ + bool wasListening = false; + + if (_server.isListening()) { + wasListening = true; + _server.close(); + } + if (_v6server.isListening()) { + wasListening = true; + _v6server.close(); + } + + if (wasListening) { + if (msg.isEmpty()) + qInfo() << "No longer listening for metrics requests."; + else + qInfo() << qPrintable(msg); + } +} + +void MetricsServer::incomingConnection() +{ + auto server = qobject_cast(sender()); + Q_ASSERT(server); + while (server->hasPendingConnections()) { + QTcpSocket* socket = server->nextPendingConnection(); + connect(socket, &QIODevice::readyRead, this, &MetricsServer::respond); + connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + } +} + +QString parseHttpString(const QByteArray& request, int& index) +{ + QString content; + int end = request.indexOf(' ', index); + if (end == -1) { + end = request.length(); + } + + if (end > -1) { + content = QString::fromUtf8(request.mid(index, end - index)); + index = end + 1; + } + else { + index = request.length(); + } + return content; +} + +void MetricsServer::respond() +{ + auto socket = qobject_cast(sender()); + Q_ASSERT(socket); + + if (!socket->canReadLine()) { + return; + } + + int index = 0; + QString verb; + QString requestPath; + QString version; + QByteArray request; + for (int i = 0; i < 5 && verb == ""; i++) { + request = socket->readLine(4096); + if (request.endsWith("\r\n")) { + request.chop(2); + } + else if (request.endsWith("\n")) { + request.chop(1); + } + + verb = parseHttpString(request, index); + requestPath = parseHttpString(request, index); + version = parseHttpString(request, index); + } + + if (requestPath == "/metrics") { + if (version == "HTTP/1.1") { + socket->write( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain; version=0.0.4\r\n" + "Connection: close\r\n" + "\r\n" + ); + } + int64_t timestamp = QDateTime::currentMSecsSinceEpoch(); + for (const auto& key : _sessions.keys()) { + const QString& name = _sessions[key]; + socket->write("# HELP quassel_network_bytes_received Number of currently open connections from quassel clients\n"); + socket->write("# TYPE quassel_client_sessions gauge\n"); + socket->write( + QString("quassel_client_sessions{user=\"%1\"} %2 %3\n") + .arg(name) + .arg(_clientSessions.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write("# HELP quassel_network_bytes_received Number of currently open connections to IRC networks\n"); + socket->write("# TYPE quassel_network_sessions gauge\n"); + socket->write( + QString("quassel_network_sessions{user=\"%1\"} %2 %3\n") + .arg(name) + .arg(_networkSessions.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write("# HELP quassel_network_bytes_received Amount of bytes sent to IRC\n"); + socket->write("# TYPE quassel_network_bytes_sent counter\n"); + socket->write( + QString("quassel_network_bytes_sent{user=\"%1\"} %2 %3\n") + .arg(name) + .arg(_networkDataTransmit.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write("# HELP quassel_network_bytes_received Amount of bytes received from IRC\n"); + socket->write("# TYPE quassel_network_bytes_received counter\n"); + socket->write( + QString("quassel_network_bytes_received{user=\"%1\"} %2 %3\n") + .arg(name) + .arg(_networkDataReceive.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write("# HELP quassel_message_queue The number of messages currently queued for that user\n"); + socket->write("# TYPE quassel_message_queue gauge\n"); + socket->write( + QString("quassel_message_queue{user=\"%1\"} %2 %3\n") + .arg(name) + .arg(_messageQueue.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write("# HELP quassel_login_attempts The number of times the user has attempted to log in\n"); + socket->write("# TYPE quassel_login_attempts counter\n"); + socket->write( + QString("quassel_login_attempts{user=\"%1\",successful=\"false\"} %2 %3\n") + .arg(name) + .arg(_loginAttempts.value(key, 0) - _successfulLogins.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + socket->write( + QString("quassel_login_attempts{user=\"%1\",successful=\"true\"} %2 %3\n") + .arg(name) + .arg(_successfulLogins.value(key, 0)) + .arg(timestamp) + .toUtf8() + ); + } + if (!_certificateExpires.isNull()) { + socket->write("# HELP quassel_ssl_expire_time_seconds Expiration of the current TLS certificate in unixtime\n"); + socket->write("# TYPE quassel_ssl_expire_time_seconds gauge\n"); + socket->write( + QString("quassel_ssl_expire_time_seconds %1 %2\n") + .arg(_certificateExpires.toMSecsSinceEpoch() / 1000) + .arg(timestamp) + .toUtf8() + ); + } + socket->write( + QString("quassel_login{successful=\"false\"} %1\n") + .arg(QString::number((float) _loginFailed)) + .toUtf8() + ); + socket->write( + QString("quassel_login{successful=\"true\"} %1\n") + .arg(QString::number((float) _loginSuccessful)) + .toUtf8() + ); + socket->close(); + } + else if (requestPath == "/healthz") { + if (version == "HTTP/1.1") { + socket->write( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + ); + } + socket->write( + "OK\n" + ); + socket->close(); + } + else { + if (version == "HTTP/1.1") { + socket->write( + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + ); + } + socket->write( + QString( + "\n" + "404 Not Found\n" + "\n" + "

404 Not Found

\n" + "
quassel %1
\n" + "\n" + "\n") + .arg(Quassel::buildInfo().baseVersion) + .toUtf8() + ); + socket->close(); + } +} + +void MetricsServer::addLoginAttempt(UserId user, bool successful) { + _loginAttempts.insert(user, _loginAttempts.value(user, 0) + 1); + if (successful) { + _successfulLogins.insert(user, _successfulLogins.value(user, 0) + 1); + } +} + +void MetricsServer::addLoginAttempt(const QString& user, bool successful) { + UserId userId = _sessions.key(user); + if (userId.isValid()) { + addLoginAttempt(userId, successful); + } +} + +void MetricsServer::addSession(UserId user, const QString& name) +{ + _sessions.insert(user, name); +} + +void MetricsServer::removeSession(UserId user) +{ + _sessions.remove(user); +} + +void MetricsServer::addClient(UserId user) +{ + _clientSessions.insert(user, _clientSessions.value(user, 0) + 1); +} + +void MetricsServer::removeClient(UserId user) +{ + int count = _clientSessions.value(user, 0) - 1; + if (count <= 0) { + _clientSessions.remove(user); + } + else { + _clientSessions.insert(user, count); + } +} + +void MetricsServer::addNetwork(UserId user) +{ + _networkSessions.insert(user, _networkSessions.value(user, 0) + 1); +} + +void MetricsServer::removeNetwork(UserId user) +{ + int count = _networkSessions.value(user, 0) - 1; + if (count <= 0) { + _networkSessions.remove(user); + } + else { + _networkSessions.insert(user, count); + } +} + +void MetricsServer::transmitDataNetwork(UserId user, uint64_t size) +{ + _networkDataTransmit.insert(user, _networkDataTransmit.value(user, 0) + size); +} + +void MetricsServer::receiveDataNetwork(UserId user, uint64_t size) +{ + _networkDataReceive.insert(user, _networkDataReceive.value(user, 0) + size); +} + +void MetricsServer::messageQueue(UserId user, uint64_t size) +{ + _messageQueue.insert(user, size); +} + +void MetricsServer::setCertificateExpires(QDateTime expires) +{ + _certificateExpires = std::move(expires); +} + +void MetricsServer::loginSuccessful() +{ + _loginSuccessful++; +} + +void MetricsServer::loginFailed() +{ + _loginFailed++; +} diff --git a/src/core/metricsserver.h b/src/core/metricsserver.h new file mode 100644 index 00000000..1d268335 --- /dev/null +++ b/src/core/metricsserver.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2005-2019 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "coreidentity.h" + +class MetricsServer : public QObject +{ + Q_OBJECT + +public: + explicit MetricsServer(QObject* parent = nullptr); + + bool startListening(); + void stopListening(const QString& msg); + + void addLoginAttempt(UserId user, bool successful); + void addLoginAttempt(const QString& user, bool successful); + + void addSession(UserId user, const QString& name); + void removeSession(UserId user); + + void addClient(UserId user); + void removeClient(UserId user); + + void addNetwork(UserId user); + void removeNetwork(UserId user); + + void transmitDataNetwork(UserId user, uint64_t size); + void receiveDataNetwork(UserId user, uint64_t size); + + void loginSuccessful(); + void loginFailed(); + + void messageQueue(UserId user, uint64_t size); + + void setCertificateExpires(QDateTime expires); + +private slots: + void incomingConnection(); + void respond(); + +private: + QTcpServer _server, _v6server; + + QHash _loginAttempts{}; + QHash _successfulLogins{}; + + QHash _sessions{}; + + QHash _clientSessions{}; + QHash _networkSessions{}; + + QHash _networkDataTransmit{}; + QHash _networkDataReceive{}; + + QHash _messageQueue{}; + + uint64_t _loginSuccessful{}; + uint64_t _loginFailed{}; + + QDateTime _certificateExpires{}; +}; diff --git a/src/core/sslserver.cpp b/src/core/sslserver.cpp index e5d6e27e..16e3f3e9 100644 --- a/src/core/sslserver.cpp +++ b/src/core/sslserver.cpp @@ -26,6 +26,7 @@ #include +#include "core.h" #include "quassel.h" #ifdef HAVE_SSL @@ -199,6 +200,11 @@ bool SslServer::setCertificate(const QString& path, const QString& keyPath) return false; } + _certificateExpires = untestedCert.expiryDate(); + if (_metricsServer) { + _metricsServer->setCertificateExpires(_certificateExpires); + } + _isCertValid = true; // All keys are valid, update the externally visible copy used for new connections. @@ -223,4 +229,11 @@ QSslKey SslServer::loadKey(QFile* keyFile) return key; } +void SslServer::setMetricsServer(MetricsServer* metricsServer) { + _metricsServer = metricsServer; + if (_metricsServer) { + _metricsServer->setCertificateExpires(_certificateExpires); + } +} + #endif // HAVE_SSL diff --git a/src/core/sslserver.h b/src/core/sslserver.h index 8e91ddd3..bd54202a 100644 --- a/src/core/sslserver.h +++ b/src/core/sslserver.h @@ -28,6 +28,8 @@ # include # include +# include "metricsserver.h" + class SslServer : public QTcpServer { Q_OBJECT @@ -52,6 +54,8 @@ public: */ bool reloadCerts(); + void setMetricsServer(MetricsServer* metricsServer); + protected: void incomingConnection(qintptr socketDescriptor) override; @@ -69,6 +73,8 @@ private: bool loadCerts(); QSslKey loadKey(QFile* keyFile); + MetricsServer* _metricsServer{nullptr}; + QLinkedList _pendingConnections; QSslCertificate _cert; QSslKey _key; @@ -78,6 +84,8 @@ private: // Used when reloading certificates later QString _sslCertPath; /// Path to the certificate file QString _sslKeyPath; /// Path to the private key file (may be in same file as above) + + QDateTime _certificateExpires; }; #endif // HAVE_SSL -- 2.20.1