From c37b0d2fcf125d4787799930d06f76fc0798ad39 Mon Sep 17 00:00:00 2001 From: Serhii Hadzhilov <71632867+SerhiiGadzhilov@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:12:13 +0300 Subject: [PATCH] Add ability to initiate connection from server (#7222) * Add a new hidden readonly option "initiateConnectionFromServer" * Add option "clientHostMode" * Add host mode to the client GUI * Hide main window controls for the client host mode * Store client address into the server config file * Add client mode the server * Pass option --host into the client * Add --host parameter into the synergyc * Add InverseSocketFactory * Add InverseClientSocket * Add InverseServerSocket * Add AutoArchSocket * Add listener on the client side * Add connection from the server side * Fix code smells * Additional code smells * Requested changes Co-authored-by: Serhii Hadzhilov --- ChangeLog | 6 + cmake/Version.cmake | 4 +- src/gui/src/AppConfig.cpp | 40 +- src/gui/src/AppConfig.h | 13 +- src/gui/src/MainWindow.cpp | 72 ++- src/gui/src/MainWindow.h | 1 + src/gui/src/MainWindowBase.ui | 35 +- src/gui/src/ServerConfig.cpp | 25 + src/gui/src/ServerConfig.h | 3 + src/gui/src/SettingsDialog.cpp | 17 +- src/gui/src/SettingsDialogBase.ui | 133 +++--- src/lib/client/Client.cpp | 50 +- src/lib/client/Client.h | 1 - src/lib/net/CMakeLists.txt | 10 +- src/lib/net/InverseSockets/AutoArchSocket.cpp | 201 ++++++++ src/lib/net/InverseSockets/AutoArchSocket.h | 53 ++ .../InverseSockets/InverseClientSocket.cpp | 452 ++++++++++++++++++ .../net/InverseSockets/InverseClientSocket.h | 111 +++++ .../InverseSockets/InverseServerSocket.cpp | 122 +++++ .../net/InverseSockets/InverseServerSocket.h | 61 +++ .../InverseSockets/InverseSocketFactory.cpp | 59 +++ .../net/InverseSockets/InverseSocketFactory.h | 34 ++ src/lib/server/Config.cpp | 8 + src/lib/server/Config.h | 6 + src/lib/synergy/ArgParser.cpp | 3 + src/lib/synergy/ClientApp.cpp | 19 +- src/lib/synergy/ClientApp.h | 3 + src/lib/synergy/ClientArgs.h | 6 + src/lib/synergy/ServerApp.cpp | 33 +- src/lib/synergy/ServerApp.h | 3 + 30 files changed, 1468 insertions(+), 116 deletions(-) create mode 100644 src/lib/net/InverseSockets/AutoArchSocket.cpp create mode 100644 src/lib/net/InverseSockets/AutoArchSocket.h create mode 100644 src/lib/net/InverseSockets/InverseClientSocket.cpp create mode 100644 src/lib/net/InverseSockets/InverseClientSocket.h create mode 100644 src/lib/net/InverseSockets/InverseServerSocket.cpp create mode 100644 src/lib/net/InverseSockets/InverseServerSocket.h create mode 100644 src/lib/net/InverseSockets/InverseSocketFactory.cpp create mode 100644 src/lib/net/InverseSockets/InverseSocketFactory.h diff --git a/ChangeLog b/ChangeLog index 6bd74966a..531bf19df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +1.14.6 +====== + +Enhancements: +- #7222 Ability to initiate connection from server + 1.14.5 ====== diff --git a/cmake/Version.cmake b/cmake/Version.cmake index 6066f2940..e64b29de4 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -6,9 +6,9 @@ cmake_minimum_required (VERSION 3.4) set (SYNERGY_VERSION_MAJOR 1) set (SYNERGY_VERSION_MINOR 14) -set (SYNERGY_VERSION_PATCH 5) +set (SYNERGY_VERSION_PATCH 6) set (SYNERGY_VERSION_BUILD 1) -set (SYNERGY_VERSION_STAGE "stable") +set (SYNERGY_VERSION_STAGE "snapshot") # # Version from CI diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 1b0877329..66d4d294b 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -83,7 +83,10 @@ const char* AppConfig::m_SynergySettingsName[] = { "eliteBackersUrl", "guid", "licenseRegistryUrl", - "licenseNextCheck" + "licenseNextCheck", + "initiateConnectionFromServer", + "clientHostMode", + "serverClientMode" }; static const char* logLevelNames[] = @@ -99,19 +102,19 @@ AppConfig::AppConfig() : m_Port(24800), m_Interface(), m_LogLevel(0), + m_LogToFile(), m_WizardLastRun(0), m_ProcessMode(DEFAULT_PROCESS_MODE), + m_StartedBefore(), m_AutoConfig(true), + m_AutoConfigServer(), m_ElevateMode(defaultElevateMode), + m_Edition(kUnregistered), m_CryptoEnabled(false), m_AutoHide(false), m_LastExpiringWarningTime(0), - m_AutoConfigServer(), - m_MinimizeToTray(false), - m_Edition(kUnregistered), - m_LogToFile(), - m_StartedBefore(), m_ActivationHasRun(), + m_MinimizeToTray(false), m_ServerGroupChecked(), m_UseExternalConfig(), m_UseInternalConfig(), @@ -253,6 +256,9 @@ void AppConfig::loadSettings() m_guid = loadCommonSetting(kGuid, QUuid::createUuid()).toString(); m_licenseRegistryUrl = loadCommonSetting(kLicenseRegistryUrl, "https://api2.prod.symless.com/license/register").toString(); m_licenseNextCheck = loadCommonSetting(kLicenseNextCheck, 0).toULongLong(); + m_ClientHostMode = loadSetting(kClientHostMode, true).toBool(); + m_ServerClientMode = loadSetting(kServerClientMode, true).toBool(); + m_InitiateConnectionFromServer = loadSetting(kInitiateConnectionFromServer, false).toBool(); //only change the serial key if the settings being loaded contains a key bool updateSerial = ConfigWriter::make() @@ -320,6 +326,8 @@ void AppConfig::saveSettings() setSetting(kPreventSleep, m_PreventSleep); setSetting(kLanguageSync, m_LanguageSync); setSetting(kInvertScrollDirection, m_InvertScrollDirection); + setSetting(kClientHostMode, m_ClientHostMode); + setSetting(kServerClientMode, m_ServerClientMode); } m_unsavedChanges = false; @@ -512,6 +520,18 @@ void AppConfig::setLanguageSync(bool newValue) { bool AppConfig::getPreventSleep() const { return m_PreventSleep; } +bool AppConfig::getClientHostMode() const { + return (m_ClientHostMode && getInitiateConnectionFromServer()); +} + +bool AppConfig::getServerClientMode() const { + return (m_ServerClientMode && getInitiateConnectionFromServer()); +} + +bool AppConfig::getInitiateConnectionFromServer() const { + return m_InitiateConnectionFromServer; +} + void AppConfig::setPreventSleep(bool newValue) { setSettingModified(m_PreventSleep, newValue); } @@ -641,6 +661,14 @@ void AppConfig::setServerHostname(const QString& newValue) { setSettingModified(m_ServerHostname, newValue); } +void AppConfig::setClientHostMode(bool newValue) { + setSettingModified(m_ClientHostMode, newValue); +} + +void AppConfig::setServerClientMode(bool newValue) { + setSettingModified(m_ServerClientMode, newValue); +} + template void AppConfig::setSettingModified(T &variable, const T& newValue) { if (variable != newValue) diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 87e155e35..5e0c56dc0 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -125,6 +125,9 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase bool getLanguageSync() const; void setPreventSleep(bool b); bool getPreventSleep() const; + bool getClientHostMode() const; + bool getServerClientMode() const; + bool getInitiateConnectionFromServer() const; #ifndef SYNERGY_ENTERPRISE bool activationHasRun() const; AppConfig& activationHasRun(bool value); @@ -157,6 +160,8 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase void setUseInternalConfig(bool) ; void setClientGroupChecked(bool) ; void setServerHostname(const QString&); + void setClientHostMode(bool newValue); + void setServerClientMode(bool newValue); /// @brief Set the path to the TLS/SSL certificate file that will be used /// @param [in] path The path to the Certificate @@ -219,7 +224,10 @@ protected: kEliteBackersUrl, kGuid, kLicenseRegistryUrl, - kLicenseNextCheck + kLicenseNextCheck, + kInitiateConnectionFromServer, + kClientHostMode, + kServerClientMode }; void setScreenName(const QString& s); @@ -269,6 +277,9 @@ protected: bool m_InvertScrollDirection = false; bool m_LanguageSync = true; bool m_PreventSleep = false; + bool m_InitiateConnectionFromServer = false; + bool m_ClientHostMode = true; + bool m_ServerClientMode = true; bool m_ServerGroupChecked; bool m_UseExternalConfig; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 106681dfe..28e804379 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -307,6 +307,7 @@ void MainWindow::loadSettings() enableClient(appConfig().getClientGroupChecked()); m_pLineEditHostname->setText(appConfig().getServerHostname()); + m_pLineEditClienIp->setText(serverConfig().getClientAddress()); } void MainWindow::initConnections() @@ -325,6 +326,7 @@ void MainWindow::saveSettings() appConfig().setServerGroupChecked(m_pRadioGroupServer->isChecked()); appConfig().setClientGroupChecked(m_pRadioGroupClient->isChecked()); appConfig().setServerHostname(m_pLineEditHostname->text()); + serverConfig().setClientAddress(m_pLineEditClienIp->text()); /* Save everything */ GUI::Config::ConfigWriter::make()->globalSave(); @@ -836,7 +838,7 @@ bool MainWindow::clientArgs(QStringList& args, QString& app) } #endif - if (m_pLineEditHostname->text().isEmpty()) + if (m_pLineEditHostname->text().isEmpty() && !appConfig().getClientHostMode()) { #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG) //check if autoconfig mode is enabled @@ -858,18 +860,24 @@ bool MainWindow::clientArgs(QStringList& args, QString& app) #endif } - QString hostName = m_pLineEditHostname->text(); - // if interface is IPv6 - ensure that ip is in square brackets - if (hostName.count(':') > 1) { - if(hostName[0] != '[') { - hostName.insert(0, '['); - } - if(hostName[hostName.size() - 1] != ']') { - hostName.push_back(']'); + if (appConfig().getClientHostMode()) { + args <<"--host"; + args <<":" + QString::number(appConfig().port()); + } + else { + QString hostName = m_pLineEditHostname->text(); + // if interface is IPv6 - ensure that ip is in square brackets + if (hostName.count(':') > 1) { + if(hostName[0] != '[') { + hostName.insert(0, '['); + } + if(hostName[hostName.size() - 1] != ']') { + hostName.push_back(']'); + } } + args << hostName + ":" + QString::number(appConfig().port()); } - args << hostName + ":" + QString::number(appConfig().port()); return true; } @@ -947,6 +955,14 @@ bool MainWindow::serverArgs(QStringList& args, QString& app) return false; } + if (appConfig().getServerClientMode() && + m_pLineEditClienIp->text().isEmpty()) + { + QMessageBox::warning(this, tr("Client IP address or name is empty"), + tr("Please fill in a client IP address or name.")); + return false; + } + #if defined(Q_OS_WIN) // wrap in quotes so a malicious user can't start \Program.exe as admin. app = QString("\"%1\"").arg(app); @@ -1330,6 +1346,8 @@ void MainWindow::on_m_pActionSettings_triggered() auto result = SettingsDialog(this, appConfig()).exec(); if(result == QDialog::Accepted) { + enableServer(appConfig().getServerGroupChecked()); + enableClient(appConfig().getClientGroupChecked()); auto state = synergyState(); if ((state == synergyConnected) || (state == synergyConnecting) || (state == synergyListening)) { restartSynergy(); @@ -1497,6 +1515,17 @@ void MainWindow::enableServer(bool enable) if (enable) { + if (m_AppConfig->getServerClientMode()) { + m_pLabelClientIp->show(); + m_pLineEditClienIp->show(); + m_pButtonConnectToClient->show(); + } + else { + m_pLabelClientIp->hide(); + m_pLineEditClienIp->hide(); + m_pButtonConnectToClient->hide(); + } + m_pButtonConfigureServer->show(); m_pLabelServerState->show(); updateLocalFingerprint(); @@ -1507,6 +1536,9 @@ void MainWindow::enableServer(bool enable) m_pLabelFingerprint->hide(); m_pButtonConfigureServer->hide(); m_pLabelServerState->hide(); + m_pLabelClientIp->hide(); + m_pLineEditClienIp->hide(); + m_pButtonConnectToClient->hide(); } } @@ -1517,9 +1549,18 @@ void MainWindow::enableClient(bool enable) if (enable) { - m_pLabelServerName->show(); - m_pLineEditHostname->show(); - m_pButtonConnect->show(); + if (m_AppConfig->getClientHostMode()) + { + m_pLabelServerName->hide(); + m_pLineEditHostname->hide(); + m_pButtonConnect->hide(); + } + else + { + m_pLabelServerName->show(); + m_pLineEditHostname->show(); + m_pButtonConnect->show(); + } m_pButtonToggleStart->setEnabled(enable); } else @@ -1558,3 +1599,8 @@ void MainWindow::on_m_pButtonConnect_clicked() on_m_pButtonApply_clicked(); } +void MainWindow::on_m_pButtonConnectToClient_clicked() +{ + on_m_pButtonApply_clicked(); +} + diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 70cf18e7d..56a6c9477 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -263,6 +263,7 @@ private slots: void on_m_pComboServerList_currentIndexChanged(const QString &arg1); void on_m_pButtonConnect_clicked(); + void on_m_pButtonConnectToClient_clicked(); signals: void windowShown(); diff --git a/src/gui/src/MainWindowBase.ui b/src/gui/src/MainWindowBase.ui index 1332559d2..dae60bc43 100644 --- a/src/gui/src/MainWindowBase.ui +++ b/src/gui/src/MainWindowBase.ui @@ -7,7 +7,7 @@ 0 0 720 - 658 + 552 @@ -244,11 +244,41 @@ background-color: rgba(192,192,192, 0.1); 20 - 40 + 20 + + + + Client IP address or name: + + + 20 + + + + + + + 20 + + + 0 + + + + + + + + Connect + + + + + @@ -685,7 +715,6 @@ background-color: rgba(192,192,192, 0.1); Arial -1 - 87 true diff --git a/src/gui/src/ServerConfig.cpp b/src/gui/src/ServerConfig.cpp index dae75aa39..e2491ecbd 100644 --- a/src/gui/src/ServerConfig.cpp +++ b/src/gui/src/ServerConfig.cpp @@ -150,6 +150,10 @@ void ServerConfig::saveSettings() settings().setValue("clipboardSharing", clipboardSharing()); settings().setValue("clipboardSharingSize", QVariant::fromValue(clipboardSharingSize())); + if (!getClientAddress().isEmpty()) { + settings().setValue("clientAddress", getClientAddress()); + } + writeSettings(settings(), switchCorners(), "switchCorner"); settings().beginWriteArray("screens"); @@ -204,6 +208,7 @@ void ServerConfig::loadSettings() setClipboardSharingSize(settings().value("clipboardSharingSize", (int) ServerConfig::defaultClipboardSharingSize()).toULongLong()); setClipboardSharing(settings().value("clipboardSharing", true).toBool()); + setClientAddress(settings().value("clientAddress", "").toString()); readSettings(settings(), switchCorners(), "switchCorner", false, NumSwitchCorners); @@ -296,6 +301,10 @@ QTextStream& operator<<(QTextStream& outStream, const ServerConfig& config) outStream << "\t" << "clipboardSharing = " << (config.clipboardSharing() ? "true" : "false") << endl; outStream << "\t" << "clipboardSharingSize = " << config.clipboardSharingSize() << endl; + if (!config.getClientAddress().isEmpty()) { + outStream << "\t" << "clientAddress = " << config.getClientAddress() << endl; + } + if (config.hasSwitchDelay()) outStream << "\t" << "switchDelay = " << config.switchDelay() << endl; @@ -554,6 +563,22 @@ size_t ServerConfig::setClipboardSharingSize(size_t size) { return size; } +void ServerConfig::setClientAddress(const QString& address) { + if (m_pAppConfig->getServerClientMode()) { + m_clientAddress = address; + } +} + +QString ServerConfig::getClientAddress() const { + QString clientAddress; + + if (m_pAppConfig->getServerClientMode()) { + clientAddress = m_clientAddress.trimmed(); + } + + return clientAddress; +} + QSettings &ServerConfig::settings() { using GUI::Config::ConfigWriter; diff --git a/src/gui/src/ServerConfig.h b/src/gui/src/ServerConfig.h index d6479e384..c71988cbd 100644 --- a/src/gui/src/ServerConfig.h +++ b/src/gui/src/ServerConfig.h @@ -89,6 +89,8 @@ class ServerConfig : public BaseConfig, public GUI::Config::ConfigBase bool isScreenExists(const QString& screenName) const; void addClient(const QString& clientName); bool isHotkeysAvailable() const; + QString getClientAddress() const; + void setClientAddress(const QString& address); protected: QSettings& settings(); @@ -147,6 +149,7 @@ class ServerConfig : public BaseConfig, public GUI::Config::ConfigBase bool m_DisableLockToScreen; bool m_ClipboardSharing; size_t m_ClipboardSharingSize; + QString m_clientAddress; MainWindow* m_pMainWindow; }; diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index 67cc368d2..7ce064e5c 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -53,8 +53,11 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); enableControls(appConfig().isWritable()); - m_pCheckBoxLanguageSync->setVisible(m_pMainWindow->synergyType() == MainWindow::synergyClient); - m_pCheckBoxScrollDirection->setVisible(m_pMainWindow->synergyType() == MainWindow::synergyClient); + const auto isClientMode = m_pMainWindow->synergyType() == MainWindow::synergyClient; + m_pCheckBoxLanguageSync->setVisible(isClientMode); + m_pCheckBoxScrollDirection->setVisible(isClientMode); + m_pCheckBoxClientHostMode->setVisible(isClientMode && appConfig().getInitiateConnectionFromServer()); + m_pCheckBoxServerClientMode->setVisible(!isClientMode && appConfig().getInitiateConnectionFromServer()); const auto& serveConfig = m_pMainWindow->serverConfig(); m_pLineEditScreenName->setValidator(new validators::ScreenNameValidator(m_pLineEditScreenName, m_pLabelNameError, (&serveConfig.screens()))); @@ -72,6 +75,8 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : connect(m_pComboElevate, SIGNAL(currentIndexChanged(int)), this, SLOT(onChange())); connect(m_pCheckBoxLanguageSync, SIGNAL(clicked()), this, SLOT(onChange())); connect(m_pCheckBoxScrollDirection, SIGNAL(clicked()), this, SLOT(onChange())); + connect(m_pCheckBoxClientHostMode, SIGNAL(clicked()), this, SLOT(onChange())); + connect(m_pCheckBoxServerClientMode,SIGNAL(clicked()), this, SLOT(onChange())); adjustSize(); } @@ -96,6 +101,8 @@ void SettingsDialog::accept() appConfig().setCryptoEnabled(m_pCheckBoxEnableCrypto->isChecked()); appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked()); + appConfig().setClientHostMode(m_pCheckBoxClientHostMode->isChecked()); + appConfig().setServerClientMode(m_pCheckBoxServerClientMode->isChecked()); appConfig().saveSettings(); QDialog::accept(); @@ -155,6 +162,8 @@ void SettingsDialog::loadFromConfig() { m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled()); m_pCheckBoxLanguageSync->setChecked(m_appConfig.getLanguageSync()); m_pCheckBoxScrollDirection->setChecked(m_appConfig.getInvertScrollDirection()); + m_pCheckBoxClientHostMode->setChecked(m_appConfig.getClientHostMode()); + m_pCheckBoxServerClientMode->setChecked(m_appConfig.getServerClientMode()); setupSeurity(); @@ -367,6 +376,8 @@ bool SettingsDialog::isModified() || appConfig().getCryptoEnabled() != m_pCheckBoxEnableCrypto->isChecked() || appConfig().isSystemScoped() != m_isSystemAtStart || appConfig().getLanguageSync() != m_pCheckBoxLanguageSync->isChecked() + || appConfig().getClientHostMode() != m_pCheckBoxClientHostMode->isChecked() + || appConfig().getServerClientMode() != m_pCheckBoxServerClientMode->isChecked() || appConfig().getInvertScrollDirection() != m_pCheckBoxScrollDirection->isChecked()) ); } @@ -390,6 +401,8 @@ void SettingsDialog::enableControls(bool enable) { m_labelAdminRightsMessage->setVisible(!enable); m_pCheckBoxLanguageSync->setEnabled(enable); m_pCheckBoxScrollDirection->setEnabled(enable); + m_pCheckBoxClientHostMode->setEnabled(enable); + m_pCheckBoxServerClientMode->setEnabled(enable); if (enable) { m_pLabelLogPath->setEnabled(m_pCheckBoxLogToFile->isChecked()); diff --git a/src/gui/src/SettingsDialogBase.ui b/src/gui/src/SettingsDialogBase.ui index 973c7889c..40be3bed2 100644 --- a/src/gui/src/SettingsDialogBase.ui +++ b/src/gui/src/SettingsDialogBase.ui @@ -7,7 +7,7 @@ 0 0 404 - 620 + 819 @@ -337,62 +337,7 @@ background-color: rgba(192,192,192, 0.1); QFrame::Raised - - - - - 0 - 0 - - - - Elevate privileges - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - &Use server's keyboard language on this machine - - - - - - - - 0 - 0 - - - - - 75 - 0 - - - - User interface language - - - m_pComboLanguage - - - - + Specify when the Synergy service should run at an elevated privilege level @@ -417,16 +362,85 @@ background-color: rgba(192,192,192, 0.1); - + + + + &Use server's keyboard language on this machine + + + + + + + Host mode + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Elevate privileges + + + + - + Invert scroll direction on this computer + + + + + 0 + 0 + + + + + 75 + 0 + + + + User interface language + + + m_pComboLanguage + + + + + + + Client mode + + + @@ -695,7 +709,6 @@ background-color: rgba(192,192,192, 0.1); - 50 false diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 1fa4fb517..c84fdafef 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -74,7 +74,6 @@ Client::Client( m_events(events), m_sendFileThread(nullptr), m_writeToDropDirThread(nullptr), - m_socket(NULL), m_useSecureNetwork(args.m_enableCrypto), m_args(args), m_enableClipboard(true), @@ -135,29 +134,32 @@ Client::connect(size_t addressIndex) } try { - // resolve the server hostname. do this every time we connect - // in case we couldn't resolve the address earlier or the address - // has changed (which can happen frequently if this is a laptop - // being shuttled between various networks). patch by Brent - // Priddy. - m_resolvedAddressesCount = m_serverAddress.resolve(addressIndex); - - // m_serverAddress will be null if the hostname address is not reolved - if (m_serverAddress.getAddress() != nullptr) { - // to help users troubleshoot, show server host name (issue: 60) - LOG((CLOG_NOTE "connecting to '%s': %s:%i", - m_serverAddress.getHostname().c_str(), - ARCH->addrToString(m_serverAddress.getAddress()).c_str(), - m_serverAddress.getPort())); + if (m_args.m_hostMode) + { + LOG((CLOG_NOTE "waiting for server conection on %i port", m_serverAddress.getPort())); + } + else { + // resolve the server hostname. do this every time we connect + // in case we couldn't resolve the address earlier or the address + // has changed (which can happen frequently if this is a laptop + // being shuttled between various networks). patch by Brent + // Priddy. + m_resolvedAddressesCount = m_serverAddress.resolve(addressIndex); + + // m_serverAddress will be null if the hostname address is not reolved + if (m_serverAddress.getAddress() != nullptr) { + // to help users troubleshoot, show server host name (issue: 60) + LOG((CLOG_NOTE "connecting to '%s': %s:%i", + m_serverAddress.getHostname().c_str(), + ARCH->addrToString(m_serverAddress.getAddress()).c_str(), + m_serverAddress.getPort())); + } } // create the socket IDataSocket* socket = m_socketFactory->create(m_useSecureNetwork, ARCH->getAddrFamily(m_serverAddress.getAddress())); - m_socket = dynamic_cast(socket); - // filter socket messages, including a packetizing filter - m_stream = socket; - m_stream = new PacketStreamFilter(m_events, m_stream, true); + m_stream = new PacketStreamFilter(m_events, socket, true); // connect LOG((CLOG_DEBUG1 "connecting to server")); @@ -546,10 +548,12 @@ Client::setupTimer() { assert(m_timer == NULL); - m_timer = m_events->newOneShotTimer(2.0, NULL); - m_events->adoptHandler(Event::kTimer, m_timer, - new TMethodEventJob(this, - &Client::handleConnectTimeout)); + if (!m_args.m_hostMode) { + m_timer = m_events->newOneShotTimer(2.0, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob(this, + &Client::handleConnectTimeout)); + } } void diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index 86ebf1631..3994e7543 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -238,7 +238,6 @@ private: using AutoThread = std::unique_ptr; AutoThread m_sendFileThread; AutoThread m_writeToDropDirThread; - TCPSocket* m_socket; bool m_useSecureNetwork; bool m_enableClipboard; size_t m_maximumClipboardSize; diff --git a/src/lib/net/CMakeLists.txt b/src/lib/net/CMakeLists.txt index 7ebb8ed02..3331420de 100644 --- a/src/lib/net/CMakeLists.txt +++ b/src/lib/net/CMakeLists.txt @@ -14,8 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -file(GLOB headers "*.h") -file(GLOB sources "*.cpp") +file(GLOB headers + "*.h" + "InverseSockets/*.h" +) +file(GLOB sources + "*.cpp" + "InverseSockets/*.cpp" +) if (SYNERGY_ADD_HEADERS) list(APPEND sources ${headers}) diff --git a/src/lib/net/InverseSockets/AutoArchSocket.cpp b/src/lib/net/InverseSockets/AutoArchSocket.cpp new file mode 100644 index 000000000..391f718a9 --- /dev/null +++ b/src/lib/net/InverseSockets/AutoArchSocket.cpp @@ -0,0 +1,201 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ +#include "AutoArchSocket.h" + +#include "base/Log.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "net/XSocket.h" + +AutoArchSocket::AutoArchSocket(IArchNetwork::EAddressFamily family) +{ + try { + m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM); + LOG((CLOG_DEBUG "opening new socket: %08X", m_socket)); + } + catch (const XArchNetwork& e) { + throw XSocketCreate(e.what()); + } +} + +AutoArchSocket::~AutoArchSocket() +{ + closeSocket(); +} + +void AutoArchSocket::setNoDelayOnSocket(bool value) +{ + if (isValid()) { + ARCH->setNoDelayOnSocket(m_socket, value); + } +} + +void AutoArchSocket::setReuseAddrOnSocket(bool value) +{ + if (isValid()) { + ARCH->setReuseAddrOnSocket(m_socket, value); + } +} + +void AutoArchSocket::closeSocket() +{ + if (isValid()) { + try { + LOG((CLOG_DEBUG "closing socket: %08X", m_socket)); + ARCH->closeSocket(m_socket); + m_socket = nullptr; + } + catch (const XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what())); + } + } + else { + LOG((CLOG_WARN "error closing socket because the socket already closed")); + } +} + +void AutoArchSocket::bindSocket(const NetworkAddress &addr) +{ + if (isValid()) { + try { + ARCH->bindSocket(m_socket, addr.getAddress()); + } + catch (const XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (const XArchNetwork& e) { + throw XSocketBind(e.what()); + } + } +} + +void AutoArchSocket::bindAndListen(const NetworkAddress &addr) +{ + try { + setReuseAddrOnSocket(); + bindSocket(addr); + listenOnSocket(); + } + catch (const XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (const XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void AutoArchSocket::listenOnSocket() +{ + if (isValid()) { + ARCH->listenOnSocket(m_socket); + } +} + +ArchSocket AutoArchSocket::acceptSocket() +{ + return ARCH->acceptSocket(m_socket, nullptr); +} + +void AutoArchSocket::closeSocketForRead() +{ + if (isValid()) { + try { + ARCH->closeSocketForRead(m_socket); + } + catch (const XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what())); + } + } +} + +void AutoArchSocket::closeSocketForWrite() +{ + if (isValid()) { + try { + ARCH->closeSocketForWrite(m_socket); + } + catch (const XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what())); + } + } +} + +bool AutoArchSocket::connectSocket(const NetworkAddress &addr) +{ + bool result = false; + + if (isValid()) { + // turn off Nagle algorithm. we send lots of very short messages + // that should be sent without (much) delay. for example, the + // mouse motion messages are much less useful if they're delayed. + setNoDelayOnSocket(); + result = ARCH->connectSocket(m_socket, addr.getAddress()); + } + + return result; +} + +size_t AutoArchSocket::readSocket(UInt8* buffer, size_t size) +{ + size_t result = 0; + + if (isValid()) { + result = ARCH->readSocket(m_socket, buffer, size); + } + + return result; +} + +size_t AutoArchSocket::writeSocket(const UInt8* buffer, size_t size) +{ + size_t result = 0; + + if (isValid()) { + result = ARCH->writeSocket(m_socket, buffer, size); + } + + return result; +} + +void AutoArchSocket::throwErrorOnSocket() +{ + if (isValid()) { + ARCH->throwErrorOnSocket(m_socket); + } +} + +ArchSocket AutoArchSocket::getRawSocket() const +{ + return m_socket; +} + +bool AutoArchSocket::isValid() const +{ + return (m_socket != nullptr); +} + +void AutoArchSocket::operator =(ArchSocket socket) +{ + if (isValid()) { + closeSocket(); + } + m_socket = socket; +} diff --git a/src/lib/net/InverseSockets/AutoArchSocket.h b/src/lib/net/InverseSockets/AutoArchSocket.h new file mode 100644 index 000000000..7fb006eab --- /dev/null +++ b/src/lib/net/InverseSockets/AutoArchSocket.h @@ -0,0 +1,53 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ +#pragma once +#include "net/NetworkAddress.h" + +class AutoArchSocket +{ +public: + explicit AutoArchSocket(IArchNetwork::EAddressFamily family); + ~AutoArchSocket(); + + AutoArchSocket(const AutoArchSocket&) = delete; + AutoArchSocket& operator =(const AutoArchSocket&) = delete; + + void setNoDelayOnSocket(bool value = true); + void setReuseAddrOnSocket(bool value = true); + + void listenOnSocket(); + ArchSocket acceptSocket(); + void bindSocket(const NetworkAddress& addr); + bool connectSocket(const NetworkAddress& addr); + void bindAndListen(const NetworkAddress& addr); + + void closeSocket(); + void closeSocketForRead(); + void closeSocketForWrite(); + + size_t readSocket(UInt8* buffer, size_t size); + size_t writeSocket(const UInt8* buffer, size_t size); + void throwErrorOnSocket(); + + bool isValid() const; + ArchSocket getRawSocket() const; + void operator =(ArchSocket socket); + +private: + ArchSocket m_socket = nullptr; +}; diff --git a/src/lib/net/InverseSockets/InverseClientSocket.cpp b/src/lib/net/InverseSockets/InverseClientSocket.cpp new file mode 100644 index 000000000..f0a8b9813 --- /dev/null +++ b/src/lib/net/InverseSockets/InverseClientSocket.cpp @@ -0,0 +1,452 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ + +#include "InverseClientSocket.h" + +#include "net/NetworkAddress.h" +#include "net/SocketMultiplexer.h" +#include "net/TSocketMultiplexerMethodJob.h" +#include "net/XSocket.h" +#include "mt/Lock.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/IEventJob.h" + +#include +#include +#include + +// +// InverseClientSocket +// + +InverseClientSocket::InverseClientSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) : + IDataSocket(events), + m_events(events), + m_socket(family), + m_listener(family), + m_flushed(&m_mutex, true), + m_socketMultiplexer(socketMultiplexer) +{ +} + +InverseClientSocket::~InverseClientSocket() +{ + try { + // warning virtual function in destructor is very danger practice + InverseClientSocket::close(); + } + catch (...) { + LOG((CLOG_DEBUG "error while TCP socket destruction")); + } +} + +void +InverseClientSocket::bind(const NetworkAddress& addr) +{ + m_socket.bindSocket(addr); +} + +void +InverseClientSocket::close() +{ + setJob(nullptr); + + Lock lock(&m_mutex); + onDisconnected(); +} + +void* +InverseClientSocket::getEventTarget() const +{ + return const_cast(static_cast(this)); +} + +UInt32 +InverseClientSocket::read(void* buffer, UInt32 n) +{ + // copy data directly from our input buffer + Lock lock(&m_mutex); + UInt32 size = m_inputBuffer.getSize(); + if (n > size) { + n = size; + } + if (buffer != nullptr && n != 0) { + memcpy(buffer, m_inputBuffer.peek(n), n); + } + m_inputBuffer.pop(n); + + // if no more data and we cannot read or write then send disconnected + if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + + return n; +} + +void +InverseClientSocket::write(const void* buffer, UInt32 n) +{ + bool wasEmpty; + { + Lock lock(&m_mutex); + + // must not have shutdown output + if (!m_writable) { + sendEvent(m_events->forIStream().outputError()); + return; + } + + // ignore empty writes + if (n == 0) { + return; + } + + // copy data to the output buffer + wasEmpty = (m_outputBuffer.getSize() == 0); + m_outputBuffer.write(buffer, n); + + // there's data to write + m_flushed = false; + } + + // make sure we're waiting to write + if (wasEmpty) { + setJob(newJob(m_socket.getRawSocket())); + } +} + +void +InverseClientSocket::flush() +{ + Lock lock(&m_mutex); + while (m_flushed == false) { + m_flushed.wait(); + } +} + +void +InverseClientSocket::shutdownInput() +{ + bool useNewJob = false; + { + Lock lock(&m_mutex); + + // shutdown socket for reading + m_socket.closeSocketForRead(); + + // shutdown buffer for reading + if (m_readable) { + sendEvent(m_events->forIStream().inputShutdown()); + onInputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob(m_socket.getRawSocket())); + } +} + +void +InverseClientSocket::shutdownOutput() +{ + bool useNewJob = false; + { + Lock lock(&m_mutex); + + // shutdown socket for writing + m_socket.closeSocketForWrite(); + + // shutdown buffer for writing + if (m_writable) { + sendEvent(m_events->forIStream().outputShutdown()); + onOutputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob(m_socket.getRawSocket())); + } +} + +bool +InverseClientSocket::isReady() const +{ + Lock lock(&m_mutex); + return (m_inputBuffer.getSize() > 0); +} + +bool +InverseClientSocket::isFatal() const +{ + // TCP sockets aren't ever left in a fatal state. + LOG((CLOG_ERR "isFatal() not valid for non-secure connections")); + return false; +} + +UInt32 +InverseClientSocket::getSize() const +{ + Lock lock(&m_mutex); + return m_inputBuffer.getSize(); +} + +void +InverseClientSocket::connect(const NetworkAddress& addr) +{ + { + Lock lock(&m_mutex); + m_listener.bindAndListen(addr); + m_writable = true; + m_readable = true; + } + setJob(newJob(m_listener.getRawSocket())); +} + +InverseClientSocket::EJobResult +InverseClientSocket::doRead() +{ + UInt8 buffer[4096] = {0}; + size_t bytesRead = m_socket.readSocket(buffer, sizeof(buffer)); + + if (bytesRead > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, static_cast(bytesRead)); + + bytesRead = m_socket.readSocket(buffer, sizeof(buffer)); + } while (bytesRead > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendEvent(m_events->forIStream().inputReady()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendEvent(m_events->forIStream().inputShutdown()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + m_readable = false; + return InverseClientSocket::EJobResult::kNew; + } + + return InverseClientSocket::EJobResult::kRetry; +} + +InverseClientSocket::EJobResult +InverseClientSocket::doWrite() +{ + UInt32 bufferSize = m_outputBuffer.getSize(); + auto buffer = static_cast(m_outputBuffer.peek(bufferSize)); + const auto bytesWrote = static_cast(m_socket.writeSocket(buffer, bufferSize)); + + if (bytesWrote > 0) { + discardWrittenData(bytesWrote); + return InverseClientSocket::EJobResult::kNew; + } + + return InverseClientSocket::EJobResult::kRetry; +} + +void +InverseClientSocket::setJob(ISocketMultiplexerJob* job) +{ + // multiplexer will delete the old job + if (job == nullptr) { + m_socketMultiplexer->removeSocket(this); + } + else { + m_socketMultiplexer->addSocket(this, job); + } +} + +ISocketMultiplexerJob* +InverseClientSocket::newJob(ArchSocket socket) +{ + // note -- must have m_mutex locked on entry + + ISocketMultiplexerJob* result = nullptr; + + if (socket) { + auto isWritable = m_writable; + auto handler = &InverseClientSocket::serviceConnecting; + + if (m_connected) { + handler = &InverseClientSocket::serviceConnected; + isWritable = (isWritable && (m_outputBuffer.getSize() > 0)); + } + + if (m_readable || isWritable) { + result = new TSocketMultiplexerMethodJob(this, handler, socket, m_readable, isWritable); + } + } + + return result; +} + +void +InverseClientSocket::sendConnectionFailedEvent(const char* msg) +{ + auto info = new ConnectionFailedInfo(msg); + m_events->addEvent(Event(m_events->forIDataSocket().connectionFailed(), + getEventTarget(), info, Event::kDontFreeData)); +} + +void +InverseClientSocket::sendEvent(Event::Type type) +{ + m_events->addEvent(Event(type, getEventTarget())); +} + +void +InverseClientSocket::discardWrittenData(int bytesWrote) +{ + m_outputBuffer.pop(bytesWrote); + if (m_outputBuffer.getSize() == 0) { + sendEvent(m_events->forIStream().outputFlushed()); + m_flushed = true; + m_flushed.broadcast(); + } +} + +void +InverseClientSocket::onConnected() +{ + sendEvent(m_events->forIDataSocket().connected()); + m_connected = true; + m_readable = true; + m_writable = true; +} + +void +InverseClientSocket::onInputShutdown() +{ + m_inputBuffer.pop(m_inputBuffer.getSize()); + m_readable = false; +} + +void +InverseClientSocket::onOutputShutdown() +{ + m_outputBuffer.pop(m_outputBuffer.getSize()); + m_writable = false; + + // we're now flushed + m_flushed = true; + m_flushed.broadcast(); +} + +void +InverseClientSocket::onDisconnected() +{ + if (m_connected) { + sendEvent(m_events->forISocket().disconnected()); + } + // disconnected + onInputShutdown(); + onOutputShutdown(); + m_connected = false; +} + +ISocketMultiplexerJob* +InverseClientSocket::serviceConnecting(ISocketMultiplexerJob* job, + bool read, bool, bool) +{ + Lock lock(&m_mutex); + + if (read) { + m_socket = m_listener.acceptSocket(); + onConnected(); + return newJob(m_socket.getRawSocket()); + } + + return job; +} + +ISocketMultiplexerJob* +InverseClientSocket::serviceConnected(ISocketMultiplexerJob* job, + bool read, bool write, bool error) +{ + Lock lock(&m_mutex); + + if (error) { + onDisconnected(); + return newJob(m_listener.getRawSocket()); + } + + EJobResult result = InverseClientSocket::EJobResult::kRetry; + if (write) { + try { + result = doWrite(); + } + catch (const XArchNetworkShutdown&) { + // remote read end of stream hungup. our output side + // has therefore shutdown. + onOutputShutdown(); + sendEvent(m_events->forIStream().outputShutdown()); + if (!m_readable && m_inputBuffer.getSize() == 0) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + result = InverseClientSocket::EJobResult::kNew; + } + catch (const XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + result = InverseClientSocket::EJobResult::kNew; + } + catch (const XArchNetwork& e) { + // other write error + LOG((CLOG_WARN "error writing socket: %s", e.what())); + onDisconnected(); + sendEvent(m_events->forIStream().outputError()); + result = InverseClientSocket::EJobResult::kNew; + } + } + + if (read && m_readable) { + try { + result = doRead(); + } + catch (const XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + result = InverseClientSocket::EJobResult::kNew; + } + catch (const XArchNetwork& e) { + // ignore other read error + LOG((CLOG_WARN "error reading socket: %s", e.what())); + } + } + + if (result == InverseClientSocket::EJobResult::kBreak) { + return nullptr; + } + + return result == InverseClientSocket::EJobResult::kNew ? newJob(m_socket.getRawSocket()) : job; +} diff --git a/src/lib/net/InverseSockets/InverseClientSocket.h b/src/lib/net/InverseSockets/InverseClientSocket.h new file mode 100644 index 000000000..79eaa5648 --- /dev/null +++ b/src/lib/net/InverseSockets/InverseClientSocket.h @@ -0,0 +1,111 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ + +#pragma once + +#include "net/IDataSocket.h" +#include "io/StreamBuffer.h" +#include "mt/CondVar.h" +#include "mt/Mutex.h" +#include "arch/IArchNetwork.h" +#include "AutoArchSocket.h" + +class ISocketMultiplexerJob; +class IEventQueue; +class SocketMultiplexer; + +class InverseClientSocket : public IDataSocket { +public: + InverseClientSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family = IArchNetwork::kINET); + InverseClientSocket(InverseClientSocket const &) =delete; + InverseClientSocket(InverseClientSocket &&) =delete; + ~InverseClientSocket() override; + + InverseClientSocket& operator=(InverseClientSocket const &) =delete; + InverseClientSocket& operator=(InverseClientSocket &&) =delete; + + // ISocket overrides + void bind(const NetworkAddress&) override; + void close() override; + void* getEventTarget() const override; + + // IStream overrides + UInt32 read(void* buffer, UInt32 n) override; + void write(const void* buffer, UInt32 n) override; + void flush() override; + void shutdownInput() override; + void shutdownOutput() override; + bool isReady() const override; + bool isFatal() const override; + UInt32 getSize() const override; + + // IDataSocket overrides + void connect(const NetworkAddress&) override; + + + virtual ISocketMultiplexerJob* + newJob(ArchSocket socket); + +protected: + enum class EJobResult { + kBreak = -1, //!< Break the Job chain + kRetry, //!< Retry the same job + kNew //!< Require a new job + }; + + ArchSocket getSocket() { return m_socket.getRawSocket(); } + IEventQueue* getEvents() { return m_events; } + virtual EJobResult doRead(); + virtual EJobResult doWrite(); + + void setJob(ISocketMultiplexerJob*); + + bool isReadable() const { return m_readable; } + bool isWritable() const { return m_writable; } + + Mutex& getMutex() { return m_mutex; } + + void sendEvent(Event::Type); + void discardWrittenData(int bytesWrote); + +private: + void sendConnectionFailedEvent(const char*); + void onConnected(); + void onInputShutdown(); + void onOutputShutdown(); + void onDisconnected(); + + ISocketMultiplexerJob* + serviceConnecting(ISocketMultiplexerJob*, + bool, bool, bool); + ISocketMultiplexerJob* + serviceConnected(ISocketMultiplexerJob*, + bool, bool, bool); + + bool m_readable = false; + bool m_writable = false; + bool m_connected = false; + IEventQueue* m_events; + StreamBuffer m_inputBuffer; + StreamBuffer m_outputBuffer; + Mutex m_mutex; + AutoArchSocket m_socket; + AutoArchSocket m_listener; + CondVar m_flushed; + SocketMultiplexer* m_socketMultiplexer; +}; diff --git a/src/lib/net/InverseSockets/InverseServerSocket.cpp b/src/lib/net/InverseSockets/InverseServerSocket.cpp new file mode 100644 index 000000000..07e7cdc04 --- /dev/null +++ b/src/lib/net/InverseSockets/InverseServerSocket.cpp @@ -0,0 +1,122 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ + +#include "InverseServerSocket.h" + +#include "net/NetworkAddress.h" +#include "net/SocketMultiplexer.h" +#include "net/TCPSocket.h" +#include "net/TSocketMultiplexerMethodJob.h" +#include "net/XSocket.h" +#include "io/XIO.h" +#include "mt/Lock.h" +#include "mt/Mutex.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" + +// +// InverseServerSocket +// + +InverseServerSocket::InverseServerSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) : + m_socket(family), + m_events(events), + m_socketMultiplexer(socketMultiplexer) +{ +} + +InverseServerSocket::~InverseServerSocket() +{ + m_socketMultiplexer->removeSocket(this); +} + +void +InverseServerSocket::bind(const NetworkAddress& addr) +{ + Lock lock(&m_mutex); + m_socket.connectSocket(addr); + setListeningJob(true); +} + +void +InverseServerSocket::close() +{ + Lock lock(&m_mutex); + m_socketMultiplexer->removeSocket(this); + m_socket.closeSocket(); +} + +void* +InverseServerSocket::getEventTarget() const +{ + return const_cast(static_cast(this)); +} + +IDataSocket* +InverseServerSocket::accept() +{ + IDataSocket* socket = nullptr; + try { + socket = new TCPSocket(m_events, m_socketMultiplexer, m_socket.getRawSocket()); + if (socket != nullptr) { + setListeningJob(); + } + return socket; + } + catch (const XArchNetwork&) { + if (socket != nullptr) { + delete socket; + setListeningJob(); + } + return nullptr; + } + catch (const std::exception &ex) { + if (socket != nullptr) { + delete socket; + setListeningJob(); + } + throw; + } +} + +void +InverseServerSocket::setListeningJob(bool read) +{ + m_socketMultiplexer->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &InverseServerSocket::serviceListening, + m_socket.getRawSocket(), true, read)); +} + +ISocketMultiplexerJob* +InverseServerSocket::serviceListening(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + if (error) { + close(); + return nullptr; + } + if (write) { + m_events->addEvent(Event(m_events->forIListenSocket().connecting(), this)); + // stop polling on this socket until the client accepts + return nullptr; + } + return job; +} diff --git a/src/lib/net/InverseSockets/InverseServerSocket.h b/src/lib/net/InverseSockets/InverseServerSocket.h new file mode 100644 index 000000000..3f1866371 --- /dev/null +++ b/src/lib/net/InverseSockets/InverseServerSocket.h @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ + +#pragma once + +#include "net/IListenSocket.h" +#include "arch/IArchNetwork.h" +#include "mt/Mutex.h" +#include "AutoArchSocket.h" + +class ISocketMultiplexerJob; +class IEventQueue; +class SocketMultiplexer; + +class InverseServerSocket : public IListenSocket { +public: + InverseServerSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family); + InverseServerSocket(InverseServerSocket const &) =delete; + InverseServerSocket(InverseServerSocket &&) =delete; + ~InverseServerSocket() override; + + InverseServerSocket& operator=(InverseServerSocket const &) =delete; + InverseServerSocket& operator=(InverseServerSocket &&) =delete; + + // ISocket overrides + void bind(const NetworkAddress&) override; + void close() override; + void* getEventTarget() const override; + + // IListenSocket overrides + IDataSocket* accept() override; + +protected: + void setListeningJob(bool read = false); + +public: + ISocketMultiplexerJob* + serviceListening(ISocketMultiplexerJob*, + bool, bool, bool); + +private: + AutoArchSocket m_socket; + Mutex m_mutex; + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; +}; diff --git a/src/lib/net/InverseSockets/InverseSocketFactory.cpp b/src/lib/net/InverseSockets/InverseSocketFactory.cpp new file mode 100644 index 000000000..171c815fa --- /dev/null +++ b/src/lib/net/InverseSockets/InverseSocketFactory.cpp @@ -0,0 +1,59 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ +#include "InverseSocketFactory.h" +#include "net/InverseSockets/InverseClientSocket.h" +#include "net/InverseSockets/InverseServerSocket.h" +#include "net/SecureSocket.h" +#include "net/SecureListenSocket.h" + +// +// InverseSocketFactory +// + +InverseSocketFactory::InverseSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_events(events), + m_socketMultiplexer(socketMultiplexer) +{ +} + +IDataSocket* +InverseSocketFactory::create(bool secure, IArchNetwork::EAddressFamily family) const +{ + if (secure) { + auto secureSocket = new SecureSocket(m_events, m_socketMultiplexer, family); + secureSocket->initSsl (false); + return secureSocket; + } + else { + return new InverseClientSocket(m_events, m_socketMultiplexer, family); + } +} + +IListenSocket* +InverseSocketFactory::createListen(bool secure, IArchNetwork::EAddressFamily family) const +{ + IListenSocket* socket = nullptr; + + if (secure) { + socket = new SecureListenSocket(m_events, m_socketMultiplexer, family); + } + else { + socket = new InverseServerSocket(m_events, m_socketMultiplexer, family); + } + + return socket; +} diff --git a/src/lib/net/InverseSockets/InverseSocketFactory.h b/src/lib/net/InverseSockets/InverseSocketFactory.h new file mode 100644 index 000000000..64f92d7a5 --- /dev/null +++ b/src/lib/net/InverseSockets/InverseSocketFactory.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2022 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package 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, see . + */ +#pragma once +#include "net/ISocketFactory.h" + +class IEventQueue; +class SocketMultiplexer; + +class InverseSocketFactory : public ISocketFactory { +public: + InverseSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + + // ISocketFactory overrides + IDataSocket* create(bool secure, IArchNetwork::EAddressFamily family = IArchNetwork::kINET) const override; + IListenSocket* createListen(bool secure, IArchNetwork::EAddressFamily family = IArchNetwork::kINET) const override; + +private: + IEventQueue* m_events = nullptr; + SocketMultiplexer* m_socketMultiplexer = nullptr; +}; diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp index 064b8b769..0856018a3 100644 --- a/src/lib/server/Config.cpp +++ b/src/lib/server/Config.cpp @@ -653,6 +653,11 @@ Config::formatInterval(const Interval& x) (int)(x.second * 100.0f + 0.5f)); } +String Config::getClientAddress() const +{ + return m_clientAddress; +} + void Config::readSection(ConfigReadContext& s) { @@ -773,6 +778,9 @@ Config::readSectionOptions(ConfigReadContext& s) else if (name == "clipboardSharingSize") { addOption("", kOptionClipboardSharingSize, s.parseInt(value)); } + else if (name == "clientAddress") { + m_clientAddress = value; + } else { handled = false; } diff --git a/src/lib/server/Config.h b/src/lib/server/Config.h index 383f0465f..92ee0ee4f 100644 --- a/src/lib/server/Config.h +++ b/src/lib/server/Config.h @@ -450,6 +450,11 @@ public: */ static String formatInterval(const Interval&); + //! Get client address as a string + /*! + * Return client address a string. + */ + String getClientAddress() const; //@} private: @@ -481,6 +486,7 @@ private: InputFilter m_inputFilter; bool m_hasLockToScreenAction; IEventQueue* m_events; + String m_clientAddress; }; //! Configuration read context diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 526429d98..646da8400 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -117,6 +117,9 @@ ArgParser::parseClientArgs(lib::synergy::ClientArgs& args, int argc, const char* else if (isArg(i, argc, argv, nullptr, "--invert-scroll")) { args.m_clientScrollDirection = lib::synergy::ClientScrollDirection::INVERT_SERVER; } + else if (isArg(i, argc, argv, nullptr, "--host")) { + args.m_hostMode = true; + } else { if (i + 1 == argc) { args.m_synergyAddress = argv[i]; diff --git a/src/lib/synergy/ClientApp.cpp b/src/lib/synergy/ClientApp.cpp index 9df001941..7ca723d7f 100644 --- a/src/lib/synergy/ClientApp.cpp +++ b/src/lib/synergy/ClientApp.cpp @@ -26,6 +26,7 @@ #include "synergy/ClientArgs.h" #include "net/NetworkAddress.h" #include "net/TCPSocketFactory.h" +#include "net/InverseSockets/InverseSocketFactory.h" #include "net/SocketMultiplexer.h" #include "net/XSocket.h" #include "mt/Thread.h" @@ -128,6 +129,7 @@ ClientApp::help() " [--yscroll ]" " [--sync-language]" " [--invert-scroll]" + " [--host]" WINAPI_ARG HELP_SYS_ARGS HELP_COMMON_ARGS @@ -142,6 +144,7 @@ ClientApp::help() " 120 by default.\n" " --sync-language set this parameter to enable language synchronization.\n" " --invert-scroll invert scroll direction on this computer.\n" + " --host client starts a listener and waits for a server connection.\n" HELP_COMMON_INFO_2 "\n" "* marks defaults.\n" @@ -366,7 +369,7 @@ ClientApp::openClient(const String& name, const NetworkAddress& address, m_events, name, address, - new TCPSocketFactory(m_events, getSocketMultiplexer()), + getSocketFactory(), screen, args()); @@ -599,3 +602,17 @@ ClientApp::startNode() m_bye(kExitFailed); } } + +ISocketFactory* ClientApp::getSocketFactory() const +{ + ISocketFactory* socketFactory = nullptr; + + if (args().m_hostMode) { + socketFactory = new InverseSocketFactory(m_events, getSocketMultiplexer()); + } + else { + socketFactory = new TCPSocketFactory(m_events, getSocketMultiplexer()); + } + + return socketFactory; +} diff --git a/src/lib/synergy/ClientApp.h b/src/lib/synergy/ClientApp.h index 56bbd3ad7..b1d6d3b8a 100644 --- a/src/lib/synergy/ClientApp.h +++ b/src/lib/synergy/ClientApp.h @@ -25,6 +25,7 @@ class Event; class Client; class NetworkAddress; class Thread; +class ISocketFactory; namespace lib { namespace synergy { class ClientArgs; @@ -82,6 +83,8 @@ public: Client* getClientPtr() { return m_client; } private: + ISocketFactory* getSocketFactory() const; + Client* m_client; synergy::Screen* m_clientScreen; NetworkAddress* m_serverAddress; diff --git a/src/lib/synergy/ClientArgs.h b/src/lib/synergy/ClientArgs.h index d445d9368..f2f2976ff 100644 --- a/src/lib/synergy/ClientArgs.h +++ b/src/lib/synergy/ClientArgs.h @@ -46,6 +46,12 @@ namespace lib { */ ClientScrollDirection m_clientScrollDirection = ClientScrollDirection::SERVER; + /** + * @brief m_hostMode - activates host mode. + * Client starts a listener and waits for a server connection. + */ + bool m_hostMode = false; + }; } } diff --git a/src/lib/synergy/ServerApp.cpp b/src/lib/synergy/ServerApp.cpp index 840e8e213..ffa1771e4 100644 --- a/src/lib/synergy/ServerApp.cpp +++ b/src/lib/synergy/ServerApp.cpp @@ -29,6 +29,7 @@ #include "synergy/ServerArgs.h" #include "net/SocketMultiplexer.h" #include "net/TCPSocketFactory.h" +#include "net/InverseSockets/InverseSocketFactory.h" #include "net/XSocket.h" #include "arch/Arch.h" #include "base/EventQueue.h" @@ -633,8 +634,8 @@ ClientListener* ServerApp::openClientListener(const NetworkAddress& address) { ClientListener* listen = new ClientListener( - address, - new TCPSocketFactory(m_events, getSocketMultiplexer()), + getAddress(address), + getSocketFactory(), m_events, args().m_enableCrypto); @@ -678,6 +679,34 @@ ServerApp::handleScreenSwitched(const Event& e, void*) { } +ISocketFactory* ServerApp::getSocketFactory() const +{ + ISocketFactory* socketFactory = nullptr; + const auto clientAddress = args().m_config->getClientAddress(); + + if (clientAddress.empty()) { + socketFactory = new TCPSocketFactory(m_events, getSocketMultiplexer()); + } + else { + socketFactory = new InverseSocketFactory(m_events, getSocketMultiplexer()); + } + + return socketFactory; +} + +NetworkAddress ServerApp::getAddress(const NetworkAddress& address) const +{ + const auto clientAddress = args().m_config->getClientAddress(); + if (clientAddress.empty()) { + return address; + } + else { + NetworkAddress addr(clientAddress.c_str(), kDefaultPort); + addr.resolve(); + return addr; + } +} + int ServerApp::mainLoop() { diff --git a/src/lib/synergy/ServerApp.h b/src/lib/synergy/ServerApp.h index c36281729..1b9a17b25 100644 --- a/src/lib/synergy/ServerApp.h +++ b/src/lib/synergy/ServerApp.h @@ -45,6 +45,7 @@ class ClientListener; class EventQueueTimer; class ILogOutputter; class IEventQueue; +class ISocketFactory; namespace lib { namespace synergy{ class ServerArgs; @@ -119,6 +120,8 @@ public: private: void handleScreenSwitched(const Event&, void* data); + ISocketFactory* getSocketFactory() const; + NetworkAddress getAddress(const NetworkAddress& address) const; }; // configuration file name