From 9a8c81cf5e8ab7cfed4097368427b9748603e43f Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sun, 3 Feb 2019 02:43:22 -0800 Subject: [PATCH] Client Websockets (#3545) * Websockets * Add setting to get websocket IP from header * Add QT version guard * Minor cleanup Signed-off-by: Zach Halpern * - Make QWebSocket required - Remove QWEBSOCEKT_LIB guards - Only TCP on port 4747 - Fix peerName lookup * fix check Signed-off-by: Zach Halpern * Update CMakeLists.txt * Update CMakeLists.txt --- cockatrice/CMakeLists.txt | 4 +- cockatrice/src/remoteclient.cpp | 102 +++++++++++++++++------ cockatrice/src/remoteclient.h | 35 +++++--- servatrice/CMakeLists.txt | 12 +-- servatrice/src/servatrice.cpp | 4 - servatrice/src/servatrice.h | 10 +-- servatrice/src/serversocketinterface.cpp | 27 ++++-- servatrice/src/serversocketinterface.h | 13 ++- 8 files changed, 130 insertions(+), 77 deletions(-) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 3e7defbe..8b4bd921 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -151,8 +151,8 @@ if(APPLE) ENDIF(APPLE) # Qt5 -find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg Widgets REQUIRED) -set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets) +find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg WebSockets Widgets REQUIRED) +set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets Qt5::WebSockets) # Qt5LinguistTools find_package(Qt5LinguistTools) diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 5d78f7fe..ec8d7d91 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -18,12 +18,13 @@ #include #include #include +#include static const unsigned int protocolVersion = 14; RemoteClient::RemoteClient(QObject *parent) : AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false), - messageLength(0) + usingWebSocket(false), messageLength(0) { clearNewClientFeatures(); @@ -38,6 +39,13 @@ RemoteClient::RemoteClient(QObject *parent) connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotSocketError(QAbstractSocket::SocketError))); + + websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); + connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived); + connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected); + connect(websocket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(slotWebSocketError(QAbstractSocket::SocketError))); + connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this, SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, @@ -69,15 +77,25 @@ void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/) emit socketError(errorString); } +void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/) +{ + + QString errorString = websocket->errorString(); + doDisconnectFromServer(); + emit socketError(errorString); +} + void RemoteClient::slotConnected() { timeRunning = lastDataReceived = 0; timer->start(); - // dirty hack to be compatible with v14 server - sendCommandContainer(CommandContainer()); - getNewCmdId(); - // end of hack + if (!usingWebSocket) { + // dirty hack to be compatible with v14 server + sendCommandContainer(CommandContainer()); + getNewCmdId(); + // end of hack + } } void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) @@ -217,7 +235,7 @@ void RemoteClient::loginResponse(const Response &response) missingFeatures << QString::fromStdString(resp.missing_features(i)); } emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), - resp.denied_end_time(), missingFeatures); + static_cast(resp.denied_end_time()), missingFeatures); setStatus(StatusDisconnecting); } } @@ -236,7 +254,7 @@ void RemoteClient::registerResponse(const Response &response) break; default: emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), - resp.denied_end_time()); + static_cast(resp.denied_end_time())); setStatus(StatusDisconnecting); doDisconnectFromServer(); break; @@ -301,21 +319,51 @@ void RemoteClient::readData() } while (!inputBuffer.isEmpty()); } +void RemoteClient::websocketMessageReceived(const QByteArray &message) +{ + lastDataReceived = timeRunning; + ServerMessage newServerMessage; + newServerMessage.ParseFromArray(message.data(), message.length()); +#ifdef QT_DEBUG + qDebug() << "IN" << messageLength << QString::fromStdString(newServerMessage.ShortDebugString()); +#endif + processProtocolItem(newServerMessage); +} + void RemoteClient::sendCommandContainer(const CommandContainer &cont) { - QByteArray buf; - unsigned int size = cont.ByteSize(); + + auto size = static_cast(cont.ByteSize()); #ifdef QT_DEBUG qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString()); #endif - buf.resize(size + 4); - cont.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); - socket->write(buf); + QByteArray buf; + if (usingWebSocket) { + buf.resize(size); + cont.SerializeToArray(buf.data(), size); + websocket->sendBinaryMessage(buf); + } else { + buf.resize(size + 4); + cont.SerializeToArray(buf.data() + 4, size); + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); + + socket->write(buf); + } +} + +void RemoteClient::connectToHost(const QString &hostname, unsigned int port) +{ + usingWebSocket = port == 443 || port == 80 || port == 4748 || port == 8080; + if (usingWebSocket) { + QUrl url(QString("%1://%2:%3").arg(port == 443 ? "wss" : "ws").arg(hostname).arg(port)); + websocket->open(url); + } else { + socket->connectToHost(hostname, static_cast(port)); + } } void RemoteClient::doConnectToServer(const QString &hostname, @@ -330,7 +378,7 @@ void RemoteClient::doConnectToServer(const QString &hostname, lastHostname = hostname; lastPort = port; - socket->connectToHost(hostname, port); + connectToHost(hostname, port); setStatus(StatusConnecting); } @@ -354,7 +402,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname, lastHostname = hostname; lastPort = port; - socket->connectToHost(hostname, port); + connectToHost(hostname, port); setStatus(StatusRegistering); } @@ -364,7 +412,7 @@ void RemoteClient::doActivateToServer(const QString &_token) token = _token; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusActivating); } @@ -377,17 +425,19 @@ void RemoteClient::doDisconnectFromServer() messageLength = 0; QList pc = pendingCommands.values(); - for (int i = 0; i < pc.size(); i++) { + for (const auto &i : pc) { Response response; response.set_response_code(Response::RespNotConnected); - response.set_cmd_id(pc[i]->getCommandContainer().cmd_id()); - pc[i]->processResponse(response); + response.set_cmd_id(i->getCommandContainer().cmd_id()); + i->processResponse(response); - delete pc[i]; + delete i; } pendingCommands.clear(); setStatus(StatusDisconnected); + if (websocket->isValid()) + websocket->close(); socket->close(); } @@ -508,7 +558,7 @@ void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsi lastHostname = hostname; lastPort = port; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusRequestingForgotPassword); } @@ -540,7 +590,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname, token = _token; password = _newpassword; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusSubmitForgotPasswordReset); } @@ -574,7 +624,7 @@ void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostna lastPort = port; email = _email; - socket->connectToHost(lastHostname, lastPort); + connectToHost(lastHostname, static_cast(lastPort)); setStatus(StatusSubmitForgotPasswordChallenge); } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index f0bc93bc..7c6ff2ab 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -3,6 +3,7 @@ #include "abstractclient.h" #include +#include class QTimer; @@ -17,7 +18,6 @@ signals: void activateError(); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); - void protocolError(); void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void sigRegisterToServer(const QString &hostname, @@ -25,7 +25,7 @@ signals: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void sigActivateToServer(const QString &_token); @@ -48,7 +48,9 @@ signals: private slots: void slotConnected(); void readData(); + void websocketMessageReceived(const QByteArray &message); void slotSocketError(QAbstractSocket::SocketError error); + void slotWebSocketError(QAbstractSocket::SocketError error); void ping(); void processServerIdentificationEvent(const Event_ServerIdentification &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event); @@ -62,7 +64,7 @@ private slots: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void doLogin(); @@ -85,28 +87,35 @@ private slots: private: static const int maxTimeout = 10; int timeRunning, lastDataReceived; - QByteArray inputBuffer; bool messageInProgress; bool handshakeStarted; - bool newMissingFeatureFound(QString _serversMissingFeatures); - void clearNewClientFeatures(); + bool usingWebSocket; int messageLength; - QTimer *timer; QTcpSocket *socket; + QWebSocket *websocket; QString lastHostname; int lastPort; - QString getSrvClientID(const QString _hostname); + + QString getSrvClientID(QString _hostname); + bool newMissingFeatureFound(QString _serversMissingFeatures); + void clearNewClientFeatures(); + void connectToHost(const QString &hostname, unsigned int port); + protected slots: - void sendCommandContainer(const CommandContainer &cont); + void sendCommandContainer(const CommandContainer &cont) override; public: - RemoteClient(QObject *parent = 0); - ~RemoteClient(); + explicit RemoteClient(QObject *parent = nullptr); + ~RemoteClient() override; QString peerName() const { - return socket->peerName(); + if (usingWebSocket) { + return websocket->peerName(); + } else { + return socket->peerName(); + } } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); @@ -115,7 +124,7 @@ public: const QString &_userName, const QString &_password, const QString &_email, - const int _gender, + int _gender, const QString &_country, const QString &_realname); void activateToServer(const QString &_token); diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index f1bf6092..0c20e54e 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -43,16 +43,8 @@ if(APPLE) ENDIF(APPLE) # Qt5 -find_package(Qt5 COMPONENTS Network Sql REQUIRED) -set(SERVATRICE_QT_MODULES Qt5::Core Qt5::Network Qt5::Sql) - -# Qt Websockets -find_package(Qt5WebSockets) -if(Qt5WebSockets_FOUND) - list(APPEND SERVATRICE_QT_MODULES Qt5::WebSockets) -else() - MESSAGE(WARNING "Qt5 websocket module not found") -endif() +find_package(Qt5 COMPONENTS Network Sql WebSockets REQUIRED) +set(SERVATRICE_QT_MODULES Qt5::Core Qt5::Network Qt5::Sql Qt5::WebSockets) QT5_ADD_RESOURCES(servatrice_RESOURCES_RCC ${servatrice_RESOURCES}) SET(QT_DONT_USE_QTGUI TRUE) diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 26031ea6..381aef2c 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -104,7 +104,6 @@ Servatrice_ConnectionPool *Servatrice_GameServer::findLeastUsedConnectionPool() return connectionPools[poolIndex]; } -#ifdef QT_WEBSOCKETS_LIB #define WEBSOCKET_POOL_NUMBER 999 Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_server, @@ -163,7 +162,6 @@ Servatrice_ConnectionPool *Servatrice_WebsocketGameServer::findLeastUsedConnecti qDebug() << "Pool utilisation:" << debugStr; return connectionPools[poolIndex]; } -#endif void Servatrice_IslServer::incomingConnection(qintptr socketDescriptor) { @@ -431,7 +429,6 @@ bool Servatrice::initServer() } } -#ifdef QT_WEBSOCKETS_LIB // WEBSOCKET SERVER if (getNumberOfWebSocketPools() > 0) { websocketGameServer = new Servatrice_WebsocketGameServer(this, getNumberOfWebSocketPools(), @@ -447,7 +444,6 @@ bool Servatrice::initServer() return false; } } -#endif if (getIdleClientTimeout() > 0) { qDebug() << "Idle client timeout value: " << getIdleClientTimeout(); diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 29eefcf9..6d2788c0 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -20,10 +20,6 @@ #ifndef SERVATRICE_H #define SERVATRICE_H -#include -#ifdef QT_WEBSOCKETS_LIB -#include -#endif #include "server.h" #include #include @@ -32,6 +28,8 @@ #include #include #include +#include +#include #include Q_DECLARE_METATYPE(QSqlDatabase) @@ -66,7 +64,6 @@ protected: Servatrice_ConnectionPool *findLeastUsedConnectionPool(); }; -#ifdef QT_WEBSOCKETS_LIB class Servatrice_WebsocketGameServer : public QWebSocketServer { Q_OBJECT @@ -86,7 +83,6 @@ protected: protected slots: void onNewConnection(); }; -#endif class Servatrice_IslServer : public QTcpServer { @@ -158,9 +154,7 @@ private: DatabaseType databaseType; QTimer *pingClock, *statusUpdateClock; Servatrice_GameServer *gameServer; -#ifdef QT_WEBSOCKETS_LIB Servatrice_WebsocketGameServer *websocketGameServer; -#endif Servatrice_IslServer *islServer; mutable QMutex loginMessageMutex; QString loginMessage; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 05a6aca1..7ea74ba0 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -1037,8 +1037,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C return Response::RespUserAlreadyExists; } - if (servatrice->getMaxAccountsPerEmail() && - !(sqlInterface->checkNumberOfUserAccounts(emailAddress) < servatrice->getMaxAccountsPerEmail())) { + if (servatrice->getMaxAccountsPerEmail() > 0 && + sqlInterface->checkNumberOfUserAccounts(emailAddress) >= servatrice->getMaxAccountsPerEmail()) { if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(), QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT", @@ -1629,7 +1629,6 @@ bool TcpServerSocketInterface::initTcpSession() return true; } -#ifdef QT_WEBSOCKETS_LIB WebsocketServerSocketInterface::WebsocketServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent) @@ -1647,6 +1646,22 @@ WebsocketServerSocketInterface::~WebsocketServerSocketInterface() void WebsocketServerSocketInterface::initConnection(void *_socket) { socket = (QWebSocket *)_socket; + address = socket->peerAddress(); + + QByteArray websocketIPHeader = settingsCache->value("server/web_socket_ip_header", "").toByteArray(); + if (websocketIPHeader.length() > 0) { +#if QT_VERSION >= 0x050600 + if (socket->request().hasRawHeader(websocketIPHeader)) { + QString header(socket->request().rawHeader(websocketIPHeader)); + QHostAddress parsed(header); + if (!parsed.isNull()) + address = parsed; + } +#else + logger->logMessage(QString("Reading the websocket IP header is unsupported on this version of QT.")); +#endif + } + connect(socket, SIGNAL(binaryMessageReceived(const QByteArray &)), this, SLOT(binaryMessageReceived(const QByteArray &))); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, @@ -1656,7 +1671,9 @@ void WebsocketServerSocketInterface::initConnection(void *_socket) // Otherwise, in case a of a socket error, it could be removed from the list before it is added. server->addClient(this); - logger->logMessage(QString("Incoming websocket connection: %1").arg(socket->peerAddress().toString()), this); + logger->logMessage( + QString("Incoming websocket connection: %1 (%2)").arg(address.toString()).arg(socket->peerAddress().toString()), + this); if (!initWebsocketSession()) prepareDestroy(); @@ -1746,5 +1763,3 @@ void WebsocketServerSocketInterface::binaryMessageReceived(const QByteArray &mes processCommandContainer(newCommandContainer); } - -#endif \ No newline at end of file diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index e4d6dbc5..6a78cfa1 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -20,13 +20,11 @@ #ifndef SERVERSOCKETINTERFACE_H #define SERVERSOCKETINTERFACE_H -#include -#ifdef QT_WEBSOCKETS_LIB -#include -#endif #include "server_protocolhandler.h" #include #include +#include +#include class Servatrice; class Servatrice_DatabaseInterface; @@ -181,7 +179,6 @@ public slots: void initConnection(int socketDescriptor); }; -#ifdef QT_WEBSOCKETS_LIB class WebsocketServerSocketInterface : public AbstractServerSocketInterface { Q_OBJECT @@ -193,11 +190,11 @@ public: QHostAddress getPeerAddress() const { - return socket->peerAddress(); + return address; } QString getAddress() const { - return socket->peerAddress().toString(); + return address.toString(); } QString getConnectionType() const { @@ -206,6 +203,7 @@ public: private: QWebSocket *socket; + QHostAddress address; protected: void writeToSocket(QByteArray &data) @@ -223,6 +221,5 @@ protected slots: public slots: void initConnection(void *_socket); }; -#endif #endif